Skip to content

Commit 2f5d96e

Browse files
fix(langfuse): make score readback robust
1 parent 21162ed commit 2f5d96e

4 files changed

Lines changed: 95 additions & 12 deletions

File tree

docs/runbooks/langfuse-observability.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,42 @@ tailscale up
157157
tailscale status
158158
```
159159

160+
Recommended tailnet policy pattern, matching the HomeLab per-service tag
161+
convention:
162+
163+
```jsonc
164+
{
165+
"tagOwners": {
166+
"tag:langfuse": ["autogroup:owner"],
167+
"tag:agents": ["autogroup:owner"]
168+
},
169+
"grants": [
170+
{
171+
"src": ["tag:agents"],
172+
"dst": ["tag:langfuse"],
173+
"ip": ["tcp:3000"]
174+
},
175+
{
176+
"src": ["autogroup:admin"],
177+
"dst": ["tag:langfuse"],
178+
"ip": ["tcp:3000"]
179+
}
180+
]
181+
}
182+
```
183+
184+
If the Mac mini already wears other service tags, add `tag:langfuse` to the
185+
same device. Owner-managed devices can self-advertise this tag when
186+
`autogroup:owner` is listed in `tagOwners`:
187+
188+
```bash
189+
sudo tailscale up --advertise-tags=tag:langfuse
190+
```
191+
192+
Agent VPSs or long-lived agent hosts should wear `tag:agents` if they are not
193+
operator-admin devices. That keeps LangFuse reachable to agent infrastructure
194+
without opening the rest of the Mac mini.
195+
160196
Pick one stable address for `LANGFUSE_BASE_URL`:
161197

162198
- MagicDNS name, for example `http://mac-mini.tailnet-name.ts.net:3000`
@@ -173,6 +209,13 @@ reverse proxy and use:
173209
export LANGFUSE_BASE_URL=https://langfuse.example.com
174210
```
175211

212+
Reachability check from each client:
213+
214+
```bash
215+
LANGFUSE_BASE_URL=http://mac-mini.tailnet-name.ts.net:3000 \
216+
scripts/langfuse-local.sh health
217+
```
218+
176219
## Client Setup
177220

178221
Run these steps on every MacBook, Mac mini shell, VPS, or long-lived workspace

plugins/observability/mcp/langfuse_server.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,13 +569,16 @@ def _direct_langfuse_scores(self, args: dict[str, Any]) -> dict[str, Any]:
569569
trace_id = args.get("trace-id") or (langfuse_trace_id_for_run(args["run-id"]) if args.get("run-id") else None)
570570
base_url = _langfuse_api_base_url(args.get("langfuse-base-url"))
571571
params: dict[str, Any] = {"limit": int(args.get("limit") or 20), "page": 1}
572-
if trace_id:
573-
params["traceId"] = trace_id
572+
# LangFuse self-host score list filters can under-return when several
573+
# filters are combined. Query with one narrowing backend filter and
574+
# enforce the full request below in _filter_score_rows.
574575
if args.get("score-ids"):
575576
params["scoreIds"] = args["score-ids"]
576-
if args.get("name"):
577+
elif trace_id:
578+
params["traceId"] = trace_id
579+
elif args.get("name"):
577580
params["name"] = args["name"]
578-
if args.get("data-type"):
581+
elif args.get("data-type"):
579582
params["dataType"] = str(args["data-type"]).upper()
580583
endpoint = f"{base_url}/api/public/scores?{parse.urlencode(params)}"
581584
response = _langfuse_request("GET", endpoint)

providers/workspaces/interactive-tmux/driver-rs/src/langfuse.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,16 +2075,16 @@ fn build_langfuse_scores_list_url(
20752075
) -> String {
20762076
let base = langfuse_api_base_url(base_url);
20772077
let mut params = vec![format!("limit={limit}"), format!("page={page}")];
2078-
if let Some(trace_id) = non_empty_str(trace_id) {
2079-
params.push(format!("traceId={}", url_query_encode(trace_id)));
2080-
}
2078+
// LangFuse self-host score list filters can under-return when several
2079+
// filters are combined. Send one narrowing filter to the backend, then
2080+
// enforce every requested filter in summarize_langfuse_scores_response.
20812081
if let Some(score_ids) = non_empty_str(score_ids) {
20822082
params.push(format!("scoreIds={}", url_query_encode(score_ids)));
2083-
}
2084-
if let Some(name) = non_empty_str(name) {
2083+
} else if let Some(trace_id) = non_empty_str(trace_id) {
2084+
params.push(format!("traceId={}", url_query_encode(trace_id)));
2085+
} else if let Some(name) = non_empty_str(name) {
20852086
params.push(format!("name={}", url_query_encode(name)));
2086-
}
2087-
if let Some(data_type) = non_empty_str(data_type) {
2087+
} else if let Some(data_type) = non_empty_str(data_type) {
20882088
params.push(format!("dataType={}", url_query_encode(data_type)));
20892089
}
20902090
format!(
@@ -2347,7 +2347,38 @@ mod tests {
23472347

23482348
assert_eq!(
23492349
url,
2350-
"https://langfuse.example.com/api/public/scores?limit=10&page=2&traceId=trace%20id%2Fwith%20spaces&scoreIds=score-a%2Cscore-b&name=agentic.learning_loop_probe&dataType=BOOLEAN"
2350+
"https://langfuse.example.com/api/public/scores?limit=10&page=2&scoreIds=score-a%2Cscore-b"
2351+
);
2352+
}
2353+
2354+
#[test]
2355+
fn langfuse_scores_list_url_uses_single_backend_filter_then_local_filtering() {
2356+
let trace_and_name = build_langfuse_scores_list_url(
2357+
"https://langfuse.example.com",
2358+
Some("trace-wanted"),
2359+
None,
2360+
Some("agentic.learning_loop_probe"),
2361+
Some("BOOLEAN"),
2362+
20,
2363+
1,
2364+
);
2365+
assert_eq!(
2366+
trace_and_name,
2367+
"https://langfuse.example.com/api/public/scores?limit=20&page=1&traceId=trace-wanted"
2368+
);
2369+
2370+
let name_and_type = build_langfuse_scores_list_url(
2371+
"https://langfuse.example.com",
2372+
None,
2373+
None,
2374+
Some("agentic.learning_loop_probe"),
2375+
Some("BOOLEAN"),
2376+
20,
2377+
1,
2378+
);
2379+
assert_eq!(
2380+
name_and_type,
2381+
"https://langfuse.example.com/api/public/scores?limit=20&page=1&name=agentic.learning_loop_probe"
23512382
);
23522383
}
23532384

scripts/langfuse-local.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Commands:
1717
start|up Run docker compose up -d from the cloned LangFuse repository
1818
stop|down Run docker compose down from the cloned LangFuse repository
1919
status Run docker compose ps from the cloned LangFuse repository
20+
health Check the configured LangFuse public health endpoint
2021
smoke Run the official-plugin setup/readiness check against local env
2122
2223
Environment:
@@ -162,6 +163,11 @@ EOF
162163
status)
163164
compose_run ps
164165
;;
166+
health)
167+
load_local_env
168+
curl -fsS "$LANGFUSE_BASE_URL/api/public/health"
169+
printf '\nLangFuse health check passed for %s\n' "$LANGFUSE_BASE_URL"
170+
;;
165171
smoke)
166172
ensure_local_files
167173
load_local_env

0 commit comments

Comments
 (0)