Skip to content

feat(observability): export Langfuse v4 traces over OTLP - #467

Merged
senamakel merged 7 commits into
tinyhumansai:mainfrom
senamakel:langfuse-v4
Oct 15, 2026
Merged

senamakel merged 7 commits into
tinyhumansai:mainfrom
senamakel:langfuse-v4

Conversation

@senamakel

@senamakel senamakel commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Langfuse v4 (changelog, ingestion migration) ingests traces only over OTLP, and Langfuse Cloud removes the legacy /api/public/ingestion trace path on 2026-11-16. Agent turns already export over OTLP. The graph exporter, and the harness send_observations, still sent traces only as legacy batches. This PR adds a v4 OTLP path for both and hardens send_otlp.

  • LangfuseClient::send_otlp now sends x-langfuse-ingestion-version: 4 (LANGFUSE_INGESTION_VERSION_HEADER), which selects v4's observations-first write path. A 2xx answer whose OTLP partialSuccess.rejectedSpans is non-zero is now an error; before, it counted as a success.
  • New trace_export::ingestion_otlp (pure): ingestion_batch_to_otlp_spans / ingestion_batch_otlp_requests convert a locally built ingestion batch into v4 OTLP spans, following the migration guide:
    • Trace name, user, session, tags, trace.metadata.*, environment, release and version go on every span. v4 filters observations directly, so a value set only on the root is invisible on a child.
    • Trace input/output move to the root observation (langfuse.observation.input/output). Nothing writes langfuse.trace.input.
    • Parents ride the OTEL parent span context. Span ids are hashed from the batch's deterministic observation ids, so a graph run's sub-agents still nest under its run span.
    • The mapping covers levels and status, generation model/usage/cost/completion start, and observation metadata. A span still open when the batch was built is closed at its start and flagged unfinished. Scores and other non-trace events are skipped and keep using the scores API.
  • LangfuseClient::send_observations_otlp / send_batch_otlp and GraphLangfuseExporter::send_observations_otlp / build_otlp_requests are the v4 export entry points. The batch methods stay, with docs pointing to the OTLP ones.
  • trace_export::otlp::chunk_otlp_spans exposes the existing 20-span / ~8 MB request chunking so both converters share it.

Docs: the READMEs for trace_export, langfuse and graph observability, plus a CHANGELOG entry.

Tests

  • trace_export::ingestion_otlp tests: trace attributes on every span, root-only trace IO, parentage/level/generation mapping, deterministic ids, open-span handling, request wrapping, rejection of a batch with no trace.
  • langfuse tests: v4 header on direct and proxy clients (failed before the fix), partial rejection is an error (failed before the fix), empty partialSuccess is a success, send_observations_otlp end to end against a loopback server.
  • Graph test: otlp_export_is_a_v4_trace_with_trace_attributes_on_every_span.

cargo test -p tinyagents-graph: 508 passed. cargo test -p tinyagents-harness: 2784 passed. The 3 providers::claude_code::driver retry tests fail locally with NotFound on a fixture binary; that is environmental and unrelated to this change. Clippy is clean.

The OpenHuman host change (flow-run export moves to OTLP) follows in a separate PR that bumps the gitlink.

Summary by CodeRabbit

  • New Features
    • Added Langfuse v4 trace export over OTLP for graph and agent observations, including batch export and chunked requests.
    • Exported spans include trace attributes, observation IDs, and parent relationships. Non-trace events are skipped.
    • OTLP requests identify the ingestion version and report errors when spans are rejected.
  • Documentation
    • Clarified the distinction between v4 OTLP trace export and legacy ingestion, including the planned removal date for the legacy Cloud trace path.

senamakel and others added 7 commits October 15, 2026 17:39
Add an OpenTelemetry Protocol exporter for trace spans so traces can be
shipped to an OTLP collector.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds an OTLP ingestion path for trace export so spans can be received
over the OpenTelemetry protocol instead of only being written out.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add an OTLP ingestion path for trace export alongside the existing Langfuse
integration, with tests covering the new ingestion behaviour.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a langfuse observability module to the harness so traces can be
exported to a langfuse endpoint. The module includes tests covering the
new tracing behaviour.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a langfuse exporter so graph runs can report traces to a langfuse
endpoint, with tests covering the new module.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a test asserting that the OTLP export produces a v4 trace with the
expected trace-level attributes on every span, a single root span, and
correct parent-child nesting for steps and nodes. Also verifies error
level and node metadata on a failing node and the event type on the
run.failed span.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Langfuse v4 ingests traces only over OTLP, and Langfuse Cloud removes the legacy ingestion trace path on 2026-11-16, so the harness now converts a locally built ingestion batch into v4 OTLP spans and sends them with the v4 ingestion version header. Trace attributes ride on every span, trace input/output move to the root observation, and a 2xx answer with rejected spans is reported as an error. The batch-path methods stay for legacy deployments.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T12:33:38.761033Z 71496c9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c3decbd5-034c-4636-aa10-dd3de68ff38b
📥 Commits

