feat(langfuse): support Langfuse ≥2.x + cost sync by YatchiYa · Pull Request #390 · raphaelmansuy/edgequake · GitHub
Skip to content

feat(langfuse): support Langfuse ≥2.x + cost sync - #390

Closed
YatchiYa wants to merge 2 commits into
raphaelmansuy:edgequake-mainfrom
YatchiYa:langfuse-adaptation-v3.1
Closed

YatchiYa wants to merge 2 commits into
raphaelmansuy:edgequake-mainfrom
YatchiYa:langfuse-adaptation-v3.1

Conversation

@YatchiYa

Copy link
Copy Markdown
Collaborator

3. Langfuse : support de toutes les versions ≥ 2.x
L'endpoint OTLP /api/public/otel/v1/traces n'existe qu'à partir de Langfuse
3.22x — sur 3.1 il renvoie 404, donc aucune trace ne pouvait arriver, quelle
que soit la configuration réseau. Ajout d'un exportateur SpanExporter natif
(langfuse_ingestion.rs) parlant /api/public/ingestion, présent depuis Langfuse v2.
Le transport est choisi au démarrage via EDGEQUAKE_LANGFUSE_API=auto|otlp|ingestion
(défaut auto, qui sonde et bascule sur un 404). Le chemin OTLP est inchangé.

4. Coûts à $0.00 pour tout modèle récent
Langfuse calcule les coûts depuis son propre catalogue et EdgeQuake n'émet jamais
d'attribut de coût (LAW-124-12 — Langfuse reste la source unique de vérité). Le
catalogue livré avec 3.1 date de 2024 : gpt-4o mais pas gpt-5, gemini-1.5 mais
pas 2.5, claude-3.5 mais pas 4.x, aucun Mistral. Ajout de
scripts/langfuse_sync_model_prices.py + cible make langfuse-sync-prices qui pousse
les tarifs de models.toml — sans contourner LAW-124-12.

Fixes # (aucune issue ouverte — bugs remontés en usage direct)

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (describe below)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes

@raphaelmansuy raphaelmansuy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: request changes — do not merge.

The Langfuse 3.1 OTLP-gap diagnosis is correct, the 404-only probe heuristic is right, and pushing models.toml via POST /api/public/models does not violate LAW-124-12. The native ingestion path as written is not a valid 3.1 compatibility shim, tests do not cover the mapping, and the PR is not merge-ready.

What is worth keeping

  • /api/public/otel arrived in Langfuse v3.22.0; 3.1 returns 404. Confirmed against Langfuse OTEL docs.
  • Probe only falls back on 404; 401 / network errors keep OTLP. That must stay.
  • OTLP path for 3.22+ / 4.x is preserved.
  • Dedicated OS thread for the blocking probe is the right nested-runtime fix.

Blockers

1. Ingestion event types are invalid on Langfuse 3.1 — the version this PR exists for

spans_to_batch emits format!("{obs_type}-create"). Langfuse 3.1.1 ingestion discriminant (ingestion.yml at v3.1.1) only allows:

trace-create | span-create | generation-create | event-create | score-create | sdk-log | legacy observation-create / observation-update

3.1 ObservationType is only SPAN | GENERATION | EVENT. There is no retriever-create, embedding-create, or chain-create.

EdgeQuake (LAW-124-13) tags spans as retriever / embedding / chain. Those become illegal envelope types. The is_generation heuristic then overrides typed observations: embedding spans carry a model → forced generation-create; retriever / ingest-chain typically do not → retriever-create / chain-create → 207 errors.

export() treats HTTP 207 as success and never reads errors[]. Partial batch failure is silent.

Required 3.1 mapping: generation → generation-create; everything else → span-create. Do not stringify arbitrary observation types into the envelope type.

Please add unit tests for spans_to_batch covering generation, retriever, embedding, chain, and a root HTTP span. Today there are only RFC3339 + endpoint-join tests.

2. Legacy ingestion is a compatibility bridge, not a replacement for upgrade

/api/public/ingestion is deprecated for traces. Cloud sunset is 2026-11-16; self-hosted v4 events_only rejects it. Internal docs now say upgrade is no longer required. Please rewrite as: 3.1 can receive traces via ingestion fallback; 3.225+ OTLP remains the supported path. Claiming “all Langfuse ≥ 2.x” is unproven (tested 3.1.1 / 3.225.5 / 4 only).

3. Product env contract is undocumented

EDGEQUAKE_LANGFUSE_API=auto|otlp|ingestion is missing from .env.example, docs/OBSERVABILITY.md, docs/operations/configuration.md, SPEC-124 (LAW-124-1 is still OTLP-only), and Helm / k8s product docs. Only internal French runbooks were updated. Also missing: LangfuseApi::from_env / probe 404-vs-other tests.

4. CI

  • Must fix on this PR: cargo fmt --check fails on langfuse_ingestion.rs, lib.rs, subscriber.rs. Do not mix streaming/workspace files into this PR.
  • Likely pre-existing, not a reject reason here: rustdoc private-intra-doc-links in edgequake-pdf; RUSTSEC-2026-0258 (h2); E2E UI pdf.worker.min.mjs missing. Confirm those on edgequake-main.

Workspace lib tests and SPEC-018 passed; they cannot catch the 3.1 envelope bug.

