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

工具参数被漏算

AIMessage 正文为 [] 时,近似计数漏掉独立 tool_calls 参数。只为这一空列表路径构造计数副本,保留原消息、预算选项与工具历史配对。

AIMessage 的正文是空列表,tool_calls 里却有很长的参数。用 count_tokens_approximately 估算时,这些参数没有计入,trim_messages 于是可能把明显超过目标预算的消息留下。只把正文从 [] 换成空字符串,估算结果又大幅增加。

这条条件有 langchain-core 1.6.9 的反馈,已核对的正式 1.6.10 仍在正文为 list 时跳过单独的 tool_calls。它原本想避免对已嵌入内容块的工具调用重复计数,却把空列表或只有普通文本块的情况也排除了。

工具调用不一定在 content 里面

LangChain 的 AIMessage 可以将工具调用保存在独立的 tool_calls 字段。正文是列表,并不自动表示那份列表已经包含所有工具调用。

当前估算器处理字符串正文时,会把独立 tool_calls 转成文本计入;处理列表正文时则遍历内容块,然后略过这份独立字段。空列表当然没有提供任何等价内容,较大的工具参数便几乎不影响这一部分的预算。

先核对实际消息类型、正文表示与 tool_calls,区分参数缺失、已嵌入内容块和计数遗漏。记录类型、工具调用 ID 与大小即可,不把完整业务参数放进公开日志。向同一份消息另外添加 tools 定义,也不会自动补上漏掉的这次调用参数:定义与调用是两种输入。

只给空列表计数路径建副本

对于已经构造好的 AIMessage 对象,正文确实是空列表、独立 tool_calls 非空时,可以只在计数副本里把 content 改成空字符串,让正式计数器走已有的工具参数分支:

from langchain_core.messages import AIMessage
from langchain_core.messages.utils import count_tokens_approximately

def count_for_budget(messages):
    counted = []
    for message in messages:
        if (
            isinstance(message, AIMessage)
            and message.content == []
            and message.tool_calls
        ):
            message = message.model_copy(update={"content": ""})
        counted.append(message)
    return count_tokens_approximately(counted)

这个函数只估算,不请求模型、执行工具或修改原消息。将它作为应用的计数回调时,保留原来使用的字符比例、额外消息开销和 tools 定义等选项;示例使用的是默认参数,不应覆盖已有的预算策略。

副本只用于计数,原消息继续沿原模型协议发送。不要把真实消息内容改成字符串来消除差异,也不要删除工具调用来让估算变小。

有文本、图片、reasoning 或嵌入工具调用的内容块列表,需要分别检查。上述空列表补救没有覆盖它们;将全部 list 统一转成字符串,可能重复计工具调用,或把 Base64 长度当成图片 token 成本。

预算正确还要保住工具配对

近似计数仍不是特定模型的精确值。模型专用计数、额外系统内容、工具定义和多模态开销应按实际请求核对,并保留应用需要的余量;不能把这项遗漏补上之后就称为绝不会超出上下文。

裁剪也不能只看总量。需要同时保留工具请求与对应结果,避免预算收紧后又出现工具历史配对丢失。不通过重跑有副作用的工具来补裁掉的历史。

验收用相同工具参数分别比较空字符串、空列表和实际内容块,确认应计入的调用没有因表示方式消失,再检查原预算下的裁剪结果与完整模型输入。最后核对发送对象仍是原消息,没有把只用于估算的副本误存回图状态。