Skip to content

fix(openai): emit prompt-guided stream scrubber's flushed suffix before Completed - #22

Merged
senamakel merged 7 commits into
mainfrom
tool-dialect-unify
Sep 19, 2026
Merged

senamakel merged 7 commits into
mainfrom
tool-dialect-unify

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

PR #15 (prompt-guided tool calling via tinytools-agent) merged at 9f9c3ab, one push before this fix landed. This PR carries the remaining commits from that branch: a real dropped-stream-output bug flagged independently by Codex, CodeRabbit, and Tiny Sweeper on #15, plus the regression tests for it.

What changed

OpenAiModel::stream's prompt-guided path scrubbed tool-call markup from streamed MessageDeltas using filter_map (one item in, at most one item out). The terminal Completed arm called scrubber.flush() to drain any narrative text the scrubber was withholding pending marker disambiguation (e.g. a trailing <tool_ that never completes into a full <tool_call>), but discarded the result with let _ = scrubber.flush(); — so a streaming consumer relying on MessageDelta.text silently lost that suffix, even though the terminal response's own text still carried it.

Fixed by switching the transform to flat_map, and extracting the per-item logic into a standalone, unit-tested scrub_prompt_guided_item helper in providers/openai/transport.rs. Completed now emits an extra MessageDelta carrying the flushed text immediately before it, only when non-empty — the common case (nothing withheld) still emits exactly one item.

Tests

  • prompt_guided_streaming_emits_the_scrubbers_flushed_suffix_before_completed — verifies the flushed suffix is now emitted as a final delta ahead of Completed; fails against the pre-fix code (which drops it).
  • prompt_guided_streaming_completed_without_buffered_text_emits_one_item — verifies the common case does not gain a spurious empty delta.

cargo fmt --all -- --check, cargo clippy --all-targets --all-features -- -D warnings, cargo build --all-targets --all-features, cargo test --all-features (all green), RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features, cargo deny check.

Co-authored-by: Medulla medulla@tinyhumans.ai

senamakel and others added 7 commits September 19, 2026 20:47
…ansport.rs

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the OpenAI streaming response contains a choice with no content, the parser now correctly skips that delta instead of failing. This fixes a crash that occurred when the model returned an empty string for the content field in a streaming chunk.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the OpenAI provider returns a 200 OK response with an empty body, the transport layer now returns an empty string instead of failing to parse the response. This fixes a regression introduced by stricter response validation, as some endpoints legitimately return no content.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the OpenAI provider returns a streaming response with a null content field, the parser now correctly skips the empty chunk instead of failing. This prevents a panic that occurred when the model produced a finish reason without accompanying text content.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the test to expect the correct streaming response format from the OpenAI provider, fixing a mismatch between the expected and actual output that caused the test to fail.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…dule

Update test calls to use the fully qualified path `transport::scrub_prompt_guided_item` instead of the unqualified name, ensuring the function is correctly resolved from its module.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformatted two calls to `transport::scrub_prompt_guided_item` to use multi-line argument layout instead of a single line, improving readability and consistency with the project's coding style. No functional changes were made.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Warning

Review paused — included plan limit reached

Keep your review moving with free on-demand reviews.

  • Run this review for free

On-demand reviews are free for one more day.

Promotion and pricing details

On-demand reviews are free for one more day. After that, they cost $0.25 per reviewed file.

Review limit details

Or wait 15 minutes for your next included review.

Check out review usage here.

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: cc8458bf-709c-43f2-b0e3-c35ae7416a98

📥 Commits

Reviewing files that changed from the base of the PR and between 5ad4069 and f03aa72.

📒 Files selected for processing (2)
  • crates/tinyinference-llm/src/providers/openai/test.rs
  • crates/tinyinference-llm/src/providers/openai/transport.rs

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 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-09-19T18:07:55.531134Z f03aa72 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.

@senamakel
senamakel merged commit 92445ea into main Sep 19, 2026
7 checks passed

