mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 07:02:00 +00:00
f43f56abb7
用户问能不能看到"正在理解需求、规划步骤"里面发生了什么。查下来是这样:
模型确实在想,reasoning 花了实打实的 token,但一路上被丢了三次。
1. langchain 的 ChatOpenAI **故意**不保留第三方的 reasoning_content——它的
文档原话是 "Non-standard response fields added by third-party providers
are not extracted or preserved",建议换 ChatDeepSeek 之类的专用包。可
"用哪个供应商"在这个产品里是用户在设置页配的(DeepSeek / Qianfan /
vLLM / OpenRouter 都可能),为每种装一个专用包不现实。在 factory 里
子类化捞回 additional_kwargs,只加字段,不动 langchain 对正文的处理。
2. thinking_text 只认 Anthropic 的 thinking 块。补上兼容端点那一种。
3. 前端把一段思考的标题固定成开头 60 个字。思考是流式的,开头往往是
"用户想要一个……"这类复述,之后几十秒里它一直在推进——固定显示开头
等于把一段活的叙述冻在起点上,看着就像卡住了。改成:还在想的时候显示
最新一句,想完了换回开头那句(那时它是一条历史记录)。
实测一次真实生成,60 秒里侧栏逐句变化:
3s 可用数据源 bi
9s 人工节点 approve 模式,审核
18s 我们可以用 db_query__sales 执行 SELECT COUNT(*) FROM orders 等
36s 更合理:rejected -> query_agent(重新
45s 对于 bi 有 53 个对象,靠
57s (plan 到达,思考收尾,换回开头那句)
顺带运行期的 LLM 节点也跟着有了思考——走的是同一个 thinking_text,之前
同样只有 Anthropic 系才有。已验证流式合并后思考连成完整一段,不是只剩
最后一片。
tests/test_thinking.py 守两种形态、合并、以及"不产思考的模型不硬凑"。
check-decode 补 5 条守"还在想显示最新一句、想完了换开头"。
55 lines
2.4 KiB
Python
55 lines
2.4 KiB
Python
"""思考内容的提取。
|
|
|
|
界面上"它大致在想什么"只有这一个来源。两家的形态完全不同,漏掉任何一种,
|
|
那一侧的模型在编排期就只剩一行不动的"正在理解需求"——而它其实想了 50 秒。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
|
|
from app.engine.state import message_text, thinking_text
|
|
|
|
|
|
def test_anthropic_thinking_blocks() -> None:
|
|
msg = AIMessage(content=[
|
|
{"type": "thinking", "thinking": "先看看有哪些表。"},
|
|
{"type": "text", "text": "查到 3 张表。"},
|
|
{"type": "thinking", "thinking": "再按月份聚合。"},
|
|
])
|
|
assert thinking_text(msg) == "先看看有哪些表。再按月份聚合。"
|
|
# 正文不能把思考混进来
|
|
assert thinking_text(msg) not in message_text(msg)
|
|
assert "查到 3 张表。" in message_text(msg)
|
|
|
|
|
|
def test_openai_compatible_reasoning_content() -> None:
|
|
"""DeepSeek / Qianfan / vLLM 这类兼容端点把思考放在 reasoning_content。
|
|
|
|
langchain 的 ChatOpenAI 明确不保留这个字段("Non-standard response fields
|
|
added by third-party providers are not extracted or preserved"),
|
|
providers/factory.py 子类化捞了回来。
|
|
"""
|
|
chunk = AIMessageChunk(content="", additional_kwargs={"reasoning_content": "先看看现有的节点。"})
|
|
assert thinking_text(chunk) == "先看看现有的节点。"
|
|
|
|
|
|
def test_merged_chunks_keep_whole_reasoning() -> None:
|
|
"""流式合并后思考要连成一整段,不能只剩最后一片。"""
|
|
parts = ["用户想要", "一个能查销量的流程。", "先看看现有的节点。"]
|
|
merged = AIMessageChunk(content="", additional_kwargs={"reasoning_content": parts[0]})
|
|
for p in parts[1:]:
|
|
merged = merged + AIMessageChunk(content="", additional_kwargs={"reasoning_content": p})
|
|
assert thinking_text(merged) == "".join(parts)
|
|
|
|
|
|
def test_plain_message_has_no_thinking() -> None:
|
|
"""不产思考的模型不该被硬凑出一段来。"""
|
|
assert thinking_text(AIMessage(content="就是 2")) == ""
|
|
assert thinking_text(AIMessageChunk(content="x", additional_kwargs={})) == ""
|
|
|
|
|
|
def test_empty_reasoning_is_not_reported() -> None:
|
|
"""空串不算思考,否则界面上会出现一行什么都没有的"思考"。"""
|
|
assert thinking_text(AIMessageChunk(content="", additional_kwargs={"reasoning_content": ""})) == ""
|