mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 04:07:44 +02:00
[eric] browser: stream the loop LLM call so tool-use works on ALL providers (gemini/openrouter/antigravity were silently broken via 9router SSE)
This commit is contained in:
@@ -1178,14 +1178,18 @@ async def run_browser_agent(
|
||||
browser_history.prune_stale_page_state(messages)
|
||||
browser_history.place_cache_marker(messages)
|
||||
p_llm_t0 = time.monotonic()
|
||||
response = await p_cancellable(client.messages.create(
|
||||
model=api_model,
|
||||
max_tokens=4096,
|
||||
# Cache the ~4k-token fixed prefix (system + tool schema) so it's reprocessed once, not on every turn: big TTFT + cost win on the first run, which is dominated by turns x per-turn prefill. The trailing cache_control marker is what Anthropic keys on; on non-Anthropic routes (9router) the marker is harmlessly ignored.
|
||||
system=p_cached_system,
|
||||
tools=p_cached_tools,
|
||||
messages=messages,
|
||||
))
|
||||
|
||||
# STREAM, don't .create(): 9Router returns non-Anthropic lanes (Gemini/OpenRouter/Antigravity) as a REAL multi-event SSE stream that the non-streaming client parses to empty content, silently breaking tool-use on every non-Claude provider (measured: only cc/ emitted tool_use before this). The streaming parser reconstructs tool_use identically for ALL providers, so this is the model-independence fix, not a UX tweak. Cache marker still rides p_cached_system (Anthropic keys on it; other routes ignore it harmlessly).
|
||||
async def p_stream_turn():
|
||||
async with client.messages.stream(
|
||||
model=api_model,
|
||||
max_tokens=4096,
|
||||
system=p_cached_system,
|
||||
tools=p_cached_tools,
|
||||
messages=messages,
|
||||
) as p_s:
|
||||
return await p_s.get_final_message()
|
||||
response = await p_cancellable(p_stream_turn())
|
||||
if response is None:
|
||||
break
|
||||
p_llm_ms = int((time.monotonic() - p_llm_t0) * 1000)
|
||||
|
||||
@@ -30,17 +30,32 @@ class Resp:
|
||||
self.usage = type("U", (), {"input_tokens": 1, "output_tokens": 1})()
|
||||
|
||||
|
||||
class FakeStream:
|
||||
# mirrors anthropic's messages.stream(): async CM whose get_final_message() returns the turn
|
||||
def __init__(self, resp): self.resp = resp
|
||||
async def __aenter__(self): return self
|
||||
async def __aexit__(self, *a): return False
|
||||
async def get_final_message(self): return self.resp
|
||||
|
||||
|
||||
class FakeLLM:
|
||||
def __init__(self, scripted):
|
||||
self.scripted = scripted; self.turn = 0; self.calls = []
|
||||
self.messages = self
|
||||
|
||||
async def create(self, **kw):
|
||||
def p_next(self, kw):
|
||||
self.calls.append(kw)
|
||||
i = min(self.turn, len(self.scripted) - 1)
|
||||
self.turn += 1
|
||||
return self.scripted[i]
|
||||
|
||||
async def create(self, **kw):
|
||||
return self.p_next(kw)
|
||||
|
||||
def stream(self, **kw):
|
||||
# the loop now streams; return an async-CM yielding the scripted turn
|
||||
return FakeStream(self.p_next(kw))
|
||||
|
||||
|
||||
class FakeAux:
|
||||
def __init__(self):
|
||||
|
||||
Reference in New Issue
Block a user