Where: framework/eino_sink.go:109-119 × framework/eino_toolloop.go:196-208, 277-290
streamSink.generate pushes content deltas live for every sink.generate call in the loop.
On TerminatedByTruncated (finish_reason=length, no tool call), each of up to maxTruncationRetries (2) retries streams its half-sentence truncated text to the client; the wind-down pass then streams the real answer on top.
The client concatenates all deltas and ends up with [garbage #1] + [garbage #2] + [wind-down answer], directly contradicting the code's stated intent ("do not treat a half-sentence as the final answer") — the partials are already on the wire and cannot be recalled.
Same divergence for TerminatedByMaxIter / EmptyFinish / Deadline / RepairExhausted: any narration on intermediate tool-call turns is delivered, then wind-down is appended.
Net effect: the streaming client's accumulated text ≠ the persisted/buffered response.Content (wind-down only). A Chat caller and a Stream caller get materially different answers for the same run.
Note: the bufferedStreamSink path (output guardrail active) is NOT affected — it sends content exactly once in finish (eino_sink.go:249-259). The bug is specific to the plain streamSink.
Where: framework/eino_sink.go:109-119 × framework/eino_toolloop.go:196-208, 277-290
streamSink.generate pushes content deltas live for every sink.generate call in the loop.
On TerminatedByTruncated (finish_reason=length, no tool call), each of up to maxTruncationRetries (2) retries streams its half-sentence truncated text to the client; the wind-down pass then streams the real answer on top.
The client concatenates all deltas and ends up with [garbage #1] + [garbage #2] + [wind-down answer], directly contradicting the code's stated intent ("do not treat a half-sentence as the final answer") — the partials are already on the wire and cannot be recalled.
Same divergence for TerminatedByMaxIter / EmptyFinish / Deadline / RepairExhausted: any narration on intermediate tool-call turns is delivered, then wind-down is appended.
Net effect: the streaming client's accumulated text ≠ the persisted/buffered response.Content (wind-down only). A Chat caller and a Stream caller get materially different answers for the same run.
Note: the bufferedStreamSink path (output guardrail active) is NOT affected — it sends content exactly once in finish (eino_sink.go:249-259). The bug is specific to the plain streamSink.