第 25 期 | LangSmith 进阶:Prompt 管理与 A/B 测试
(申请发送: agentupdate)
🎯 本期学习目标
好了,各位未来的 AI 大师们,欢迎来到《LangChain 全栈大师课》的第一期!从今天起,我们将一起踏上构建生产级 AI 应用的征途。本期作为开篇,我们不玩虚的,直捣黄龙,让你彻底掌握 LangChain 应用的基石。
学完本期,你将:
- 彻底理解 LangChain Core 的三大核心组件:LLM 模型、Prompt 模板和 Output Parser 的作用与协作机制。
- 掌握 LangChain Expression Language (LCEL):学会如何用优雅的管道符
|将这些核心组件串联起来,构建一个基础的 LLM 工作流。 - 为智能客服项目打下坚实基础:亲手搭建我们智能客服小助手的第一个“大脑”,让它能根据你的指令进行初步的问答。
- 避开初学者常犯的“大坑”:了解 API Key 安全、Prompt 工程思维以及 LangChain 的性能考量,让你少走弯路。
📖 原理解析
为什么是 LangChain?难道我们不能直接调用 OpenAI 或者 Anthropic 的 API 吗?当然可以!但那就像你只想造个轮子,却要每次都从炼钢开始。LangChain 就像是你手里的乐高积木,它把那些重复的、繁琐的、但又必不可少的部分都给你模块化了。它不仅仅是一个调用 LLM 的库,更是一个编排 LLM 应用的工作流框架。
想象一下我们的“智能客服知识库”项目:它不仅仅需要回答问题,还需要理解用户意图、从海量知识库中检索信息、总结回答、甚至与外部工具交互。如果每次都从头写逻辑,你很快就会陷入意大利面条式的代码泥潭。LangChain 的出现,就是为了解决这个痛点,它提供了一套标准化的接口和组件,让你能像搭积木一样,快速、灵活地构建复杂的 LLM 应用。
LangChain Core 是 LangChain 的心脏,它定义了所有 LLM 应用最基础、最通用的抽象和功能。本期,我们聚焦于三大核心组件:
LLM (Large Language Model) 模型:
- 道的层面:这是我们智能客服的“大脑”,负责理解、推理和生成文本。LangChain 对各种 LLM 提供了统一的接口封装,无论是 OpenAI 的 GPT 系列,还是 Anthropic 的 Claude,抑或是 Hugging Face 上的开源模型,你都可以用相似的方式来调用它们。这大大降低了模型切换的成本,让你能专注于业务逻辑,而不是适配不同模型的 API。
- 术的层面:在 LangChain 中,LLM 通常分为两种类型:
BaseLLM(用于文本补全,如text-davinci-003) 和BaseChatModel(用于对话,如gpt-3.5-turbo)。对于智能客服这种对话场景,我们几乎总是使用BaseChatModel,因为它更擅长处理对话历史,理解角色和多轮交互。
Prompt (提示) 模板:
- 道的层面:Prompt,这玩意儿是你的 LLM 应用的灵魂,是它让你的 AI 从一个无所不知但又啥都不懂的“傻白甜”变成一个目标明确、专业对口的“小能手”。一个好的 Prompt 能够引导 LLM 按照你期望的方式进行输出,无论是扮演客服角色、总结文本还是生成特定格式的数据。对于智能客服来说,Prompt 是我们定义“客服小助手”人设、回答风格和专业知识的关键。
- 术的层面:LangChain 提供了强大的
PromptTemplate和ChatPromptTemplate。ChatPromptTemplate更适用于对话模型,它允许你定义不同角色的消息(SystemMessage、HumanMessage、AIMessage),让 LLM 更好地理解上下文和角色扮演。比如,我们可以通过SystemMessage来告诉 LLM:“你是一个专业的智能客服,请用友好、简洁的语气回答问题。”
Output Parser (输出解析器):
- 道的层面:LLM 吐出来的都是自由文本,但很多时候我们希望得到结构化的数据,比如一个 JSON 对象、一个列表、或者一个布尔值。Output Parser 的作用就像一个“翻译官”,它能把 LLM 生成的非结构化文本,按照我们预设的格式进行解析和转换。这对于后续的程序逻辑处理至关重要。
- 术的层面:LangChain 提供了多种 Output Parser,从最简单的
StrOutputParser(直接返回字符串) 到复杂的PydanticOutputParser(解析成 Pydantic 模型对象),应有尽有。对于智能客服,如果我们希望它不仅给出答案,还能标记问题类别(如“这是关于计费的问题”),Output Parser 就变得不可或缺。
而将这些组件连接起来的“胶水”,就是 LangChain Expression Language (LCEL)。
LCEL
- 道的层面:LCEL,这名字听起来有点酷,有点高级,但本质上,它就是 LangChain 提供给你的一套超能力,让你能像搭积木一样,把 LLM 应用的各个模块,用管道符
|优雅地连接起来。它不仅让代码变得简洁易读,更重要的是,它提供了异步执行、流式传输、并行处理、回退机制等高级功能,为构建高性能、鲁棒的生产级应用打下了基础。 - 术的层面:在 LCEL 中,你可以像 Unix 管道一样,将一个组件的输出作为下一个组件的输入。例如
prompt | llm | output_parser就构成了一个完整的链条。
现在,让我们用一张 Mermaid 图来直观地感受一下 LangChain Core 的这些组件是如何协同工作的:
graph TD
subgraph LangChain Core - LLM 应用的骨架
A[Prompt Template
定义意图与格式] --> B[LLM Model
智能核心,生成响应];
B --> C[Output Parser
结构化输出,确保格式];
end
U[用户输入
原始问题] --> A;
C --> D[结构化/格式化输出
客服回答];
style A fill:#e0f7fa,stroke:#00796b,stroke-width:2px,color:#000,font-weight:bold
style B fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000,font-weight:bold
style C fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#000,font-weight:bold
style U fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#000,font-weight:bold
style D fill:#f0f4c3,stroke:#9e9d24,stroke-width:2px,color:#000,font-weight:bold
linkStyle 0 stroke-width:2px,stroke:red;
linkStyle 1 stroke-width:2px,stroke:blue;
linkStyle 2 stroke-width:2px,stroke:green;
linkStyle 3 stroke-width:2px,stroke:purple;这张图清晰地展示了从用户输入到最终输出的整个流程。用户的问题首先经过 Prompt Template 进行格式化和包装,然后发送给 LLM Model 进行处理,LLM 生成的原始响应再由 Output Parser 进行结构化,最终得到我们期望的客服回答。这个流程就是我们智能客服小助手的第一个“大脑”工作流!
💻 实战代码演练 (客服项目中的具体应用)
好了,原理讲明白了,是时候撸起袖子,把这些概念落地到我们的“智能客服知识库”项目上了。本期,我们将构建一个最基础的智能客服问答机器人。它能接收用户问题,然后以一个专业的客服助理的身份给出回答。
准备工作
首先,确保你的开发环境已准备就绪。我们需要安装 LangChain 库以及你选择的 LLM 提供商的 SDK。这里我们以 OpenAI 为例。
# Python 环境
pip install langchain langchain-openai python-dotenv
# TypeScript/JavaScript 环境
npm install langchain @langchain/openai dotenv
别告诉我你还在代码里硬编码 API Key!这是大忌!我们使用 python-dotenv 或 dotenv 来管理环境变量。在项目根目录下创建一个 .env 文件,并填入你的 OpenAI API Key:
# .env 文件
OPENAI_API_KEY="sk-your-openai-api-key-here"
Python 代码实现
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, SystemMessage
# 1. 加载环境变量
load_dotenv()
# 2. 初始化 LLM 模型
# 我们选择 ChatOpenAI,因为它是对话模型,更适合客服场景
# model_name 可以根据你的需求选择,例如 "gpt-4" 获取更高质量回答,但成本更高
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.7)
# 3. 定义 Prompt 模板
# 这是我们智能客服的“人设”和“指令”
# SystemMessagePromptTemplate 定义了客服的背景和行为准则
# HumanMessagePromptTemplate 接收用户的问题
prompt_template = ChatPromptTemplate.from_messages(
[
SystemMessagePromptTemplate.from_template(
"你是一个专业的智能客服助理,你的任务是简洁、准确地回答用户关于我们产品(智能客服知识库)的问题。"
"请保持友好和乐于助人的语气。如果问题超出你的知识范围,请礼貌地告知用户你无法回答,并建议他们联系人工客服。"
),
HumanMessagePromptTemplate.from_template("{user_question}"), # {user_question} 是一个占位符
]
)
# 4. 初始化 Output Parser
# 最简单的输出解析器,直接将 LLM 的响应作为字符串返回
output_parser = StrOutputParser()
# 5. 使用 LCEL 串联组件,构建智能客服链
# 管道符 `|` 将 Prompt 的输出作为 LLM 的输入,LLM 的输出作为 Output Parser 的输入
support_copilot_chain = prompt_template | llm | output_parser
# 6. 模拟用户提问并获取回答
def get_copilot_response(question: str) -> str:
"""
模拟智能客服接收用户问题并返回回答。
"""
print(f"\n--- 用户提问 ---")
print(f"用户: {question}")
# 调用链来获取回答
# input 字典的键需要与 prompt_template 中定义的占位符名称一致
response = support_copilot_chain.invoke({"user_question": question})
print(f"\n--- 智能客服回答 ---")
print(f"客服: {response}")
return response
if __name__ == "__main__":
print("欢迎使用智能客服小助手!输入 '退出' 结束对话。")
while True:
user_input = input("你: ")
if user_input.lower() == "退出":
print("感谢您的使用,再见!")
break
get_copilot_response(user_input)
# 我们可以尝试一些边界情况
if user_input.lower() == "测试边界":
get_copilot_response("你们产品的退款政策是什么?")
get_copilot_response("给我讲个笑话吧。") # 应该会被System Prompt限制
get_copilot_response("如何配置知识库的检索策略?")
get_copilot_response("请问今天天气怎么样?") # 应该会被System Prompt限制
TypeScript 代码实现
import 'dotenv/config'; // 确保在文件顶部加载环境变量
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { RunnableSequence } from '@langchain/core/runnables';
// 1. 环境变量已通过 'dotenv/config' 加载
// 2. 初始化 LLM 模型
// 使用 process.env.OPENAI_API_KEY 确保 API Key 被正确加载
const llm = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
temperature: 0.7,
openAIApiKey: process.env.OPENAI_API_KEY, // 明确传入 API Key
});
// 3. 定义 Prompt 模板
const promptTemplate = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(
"你是一个专业的智能客服助理,你的任务是简洁、准确地回答用户关于我们产品(智能客服知识库)的问题。" +
"请保持友好和乐于助人的语气。如果问题超出你的知识范围,请礼貌地告知用户你无法回答,并建议他们联系人工客服。"
),
HumanMessagePromptTemplate.fromTemplate("{user_question}"), // {user_question} 是一个占位符
]);
// 4. 初始化 Output Parser
const outputParser = new StringOutputParser();
// 5. 使用 LCEL 串联组件,构建智能客服链
// 在 TypeScript 中,RunnableSequence 是构建链的常用方式
const supportCopilotChain = RunnableSequence.from([
promptTemplate,
llm,
outputParser,
]);
// 6. 模拟用户提问并获取回答
async function getCopilotResponse(question: string): Promise<string> {
console.log(`\n--- 用户提问 ---`);
console.log(`用户: ${question}`);
// 调用链来获取回答
const response = await supportCopilotChain.invoke({ user_question: question });
console.log(`\n--- 智能客服回答 ---`);
console.log(`客服: ${response}`);
return response;
}
// 模拟交互
async function main() {
console.log("欢迎使用智能客服小助手!输入 '退出' 结束对话。");
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
for await (const line of readline) {
const userInput = line.trim();
if (userInput.toLowerCase() === "退出") {
console.log("感谢您的使用,再见!");
readline.close();
break;
}
await getCopilotResponse(userInput);
// 我们可以尝试一些边界情况
if (userInput.toLowerCase() === "测试边界") {
await getCopilotResponse("你们产品的退款政策是什么?");
await getCopilotResponse("给我讲个笑话吧。"); // 应该会被System Prompt限制
await getCopilotResponse("如何配置知识库的检索策略?");
await getCopilotResponse("请问今天天气怎么样?"); // 应该会被System Prompt限制
}
}
}
if (require.main === module) {
main();
}
代码解析:
- 加载环境变量:这是安全实践!永远不要把敏感信息直接写在代码里。
- 初始化 LLM:我们实例化了
ChatOpenAI。model_name(或modelName) 决定了使用哪个具体的模型,temperature控制生成文本的随机性(0.0 最确定,1.0 最随机,客服场景通常设为 0.5-0.7 比较合适)。 - 定义 Prompt 模板:
ChatPromptTemplate.from_messages是构建对话型 Prompt 的推荐方式。SystemMessagePromptTemplate用来设置 AI 的“角色”和“行为准则”。这是我们定义智能客服的关键!它让 AI 知道自己是一个专业的客服,应该怎么说话,什么能说,什么不能说。HumanMessagePromptTemplate则用于接收用户的实际输入。{user_question}是一个占位符,LangChain 会在invoke时用实际的用户问题替换它。
- 初始化 Output Parser:这里我们使用了最基础的
StrOutputParser(Python) 或StringOutputParser(TypeScript),它只是简单地把 LLM 的输出直接作为字符串返回。在后续的课程中,我们将引入更复杂的解析器来获取结构化数据。 - 构建链:这是 LCEL 的魅力所在!
- Python 中,我们直接使用管道符
|将prompt_template、llm和output_parser串联起来。 - TypeScript 中,我们使用
RunnableSequence.from来构建同样的链。 无论哪种语言,其核心思想都是:上一个组件的输出作为下一个组件的输入。
- Python 中,我们直接使用管道符
- 调用链:通过
support_copilot_chain.invoke({"user_question": question})(Python) 或await supportCopilotChain.invoke({ user_question: question })(TypeScript),我们将用户问题传递给链,并获得最终的客服回答。invoke方法接收一个字典(或 JavaScript 对象),其键名必须与 Prompt 模板中的占位符名称一致。
运行这些代码,你就能看到你的第一个智能客服小助手在命令行中与你对话了!尝试问一些关于“智能客服知识库”的问题,也试试问一些超出它知识范围的问题,看看它是否能按照 System Prompt 的指示,礼貌地拒绝并引导用户联系人工客服。这就是 Prompt Engineering 的力量!
坑与避坑指南
作为一名资深 AI 架构师,我见过太多新手在这里栽跟头。这些“坑”你必须提前知道,才能走得更远:
API Key 安全是头等大事:
- 坑:把
OPENAI_API_KEY硬编码在代码里,或者直接上传到 GitHub 公开仓库。这相当于把你的银行卡密码贴在额头上出门。 - 避坑:永远使用环境变量!
python-dotenv或dotenv是你的好朋友。在生产环境中,使用云服务商提供的密钥管理服务(如 AWS Secrets Manager, Azure Key Vault, Google Secret Manager)。本地开发时,确保.env文件被.gitignore忽略。
- 坑:把
Prompt Engineering 是艺术也是科学:
- 坑:认为只要把问题扔给 LLM 就行了,不花时间精心设计 Prompt。结果 LLM 乱答一通,或者答非所问。
- 避坑:Prompt 是你与 LLM 沟通的唯一桥梁。它需要清晰、明确、有结构。
- 角色定义:清楚地告诉 LLM 它应该扮演什么角色(“你是一个专业的客服助理”)。
- 任务说明:明确你的任务是什么(“回答用户关于产品的问题”)。
- 约束条件:设定输出的格式、语气、长度限制,以及处理未知情况的策略(“如果超出知识范围,请礼貌告知”)。
- 迭代优化:Prompt 不是一蹴而就的,需要反复测试、调整和优化。
LLM 模型的选择与成本考量:
- 坑:一开始就无脑使用最强大的模型(如
gpt-4),导致成本飙升,响应速度慢。或者为了省钱使用性能不足的模型,导致效果差。 - 避坑:根据你的业务需求和预算选择合适的模型。
gpt-3.5-turbo:性价比高,速度快,适合大部分日常客服问答。gpt-4:推理能力更强,适合复杂问题、多步骤推理,但成本更高,速度较慢。- 开源模型 (通过 Ollama/HuggingFace):成本最低(自托管),但需要管理基础设施,性能可能不如商业模型,适合对数据隐私有极高要求或预算有限的场景。
- 温度 (temperature):设置为 0.0 会让模型输出更确定、重复性高;设置为 1.0 会更具创造性、随机性。客服场景通常在 0.5-0.7 之间,既能保持一定灵活性,又不至于胡编乱造。
- 坑:一开始就无脑使用最强大的模型(如
LLM 的“幻觉”与事实核查:
- 坑:盲目信任 LLM 生成的所有内容,不进行任何事实核查,尤其是在关键业务场景。
- 避坑:LLM 可能会“一本正经地胡说八道”,这被称为“幻觉”。对于智能客服,这意味着它可能会捏造产品功能、退款政策等信息。
- Prompt 限制:在 Prompt 中明确要求“只根据已知信息回答,如果不知道,请告知”。
- RAG (Retrieval Augmented Generation):这是我们后续课程会深入讲解的核心技术。通过从外部知识库中检索相关信息,并将其作为上下文提供给 LLM,可以大大减少幻觉,提高回答的准确性。这是生产级智能客服的标配!
LCEL 的异步与流式处理:
- 坑:在处理大量请求或需要实时反馈的场景中,同步调用 LLM 导致响应慢,用户体验差。
- 避坑:LCEL 天生支持异步
ainvoke()和流式astream()。对于客服应用,用户希望快速得到回答,所以异步和流式输出是必须掌握的技能,我们会在后续课程中逐步引入。提前知道这一点,你就赢在了起跑线上。
📝 本期小结
恭喜你!在本期《LangChain 全栈大师课》中,我们不仅深入理解了 LangChain Core 的三大基石:LLM 模型、Prompt 模板和 Output Parser,更重要的是,你亲手使用 LangChain Expression Language (LCEL) 构建了你的第一个智能客服小助手。
我们为客服小助手赋予了初步的“大脑”和“人设”,让它能够根据你的指令,以专业的态度进行基础问答。这个看似简单的链条——Prompt | LLM | Output Parser——却是所有复杂 LLM 应用的起点。
你还学到了作为一名资深开发者,在构建 LLM 应用时必须注意的“坑”和“避坑指南”,包括 API Key 安全、Prompt 工程思维、模型选择以及对 LLM 局限性的认识。
这只是万里长征的第一步。在接下来的课程中,我们将在此基础上不断迭代,为我们的“智能客服知识库”添加记忆、集成外部知识库(RAG)、调用工具、实现多轮对话等更高级的功能,逐步把它打造成一个真正的生产级 AI 应用。
准备好了吗?下一