一、什么是思维链(Chain of Thought)?

“思维链”(CoT)通常指的是一种提示工程(Prompt Engineering)技术,让模型在给出最终答案之前,先生成推理步骤(即“思考过程”)。

实现思维链有两种主要方式,它们的大模型调用次数不同:

  1. 单次调用 CoT(Prompt 层面):

    • 原理: 在 Prompt 中加入 “Let’s think step by step”(让我们一步步思考)。模型会在一次生成中输出思考过程和结果。
    • 调用次数: 1 次
    • 适用场景: 逻辑推理、数学题、常识问答。
  2. 多次调用 CoT(架构层面/Agent):

    • 原理: 将复杂的任务拆解为多个环节(Chain),或者使用 Agent(智能体)。比如:步骤 1 生成大纲 -> 步骤 2 扩写内容 -> 步骤 3 翻译。
    • 调用次数: 多次(每个环节调用一次)。
    • 适用场景: 需要外部工具、长篇写作、多步骤复杂任务。

二、例子说明

下面我给出两个具体的 LangChain 代码例子,分别展示这两种情况。

1. 单次调用的思维链(通过 Prompt 实现)

这是最常见的 CoT 形式。即便使用了 LangChain 的 Chain 概念,它实际上只与 LLM 交互一次。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 初始化 LLM
llm = ChatOpenAI(model="gpt-4o")

# 定义带有思维链指令的 Prompt
# 关键在于指令: "请一步步思考 (Think step by step)"
template = """
你是一个逻辑专家。回答用户的问题。
在给出最终答案之前,请先一步步写出你的推理过程。

问题: {question}
"""

prompt = ChatPromptTemplate.from_template(template)

# 构建 Chain (LCEL 语法)
# 这里的流程和你提供的一样
cot_chain = prompt | llm | StrOutputParser()

# 调用
question = "如果我有3个苹果,吃了一个,又买了5个,分给朋友2个,我还剩几个?"
result = cot_chain.invoke({"question": question})

print(result)

输出示例(一次生成):

推理过程:

  1. 开始有 3 个苹果。
  2. 吃掉 1 个:3 - 1 = 2 个。
  3. 买了 5 个:2 + 5 = 7 个。
  4. 分给朋友 2 个:7 - 2 = 5 个。
    最终答案: 5 个。

2. 多次调用的思维链(顺序链 Sequential Chain)

如果你指的是让模型先“思考”生成一个中间结果,然后把这个结果作为输入进行下一步处理,这需要多次调用。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough

llm = ChatOpenAI(model="gpt-4o")

# --- 步骤 1: 生成大纲 (第一次调用) ---
outline_prompt = ChatPromptTemplate.from_template("请为主题 '{topic}' 写一个简短的博客大纲。")
outline_chain = outline_prompt | llm | StrOutputParser()

# --- 步骤 2: 根据大纲写文章 (第二次调用) ---
# 这里的输入来自上一步的输出
article_prompt = ChatPromptTemplate.from_template("根据以下大纲写一篇 100 字的短文:\n\n{outline}")
article_chain = article_prompt | llm | StrOutputParser()

# --- 组合链 (Sequential Chain) ---
# 这种写法不仅是思维上的链,也是执行上的链
full_chain = (
    {"outline": outline_chain}  # 这里执行第一个链,消耗一次 LLM 调用
    | RunnablePassthrough.assign(article=article_chain) # 这里执行第二个链,消耗第二次 LLM 调用
)

# 调用
result = full_chain.invoke({"topic": "人工智能的未来"})

print("=== 大纲 (Step 1) ===")
print(result['outline'])
print("\n=== 文章 (Step 2) ===")
print(result['article'])
  • 你提供的代码 (RunnablePassthrough | prompt | llm | parser):是一个标准的单步执行单元,只调用一次大模型。
  • 思维链 (CoT):通常指通过 Prompt 引导模型在一次调用中输出推理步骤。
  • 多步调用:如果你需要模型“先想A,再根据A做B”,则需要构建顺序链(Sequential Chain)或使用 Agent,这时才会涉及多次调用。

