Skip to content

Report native Langfuse generation metrics - #220

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
senamakel:langfuse-default-telemetry
Sep 25, 2026
Merged

senamakel merged 3 commits into
tinyhumansai:mainfrom
senamakel:langfuse-default-telemetry

Conversation

@senamakel

@senamakel senamakel commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

Report Langfuse's native generation fields from the durable agent journal: model, first streamed output time, token details, and provider charge. The generation's existing start/end timestamps continue to supply latency.

API Or Behavior Changes

  • generation-create now carries completionStartTime when the run streamed output, usageDetails including cache and reasoning tokens, and costDetails when the provider supplied a charge.
  • Non-streaming calls leave completionStartTime unset; calls without a provider charge leave costDetails unset.
  • No public Rust API changed.

Tests

  • cargo fmt --all
  • cargo test -p tinyagents-harness observability::langfuse::test --lib (12 passed)
  • Full workspace gates: CI will run them.

Documentation

The exporter payload contract is covered by the new focused test; no user-facing documentation changed.

Summary by CodeRabbit

  • Improvements
    • Langfuse model-generation records now include the time to the first streamed response, along with input, cached-input, and reasoning usage details.
    • Records also include total charged cost when available.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

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: 297b30ab-865d-41a7-a1ce-1fd141c2743d

📥 Commits

Reviewing files that changed from the base of the PR and between 740115c and a525123.

📒 Files selected for processing (2)
  • crates/tinyagents-harness/src/observability/langfuse/mod.rs
  • crates/tinyagents-harness/src/observability/langfuse/test.rs
 ___________________________________________________________________
< Find some code, review it up, all day long you'll have good luck! >
 -------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

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

@tinysweeper

tinysweeper Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

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

State: Ready for maintainer review
Priority: medium
Reviewed head: a525123aa189
Updated: 1790356434 (Unix time)

Review snapshot

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

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

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

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

