Skip to content

fix(observability): chunk Langfuse ingestion batches to <=500 events#4676

Merged
M3gA-Mind merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/langfuse-ingestion-batch-chunk
Jul 7, 2026
Merged

fix(observability): chunk Langfuse ingestion batches to <=500 events#4676
M3gA-Mind merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/langfuse-ingestion-batch-chunk

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Large agent turns (especially ones that spawn sub-agents) produce more than Langfuse's 500-event-per-batch limit. push_observations sent them as one batch, so Langfuse rejected the entire trace with 400 "batch cannot exceed 500 events" and the turn never appeared in the dashboard — even though the turn itself succeeded.

Fix: split the ingestion batch into ≤500-event chunks and send each sequentially. Langfuse dedupes by event id and resolves observations to their trace by traceId, so a run's events can be delivered across several requests (the trace-create stays in chunk 1). Adds two unit tests.

Note: the sibling legacy push_spans path has the same one-shot POST and latent limit — left as a follow-up since the live journal path (push_observations) is the one producing these traces.

Verified: GGML_NATIVE=OFF cargo check passes.

Closes #4675

Summary by CodeRabbit

  • Bug Fixes
    • Prevents ingestion failures when sending large trace batches by automatically splitting them into smaller requests.
    • Reduces rejected uploads from the tracing service when event counts exceed its limit.
    • Preserves payload details and event order while processing large submissions.

Large agent turns — especially ones that spawn sub-agents — can produce far
more than 500 trace events. push_observations POSTed them as a single Langfuse
ingestion batch, which Langfuse rejects with 400 "batch cannot exceed 500
events", dropping the ENTIRE trace. Split the batch into <=500-event chunks and
send each; Langfuse dedupes by event id and resolves observations to their
trace by traceId, so a run's events may span several requests.
@M3gA-Mind M3gA-Mind requested a review from a team July 7, 2026 22:07
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds batching support to Langfuse ingestion to avoid 400 errors when payloads exceed the 500-event limit. Introduces a LANGFUSE_MAX_BATCH_EVENTS constant and split_ingestion_batch helper, updates push_observations to send chunked requests, and adds unit tests.

Changes

Langfuse Ingestion Batch Chunking

Layer / File(s) Summary
Batch splitting helper and wiring
src/openhuman/agent/progress_tracing/langfuse.rs
Adds LANGFUSE_MAX_BATCH_EVENTS constant and split_ingestion_batch helper that chunks oversized batch arrays while preserving other payload fields; updates push_observations to iterate chunks and call send_batch per chunk instead of once.
Unit tests for chunking behavior
src/openhuman/agent/progress_tracing/langfuse.rs
Adds tests verifying correct chunk sizes, metadata preservation, event ordering for over-limit payloads, and pass-through for small or batch-less payloads.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PushObservations as push_observations
  participant Splitter as split_ingestion_batch
  participant Client as LangfuseClient

  PushObservations->>Splitter: split_ingestion_batch(payload, 500)
  Splitter-->>PushObservations: Vec of chunked payloads
  loop each chunk
    PushObservations->>Client: send_batch(chunk)
    Client-->>PushObservations: result / timeout error
  end
Loading

Possibly related PRs

  • tinyhumansai/openhuman#4562: Introduced the journal-based push_observations ingestion pathway that this PR extends with batch chunking.

Suggested labels: rust-core, agent

Poem

A hop, a batch, five hundred strong,
Too many events, the queue was long!
So I split the pile, chunk by chunk,
No more four-hundreds, no more junk 🐰
Off to Langfuse, hop hop hop —
Every trace lands, none will drop!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: chunking Langfuse ingestion batches to stay within the 500-event limit.
Linked Issues check ✅ Passed The change splits Langfuse ingestion into <=500-event chunks and updates push_observations as required by issue #4675.
Out of Scope Changes check ✅ Passed The changes stay focused on Langfuse batch chunking and related tests, with no obvious unrelated scope creep.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 7, 2026
@M3gA-Mind M3gA-Mind merged commit c38d4c9 into tinyhumansai:main Jul 7, 2026
15 of 20 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/openhuman/agent/progress_tracing/langfuse.rs (1)

480-493: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Minor: redundant cloning for large batches.

For a batch that needs chunking, this clones the events vec once (line 482), then clones the full payload once per chunk (line 488) before overwriting batch, plus chunk.to_vec() copies each chunk again. For a ~1200-event payload this means the roughly-full JSON structure gets copied multiple times. Given this only triggers on rare oversized turns, the cost is likely negligible, but if oversized turns become common (e.g. many sub-agents), building each chunk from a shared Map (cloning only non-batch keys once) would avoid the repeated full-payload clones.

♻️ Possible optimization
 fn split_ingestion_batch(payload: Value, max: usize) -> Vec<Value> {
     let events = match payload.get("batch").and_then(Value::as_array) {
         Some(events) if max > 0 && events.len() > max => events.clone(),
         _ => return vec![payload],
     };
-    events
-        .chunks(max)
-        .map(|chunk| {
-            let mut part = payload.clone();
-            part["batch"] = Value::Array(chunk.to_vec());
-            part
-        })
-        .collect()
+    let mut base = payload;
+    if let Some(obj) = base.as_object_mut() {
+        obj.remove("batch");
+    }
+    events
+        .chunks(max)
+        .map(|chunk| {
+            let mut part = base.clone();
+            part["batch"] = Value::Array(chunk.to_vec());
+            part
+        })
+        .collect()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent/progress_tracing/langfuse.rs` around lines 480 - 493, The
chunking path in split_ingestion_batch redundantly clones the full payload for
every chunk and copies each chunk again, which is wasteful for large oversized
batches. Refactor split_ingestion_batch to build each part from shared non-batch
data instead of cloning the entire Value repeatedly, and only materialize the
per-chunk batch contents once; keep the same behavior for max <= 0 or non-array
batch inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/openhuman/agent/progress_tracing/langfuse.rs`:
- Around line 480-493: The chunking path in split_ingestion_batch redundantly
clones the full payload for every chunk and copies each chunk again, which is
wasteful for large oversized batches. Refactor split_ingestion_batch to build
each part from shared non-batch data instead of cloning the entire Value
repeatedly, and only materialize the per-chunk batch contents once; keep the
same behavior for max <= 0 or non-array batch inputs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 297418d5-4b66-4e40-8e78-2258144265e9

📥 Commits

Reviewing files that changed from the base of the PR and between 9168365 and 164e928.

📒 Files selected for processing (1)
  • src/openhuman/agent/progress_tracing/langfuse.rs

@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: 164e92848a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +540 to +541
for chunk in split_ingestion_batch(payload, LANGFUSE_MAX_BATCH_EVENTS) {
tokio::time::timeout(PUSH_TIMEOUT, client.send_batch(chunk))

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 Keep Langfuse chunking within the push timeout

When a large trace is split into many chunks and the Langfuse proxy is slow or hung, this applies the 10s PUSH_TIMEOUT to each chunk rather than to the whole best-effort push. The previous single POST could delay teardown by at most one timeout, but a 20-chunk trace can now block this export path for roughly 200s before returning. Wrap the whole chunk loop in a single timeout/deadline, or track the remaining budget per chunk.

Useful? React with 👍 / 👎.

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

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[observability] Langfuse traces dropped for large turns — ingestion batch >500 events rejected (no chunking)

2 participants