三、LangChain思维链与prompt思维链区别

“工程化思维(LangChain LCEL)”与“脚本化思维(Direct API Call)”的区别

如果单纯从大模型(LLM)的角度看,两者没有区别。无论你是用 LangChain 封装,还是直接写 Python 代码调用 OpenAI API,只要发给模型的最终字符串(Prompt)是一样的,模型的回答就是一样的。

但是,从开发者的思维和代码架构上看,差异巨大。你提供的这段 LangChain 代码体现的是一种流水线(Pipeline)思维

下面通过对比来详细说明:

1. 核心思维对比
特性 直接调用 (Direct Prompt / API) LangChain LCEL (chain = ...)
思维模式 命令式 (Imperative) 声明式 (Declarative)
逻辑 “先做A,拿到结果赋值给B,再把B传给C…” “定义一条流水线:水从入口流过A、B、C,最后流出”
关注点 关注每一步的执行细节和变量传递 关注数据流向和组件组合
修改成本 修改 Prompt 或模型通常需要改动多处代码 像积木一样替换组件(换模型、换 Prompt 只是换个变量)
复杂性 简单任务只需几行代码,复杂任务会变成面条代码 简单任务显得繁琐,但极度适合复杂任务

2. 代码层面的直观对比

为了让你看清“思维”的不同,我们实现同一个功能:输入一个主题,让 AI 讲个笑话,并只返回笑话文本。

方式 A:直接 Prompt 思维 (脚本式)

这种方式是你自己处理所有的拼接、调用、解析。

import openai

client = openai.OpenAI(api_key="sk-...")

# 1. 准备变量
topic = "程序员"

# 2. 手动拼接 Prompt (字符串操作)
prompt_text = f"请讲一个关于{topic}的笑话。"

# 3. 发起调用 (关注具体的 API 参数)
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": prompt_text}]
)

# 4. 手动解析结果 (需要知道 response 的深层结构)
result = response.choices[0].message.content

print(result)
  • 思维特点:我要亲自控制每一步,我要知道 .choices[0].message 这种细节。
方式 B:LangChain LCEL 思维 (你提供的代码)

这种方式是定义流程,不需要关心底层 API 的 JSON 结构。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough

# 1. 定义组件 (积木)
prompt = ChatPromptTemplate.from_template("请讲一个关于{topic}的笑话。")
llm = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()

# 2. 定义流程 (流水线)
# 这里的 RunnablePassthrough 自动把 invoke 的参数传给 prompt
chain = (
    RunnablePassthrough() 
    | prompt 
    | llm 
    | parser
)

# 3. 执行 (只需关注输入和输出)
result = chain.invoke({"topic": "程序员"})

print(result)
  • 思维特点:我只定义数据的流向(|),不管具体的网络请求和 JSON 解析。

3. 深入解析 RunnablePassthrough 的作用

在你提供的代码中,RunnablePassthrough() 是这一“流水线思维”的关键体现。

  • 直接思维:你需要显式地写 format(topic="xxx")
  • LangChain 思维
    • chain.invoke({"topic": "xxx"}) 传入了一个字典。
    • RunnablePassthrough() 的作用是说:“我不做处理,直接把这个输入字典透传给下一步(Prompt)”。
    • prompt 收到字典,自动匹配 {topic}

为什么要这么做?
为了组合。如果你的流程变复杂了,比如:
检索数据库 -> (检索结果 + 用户问题) -> Prompt -> LLM
LangChain 可以通过 RunnablePassthrough.assign(...) 轻松地向数据流中“注入”新数据,而不需要重写整个函数。

四、总结

你给出的这段代码,其核心价值不在于“调用大模型”这件事本身,而在于它标准化了与大模型交互的接口

  • 直接 Prompt:像在手工作坊里干活,灵活但难以规模化。
  • LangChain 代码:像在搭建工厂流水线,前期配置多,但一旦跑通,处理复杂任务、替换零件(模型)、增加工序(OutputParser)都非常容易。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