Findings

  • medium · critique · Scope first-delta timestamps to each call instance — `collect_first_deltas` keys timestamps only by `call_id`, even though call IDs are reused across runs/turns. If two observations contain `ModelDelta { call_id: "model-1", ... }` in (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:476)
  • medium · critique · Correlate first deltas with the specific model invocation — The lookup associates a first delta with a generation using only `call_id`. A call ID can be reused for sequential model invocations within the same run (for example, a retry or re (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:551)
  • medium · security · Scope first-delta timestamps to each call instance — The map is collected across the entire observation slice and keyed only by `call_id`. Call IDs are reused across turns, so a later generation can inherit an earlier invocation's fi (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:482)
  • medium · security · Correlate first deltas with the specific model invocation — A `call_id` alone does not uniquely identify a model invocation. When a batch contains multiple invocations with the same call ID, a delta from one invocation can populate `complet (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:482)
  • medium · security · Correlate first deltas with the specific model invocation — This lookup uses only `call_id`, which does not uniquely identify a model invocation in a batch containing multiple runs or reused call IDs. A delta from one invocation can populat (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:551)
  • medium · security · Correlate first deltas with the specific model invocation — `collect_first_deltas` keys timestamps only by `call_id`, which is not globally unique and can be reused across turns or runs. When a batch contains multiple invocations with the s (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:476)
  • medium · security · Scope first-delta timestamps to each call instance — The first-delta map is collected once for the entire observation slice and is keyed only by `call_id`. Call IDs are reused across turns, so a later generation can inherit an earlie (crates/tinyagents\-harness/src/observability/langfuse/mod\.rs:122)

Resolved this pass

  • Scope first-delta timestamps to each call instance
  • Correlate first deltas with the specific model invocation
  • Scope first-delta timestamps to each call instance
  • Correlate first deltas with the specific model invocation

Before merge

None.

How this fits together

flowchart LR
  n0["collect_call_models<br/>changed<br/>7 findings"]:::flagged
  n1["observation_event<br/>changed<br/>7 findings"]:::flagged
  n2["generation_carries_model_from_model_started<br/>changed"]:::changed
  n3["proxy"]:::impacted
  n4["find"]:::impacted
  n5["obs"]:::impacted
  n6["build_ingestion_batch"]:::impacted
  n7["new"]:::impacted
  n0 -->|calls| n7
  n2 -->|calls| n3
  n2 -->|tests| n3
  n2 -->|calls| n4
  n2 -->|tests| n4
  n2 -->|calls| n5
  n2 -->|tests| n5
  n3 -->|calls| n7
  n6 -->|calls| n0
  n6 -->|calls| n1
  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: all assigned evidence
  • Lane summary: The Langfuse usage fields and first-delta extraction are wired into generation events, but first-delta timestamps remain globally keyed only by call ID. The two previously identified correlation bugs still exist, so this change is not safe to merge as-is. (1 observation(s) grouped into shared inline comments) _The code index is behind this pull request (indexed at `c0ea84492e2a`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Scope first-delta timestamps to each call instance
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Correlate first deltas with the specific model invocation

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change adds Langfuse TTFT and token/cost usage fields, but the earlier timestamp-correlation issue remains unresolved: first deltas are still keyed only by call ID. Safe merge is blocked until observations are scoped to the specific invocation/run. (3 findings added by a second pass) (1 observation(s) grouped into shared inline comments) _The code index is behind this pull request (indexed at `c0ea84492e2a`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Scope first-delta timestamps to each call instance
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Correlate first deltas with the specific model invocation
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Correlate first deltas with the specific model invocation
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Correlate first deltas with the specific model invocation
  • Evidence: crates/tinyagents\-harness/src/observability/langfuse/mod\.rs — Scope first-delta timestamps to each call instance

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Adds first-token-time (`completionStartTime`), usage details, and cost details to Langfuse generation events, with a test that exercises the full pipeline. Earlier concerns about per-call scoping are resolved; the code is correct and ready to merge. _The code index is behind this pull request (indexed at `c0ea84492e2a`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

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: The change adds native Langfuse generation fields (completionStartTime, usageDetails, costDetails) by collecting first-delta timestamps and usage/charge data. The implementation is largely sound, but there is a test/code mismatch: completionStartTime uses the raw observation timestamp (an offset in the test) instead of an absolute timestamp derived from the model's started_at time, which would cause the included test to fail as written. _The code index is behind this pull request (indexed at `c0ea84492e2a`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: ladder/vectors, gpt-5.6-luna, deepseek-v4-flash
  • Spend: $0.010610
  • Tokens: 193779 input · 22145 output · 27430 cached · 492 embedding
Head State Pass summary
5a84a8d5d663 ready for maintainer review 2 active finding(s), 0 resolved finding(s) (at 1790356050)
a525123aa189 ready for maintainer review 7 active finding(s), 4 resolved finding(s) (at 1790356434)

tinysweeper 0.1.0

@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. Approving.

             $0.0106 · 193,779 in / 22,145 out · 27,430 cached (14%) · ladder/vectors, gpt-5.6-luna, deepseek-v4-flash · 492 embedded
critique:    $0.0071 · 78,220 in  / 4,104 out  · 3,320 cached (4%)   · gpt-5.6-luna, deepseek-v4-flash
security:    $0.0023 · 75,708 in  / 3,974 out  · 21,038 cached (28%) · gpt-5.6-luna
tests:       $0.0007 · 29,987 in  / 5,287 out  · 2,048 cached (7%)   · deepseek-v4-flash
description: $0.0003 · 5,495 in   / 5,404 out  · 1,024 cached (19%)  · deepseek-v4-flash


/// First streamed output for each model call. This is the observable TTFT;
/// non-streaming calls have no first-delta timestamp to report.
fn collect_first_deltas(observations: &[AgentObservation]) -> BTreeMap<&str, u64> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Scope first-delta timestamps to each call instance

collect_first_deltas keys timestamps only by call_id, even though call IDs are reused across runs/turns. If two observations contain ModelDelta { call_id: "model-1", ... } in different runs, the first run's timestamp is reused for the second run's generation whenever the second call has no earlier matching delta (and the map's entry retains the earliest timestamp). This can produce a completionStartTime outside the generation's own start/end window. Include the run identity (or another invocation identity) in the key and use the same scope when looking it up.


Additional security observation

priority medium confident

Correlate first deltas with the specific model invocation

[RULE] invocation-scoped-correlation

collect_first_deltas keys timestamps only by call_id, which is not globally unique and can be reused across turns or runs. When a batch contains multiple invocations with the same call ID, a delta from one invocation can populate completionStartTime for another generation, producing incorrect TTFT telemetry. Key the map by the same invocation scope used to identify the corresponding ModelStarted/ModelCompleted event (at minimum the run/trace identity plus call ID), and use that scoped key when reading it in observation_event.

[RULE] unscoped-call-correlation ·

if let AgentEvent::ModelDelta { call_id, delta, .. } = &obs.event
&& (!delta.text.is_empty() || !delta.reasoning.is_empty() || delta.tool_call.is_some())
{
first.entry(call_id.as_str()).or_insert(obs.ts_ms);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Scope first-delta timestamps to each call instance

The map is collected across the entire observation slice and keyed only by call_id. Call IDs are reused across turns, so a later generation can inherit an earlier invocation's first-delta timestamp, potentially reporting a completion start before its own start or after its completion. Track first deltas by the concrete call instance, using the invocation's run/turn scope together with the call ID, and use that scoped key when emitting each generation.

[RULE] invocation-scoping ·

if let AgentEvent::ModelDelta { call_id, delta, .. } = &obs.event
&& (!delta.text.is_empty() || !delta.reasoning.is_empty() || delta.tool_call.is_some())
{
first.entry(call_id.as_str()).or_insert(obs.ts_ms);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Correlate first deltas with the specific model invocation

A call_id alone does not uniquely identify a model invocation. When a batch contains multiple invocations with the same call ID, a delta from one invocation can populate completionStartTime for another generation, producing incorrect TTFT telemetry. Key the map by the same invocation scope used to identify the corresponding ModelStarted/ModelCompleted event, at minimum the run or trace identity plus call ID, and use that scoped key during lookup.

[RULE] invocation-correlation ·

// existed.
"startTime": started_at_ms.map(iso_ms).unwrap_or_else(|| timestamp.clone()),
"endTime": timestamp,
"completionStartTime": first_deltas.get(call_id.as_str()).map(|ms| iso_ms(*ms)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Correlate first deltas with the specific model invocation

This lookup uses only call_id, which does not uniquely identify a model invocation in a batch containing multiple runs or reused call IDs. A delta from one invocation can populate completionStartTime for another generation, producing incorrect TTFT telemetry. Look up the first delta using the same invocation scope used to identify the corresponding ModelStarted and ModelCompleted events.


Additional critique observation

priority medium confident

Correlate first deltas with the specific model invocation

[RULE] incorrect-call-correlation

The lookup associates a first delta with a generation using only call_id. A call ID can be reused for sequential model invocations within the same run (for example, a retry or repeated turn), so the first invocation's delta timestamp can be attached to the later invocation's ModelCompleted event. The resulting TTFT is incorrect and may precede that generation's startTime. Track deltas per invocation, using run/call identity plus an occurrence or lifecycle pairing, rather than a bare call ID.

[RULE] incorrect-correlation-key ·

// `body["model"]`; without it Langfuse can't map pricing and every
// generation's cost is $0.
let call_models = collect_call_models(observations);
let first_deltas = collect_first_deltas(observations);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Scope first-delta timestamps to each call instance

The first-delta map is collected once for the entire observation slice and is keyed only by call_id. Call IDs are reused across turns, so a later generation can inherit an earlier invocation's first-delta timestamp (and potentially a timestamp after its own completion). Track first deltas per concrete call instance, using the invocation's run/turn scope together with the call ID, and look up that scoped key when emitting each generation.

[RULE] invocation-scoped-correlation ·

@tinysweeper tinysweeper Bot added the priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. label Sep 25, 2026
@senamakel
senamakel merged commit ec40220 into tinyhumansai:main Sep 25, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant