Github开源生信云平台 DEMO
这个问题我换一种更“底层、去玄学”的方式再讲一遍,直接回答你这两点,不绕 SDK、不绕 JSON。
LLM 的实际输出永远只是 token 序列 不存在“JSON / tool_calls / role / finish_reason”这些东西 👉 这些都是 API 在解码阶段附加的结构化视图
LLM 的实际输出永远只是 token 序列
不存在“JSON / tool_calls / role / finish_reason”这些东西
👉 这些都是 API 在解码阶段附加的结构化视图
在模型内部,大致是这样(概念级):
你 | 好 | ! | 我 | 是 | …
关键点:不是“文本”,而是“受约束 token”
当模型决定调用工具,它生成的 token 语义上更像:
CALL get_current_weather ARG { "location": "杭州市" } END_CALL
但注意 👇这 不是字符串,而是:
在 tokenizer 级别可能是:
▁get_current_weather ▁{ ▁"location" ▁: ▁"杭州 市" ▁}
模型只是:
“我现在在输出一个 tool call 的 name 和 arguments”
API 不是“识别”出来的 而是模型在解码阶段“明确告诉 API:我现在在 tool-call 模式”
API 不是“识别”出来的
而是模型在解码阶段“明确告诉 API:我现在在 tool-call 模式”
不是靠猜、不是靠正则、不是靠 JSON。
tools: [ { name: "get_current_weather", parameters: { ... } } ]
这一步非常关键。
在模型内部,变成类似这样的规则:
输出可以是 TEXT
或者是 TOOL_CALL
如果进入 TOOL_CALL:
只能输出合法的 tool name
arguments 必须符合 schema
不能再输出普通文本 token
👉 这一步发生在 decoder / sampler 层
可以把它想成:
mode ∈ { TEXT, TOOL }
当模型在某一步选择了 TOOL:
所以 API 并不是事后分析文本,而是同步知道状态变化。
因为流程是这样的:
模型 step 1: emit token signal: TOOL_CALL_START API: 好,我走 tool_calls 通道,不走 content
这就是你看到的:
delta.tool_calls = [...] delta.content = null
👉 不是解析出来的,是实时状态
因为:
所以 API 只能:
arguments += new_token
而不是等一个完整对象。
不是模型输出的文本。
流程是:
finish_reason: "tool_calls"
👉 这是 API 的控制信号
我在一步一步生成 token 在某一步,我选择了「工具调用语义」 后续 token 只用于补全这个调用
我在一步一步生成 token
在某一步,我选择了「工具调用语义」
后续 token 只用于补全这个调用
我在解码阶段: 约束 token 空间 监听模型当前模式 把 token 分流成 content 或 tool_calls 在结构完成时发结束信号
我在解码阶段:
如果你没有:
那你只能:
❌ 猜 JSON❌ 靠 prompt 约定❌ 靠正则
而 API 厂商是在“解码器这一层”做的
如果你愿意,下一步我可以:
你已经问到 模型接口设计的最底层 了,这个问题非常专业。