mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
fix(assistant): 上游返回空串 reasoning_content 时也保留该字段
按字段存在性 (is not None) 判断, 区分"上游未返回"与"返回空串"; 流式 round_end、chat_service 下一轮请求、非流式工具循环三处均补齐, 并加回归用例。 Signed-off-by: kevin9327 <5299031+kevin9327@users.noreply.github.com>
This commit is contained in:
@@ -268,14 +268,14 @@ async def chat_stream(
|
||||
while True:
|
||||
round_text: list[str] = []
|
||||
tool_calls: list[dict[str, Any]] = []
|
||||
reasoning_content = ""
|
||||
reasoning_content: str | None = None
|
||||
async for event in stream_round(req_messages, schemas, temperature=0.3, timeout=240.0):
|
||||
if event["type"] == "text":
|
||||
round_text.append(event["delta"])
|
||||
await queue.put({"type": "delta", "content": event["delta"]})
|
||||
elif event["type"] == "round_end":
|
||||
tool_calls = event.get("tool_calls") or []
|
||||
reasoning_content = event.get("reasoning_content") or ""
|
||||
reasoning_content = event.get("reasoning_content")
|
||||
|
||||
if not tool_calls:
|
||||
if not round_text and not text_seen:
|
||||
@@ -322,7 +322,7 @@ async def chat_stream(
|
||||
assistant_message["content"] = "".join(round_text)
|
||||
text_seen = True
|
||||
# DeepSeek 思考模式要求工具轮的 reasoning_content 原样回传, 否则下一轮 400。
|
||||
if reasoning_content:
|
||||
if reasoning_content is not None:
|
||||
assistant_message["reasoning_content"] = reasoning_content
|
||||
req_messages.append(assistant_message)
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ async def stream_openai_round(
|
||||
content_seen = False
|
||||
reasoning_seen = False
|
||||
reasoning_parts: list[str] = []
|
||||
# 上游显式返回 reasoning_content (哪怕是空串) 时也要在 round_end 里回传该字段。
|
||||
reasoning_field_seen = False
|
||||
finish_reason = ""
|
||||
calls: dict[int, dict[str, Any]] = {}
|
||||
ordered: list[dict[str, Any]] = []
|
||||
@@ -141,6 +143,8 @@ async def stream_openai_round(
|
||||
if delta is None:
|
||||
continue
|
||||
reasoning = getattr(delta, "reasoning_content", None)
|
||||
if reasoning is not None:
|
||||
reasoning_field_seen = True
|
||||
if reasoning:
|
||||
reasoning_seen = True
|
||||
reasoning_parts.append(str(reasoning))
|
||||
@@ -191,7 +195,7 @@ async def stream_openai_round(
|
||||
"finish_reason": finish_reason,
|
||||
}
|
||||
# DeepSeek 思考模式: 带 tools 的后续请求缺了这一轮的 reasoning_content 会被 400。
|
||||
if reasoning_parts:
|
||||
if reasoning_field_seen:
|
||||
end["reasoning_content"] = "".join(reasoning_parts)
|
||||
yield end
|
||||
return
|
||||
|
||||
@@ -426,7 +426,7 @@ async def generate_ai_text_with_tools(
|
||||
assistant["content"] = message.content
|
||||
# DeepSeek 思考模式要求工具轮的 reasoning_content 原样回传, 否则下一轮 400。
|
||||
reasoning = getattr(message, "reasoning_content", None)
|
||||
if reasoning:
|
||||
if reasoning is not None:
|
||||
assistant["reasoning_content"] = reasoning
|
||||
req_messages.append(assistant)
|
||||
|
||||
|
||||
@@ -83,11 +83,24 @@ async def test_stream_round_end_without_reasoning_has_no_key(monkeypatch: pytest
|
||||
assert "reasoning_content" not in events[-1]
|
||||
|
||||
|
||||
async def test_chat_service_sends_reasoning_content_back(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_stream_round_end_keeps_empty_reasoning_content(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# 上游显式返回空串时也要保留字段, 与"未返回该字段"区分开。
|
||||
events = await _events(monkeypatch, [
|
||||
_chunk({"reasoning_content": ""}),
|
||||
_chunk({"tool_calls": [{"index": 0, "id": "call_1", "type": "function",
|
||||
"function": {"name": "list_strategies", "arguments": "{}"}}]},
|
||||
finish_reason="tool_calls"),
|
||||
])
|
||||
|
||||
assert events[-1]["reasoning_content"] == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reasoning", ["先看看有哪些策略", ""])
|
||||
async def test_chat_service_sends_reasoning_content_back(monkeypatch: pytest.MonkeyPatch, reasoning: str) -> None:
|
||||
captured: list[list[dict[str, Any]]] = []
|
||||
script = [
|
||||
{"tool_calls": [{"id": "call_1", "name": "list_strategies", "arguments": "{}"}],
|
||||
"reasoning_content": "先看看有哪些策略"},
|
||||
"reasoning_content": reasoning},
|
||||
{"text": "你有 3 个策略"},
|
||||
]
|
||||
|
||||
@@ -97,7 +110,7 @@ async def test_chat_service_sends_reasoning_content_back(monkeypatch: pytest.Mon
|
||||
if step.get("text"):
|
||||
yield {"type": "text", "delta": step["text"]}
|
||||
end = {"type": "round_end", "tool_calls": step.get("tool_calls", []), "finish_reason": "stop"}
|
||||
if step.get("reasoning_content"):
|
||||
if "reasoning_content" in step:
|
||||
end["reasoning_content"] = step["reasoning_content"]
|
||||
yield end
|
||||
|
||||
@@ -115,15 +128,16 @@ async def test_chat_service_sends_reasoning_content_back(monkeypatch: pytest.Mon
|
||||
assert [json.loads(line) for line in lines if '"error"' in line] == []
|
||||
assistant_msg = captured[1][-2]
|
||||
assert assistant_msg["role"] == "assistant"
|
||||
assert assistant_msg["reasoning_content"] == "先看看有哪些策略"
|
||||
assert assistant_msg["reasoning_content"] == reasoning
|
||||
|
||||
|
||||
async def test_tool_loop_sends_reasoning_content_back(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@pytest.mark.parametrize("reasoning", ["先跑一次回测", ""])
|
||||
async def test_tool_loop_sends_reasoning_content_back(monkeypatch: pytest.MonkeyPatch, reasoning: str) -> None:
|
||||
replies = [
|
||||
ChatCompletionMessage.model_validate({
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"reasoning_content": "先跑一次回测",
|
||||
"reasoning_content": reasoning,
|
||||
"tool_calls": [{"id": "call_1", "type": "function",
|
||||
"function": {"name": "run_backtest", "arguments": "{}"}}],
|
||||
}),
|
||||
@@ -147,5 +161,5 @@ async def test_tool_loop_sends_reasoning_content_back(monkeypatch: pytest.Monkey
|
||||
execute_tool=fake_execute,
|
||||
)
|
||||
|
||||
assert messages[1]["reasoning_content"] == "先跑一次回测"
|
||||
assert messages[1]["reasoning_content"] == reasoning
|
||||
assert "reasoning_content" not in messages[3]
|
||||
|
||||
Reference in New Issue
Block a user