From b479294af4a06ec59acf9d3fd4c56ebce4ba698d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 22 Apr 2026 08:07:10 +0800 Subject: [PATCH] refactor: share streaming tool event normalization Co-Authored-By: Claude Opus 4.7 --- app/http/tool_bridge.py | 17 +++++++++++++++++ app/main.py | 39 ++++++++++++--------------------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/http/tool_bridge.py b/app/http/tool_bridge.py index f4e49fb..8f3b8fd 100644 --- a/app/http/tool_bridge.py +++ b/app/http/tool_bridge.py @@ -92,6 +92,23 @@ def _allowed_tool_events( return out +def _allowed_stream_tool_event( + event: Any, + *, + tool_config: dict[str, Any] | None, + forced_tool_name: str | None = None, +) -> dict[str, Any] | None: + if not isinstance(event, dict) or event.get("type") != "tool": + return None + tool = event.get("tool") + if not isinstance(tool, dict): + return None + tool_name = str(tool.get("name") or "") + if not _tool_event_allowed(tool_name, tool_config, forced_tool_name=forced_tool_name): + return None + return tool + + def _openai_forced_tool_name(tool_choice: Any) -> str | None: if not isinstance(tool_choice, dict): return None diff --git a/app/main.py b/app/main.py index 9965505..46eeb52 100644 --- a/app/main.py +++ b/app/main.py @@ -39,6 +39,7 @@ from .http.responses_adapter import ( _sse_data, ) from .http.tool_bridge import ( + _allowed_stream_tool_event, _allowed_tool_events, _anthropic_forced_tool_name, _anthropic_tool_name as _shared_anthropic_tool_name, @@ -51,7 +52,6 @@ from .http.tool_bridge import ( _openai_tool_call, _openai_tool_name as _shared_openai_tool_name, _tool_code_single_arg_name, - _tool_event_allowed, ) from .lingma_pool import LingmaPool, PoolInstance from .logging_config import configure_logging, get_logger, request_id_var @@ -551,14 +551,6 @@ def _stream_text(event: Any) -> str: return "" -def _stream_tool_event(event: Any) -> dict[str, Any] | None: - if isinstance(event, dict) and event.get("type") == "tool": - tool = event.get("tool") - if isinstance(tool, dict): - return tool - return None - - @app.post("/v1/chat/completions", dependencies=[Depends(auth_guard)]) async def v1_chat_completions(req: ChatCompletionsRequest, request: Request): p = _require_pool() @@ -693,16 +685,12 @@ async def v1_chat_completions(req: ChatCompletionsRequest, request: Request): out_meta=_meta, ): if _stream_event_type(chunk) == "tool": - tool = _stream_tool_event(chunk) - if not tool: - continue - - tool_name = str(tool.get("name") or "") - if not _tool_event_allowed( - tool_name, - tool_config, + tool = _allowed_stream_tool_event( + chunk, + tool_config=tool_config, forced_tool_name=forced_tool_name, - ): + ) + if not tool: continue if buffered_text_parts: @@ -1430,6 +1418,7 @@ async def v1_messages(req: AnthropicMessagesRequest, request: Request): completion_tokens_holder = {"n": 0} stream_meta: dict = {} max_tokens = req.max_tokens + forced_tool_name = _anthropic_forced_tool_name(req.tool_choice) async def event_stream(_ticket=ticket, _inst=inst, _meta=stream_meta): success = False @@ -1477,17 +1466,13 @@ async def v1_messages(req: AnthropicMessagesRequest, request: Request): block_index += 1 text_block_open = False - tool = _stream_tool_event(chunk) + tool = _allowed_stream_tool_event( + chunk, + tool_config=tool_config, + forced_tool_name=forced_tool_name, + ) if not tool: continue - - tool_name = str(tool.get("name") or "") - if not _tool_event_allowed( - tool_name, - tool_config, - forced_tool_name=_anthropic_forced_tool_name(req.tool_choice), - ): - continue tool_id = str(tool.get("id") or f"toolu_stream_{block_index}")