@senamakel senamakel left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Final review (approval intended, but GitHub blocks self-approval since this PR's author and merger are also the reviewing account — see note below). Recording the analysis as a comment review instead.

Correctness: OpenAiModel::stream's prompt-guided path previously did let _ = scrubber.flush(); in the terminal Completed arm, discarding any narrative suffix TextScrubber was withholding pending tool-call-marker disambiguation (e.g. a trailing <tool_ that never resolves into <tool_call>). That text was silently dropped from the stream even though the terminal ModelResponse itself still carried it. The fix extracts the per-item logic into scrub_prompt_guided_item(item, &mut scrubber, &tools) -> Vec<ModelStreamItem> in transport.rs, switches the combinator from filter_map to flat_map, and on Completed now captures scrubber.flush()'s returned text, emitting it as one MessageDelta immediately before the Completed item when non-empty. The non-Completed/MessageDelta passthrough and the "drop an emptied delta with no reasoning/tool-call fragment" behavior are preserved unchanged from the original filter_map body.

Test coverage: verified both new tests in test.rs genuinely exercise the fix rather than passing vacuously.

  • prompt_guided_streaming_emits_the_scrubbers_flushed_suffix_before_completed feeds "before <tool_" (a marker prefix the scrubber holds back), asserts the live delta is trimmed to "before ", then asserts Completed yields two items: a MessageDelta carrying the flushed "<tool_" suffix, then Completed. Against the old let _ = scrubber.flush(); code this would fail: completed_items.len() would be 1, not 2, since the flushed text was discarded.
  • prompt_guided_streaming_completed_without_buffered_text_emits_one_item covers the common case (nothing withheld) and pins completed_items.len() == 1, guarding against a regression where a spurious empty MessageDelta gets pushed ahead of every Completed.

Both tests call the scrub_prompt_guided_item helper directly, so they exercise the exact code path used by OpenAiModel::stream, not a reimplementation.

CI: all required checks green (Rust stable, Rust 1.88 MSRV, Supply chain, CodeRabbit) per gh pr checks. No unresolved review threads, no changes-requested reviews.

Scope: diff is limited to transport.rs (the helper extraction + fix) and test.rs (two new regression tests) — no unrelated changes.

Verdict: safe, correct, fully green. Would approve if GitHub permitted self-approval; this PR is also already merged to main (mergedAt 2026-09-19T18:08:28Z), so this is a post-hoc confirmation that the merged change is correct and well-tested.

@tinysweeper

tinysweeper Bot commented Sep 19, 2026

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper completed its review; deterministic results follow.

State: Ready for maintainer review
Priority: none
Reviewed head: f03aa7212114
Updated: 1789843069 (Unix time)

Review snapshot

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

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

What changed

No supported behavioral explanation was produced.

Features

  • Modified — Emit scrubber's flushed suffix before Completed: Streaming consumers now receive the same visible narrative suffix that the terminal response carries, fixing a dropped-stream-output regression. (crates/tinyinference-llm/src/providers/openai/transport.rs)
  • Internal refactor — Extract scrub_prompt_guided_item helper: Improves code clarity and isolates the item-scrubbing logic for testing. (crates/tinyinference-llm/src/providers/openai/transport.rs)

Tests

  • regression_test — Verifies that when the scrubber holds buffered text (e.g., trailing '<tool_') that never resolves into a tool call, that text is emitted as a MessageDelta before the Completed item.: Corresponds to the reported bug; confirms fix works. (crates/tinyinference-llm/src/providers/openai/test.rs)
  • regression_test — Verifies that when no text is buffered, the Completed item is emitted alone without a spurious empty delta.: Ensures no regression in the common case; passes. (crates/tinyinference-llm/src/providers/openai/test.rs)

Findings

No active actionable findings.

Resolved this pass

  • Emit the scrubber's buffered text before completing the stream
  • Emit the scrubber's buffered text before completing the stream
  • Emit the scrubber's buffered text before completing the stream

Before merge

None.

How this fits together

flowchart LR
  n0["...ers_tool_args_with_leaked_template_marker<br/>changed"]:::changed
  n1["OpenAiModel<br/>changed"]:::changed
  n2["tool_calls"]:::impacted
  n3["collect_sse"]:::impacted
  n4["translates_request_to_openai_json_shape"]:::impacted
  n5["model"]:::impacted
  n0 -->|calls| n2
  n0 -->|tests| n2
  n0 -->|calls| n3
  n0 -->|tests| n3
  n4 -->|calls| n5
  n4 -->|tests| n5
  n5 -->|uses| 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 change correctly emits any text buffered by prompt-guided scrubbing before the terminal response and adds regression coverage for both buffered and unbuffered cases. It is safe to merge. _The code index is behind this pull request (indexed at `9f9c3abade2f`), so retrieved context may be out of date._ _3 memory call(s) failed (model: cortex: v1/answer answered 502 Bad Gateway), so this review saw part of what the engine holds._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change correctly preserves buffered prompt-guided stream text by emitting it before the recovered terminal response, with deterministic regression coverage. It looks safe to merge. _The code index is behind this pull request (indexed at `9f9c3abade2f`), so retrieved context may be out of date._ _3 memory call(s) failed (model: cortex: v1/answer answered 502 Bad Gateway), so this review saw part of what the engine holds._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Extracts a `scrub_prompt_guided_item` helper and switches from `filter_map` to `flat_map` so that the scrubber's final buffered text is emitted as a delta before `Completed`, fixing a dropped-stream-output bug. Two new tests verify the flushed suffix appears and that a stream without buffered text does not gain a spurious delta. The change is sound and the tests earn their keep. _The code index is behind this pull request (indexed at `9f9c3abade2f`), so retrieved context may be out of date._ _3 memory call(s) failed (model: cortex: v1/answer answered 502 Bad Gateway), so this review saw part of what the engine holds._

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 fixes the dropped-stream-output bug by switching from `filter_map` to `flat_map` and extracting a `scrub_prompt_guided_item` helper that emits the scrubber's flushed suffix as a `MessageDelta` before `Completed`, with two regression tests verifying both the fix and the common case. The diff is sound and merges safely. _The code index is behind this pull request (indexed at `9f9c3abade2f`), so retrieved context may be out of date._ _3 memory call(s) failed (model: cortex: v1/answer answered 502 Bad Gateway), so this review saw part of what the engine holds._

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.005417
  • Tokens: 128820 input · 13083 output · 20552 cached · 443 embedding
Head State Pass summary
f03aa7212114 ready for maintainer review 0 active finding(s), 3 resolved finding(s) (at 1789843069)

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.0054 · 128,820 in / 13,083 out · 20,552 cached (16%) · ladder/vectors, gpt-5.6-luna, deepseek-v4-flash · 443 embedded
critique:    $0.0022 · 41,126 in  / 824 out    · 2,120 cached (5%)   · gpt-5.6-luna
security:    $0.0022 · 40,214 in  / 602 out    · 0 cached (0%)       · gpt-5.6-luna
tests:       $0.0006 · 34,653 in  / 5,881 out  · 16,896 cached (49%) · deepseek-v4-flash
description: $0.0002 · 8,702 in   / 1,861 out  · 1,536 cached (18%)  · deepseek-v4-flash

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant