Python: fix: preserve function-call name when merging streaming deltas#6809
Python: fix: preserve function-call name when merging streaming deltas#6809Osamaali313 wants to merge 3 commits into
Conversation
`Content._add_function_call_content` built the merged name with `getattr(self, "name", getattr(other, "name", None))`. Because `Content.__init__` always sets `self.name` (defaulting to `None`), the attribute is never missing, so the `getattr` default is never consulted and `other.name` is ignored. When two function_call contents are merged and only the second carries the name -- e.g. a streaming delta where the function name arrives after the first chunk -- the name was silently dropped. Use the same "either side" pattern already used for the sibling `exception` field on the next line: `getattr(self, "name", None) or getattr(other, "name", None)`. Extend the existing merge test to cover the late-name and both-None cases.
4eac647 to
6dc4250
Compare
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
Per review (pyright `reportArgumentType`): `Content.from_function_call`
annotates `name: str`, so passing `name=None` to model a streaming delta
with no name yet tripped the typing gate. Build those nameless deltas
with the `Content("function_call", ...)` constructor instead (its `name`
param is `str | None`) — the factory just wraps that same constructor, so
the runtime objects and the merge assertions are unchanged.
|
Good catch — fixed in d82c14f. I went with the second option you offered (construct the nameless delta via I chose this over widening |
Problem
Content._add_function_call_content(the+operator forfunction_callcontent, used byChatResponse.from_updates/AgentResponse.from_updatesto merge streaming deltas) builds the merged function name like this:Content.__init__always assignsself.name(defaulting toNone), so the attribute is never missing.getattr(self, "name", <fallback>)therefore always returnsself.name— even when it isNone— and the<fallback>(other.name) is never consulted.As a result, when two
function_callcontents are merged and only the second carries the name, the name is silently dropped. This happens for any streaming path where the function name arrives in a later delta than the first chunk:The asymmetry confirms it's an oversight: the sibling fields in the same
returnuse the correct "either side" pattern —exception=getattr(self, "exception", None) or getattr(other, "exception", None)on the very next line, and_add_text_reasoning_contentusesself.id or other.id.Fix
Mirror the adjacent
exceptionfield:After the fix, the name is preserved regardless of which side carries it, both-
NonestaysNone, and the normal both-set case is unchanged (self wins).Tests
Extended
test_function_call_content_add_merging_and_errorsto cover the late-name (a + bandb + a) and both-Nonecases. Verified RED→GREEN: the new assertions fail onmainand pass with the fix; the fulltests/core/test_types.pysuite stays green (267 passed).