fix(stream): prevent tool-call argument leakage into text chunks
This commit is contained in:
parent
494edc53e9
commit
ce02ea21fb
@ -15,6 +15,7 @@ from .state import get_stop_flag, clear_stop_flag
|
||||
|
||||
async def run_streaming_attempts(*, web_terminal, messages, tools, sender, client_sid: str, username: str, conversation_id: Optional[str], current_iteration: int, max_api_retries: int, retry_delay_seconds: int, detected_tool_intent: Dict[str, str], full_response: str, tool_calls: list, current_thinking: str, detected_tools: Dict[str, str], last_usage_payload, in_thinking: bool, thinking_started: bool, thinking_ended: bool, text_started: bool, text_has_content: bool, text_streaming: bool, text_chunk_index: int, last_text_chunk_time, chunk_count: int, reasoning_chunks: int, content_chunks: int, tool_chunks: int, last_finish_reason: Optional[str], accumulated_response: str) -> Dict[str, Any]:
|
||||
api_error = None
|
||||
tool_call_stream_active = False
|
||||
for api_attempt in range(max_api_retries + 1):
|
||||
api_error = None
|
||||
if api_attempt > 0:
|
||||
@ -36,6 +37,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien
|
||||
content_chunks = 0
|
||||
tool_chunks = 0
|
||||
last_finish_reason = None
|
||||
tool_call_stream_active = False
|
||||
|
||||
# 收集流式响应
|
||||
async for chunk in web_terminal.api_client.chat(messages, tools, stream=True):
|
||||
@ -128,51 +130,12 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien
|
||||
current_thinking += reasoning_content
|
||||
sender('thinking_chunk', {'content': reasoning_content})
|
||||
|
||||
# 处理正常内容
|
||||
if "content" in delta:
|
||||
content = delta["content"]
|
||||
if content:
|
||||
content_chunks += 1
|
||||
debug_log(f" 正式内容 #{content_chunks}: {repr(content[:100] if content else 'None')}")
|
||||
|
||||
if in_thinking and not thinking_ended:
|
||||
in_thinking = False
|
||||
thinking_ended = True
|
||||
sender('thinking_end', {'full_content': current_thinking})
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
if not text_started:
|
||||
text_started = True
|
||||
text_streaming = True
|
||||
sender('text_start', {})
|
||||
brief_log("模型输出了内容")
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
full_response += content
|
||||
accumulated_response += content
|
||||
text_has_content = True
|
||||
emit_time = time.time()
|
||||
elapsed = 0.0 if last_text_chunk_time is None else emit_time - last_text_chunk_time
|
||||
last_text_chunk_time = emit_time
|
||||
text_chunk_index += 1
|
||||
log_backend_chunk(
|
||||
conversation_id,
|
||||
current_iteration,
|
||||
text_chunk_index,
|
||||
elapsed,
|
||||
len(content),
|
||||
content[:32]
|
||||
)
|
||||
sender('text_chunk', {
|
||||
'content': content,
|
||||
'index': text_chunk_index,
|
||||
'elapsed': elapsed
|
||||
})
|
||||
|
||||
# 收集工具调用 - 实时发送准备状态
|
||||
if "tool_calls" in delta:
|
||||
delta_tool_calls = delta.get("tool_calls")
|
||||
if isinstance(delta_tool_calls, list) and delta_tool_calls:
|
||||
tool_call_stream_active = True
|
||||
tool_chunks += 1
|
||||
for tc in delta["tool_calls"]:
|
||||
for tc in delta_tool_calls:
|
||||
found = False
|
||||
for existing in tool_calls:
|
||||
if existing.get("index") == tc.get("index"):
|
||||
@ -261,6 +224,56 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien
|
||||
await asyncio.sleep(0.01)
|
||||
debug_log(f" 新工具: {tool_name}")
|
||||
|
||||
# 处理正常内容
|
||||
if "content" in delta:
|
||||
content = delta["content"]
|
||||
if content:
|
||||
# 某些供应商在 tool_calls 流式阶段会把参数碎片误放进 content。
|
||||
# 一旦进入工具调用流,抑制正文 chunk,避免 write_file 参数泄露到前端。
|
||||
if tool_call_stream_active:
|
||||
debug_log(
|
||||
" 抑制可疑正文chunk(tool_call阶段): "
|
||||
f"{repr((content or '')[:100])}"
|
||||
)
|
||||
continue
|
||||
|
||||
content_chunks += 1
|
||||
debug_log(f" 正式内容 #{content_chunks}: {repr(content[:100] if content else 'None')}")
|
||||
|
||||
if in_thinking and not thinking_ended:
|
||||
in_thinking = False
|
||||
thinking_ended = True
|
||||
sender('thinking_end', {'full_content': current_thinking})
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
if not text_started:
|
||||
text_started = True
|
||||
text_streaming = True
|
||||
sender('text_start', {})
|
||||
brief_log("模型输出了内容")
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
full_response += content
|
||||
accumulated_response += content
|
||||
text_has_content = True
|
||||
emit_time = time.time()
|
||||
elapsed = 0.0 if last_text_chunk_time is None else emit_time - last_text_chunk_time
|
||||
last_text_chunk_time = emit_time
|
||||
text_chunk_index += 1
|
||||
log_backend_chunk(
|
||||
conversation_id,
|
||||
current_iteration,
|
||||
text_chunk_index,
|
||||
elapsed,
|
||||
len(content),
|
||||
content[:32]
|
||||
)
|
||||
sender('text_chunk', {
|
||||
'content': content,
|
||||
'index': text_chunk_index,
|
||||
'elapsed': elapsed
|
||||
})
|
||||
|
||||
# 检查是否被停止
|
||||
client_stop_info = get_stop_flag(client_sid, username)
|
||||
if client_stop_info:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user