背景
先看两段langchain调用模型的代码
python
def chat():
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名资深的开发工程师,请回答用户提的问题"),
("human", "{user_input}"),
])
llm = ChatOllama(
model="qwen3-vl:8b",
temperature=0,
)
ai_msg = llm.invoke(prompt.format_messages(user_input="请给我一个关于机器学习的例子"))
print(ai_msg)
def chat_chain_call():
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名资深的开发工程师,请回答用户提的问题"),
("human", "{user_input}"),
])
llm = ChatOllama(
model="qwen3-vl:8b",
temperature=0,
)
# 创建链
chain=prompt | llm
# 调用
ai_msg = chain.invoke({"user_input": "请给我一个关于机器学习的例子"})
print(ai_msg)
核心代码为
python
# 创建链
chain=prompt | llm
# 调用
ai_msg = chain.invoke({"user_input": "请给我一个关于机器学习的例子"})
print(ai_msg)
这是 LangChain (或类似现代 Python AI 框架,如 LlamaIndex、Haystack)中典型的**链式调用(Chaining)**写法。其核心原理是 利用 Python 的 __or__ 魔术方法(即 | 运算符重载)实现管道式(pipeline)组合。
下面从原理到实现一步步解释:
🔧 一、链式调用的本质:函数/对象的组合(Composition)
目标:将多个处理步骤(如 prompt 模板 → LLM → 解析器)像水管一样串起来,数据自动从前一个流向后一个。
传统写法:
python
ai_msg = llm.invoke(prompt.format_messages(user_input="请给我一个关于机器学习的例子"))
print(ai_msg)
链式写法(更简洁、可复用、可嵌套):
python
# 创建链
chain=prompt | llm
# 调用
ai_msg = chain.invoke({"user_input": "请给我一个关于机器学习的例子"})
print(ai_msg)
⚙️ 二、技术原理:__or__ 运算符重载 + Runnable 协议
在 LangChain 中(v0.1+),所有可组合的组件(如 PromptTemplate, LLM, RunnableLambda 等)都继承自 Runnable 基类。
关键机制:
-
Runnable类定义了__or__方法pythonclass Runnable: def __or__(self, other): return RunnableSequence(first=self, second=other) -
|运算符被重载为返回一个新的RunnableSequence对象prompt | llm→ 返回RunnableSequence(prompt, llm)
-
RunnableSequence.invoke()实现自动流水线执行pythondef invoke(self, input, config=None): output = self.first.invoke(input, config) return self.second.invoke(output, config)
所以
chain.invoke(...)实际上是依次调用每个组件的.invoke()方法,并把前一个的输出作为后一个的输入。
🧩 三、数据流示例
python
chain = prompt | llm
result = chain.invoke({"text": "Thank you"})
执行过程:
-
prompt.invoke({"text": "Thank you"})→ 返回格式化后的字符串,如
"Classify: Thank you -> Positive" -
llm.invoke("Classify: Thank you -> Positive")→ 返回 LLM 的输出,如
"Positive" -
最终
result = "Positive"
✅ 输入是 dict,中间变成 str,输出还是 str ------ LangChain 自动处理类型转换(前提是组件兼容)。
📦 四、扩展:多级链式调用
你可以继续拼接更多组件:
python
from langchain_core.output_parsers import StrOutputParser
chain = (
prompt
| llm
| StrOutputParser() # 把 LLM 的 AIMessage 转为纯字符串
)
result = chain.invoke({"text": "Thank you"}) # 直接得到 str,无需 .content
这相当于:
python
prompt_str = prompt.format(**{"text": "Thank you"})
ai_msg = llm.invoke(prompt_str)
result = StrOutputParser().invoke(ai_msg)
但链式写法更清晰、模块化、可测试。
🧪 五、自己实现一个简易版(理解原理)
python
class MyRunnable:
def __or__(self, other):
return MyChain(self, other)
def invoke(self, x):
raise NotImplementedError
class PromptTemplate(MyRunnable):
def invoke(self, inputs):
return f"Input: {inputs['text']}"
class DummyLLM(MyRunnable):
def invoke(self, prompt):
return f"Response to '{prompt}'"
class MyChain(MyRunnable):
def __init__(self, first, second):
self.first = first
self.second = second
def invoke(self, x):
return self.second.invoke(self.first.invoke(x))
# 使用
chain = PromptTemplate() | DummyLLM()
print(chain.invoke({"text": "Hello"}))
# 输出: Response to 'Input: Hello'
这就是 | 链式调用的核心思想!
✅ 总结
| 概念 | 说明 |
|---|---|
| 链式调用 | 用 ` |
| 底层原理 | __or__ 运算符重载 + Runnable.invoke() 协议 |
| 优势 | 代码简洁、可读性强、易于组合和复用 |
| 适用框架 | LangChain、LlamaIndex(部分)、自定义 AI pipeline |
💡 记住:
a | b | c不是"或"逻辑,而是c(b(a(input)))的函数组合!
这种设计灵感来源于 Unix 管道(cat file | grep word | sort),是现代 AI 工程中非常优雅的抽象方式。