Reviewing files that changed from the base of the PR and between c11ff63 and 71496c9.

📒 Files selected for processing: 12
  • CHANGELOG.md
  • crates/tinyagents-graph/src/observability/README.md
  • crates/tinyagents-graph/src/observability/langfuse.rs
  • crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs
  • crates/tinyagents-harness/src/observability/langfuse/README.md
  • crates/tinyagents-harness/src/observability/langfuse/mod.rs
  • crates/tinyagents-harness/src/observability/langfuse/mod_tests.rs
  • crates/tinyagents-harness/src/observability/trace_export/README.md
  • crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs
  • crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs
  • crates/tinyagents-harness/src/observability/trace_export/mod.rs
  • crates/tinyagents-harness/src/observability/trace_export/otlp.rs
+--------------------------------------------------------------------------+
| Your code is like a magic trick: now you see it... now it's `undefined`. |
+--------------------------------------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71496c9394

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.get("id")
.and_then(Value::as_str)
.ok_or_else(|| TinyAgentsError::Validation("trace-create has no id".into()))?;
let trace_hex = hex_id(trace_id, 16);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep score targets aligned with hashed OTLP IDs

When a trace exported through the new OTLP path is later graded through create_score, this unconditionally changes the configured trace ID into the SHA-derived OTLP traceId (and observation_span does the same for observation IDs), while build_score_batch still writes score.trace_id and score.observation_id unchanged in langfuse/mod.rs:283-285. Thus the documented LangfuseScore::numeric("trace-1", ...) flow points at a nonexistent trace after an OTLP export, and observation-scoped scores are orphaned as well; expose/return this ID transformation and apply it to score targets, or otherwise preserve the same externally visible target IDs.

Useful? React with 👍 / 👎.

Comment on lines +318 to +319
let payload = self.build_ingestion_batch(trace, observations)?;
self.send_batch_otlp(&payload, service).await

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent repeated OTLP exports from duplicating spans

When callers follow langfuse/README.md:9-13 and export an accumulated journal on a timer (or retry an export), this method rebuilds and sends every previously seen span again. Langfuse v4 duplicates an already-received span ID instead of merging it (trace_export/README.md:61-62), so repeated calls silently duplicate generations and their token/cost totals; even passing only newly read events re-synthesizes the deterministic run span on every call. Track which finalized spans were sent, or restrict this API to a documented one-shot terminal batch rather than recommending it for periodic export.

AGENTS.md reference: AGENTS.md:L86-L90

Useful? React with 👍 / 👎.

//! [`ExportBrand`] so product identity strings stay out of this crate.

pub mod ingestion_batch;
pub mod ingestion_otlp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-export the OTLP helpers from the crate root

For downstream users, this new public module exposes ingestion_batch_to_otlp_spans and ingestion_batch_otlp_requests only through tinyagents_harness::observability::trace_export, and the new chunk_otlp_spans helper is likewise absent from src/lib.rs. Re-export the intended public helpers from the package root (or keep them private) so this addition follows the repository's centralized public-surface convention.

AGENTS.md reference: AGENTS.md:L68-L70

Useful? React with 👍 / 👎.

@tinysweeper

tinysweeper Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 5 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Incomplete
Priority: none
Reviewed head: 71496c9394e0
Updated: 2026-10-15T12:51:01Z

Review snapshot

Change surface Files Review signal Count
Production 5 Active findings 0
Tests 3 Noted findings 0
Documentation 4 Resolved findings 0
Configuration 0 Pending checks/questions 13

Completeness: Incomplete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

  • Unreviewed: tinysweeper/tests

Findings

No active actionable findings.

Could not review: crates/tinyagents-graph/src/observability/README.md, crates/tinyagents-graph/src/observability/langfuse.rs, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/langfuse/mod.rs, crates/tinyagents-harness/src/observability/trace_export/README.md, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs, tinysweeper/tests

Before merge

  • Complete the critique review for crates/tinyagents-graph/src/observability/README.md, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/trace_export/README.md, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs.
  • Complete the security review for crates/tinyagents-graph/src/observability/langfuse.rs, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/langfuse/mod.rs, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs.
  • Complete the tests review for tinysweeper/tests.

How this fits together