5. Hygiene

  • Both commits are titled fix(streaming, conversations): restore SSE token streaming… while this PR is Langfuse. Squash / retitle before merge.
  • Direct reqwest 0.12 sits beside opentelemetry-otlp’s own reqwest. Document why a second client is required, or reuse the OTLP stack.
  • make langfuse-sync-prices FORCE=1 still POSTs; Langfuse update is PUT /api/public/models/{id}. Implement PUT or drop --force.

Do not approve until

  1. 3.1-safe envelope mapping + spans_to_batch unit tests (generation / retriever / embedding / chain / HTTP root).
  2. 207 errors[] logged (truncated is fine).
  3. cargo fmt; commits retitled; no streaming files.
  4. Product env docs + SPEC-124 note that ingestion is a 3.x compatibility fallback; upgrade to 3.225+ still recommended.
  5. --force via PUT, or removed.

After you push, I will re-review langfuse_ingestion.rs + subscriber.rs only.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker. This heuristic plus format!("{obs_type}-create") (L333) is invalid on Langfuse 3.1.1, which is the version this fallback exists for.

3.1.1 ingestion.yml envelope types are only trace-create / span-create / generation-create / event-create (plus score/sdk-log and legacy observation-*). ObservationType is SPAN | GENERATION | EVENT.

LAW-124-13 tags retriever / embedding / chain. Embedding spans carry a model → forced generation-create. Retriever / chain typically do not → retriever-create / chain-create → 207 errors.

Required mapping for 3.1: generation → generation-create; everything else → span-create. Do not stringify arbitrary observation types into the envelope type.

Please unit-test spans_to_batch for generation, retriever, embedding, chain, and a root HTTP span.

let code = resp.status().as_u16();
if resp.status().is_success() || code == 207 {
Ok(())
} else {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker. HTTP 207 is already 2xx (is_success()), so || code == 207 is redundant — and this branch never reads errors[]. The ingestion API returns 207 with per-event failures for illegal types (retriever-create, etc.). Silent Ok(()) is how traces can look enabled while retrieval/ingest observations never land.

Please parse the JSON body and log/surface truncated errors[] even on 207.

skipped += 1
continue
try:
status, _ = request(api(args.base_url, "/api/public/models"), token, "POST", m)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--force does not overwrite. It only skips the GET of existing names (L148), then still POSTs. Langfuse updates via PUT /api/public/models/{id}. Duplicate POSTs will fail; the flag is misleading. Implement PUT or remove --force.

@raphaelmansuy

Copy link
Copy Markdown
Owner

Status vs v0.26.2: this PR was not merged. The 3.1 OTLP-gap diagnosis was correct; the product cut that landed is v0.26.2 (#391), after the review blockers here (illegal 3.1 envelope types, silent 207, docs, fmt).

What 0.26.2 actually does

One env, two transports. Default EDGEQUAKE_LANGFUSE_API=auto:

  1. Probe POST {LANGFUSE_BASE_URL}/api/public/otel/v1/traces once at startup.
  2. HTTP 404 only → native POST /api/public/ingestion.
  3. Anything else (200, 401, network error, …) → keep OTLP. 401/network do not fall back.

Settings/health expose requested api and resolved api_resolved. On 3.1.x you must see api_resolved=ingestion. Never set EDGEQUAKE_LANGFUSE_API=otlp against 3.1.

Operator notes: docs/operations/langfuse-3.1.md · upgrade-to-0.26.2.md.

Honest split: 3.1.x vs newer 3.x (not “3.2”)

Langfuse added OTLP in v3.22.0, not 3.2. We do not special-case a 3.2 line.

Langfuse Transport in 0.26.2 What you see in the UI
3.1.x (proven 3.1.1) Ingestion fallback LLM calls as GENERATION; retriever / embedding / chain (LAW-124-13) land as SPAN. 3.1.1 has no those observation types.
≥ 3.22 / Cloud / in-repo v4 OTLP (unchanged) Full OTEL types. Probe does not 404 → api_resolved=otlp.

We did not certify “every Langfuse ≥ 2.x”. Live proof on this cut: isolated 3.1.1 (make spec124-langfuse-3.1-e2e) plus the existing OTLP path for v4/kind. Intermediate 3.x between 3.1 and 3.22 is the same as 3.1 if OTLP 404s, and the same as 3.22+ if it does not.

Envelope mapping on the 3.1 bridge (the blocker on this PR): generation → generation-create; everything else → span-create. We never stringify retriever-create / embedding-create / chain-create. HTTP 207 reads errors[] and fails closed if any event was rejected.

Ingestion is a compatibility bridge, not the long-term path. Cloud sunsets it 2026-11-16; self-hosted v4 events_only rejects it. Upgrade Langfuse to ≥ 3.22 (or v4) is still the recommendation. No EdgeQuake code change when you do: leave auto.

What this PR asked for that is not “dual version support”

  • $0.00 costs are still Langfuse’s catalogue (LAW-124-12): EdgeQuake does not emit cost attributes. Optional make langfuse-sync-prices can push models.toml prices; it does not change the export path.
  • Streaming/SSE on this branch’s commits is a separate 0.26.2 fix, not Langfuse transport.

If this fork has no remaining unique work (price-sync --force PUT, etc.), it can be closed as superseded by v0.26.2. Happy to re-review a narrow follow-up PR if something here is still missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants