DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

工具结果多出 None

ToolMessage 的 None 变成字面文本,检查构造前后。仅按业务空文本契约处理 None,保留零值、内容块、失败状态和 tool_call_id。

工具没有文本结果,调用方把 Python 的 None 交给 ToolMessage,模型上下文却出现四个字符 None。省略 content 也可能得到相同结果。先检查消息构造前后,而不是让模型忽略这段突然出现的文字。

这条条件有 langchain-core 1.6.5 的反馈,已核对的正式 1.6.10 仍将非字符串、非列表的 content 转为 str(content),没有单独处理 None。构造器允许 content 默认为 None,不等于最终消息会保存空内容。

空值和字符串不是同一份结果

str(None) 是字符串 "None";它不是 JSON null,也不是空字符串。工具结果被交给下一轮模型时,这段文字会被当成实际内容。

数值零转成 "0" 是另一种输入,不能用 result or "" 一起清空。False、空列表与合法的零值都可能有业务含义;只想处理 None 时,应按身份明确判断。

也要先确定 None 的含义。工具正常完成且没有可展示文本,可以采用空文本;工具执行失败、结果尚未返回或适配器漏字段,则不能直接标成空白成功。需要表达结构化 null 的接口,也应按自己的 JSON 契约处理,不能与空字符串互换。

在明确的消息适配边界处理

对于“正常完成,无文本”的工具契约,可以在创建消息时只归一化 None。下面只定义构造函数,不执行工具或调用模型:

from langchain_core.messages import ToolMessage

def text_result_message(result, tool_call_id):
    content = "" if result is None else result
    return ToolMessage(content=content, tool_call_id=tool_call_id)

这是最小的文本结果适配器。真实消息已有 name、status、artifact 或其他字段时,应沿用原来的消息构造流程保留它们。失败状态不能因为整理 content 又变成默认 success,完整 artifact 也不应为了消除一个 None 被丢弃。

content 可以是字符串或受支持的内容块列表,不能把所有结果强制写成字符串。现有工具框架若自己包装结果,应在那条实际路径核对,不假定应用手写的 ToolMessage 工厂一定被调用。

不需要修改全部消息类或放宽 Pydantic 校验,也不需要删除 tool_call_id。调用 ID 仍用于把结果交还给对应请求,正文为空不代表配对关系可以省略。

看实际上下文里保存了什么

获准的无副作用验收应区分 None、零、False、字符串 "None"、空字符串和内容块列表。检查 None 的业务约定得到正确表示,原本就存在的字符串没有被擅自删除,合法零值仍保留。

再核对构造后的消息、序列化历史与真实模型输入。供应商对空工具结果另有要求时,应按该协议提供明确表示;仅在本地构造成功,不代表模型入口一定接受。

旧历史里已经有 "None" 时,不能全库批量删除这个字面字符串:它也可能是工具真实返回的内容。只有结合当次工具结果与消息记录确认是空值转写,才有依据沿原有数据修订流程处理。