flowchart LR
  n0["...s_root_identity_without_an_explicit_trace<br/>changed"]:::changed
  n1["...ce_endpoint_beside_the_ingestion_endpoint<br/>changed"]:::changed
  n2["reports_a_refused_otlp_request<br/>changed"]:::changed
  n3["...son_to_the_trace_endpoint_with_basic_auth<br/>changed"]:::changed
  n4["unwrap"]:::impacted
  n5["Value"]:::impacted
  n6["build_graph_langfuse_batch"]:::impacted
  n7["...ata_injector_merges_and_wins_on_collision"]:::impacted
  n0 -->|calls| n4
  n0 -->|tests| n4
  n0 -->|calls| n6
  n0 -->|tests| n6
  n1 -->|calls| n4
  n1 -->|tests| n4
  n2 -->|calls| n4
  n2 -->|tests| n4
  n3 -->|calls| n4
  n3 -->|tests| n4
  n4 -->|uses| n5
  n6 -->|uses| n5
  n7 -->|calls| n4
  n7 -->|tests| n4
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: incomplete; unanswered: crates/tinyagents-graph/src/observability/README.md, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/trace_export/README.md, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs
  • Lane summary: Reviewed 6 files; 0 findings. 6 files could not be reviewed: crates/tinyagents-graph/src/observability/README.md, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/trace_export/README.md, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs. _The code index is behind this pull request (indexed at `224295d664cc`), so retrieved context may be out of date._

security

  • Conclusion: Success
  • Scope reviewed: incomplete; unanswered: crates/tinyagents-graph/src/observability/langfuse.rs, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/langfuse/mod.rs, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs
  • Lane summary: Reviewed 2 files; 0 findings. 6 files could not be reviewed: crates/tinyagents-graph/src/observability/langfuse.rs, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/langfuse/mod.rs, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs, crates/tinyagents-harness/src/observability/trace_export/otlp.rs. 4 files were not security-reviewed: CHANGELOG.md (prose or tabular data), crates/tinyagents-graph/src/observability/README.md (prose or tabular data), crates/tinyagents-harness/src/observability/langfuse/README.md (prose or tabular data), crates/tinyagents-harness/src/observability/trace_export/README.md (prose or tabular data). _The code index is behind this pull request (indexed at `224295d664cc`), so retrieved context may be out of date._

tests

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: tinysweeper/tests
  • Lane summary: No reviewer produced a usable response.

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Provisional: I have the complete diff and the description is a detailed match for it. Checking the visible code against the claims — the v4 header, partialSuccess handling, the ingestion_otlp converter, the new send_observations_otlp entry points, and the tests — everything lines up. One thing I would like to verify before a final verdict is that the `observation_type` helper's comment claims `trace-create` is 'not an observation', which is consistent; and that `chunk_otlp_spans` exists at the signature shown. But since the change calls into helpers like `hex_id`, `attribute`, and `chunk_otlp_spans` that I can see in the diff, and the module docs and test names match the body's bullet points exactly, I have no unresolved doubts that require lookups. The description accurately describes what the diff does: it adds a v4 OTLP path for both graph and harness exporters, hardens `send_otlp` to reject partial successes, documents the migration, and includes tests for each claimed behavior. I found no discrepancy between the description and the change. _The code index is behind this pull request (indexed at `224295d664cc`), so retrieved context may be out of date._
Evidence and run details
  • Models: ladder/vectors-oai3, glm-5.3-flash,
  • Spend: $0.004204
  • Tokens: 290311 input · 15922 output · 0 cached · 793 embedding
Head State Pass summary
71496c9394e0 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-10-15T12:51:01Z)

tinysweeper 0.1.2

@senamakel
senamakel merged commit 5f6dce5 into tinyhumansai:main Oct 15, 2026
11 of 12 checks passed

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinyagents-graph/src/observability/README.md, crates/tinyagents-graph/src/observability/langfuse.rs, crates/tinyagents-graph/src/observability/langfuse/langfuse_tests.rs, crates/tinyagents-harness/src/observability/langfuse/mod.rs, crates/tinyagents-harness/src/observability/trace_export/README.md, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp.rs, crates/tinyagents-harness/src/observability/trace_export/ingestion_otlp_tests.rs, crates/tinyagents-harness/src/observability/trace_export/mod.rs and 2 more.

             $0.0042 · 290,311 in / 15,922 out · 0 cached (0%) · ladder/vectors-oai3, glm-5.3-flash,  · 793 embedded
critique:    $0.0034 · 213,914 in / 14,900 out · 0 cached (0%) · glm-5.3-flash,
security:    $0.0005 · 55,251 in  / 781 out    · 0 cached (0%) · glm-5.3-flash,
description: $0.0002 · 21,146 in  / 241 out    · 0 cached (0%) · glm-5.3-flash

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant