Spaces:
Running
Running
fix: eliminate redundant stream_complete() call in run_stream
Browse filesThe tool-use loop's final complete() already produces the synthesis
when it returns with no tool_calls. Previously, run_stream discarded
that response and made a second stream_complete() call — doubling
the API cost for tool-using requests.
Now always emit response.content from the loop's final complete() as
a single chunk. This means /ask/stream no longer incrementally streams
tokens for the synthesis, but it eliminates the extra LLM call and
makes cost accounting exact (total_cost now covers all LLM calls).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- agent_bench/agents/orchestrator.py +4 -13
- tests/test_serving.py +4 -3
agent_bench/agents/orchestrator.py
CHANGED
|
@@ -200,7 +200,6 @@ class Orchestrator:
|
|
| 200 |
total_cost = 0.0
|
| 201 |
|
| 202 |
# Step 1: Run tool-use loop normally (non-streamed)
|
| 203 |
-
used_tools = False
|
| 204 |
for _ in range(self.max_iterations):
|
| 205 |
response = await self.provider.complete(
|
| 206 |
messages, tools=tools, temperature=self.temperature
|
|
@@ -209,7 +208,6 @@ class Orchestrator:
|
|
| 209 |
if not response.tool_calls:
|
| 210 |
break
|
| 211 |
|
| 212 |
-
used_tools = True
|
| 213 |
messages.append(
|
| 214 |
Message(
|
| 215 |
role=Role.ASSISTANT,
|
|
@@ -235,17 +233,10 @@ class Orchestrator:
|
|
| 235 |
sources=[{"source": s} for s in dict.fromkeys(all_sources)],
|
| 236 |
)
|
| 237 |
|
| 238 |
-
# Step 3:
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
messages, temperature=self.temperature
|
| 243 |
-
):
|
| 244 |
-
yield StreamEvent(type="chunk", content=chunk)
|
| 245 |
-
else:
|
| 246 |
-
# No tools needed — response already has the answer, emit it
|
| 247 |
-
# without a redundant second LLM call
|
| 248 |
-
yield StreamEvent(type="chunk", content=response.content)
|
| 249 |
|
| 250 |
yield StreamEvent(
|
| 251 |
type="done",
|
|
|
|
| 200 |
total_cost = 0.0
|
| 201 |
|
| 202 |
# Step 1: Run tool-use loop normally (non-streamed)
|
|
|
|
| 203 |
for _ in range(self.max_iterations):
|
| 204 |
response = await self.provider.complete(
|
| 205 |
messages, tools=tools, temperature=self.temperature
|
|
|
|
| 208 |
if not response.tool_calls:
|
| 209 |
break
|
| 210 |
|
|
|
|
| 211 |
messages.append(
|
| 212 |
Message(
|
| 213 |
role=Role.ASSISTANT,
|
|
|
|
| 233 |
sources=[{"source": s} for s in dict.fromkeys(all_sources)],
|
| 234 |
)
|
| 235 |
|
| 236 |
+
# Step 3: Emit the final answer as a single chunk.
|
| 237 |
+
# The loop's last complete() already produced the synthesis — reuse it
|
| 238 |
+
# instead of making a redundant stream_complete() call.
|
| 239 |
+
yield StreamEvent(type="chunk", content=response.content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
yield StreamEvent(
|
| 242 |
type="done",
|
tests/test_serving.py
CHANGED
|
@@ -400,8 +400,8 @@ class TestStreaming:
|
|
| 400 |
assert "FastAPI" in full_text # MockProvider mentions FastAPI
|
| 401 |
|
| 402 |
@pytest.mark.asyncio
|
| 403 |
-
async def
|
| 404 |
-
"""
|
| 405 |
import json as json_mod
|
| 406 |
|
| 407 |
async with AsyncClient(
|
|
@@ -417,7 +417,8 @@ class TestStreaming:
|
|
| 417 |
if line.startswith("data: ")
|
| 418 |
and json_mod.loads(line[6:])["type"] == "chunk"
|
| 419 |
]
|
| 420 |
-
assert len(chunks) ==
|
|
|
|
| 421 |
|
| 422 |
@pytest.mark.asyncio
|
| 423 |
async def test_non_streaming_unchanged(self, test_app):
|
|
|
|
| 400 |
assert "FastAPI" in full_text # MockProvider mentions FastAPI
|
| 401 |
|
| 402 |
@pytest.mark.asyncio
|
| 403 |
+
async def test_stream_emits_single_answer_chunk(self, test_app):
|
| 404 |
+
"""Stream emits the complete answer as a single chunk (no redundant LLM call)."""
|
| 405 |
import json as json_mod
|
| 406 |
|
| 407 |
async with AsyncClient(
|
|
|
|
| 417 |
if line.startswith("data: ")
|
| 418 |
and json_mod.loads(line[6:])["type"] == "chunk"
|
| 419 |
]
|
| 420 |
+
assert len(chunks) == 1
|
| 421 |
+
assert len(chunks[0]["content"]) > 0
|
| 422 |
|
| 423 |
@pytest.mark.asyncio
|
| 424 |
async def test_non_streaming_unchanged(self, test_app):
|