Appearance
到目前为止我们都在用裸 openai SDK。它够用,但当应用变复杂——要管模板、要解析输出、要接检索、要串多个步骤——手写会越来越乱。LangChain 就是把这些环节标准化的框架。这一篇讲它最核心的三件套:模型接入、PromptTemplate(提示词模板)、LCEL(把步骤串成链),以及 OutputParser(输出解析)。顶部可切换 Python / Node.js。
本系列基于 LangChain 1.x(Python)/ LangChain.js 1.0(Node)。这是目前的最新大版本,接口和网上很多旧教程不一样,请以本文为准。
装依赖
我们继续用通义千问的 OpenAI 兼容接口,所以模型走 langchain-openai / @langchain/openai 这个适配包。
bash
# -U 是 --upgrade 的简写:把包装成最新版本(已经装过的会升级到最新)
pip install -U langchain langchain-openaibash
npm install @langchain/core @langchain/openai一、接入模型
LangChain 把「聊天模型」抽象成一个统一对象。因为通义千问兼容 OpenAI,我们用 ChatOpenAI,把地址指到百炼即可。注意两边传 base_url 的写法略有不同:Python 直接传 base_url,Node 放在 configuration.baseURL 里。
python
import os
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
temperature=0,
)
# 最简单的一次调用:传入一段文本,拿到一个消息对象
resp = model.invoke("用一句话介绍 LangChain。")
print(resp.content) # .content 才是文本javascript
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "qwen-plus",
apiKey: process.env.DASHSCOPE_API_KEY,
temperature: 0,
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
});
// 最简单的一次调用:传入一段文本,拿到一个消息对象
const resp = await model.invoke("用一句话介绍 LangChain。");
console.log(resp.content); // .content 才是文本注意 invoke 返回的是一个消息对象(AIMessage),文本在 .content 里,而不是像裸 SDK 那样深挖 choices[0].message.content。
二、PromptTemplate:把提示词做成模板
真实应用里,提示词往往是「固定框架 + 变量」。PromptTemplate 让你用 {变量} 占位,调用时再填值,避免手动拼字符串。
python
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个{role},回答尽量简洁。"),
("human", "请解释:{topic}"),
])
# 填入变量,得到最终的一组消息
messages = prompt.invoke({"role": "Python 老师", "topic": "什么是装饰器"})
print(messages)javascript
import { ChatPromptTemplate } from "@langchain/core/prompts";
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是一个{role},回答尽量简洁。"],
["human", "请解释:{topic}"],
]);
// 填入变量,得到最终的一组消息
const messages = await prompt.invoke({ role: "JS 老师", topic: "什么是闭包" });
console.log(messages);每条消息前面的 system / human 就是角色,和第一篇讲的 messages 角色是同一回事,只是 LangChain 换了两个叫法:
system:系统设定,定人设和规则(和裸 SDK 完全一样)。human:用户说的话,等同于裸 SDK 里的user。ai:模型的回复,等同于裸 SDK 里的assistant(多轮对话时才用到)。
也就是说,human = user、ai = assistant,含义没变,只是 LangChain 习惯用「human / ai」这套更口语化的命名。上面模板里 {role}、{topic} 这种花括号则是变量占位符,invoke 时用同名字段填进去——注意别把「消息角色」和「变量」搞混,前者是每条消息的身份,后者是模板里待填的空。
三、LCEL:用管道把步骤串起来
这是 LangChain 的精髓。LCEL(LangChain Expression Language)让你像搭水管一样,把「模板 → 模型 → 解析器」串成一条链:数据从左流到右。Python 用 | 运算符,Node 用 .pipe()。
这个 | 背后的原理——为什么组件能随意拼接、为什么串完就自带流式和批处理——我单独写了一篇深入讲解,想搞懂机制的话强烈推荐先看它:
StrOutputParser / StringOutputParser 的作用是把模型返回的消息对象自动«拆»成纯文本字符串,省得每次自己取 .content。
python
from langchain_core.output_parsers import StrOutputParser
chain = prompt | model | StrOutputParser()
# 模板 → 模型 → 取出纯文本
text = chain.invoke({"role": "Python 老师", "topic": "什么是装饰器"})
print(text) # 直接就是字符串javascript
import { StringOutputParser } from "@langchain/core/output_parsers";
const chain = prompt.pipe(model).pipe(new StringOutputParser());
// 模板 → 模型 → 取出纯文本
const text = await chain.invoke({ role: "JS 老师", topic: "什么是闭包" });
console.log(text); // 直接就是字符串一旦串成链,它就自带 invoke(要结果)、stream(流式)、batch(批量)等能力。比如流式输出,只需把 invoke 换成 stream:
python
for chunk in chain.stream({"role": "Python 老师", "topic": "什么是装饰器"}):
print(chunk, end="", flush=True) # 链已解析,chunk 直接是文本片段
print()javascript
const stream = await chain.stream({ role: "JS 老师", topic: "什么是闭包" });
for await (const chunk of stream) {
process.stdout.write(chunk); // 链已解析,chunk 直接是文本片段
}
process.stdout.write("\n");对比第二篇里手写流式的代码,这里干净了很多——因为解析、拼接这些活儿链帮你干了。
四、JsonOutputParser:结构化输出更省心
上一篇我们手动 json.loads。LangChain 提供 JsonOutputParser,串进链里就能自动把模型输出解析成对象。
python
from langchain_core.output_parsers import JsonOutputParser
json_prompt = ChatPromptTemplate.from_messages([
("system", "你是信息抽取助手,只输出 json:{{\"city\": ..., \"temp\": ...}}"),
("human", "{text}"),
])
json_chain = json_prompt | model | JsonOutputParser()
data = json_chain.invoke({"text": "今天杭州 28 度。"})
print(data["city"], data["temp"]) # 已经是 dictjavascript
import { JsonOutputParser } from "@langchain/core/output_parsers";
const jsonPrompt = ChatPromptTemplate.fromMessages([
["system", '你是信息抽取助手,只输出 json:{{"city": ..., "temp": ...}}'],
["human", "{text}"],
]);
const jsonChain = jsonPrompt.pipe(model).pipe(new JsonOutputParser());
const data = await jsonChain.invoke({ text: "今天杭州 28 度。" });
console.log(data.city, data.temp); // 已经是对象小坑:模板里如果要输出字面量的花括号(比如给模型看 JSON 例子),要写成双花括号
,否则会被当成变量占位符。
小结与预告
这一篇你掌握了 LangChain 的骨架:模型(ChatOpenAI)→ 模板(PromptTemplate)→ 链(LCEL 的 | / .pipe())→ 解析(OutputParser)。以后无论多复杂的应用,本质都是往这条链上加环节。
但现在这条链还是「一问一答、转身就忘」。下一篇《会话记忆》,我们给它接上记忆,让它能记住多轮对话——并且用比第二篇「手动维护 messages 列表」优雅得多的方式实现。