Node.js 里实现 Chain of Thought 思维链推理,并不是要让服务端自己执行逻辑推理,而是借助大语言模型,通过提示词把推理过程显式地逼出来。实际开发中,真正可落地的思维链实现通常包含三部分:设计稳定的提示词模板、调用模型接口、解析并复用返回的推理内容。

如果不做任何引导,模型面对稍复杂的问题时倾向于直接给结论。比如问一道应用题,它可能跳过列式过程直接输出答案,而答案是错的时候很难定位问题。思维链的价值在于强制中间步骤可见,让用户或后续节点可以校验。
先设计一个最小可用的思维链提示词
思维链提示词的关键不是把 prompt 写得多长,而是明确要求模型按步骤推理,并且把推理过程保留在输出里。一个最小可用的系统提示可以这样写:你是一个严谨的推理助手,回答复杂问题时先逐步分析,再给出最终结论。用户消息中最好再次强调请先写出推理步骤,因为系统提示的约束在长上下文中可能被稀释。
下面这段 Node.js 代码构造了一条最简单的消息数组,作为后续所有封装的基础。可以看到,这里没有引入任何第三方 SDK,直接用 fetch 调用兼容 OpenAI 风格的接口,依赖只有环境变量里的 API Key。
const messages = [
{
role: "system",
content: "你是一个严谨的推理助手。回答复杂问题时,请先逐步分析,再给出最终结论。"
},
{
role: "user",
content: "一个长方形长是宽的2倍,周长是36厘米,求面积。请先写出推理步骤。"
}
];
这个模板看起来简单,但已经足够让模型改变输出行为。实际测试中,加上“先推理后答案”的指令后,模型会倾向于列出条件、公式和中间结果。需要注意的是,提示词里最好固定一个结束标记,比如【答案】,否则后续想从文本中提取最终结论会比较麻烦。
在 Node.js 中封装一个可复用的思维链推理函数
业务代码里不适合每次都重写消息构造和请求逻辑。更合理的做法是封装一个 runChainOfThought 函数,接收问题、模型名称、温度参数,内部完成消息拼接、调用、错误处理和结果返回。下面是一个可以直接运行的版本。
async function runChainOfThought(question, options = {}) {
const apiKey = process.env.OPENAI_API_KEY;
const model = options.model || "gpt-4o-mini";
const temperature = options.temperature ?? 0.3;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
},
body: JSON.stringify({
model,
temperature,
messages: [
{
role: "system",
content: "请使用思维链方式推理。先列出关键条件,再分步骤计算,最后用【答案】标记最终结果。"
},
{
role: "user",
content: question
}
]
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(`调用模型失败:${response.status} ${text}`);
}
const data = await response.json();
const content = data.choices[0]?.message?.content ?? "";
return content;
}
这个函数有几个设计点值得说明。第一,默认模型选择了成本较低的 gpt-4o-mini,如果项目里已经接入其他模型,只需要替换 model 字段;第二,温度参数默认设成 0.3,思维链任务通常不希望模型发散,较低的温度可以让推理更稳定;第三,返回的是原始文本,调用方可以自行决定展示或继续解析。
错误处理部分没有做重试,因为思维链请求通常 token 消耗较大,盲目重试会放大成本。更实际的做法是遇到 429 或 5xx 时做有限次退避重试,遇到 400 时立刻抛出并记录请求体,方便排查提示词问题。
流式输出:让思维链逐步显示出来
思维链内容可能很长,如果等模型全部生成完再一次性返回,前端会长时间白屏。流式输出可以边生成边展示,用户体验更接近 ChatGPT 的思考过程。Node.js 中实现流式调用主要使用 fetch 的 ReadableStream,逐行解析 SSE 数据。
async function streamChainOfThought(question, onToken) {
const apiKey = process.env.OPENAI_API_KEY;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`
},
body: JSON.stringify({
model: "gpt-4o-mini",
stream: true,
messages: [
{
role: "system",
content: "先展示推理步骤,再用【答案】给出结论。"
},
{
role: "user",
content: question
}
]
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) continue;
const payload = trimmed.slice(5).trim();
if (payload === "[DONE]") return;
try {
const json = JSON.parse(payload);
const delta = json.choices[0]?.delta?.content ?? "";
if (delta) onToken(delta);
} catch (err) {
// 忽略不完整的事件
}
}
}
}
上面的代码把思维链输出拆成 token 级别的回调,调用方可以传一个 onToken 函数,把增量内容写入 HTTP 响应或 WebSocket。实际项目中,如果使用 Express,可以在 res.write 中直接传递每个 token,前端用 fetch 读取流并渲染。
处理 SSE 时有一个容易忽略的细节:网络分包可能导致一行数据被截断,所以代码里用 buffer 缓存未完成的行,每次只处理完整的 data: 行。对于 payload 为 [DONE] 的事件直接结束读取,避免继续等待。
把推理过程和最终答案拆成结构化数据
很多场景下,业务系统不只需要展示整段思维链,还希望把推理过程和最终答案分开存储。可以直接要求模型输出 JSON,字段包含 reasoning 和 answer。部分接口支持 response_format 参数,能进一步提高 JSON 输出稳定性。
const prompt = `
请分析下面的问题,并严格按 JSON 格式返回:
{
"reasoning": "完整的思维链步骤",
"answer": "最终答案"
}
问题:${question}
`;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
model: "gpt-4o-mini",
response_format: { type: "json_object" },
messages: [
{ role: "system", content: "你只输出 JSON,不要输出其他内容。" },
{ role: "user", content: prompt }
]
})
});
拿到模型返回后,用 JSON.parse 解析,再做一层字段存在性校验。需要提醒的是,即使设置了 json_object 模式,模型偶尔仍可能输出多余文本,所以最好用正则先截取第一个 { 到最后一个 } 之间的内容,再进行解析。
结构化输出的另一个好处是方便做自动化评测。比如把 reasoning 单独记录,后续发现答案错误时可以回看推理过程,判断是理解偏差还是算术错误。对于线上业务,answer 字段可以直接作为接口返回值。
实际使用中需要控制的几个成本点
思维链的代价是 token 消耗明显增加。一个原本 50 token 的答案,加上完整推理过程可能膨胀到 500 token 甚至更多。对于高频接口,可以考虑使用更小的模型处理简单问题,只在复杂任务上启用思维链。另一个思路是缓存相同问题的推理结果,但要注意模型输出本身不是确定性的,缓存只能作为降本策略,不能保证完全一致。
上下文长度也需要警惕。思维链适合的问题通常输入较短,但推理过程较长。如果连续多轮对话都携带完整思维链,上下文很快会被占满。解决方案是在每轮结束后只保留最终答案,或者把思维链压缩成摘要后再进入下一轮。
最后是提示注入风险。思维链输出往往会被展示给用户,如果用户输入中包含“忽略之前的指令”等对抗性内容,模型可能在推理过程中执行额外动作。因此,在面向外部用户的产品中,要给用户输入增加过滤,并避免把不可信内容直接拼接到系统提示里。
Node.jsChain of Thought思维链推理修改时间:2026-10-06 16:00:52