Skip to content

Commit cb358b6

Browse files
committed
fix(llm): stream tool call deltas immediately
1 parent 03539a8 commit cb358b6

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/llm_core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2820,10 +2820,13 @@ async def stream_llm_with_fallback(candidates, messages, **kwargs):
28202820
pass
28212821

28222822
delta = event_data.get("delta")
2823+
event_type = event_data.get("type")
28232824
substantive = (
28242825
isinstance(delta, str) and bool(delta)
28252826
) or (
2826-
event_data.get("type") == "tool_calls"
2827+
event_type == "tool_call_delta"
2828+
) or (
2829+
event_type == "tool_calls"
28272830
and bool(event_data.get("calls"))
28282831
)
28292832

tests/test_llm_core_fallback.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,41 @@ def per_model(model):
144144
assert not any('"fallback"' in c for c in chunks)
145145

146146

147+
def test_tool_call_delta_is_forwarded_immediately_and_prevents_fallback(monkeypatch):
148+
calls = []
149+
advanced_past_delta = False
150+
tool_delta = 'data: {"type": "tool_call_delta", "index": 0, "arg_delta": "{\\"path\\":"}\n\n'
151+
tool_calls = 'data: {"type": "tool_calls", "calls": [{"id": "c1", "name": "write_file", "arguments": "{\\"path\\":\\"x\\"}"}]}\n\n'
152+
153+
async def fake_stream(url, model, messages, **kw):
154+
nonlocal advanced_past_delta
155+
calls.append(model)
156+
yield tool_delta
157+
advanced_past_delta = True
158+
yield tool_calls
159+
yield "data: [DONE]\n\n"
160+
161+
monkeypatch.setattr(llm_core, "stream_llm", fake_stream)
162+
163+
async def run():
164+
stream = llm_core.stream_llm_with_fallback(
165+
[("u1", "primary", {}), ("u2", "backup", {})],
166+
[{"role": "user", "content": "hi"}],
167+
)
168+
first = await anext(stream)
169+
assert first == tool_delta
170+
assert not advanced_past_delta
171+
chunks = [first]
172+
async for chunk in stream:
173+
chunks.append(chunk)
174+
return chunks
175+
176+
chunks = asyncio.run(run())
177+
assert calls == ["primary"]
178+
assert tool_calls in chunks
179+
assert not any('"type": "fallback"' in c for c in chunks)
180+
181+
147182
def test_empty_final_candidate_surfaces_terminal_error(monkeypatch):
148183
calls = []
149184

0 commit comments

Comments
 (0)