LangChain 环境搭建指南
目标
搭建 Python 开发环境,安装 LangChain,运行第一个程序。
步骤 1:创建项目目录
mkdir -p /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
步骤 2:创建 Python 虚拟环境
python3 -m venv venv
虚拟环境会创建在项目目录下的 venv/ 文件夹中。
步骤 3:安装依赖包
激活虚拟环境并安装依赖:
# 激活虚拟环境
source venv/bin/activate
# 安装核心依赖
pip install langchain langchain-community dashscope python-dotenv
已安装的依赖:
| 包名 | 版本 | 作用 |
|---|---|---|
| langchain | 最新 | LangChain 核心框架 |
| langchain-community | 最新 | 社区模型支持(含通义千问) |
| dashscope | 最新 | 阿里云 DashScope SDK |
| python-dotenv | 最新 | 加载 .env 环境变量 |
步骤 4:获取通义千问 API Key
- 访问阿里云 DashScope 控制台:dashscope.console.aliyun.com/apiKey
- 注册/登录阿里云账号(新用户有免费额度)
- 点击「创建 API Key」
- 复制生成的 Key(格式:
sk-xxxxxxxxxx)
步骤 5:配置环境变量
# 复制配置模板
cp .env.example .env
# 编辑 .env 文件(用文本编辑器)
open -e .env
将文件内容修改为:
DASHSCOPE_API_KEY=sk-你的真实API-Key
步骤 6:运行第一个程序
# 确保在虚拟环境中
source venv/bin/activate
# 运行程序
python 01_hello_langchain.py
预期输出:
==================================================
基础用法 1:直接字符串调用
==================================================
AI 回答: LangChain 是一个用于开发大语言模型应用的框架...
==================================================
基础用法 2:使用消息列表
==================================================
AI 回答:
1. ...
2. ...
3. ...
==================================================
基础用法 3:流式输出
==================================================
AI 回答: Agent 是...
==================================================
🎉 恭喜!你已经完成了第一个 LangChain 程序!
==================================================
常见问题
Q1: 提示 "DASHSCOPE_API_KEY not found"
解决:检查 .env 文件是否正确创建,且 Key 格式正确。
Q2: 提示模型调用失败
解决:
- 检查 API Key 是否有效
- 检查阿里云账号是否有免费额度
- 确认网络连接正常
Q3: 如何退出虚拟环境
deactivate
二、 LLM 基础调用指南
目标
掌握 LangChain 中 LLM 的基本使用,包括消息类型、提示词模板、输出解析器。
前置要求
- 已完成 Task 1(环境搭建)
- 虚拟环境已激活
- API Key 配置正确
步骤 1:理解核心概念
1.1 Chat Models vs LLMs
| 类型 | 说明 | 使用场景 |
|---|---|---|
| Chat Models | 对话模型,接收消息列表 | 大多数场景(推荐) |
| LLMs | 文本补全模型,接收纯文本 | 特定补全任务 |
我们使用 Chat Models(如通义千问)。
1.2 消息类型
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
# SystemMessage: 设置 AI 角色和行为
system_msg = SystemMessage(content="你是一个专业翻译")
# HumanMessage: 用户输入
human_msg = HumanMessage(content="把这句话翻译成英文")
# AIMessage: AI 回复(通常用于历史记录)
ai_msg = AIMessage(content="This is the translation")
步骤 2:创建示例代码文件
创建文件 02_llm_basics.py:
touch /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn/02_llm_basics.py
步骤 3:基础调用练习
3.1 直接字符串调用
from langchain_community.chat_models import ChatTongyi
llm = ChatTongyi(model="qwen-turbo")
# 方式1:直接传字符串
response = llm.invoke("什么是机器学习?")
print(response.content)
3.2 使用消息列表(推荐)
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(content="你是一位耐心的老师,用简单语言解释概念。"),
HumanMessage(content="什么是机器学习?")
]
response = llm.invoke(messages)
print(response.content)
步骤 4:提示词模板(PromptTemplate)
4.1 基础模板
from langchain_core.prompts import ChatPromptTemplate
# 创建模板
template = ChatPromptTemplate.from_messages([
("system", "你是一位{role},擅长{skill}。"),
("human", "请帮我{task}")
])
# 填充变量
messages = template.format_messages(
role="Python专家",
skill="数据分析",
task="写一个读取CSV的代码"
)
response = llm.invoke(messages)
print(response.content)
4.2 链式调用(LCEL)
# 使用管道符 | 构建链
chain = template | llm
# 直接传入变量
response = chain.invoke({
"role": "Python专家",
"skill": "数据分析",
"task": "写一个读取CSV的代码"
})
print(response.content)
步骤 5:输出解析器(OutputParser)
5.1 字符串输出解析
from langchain_core.output_parsers import StrOutputParser
# 创建解析器
parser = StrOutputParser()
# 构建链:模板 -> LLM -> 解析器
chain = template | llm | parser
# 获取纯字符串输出(不含元数据)
result = chain.invoke({
"role": "Python专家",
"skill": "数据分析",
"task": "写一个读取CSV的代码"
})
print(result) # 纯字符串
5.2 JSON 结构化输出
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
# 定义输出结构
class Person(BaseModel):
name: str = Field(description="姓名")
age: int = Field(description="年龄")
hobbies: list = Field(description="爱好列表")
# 创建解析器
parser = JsonOutputParser(pydantic_object=Person)
# 创建提示词模板
template = ChatPromptTemplate.from_template("""
提取以下文本中的信息:
{text}
{format_instructions}
""")
# 构建链
chain = template.partial(format_instructions=parser.get_format_instructions()) | llm | parser
# 调用
result = chain.invoke({
"text": "张三今年25岁,喜欢打篮球和编程。"
})
print(result) # {'name': '张三', 'age': 25, 'hobbies': ['打篮球', '编程']}
步骤 6:流式输出
# 流式输出,适合长文本
print("AI 回答: ", end="")
for chunk in llm.stream("写一个100字的自我介绍"):
print(chunk.content, end="", flush=True)
print()
完整练习代码
我已经为你准备了完整的练习代码,包含以上所有内容。
运行方式:
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
source venv/bin/activate
python 02_llm_basics.py
练习任务
完成以下任务巩固学习:
练习 1:多语言翻译器
创建一个支持多种语言翻译的提示词模板。
练习 2:信息提取器
从一段文本中提取:人名、地点、时间、事件。
练习 3:代码解释器
输入一段代码,AI 解释其功能和关键步骤。
常见问题
Q1: 提示词模板变量怎么写?
使用 {variable_name} 格式,调用时传入字典。
Q2: 输出解析失败怎么办?
- 检查模型输出是否符合预期格式
- 尝试使用更强大的模型(如 qwen-plus)
- 在提示词中明确要求 JSON 格式
Q3: 流式输出和普通输出区别?
- 普通输出:等待完整响应后返回
- 流式输出:逐字返回,用户体验更好
三、 Chain 链式调用指南
目标
理解 Chain 的概念,掌握 LCEL(LangChain Expression Language)语法,学会组合多个步骤。
前置要求
- 已完成 Task 2(LLM 基础调用)
- 理解 PromptTemplate 和 OutputParser
核心概念:LCEL
LCEL = LangChain Expression Language,用 | 管道符像拼积木一样组合组件。
# 基本语法
chain = 组件A | 组件B | 组件C
# 例如
chain = prompt | llm | parser
result = chain.invoke({"key": "value"})
步骤 1:Runnable 接口
LangChain 中所有组件都实现 Runnable 接口,支持统一的操作方法:
| 方法 | 说明 |
|---|---|
invoke() | 单次调用,传入单个输入 |
batch() | 批量调用,传入列表 |
stream() | 流式输出 |
ainvoke() | 异步调用 |
步骤 2:简单链(RunnableSequence)
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.chat_models import ChatTongyi
from langchain_core.output_parsers import StrOutputParser
# 定义组件
prompt = ChatPromptTemplate.from_template("把以下翻译成英文:{text}")
llm = ChatTongyi(model="qwen-turbo")
parser = StrOutputParser()
# 用 | 串联成链
chain = prompt | llm | parser
# 调用
result = chain.invoke({"text": "你好,世界"})
print(result) # Hello, world
步骤 3:并行执行(RunnableParallel)
同时执行多个任务,结果合并:
from langchain_core.runnables import RunnableParallel
# 定义两个并行的链
chain_a = ChatPromptTemplate.from_template("总结以下内容:{text}") | llm | parser
chain_b = ChatPromptTemplate.from_template("提取关键词:{text}") | llm | parser
# 并行执行
parallel_chain = RunnableParallel(
summary=chain_a,
keywords=chain_b
)
result = parallel_chain.invoke({"text": "人工智能正在改变我们的生活..."})
print(result)
# {
# "summary": "人工智能对生活的影响...",
# "keywords": "人工智能, 生活, 改变"
# }
步骤 4:条件分支(RunnableBranch)
根据条件选择不同的处理路径:
from langchain_core.runnables import RunnableBranch
# 定义不同场景的链
code_chain = ChatPromptTemplate.from_template("解释这段代码:{input}") | llm | parser
qa_chain = ChatPromptTemplate.from_template("回答问题:{input}") | llm | parser
# 条件分支
branch = RunnableBranch(
(lambda x: "代码" in x["input"], code_chain), # 如果包含"代码",走 code_chain
(lambda x: "问题" in x["input"], qa_chain), # 如果包含"问题",走 qa_chain
qa_chain # 默认分支
)
result = branch.invoke({"input": "这段代码是什么意思:print('hello')"})
步骤 5:自定义函数(RunnableLambda)
在链中插入自定义 Python 函数:
from langchain_core.runnables import RunnableLambda
# 定义自定义函数
def add_hello(text: str) -> str:
return f"你好,{text}"
# 包装成 Runnable
hello_step = RunnableLambda(add_hello)
# 插入到链中
chain = prompt | hello_step | llm | parser
步骤 6:链的组合与嵌套
复杂场景可以组合多个链:
# 链1:生成内容
generate_chain = (
ChatPromptTemplate.from_template("写一篇关于{topic}的短文")
| llm
| parser
)
# 链2:翻译内容
translate_chain = (
ChatPromptTemplate.from_template("把以下内容翻译成英文:{text}")
| llm
| parser
)
# 组合:先生成,再翻译
full_chain = (
generate_chain
| (lambda x: {"text": x}) # 转换格式
| translate_chain
)
result = full_chain.invoke({"topic": "人工智能"})
完整练习代码
运行方式:
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
source venv/bin/activate
python 03_chain_lcel.py
练习任务
练习 1:多语言翻译器
创建一个链,支持中文 → 英文 → 日文的多步翻译。
练习 2:智能客服路由
根据用户问题类型(订单/售后/咨询),路由到不同的处理链。
练习 3:内容生成流水线
构建链:主题 → 生成大纲 → 生成内容 → 润色 → 输出。
常见问题
Q1: 链中报错怎么调试?
解决:逐步测试每个组件,确保中间输出格式正确。
Q2: 如何查看链的中间结果?
解决:使用 RunnablePassthrough 或分步调用。
Q3: 链的性能优化?
解决:使用 batch() 批量处理,或启用异步 ainvoke()。
四、Memory 对话记忆指南
目标
让 AI 具备上下文记忆能力,实现多轮对话。
前置要求
- 已完成 Task 3(Chain 链式调用)
- 理解 LCEL 语法
- 注意:本教程基于 LangChain 0.2+ 版本(API 有较大变化)
为什么需要 Memory?
默认情况下,LLM 是无状态的,每次调用都是独立的:
# 第一次调用
llm.invoke("我叫张三") # AI: 你好张三
# 第二次调用
llm.invoke("我叫什么名字?") # AI: 我不知道(因为没有记忆)
Memory 解决这个问题,让 AI 记住对话历史。
核心概念
1. 消息类型回顾
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage(content="你是助手"), # 系统设定
HumanMessage(content="你好"), # 用户说
AIMessage(content="你好!有什么可以帮你?"), # AI 回复
HumanMessage(content="我叫张三"), # 用户说
AIMessage(content="你好张三!"), # AI 回复
]
2. Memory 的作用
自动管理消息历史,决定:记住什么、怎么记住、记住多少。
步骤 1:ChatMessageHistory(基础消息历史)
LangChain 0.2+ 使用 ChatMessageHistory 管理对话历史。
from langchain_community.chat_message_histories import ChatMessageHistory
# 创建消息历史
history = ChatMessageHistory()
# 添加消息
history.add_user_message("我叫张三")
history.add_ai_message("你好张三!")
# 查看消息
for msg in history.messages:
print(f"{msg.type}: {msg.content}")
# 输出:
# human: 我叫张三
# ai: 你好张三!
步骤 2:RunnableWithMessageHistory(自动记忆)
LangChain 0.2+ 推荐使用 RunnableWithMessageHistory 包装链,自动处理记忆。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_models import ChatTongyi
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 创建带记忆的提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的助手,记住用户的信息。"),
MessagesPlaceholder(variable_name="history"), # 历史消息插入位置
("human", "{input}")
])
# 初始化
llm = ChatTongyi(model="qwen-turbo")
chain = prompt | llm | StrOutputParser()
# 存储会话历史的字典
store = {}
def get_session_history(session_id: str) -> ChatMessageHistory:
"""获取或创建会话历史"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 包装成带记忆的链
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
# 调用(使用 session_id 区分不同用户)
response = chain_with_history.invoke(
{"input": "我叫张三"},
config={"configurable": {"session_id": "user_001"}}
)
print(response)
# 再次调用,AI 能记住名字
response = chain_with_history.invoke(
{"input": "我叫什么名字?"},
config={"configurable": {"session_id": "user_001"}} # 相同的 session_id
)
print(response) # AI: 你叫张三
步骤 3:滑动窗口记忆(自定义实现)
只保留最近 N 条消息,节省 Token。
class WindowChatMessageHistory(ChatMessageHistory):
"""带窗口限制的消息历史"""
def __init__(self, max_messages=4, **kwargs):
super().__init__(**kwargs)
self._max_messages = max_messages
def add_message(self, message):
super().add_message(message)
# 只保留最近 max_messages 条
if len(self.messages) > self._max_messages:
self.messages = self.messages[-self._max_messages:]
# 使用
window_history = WindowChatMessageHistory(max_messages=4)
步骤 4:摘要记忆(Summary Memory)
当对话过长时,对历史进行摘要,而不是完整保存。
from langchain_core.messages import SystemMessage
class SummaryChatMessageHistory(ChatMessageHistory):
"""带摘要功能的消息历史"""
def __init__(self, llm, max_messages=6, **kwargs):
super().__init__(**kwargs)
self._llm = llm # 用于生成摘要的 LLM
self._max_messages = max_messages
self._summary = ""
def add_message(self, message):
super().add_message(message)
# 当消息超过阈值时,进行摘要
if len(self.messages) > self._max_messages:
self._summarize()
def _summarize(self):
"""对历史消息进行摘要"""
# 取前一半消息进行摘要
messages_to_summarize = self.messages[:self._max_messages//2]
# 构建摘要提示
summary_prompt = f"请对以下对话进行简洁摘要:\n{messages_to_summarize}"
# 调用 LLM 生成摘要
summary_response = self._llm.invoke(summary_prompt)
self._summary = summary_response.content
# 保留后半部分消息,并在开头添加摘要
self.messages = self.messages[self._max_messages//2:]
system_msg = SystemMessage(content=f"【历史摘要】{self._summary}")
self.messages.insert(0, system_msg)
三种 Memory 对比
| Memory 类型 | 特点 | 适用场景 | Token 消耗 |
|---|---|---|---|
| 基础记忆 | 完整保存所有消息 | 对话轮数少 | 高 |
| 滑动窗口 | 只保留最近 N 条 | 只关心近期对话 | 中 |
| 摘要记忆 | LLM 自动摘要历史 | 长对话,需了解全貌 | 低 |
实际好处详解
滑动窗口记忆的好处
1. 节省 Token 成本
场景:客服机器人,用户已对话 50 轮
- 基础记忆:发送 50 轮历史 = 消耗 3000+ Token
- 滑动窗口(k=4):只发送最近 4 轮 = 消耗 400 Token
- 节省:86% 的 Token 费用
2. 提高响应速度
- 每次请求携带的历史越少,API 响应越快
- 减少网络传输时间
3. 避免无关信息干扰
用户早期说:"我喜欢红色"
用户后期讨论:技术问题
→ 滑动窗口自动"遗忘"颜色偏好,避免 AI 在技术回答中突然提到红色
适用场景:
- 实时客服(只关心当前问题)
- 代码助手(只关心当前代码上下文)
- 翻译助手(只关心当前句子)
摘要记忆的好处
1. 兼顾长历史与 Token 成本
场景:医疗问诊,已对话 30 轮
- 基础记忆:30 轮完整对话 = 5000 Token
- 滑动窗口:丢失早期症状描述
- 摘要记忆:
【摘要】患者男,35岁,3天前发热,昨日咳嗽,体温最高39度...
+ 最近 4 轮详细对话 = 800 Token
2. 保留关键信息,丢弃细节噪音
原始对话(10轮):
- 用户:你好
- AI:你好
- 用户:我想订机票
- AI:好的,去哪里
- 用户:北京
- AI:什么时候
- 用户:下周一
- AI:几点
- 用户:上午
- AI:好的...
摘要结果:
【摘要】用户要订下周一上午去北京的机票
→ 保留了核心意图,去除了寒暄和确认过程
3. 支持超长对话
- 理论上可以支持无限轮对话
- 摘要可以再次摘要(分层摘要)
适用场景:
- 医疗问诊(需要了解病史全貌)
- 法律助手(需要了解案件背景)
- 小说创作(需要记住人物设定和剧情)
- 项目管理(需要了解项目历史决策)
如何选择?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单问答机器人 | 基础记忆 | 对话短,Token 够用 |
| 实时客服 | 滑动窗口 | 只关心当前问题 |
| 医疗/法律咨询 | 摘要记忆 | 需要历史全貌 |
| 个人助手 | 摘要记忆 | 长期陪伴,记住偏好 |
| 代码助手 | 滑动窗口 | 代码上下文变化快 |
步骤 5:多用户会话隔离
使用不同的 session_id 实现多用户隔离:
# 用户 A
response_a = chain_with_history.invoke(
{"input": "我叫王五"},
config={"configurable": {"session_id": "user_A"}}
)
# 用户 B(完全不同的会话)
response_b = chain_with_history.invoke(
{"input": "我叫赵六"},
config={"configurable": {"session_id": "user_B"}}
)
# 用户 A 再次询问 - 能记住自己是王五
response_a2 = chain_with_history.invoke(
{"input": "我叫什么名字?"},
config={"configurable": {"session_id": "user_A"}}
)
LangChain 0.1 vs 0.2 API 对比
| 功能 | 0.1.x 旧版本 | 0.2+ 新版本 |
|---|---|---|
| 消息历史 | ConversationBufferMemory | ChatMessageHistory |
| 自动记忆链 | ConversationChain | RunnableWithMessageHistory |
| 保存消息 | memory.save_context() | history.add_user_message() / add_ai_message() |
| 加载历史 | memory.load_memory_variables() | history.messages |
完整练习代码
运行方式:
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
source venv/bin/activate
python 04_memory.py
练习任务
练习 1:带记忆的翻译助手
创建一个翻译助手,记住用户的偏好(如专业领域、目标语言)。
练习 2:多角色对话
实现一个系统,AI 根据用户选择的角色(老师/朋友/专家)调整对话风格。
练习 3:持久化记忆
将对话历史保存到文件或数据库,程序重启后仍能恢复。
常见问题
Q1: Token 超限怎么办?
解决:使用滑动窗口记忆(WindowChatMessageHistory),限制历史长度。
Q2: 如何清空记忆?
解决:store[session_id].clear() 或重新创建 ChatMessageHistory。
Q3: session_id 有什么用?
解决:区分不同用户或不同会话,实现记忆隔离。
Q4: 旧版代码还能用吗?
解决:LangChain 0.2 仍然兼容旧 API,但推荐使用新 API。
五、Tools 与 Agent 指南 ⭐核心
目标
掌握 Agent 的核心概念,学会创建自定义工具,构建能自主决策的 AI Agent。
前置要求
- 已完成 Task 4(Memory 对话记忆)
- 理解 LCEL 链式调用
- 理解 Python 装饰器概念
核心概念
什么是 Agent?
Agent = LLM + 工具 + 决策能力
普通 LLM 只能回答问题,Agent 可以:
- 理解用户意图 → 决定做什么
- 选择合适工具 → 执行具体操作
- 观察执行结果 → 决定下一步
- 循环直到完成 → 给出最终答案
ReAct 模式
Agent 的核心思考模式:Reason(推理)+ Act(行动)
用户:北京今天天气怎么样?
Agent 思考:
1. 用户问天气 → 需要调用天气工具
2. 调用 get_weather("北京")
3. 观察结果:晴天,25°C
4. 组织语言回答用户
最终回答:北京今天晴天,25°C,适合出行!
步骤 1:创建自定义 Tool
使用 @tool 装饰器将 Python 函数变成 Agent 可用的工具。
from langchain.tools import tool
@tool
def calculate_bmi(weight: float, height: float) -> str:
"""
计算 BMI 指数,评估健康状况。
Args:
weight: 体重(公斤)
height: 身高(米)
Returns:
BMI 值和健康建议
"""
bmi = weight / (height ** 2)
if bmi < 18.5:
status = "偏瘦"
elif bmi < 24:
status = "正常"
elif bmi < 28:
status = "偏胖"
else:
status = "肥胖"
return f"BMI: {bmi:.1f},{status}"
# 测试工具
print(calculate_bmi.invoke({"weight": 70, "height": 1.75}))
# 输出:BMI: 22.9,正常
Tool 的关键要素
| 要素 | 说明 |
|---|---|
| 函数名 | 工具的名称,Agent 通过名称识别 |
| 参数类型 | 必须标注类型(str, int, float 等) |
| 文档字符串 | 最重要,描述工具功能和参数,Agent 靠这个理解工具 |
| 返回值 | 工具执行结果,str 格式最佳 |
步骤 2:创建多个工具
@tool
def get_current_time() -> str:
"""获取当前时间"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def word_count(text: str) -> str:
"""
统计文本字数
Args:
text: 要统计的文本
"""
return f"字数:{len(text)}"
# 工具列表
tools = [calculate_bmi, get_current_time, word_count]
步骤 3:创建简化版 Agent
本教程使用简化版 Agent 实现,不依赖 create_react_agent 等高级 API,更容易理解 Agent 的核心原理。
3.1 核心思路
Agent = 决策(LLM)+ 执行(工具)
- 把可用工具信息告诉 LLM
- 让 LLM 决定使用哪个工具,按固定格式返回
- 解析 LLM 的决策,执行对应工具
- 返回结果给用户
3.2 完整代码
from langchain_community.chat_models import ChatTongyi
from langchain_core.runnables import RunnableLambda
# 初始化模型
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
def create_simple_agent(tools, llm):
"""创建简化版 Agent"""
# 构建工具描述
tools_description = "\n".join([
f"- {tool.name}: {tool.description.split(chr(10))[0]}"
for tool in tools
])
def agent_invoke(inputs):
question = inputs["input"]
# 让 LLM 决策
decision_prompt = f"""你是一个智能助手,可以使用以下工具:
{tools_description}
用户问题:{question}
请分析需要使用哪个工具,按以下格式回答:
TOOL|工具名称|参数1=值1|参数2=值2
示例:
- TOOL|calculate_bmi|weight=70|height=1.75
- TOOL|get_current_time
- TOOL|word_count|text=Hello World
如果不需要工具,直接回答:
ANSWER|你的回答内容
你的决策:"""
# 调用 LLM 获取决策
decision = llm.invoke(decision_prompt).content.strip()
print(f"决策:{decision}")
# 解析决策
parts = decision.split("|")
action = parts[0].strip()
if action == "TOOL" and len(parts) >= 2:
tool_name = parts[1].strip()
# 解析参数
kwargs = {}
for param in parts[2:]:
if "=" in param:
key, value = param.split("=", 1)
kwargs[key.strip()] = value.strip()
# 执行工具
for tool in tools:
if tool.name == tool_name:
result = tool.invoke(kwargs)
return f"[{tool_name}] {result}"
return f"未找到工具:{tool_name}"
elif action == "ANSWER":
return parts[1] if len(parts) > 1 else decision
else:
return decision
return RunnableLambda(agent_invoke)
# 创建 Agent
agent = create_simple_agent(tools, llm)
# 运行
result = agent.invoke({"input": "我的身高1.75米,体重70公斤,BMI是多少?"})
print(result) # [calculate_bmi] BMI: 22.9,正常,继续保持
3.3 关键概念解析
决策格式设计
我们设计了一个简单的格式让 LLM 返回决策:
| 格式 | 含义 | 示例 | |||||
|---|---|---|---|---|---|---|---|
| `TOOL | 工具名 | 参数=值` | 调用工具 | `TOOL | calculate_bmi | weight=70 | height=1.75` |
| `ANSWER | 回答内容` | 直接回答 | `ANSWER | 我无法获取天气信息` |
执行流程
用户问题
↓
构建决策提示词(包含工具描述)
↓
LLM 思考并返回决策格式
↓
解析决策字符串
↓
如果是 TOOL → 查找并执行工具 → 返回结果
如果是 ANSWER → 直接返回回答内容
与标准 ReAct Agent 的对比
| 方案 | 本教程简化版 | 标准 ReAct Agent |
|---|---|---|
| 依赖 | 仅依赖基础 LangChain | 需要 langgraph 等扩展 |
| 格式 | 简单自定义格式 | 严格的 ReAct 格式 |
| 复杂度 | 单步决策,容易理解 | 多步思考-行动循环 |
| 适用 | 学习原理、简单任务 | 生产环境、复杂任务 |
步骤 4:扩展——使用官方 ReAct Agent(可选进阶)
本教程使用简化版 Agent 帮助理解原理。生产环境中,可以使用 LangChain 官方提供的 create_react_agent(需要安装 langgraph):
pip install langgraph
from langgraph.prebuilt import create_react_agent
# 创建官方 ReAct Agent
agent = create_react_agent(llm, tools)
# 运行
result = agent.invoke({"messages": [("human", "我的BMI是多少?体重70,身高1.75")]})
官方实现的优势:
- 支持多步思考-行动循环
- 自动处理工具调用和结果观察
- 更好的错误处理和边界情况
建议先掌握简化版的原理,再过渡到官方实现。
两种方案对比
| 方案 | 本教程简化版 | 官方 ReAct Agent |
|---|---|---|
| 依赖 | 基础 LangChain | 需要 langgraph |
| 学习曲线 | 平缓,容易理解原理 | 较陡,但功能更强大 |
| 多步推理 | 单步决策 | 支持多轮思考-行动 |
| 适用场景 | 学习、简单任务 | 生产环境、复杂任务 |
完整练习代码
运行方式:
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
source venv/bin/activate
python 05_tools_agent.py
练习任务
练习 1:天气查询 Agent
创建一个工具查询天气,Agent 根据用户问题决定是否调用。
练习 2:多功能助手
创建工具:计算器、翻译器、单位转换器,Agent 自动选择使用。
练习 3:结合 Memory 的 Agent
让 Agent 记住用户偏好,在后续对话中使用。
常见问题
Q1: Agent 和 Chain 的区别?
Chain:固定流程,按预设步骤执行
Agent:自主决策,根据情况选择工具和步骤
Q2: 工具文档字符串有多重要?
极其重要! Agent 完全靠文档字符串理解工具功能,写得不清楚会导致 Agent 选错工具。
Q3: Agent 调用工具失败怎么办?
- 检查工具文档是否清晰
- 添加错误处理逻辑
- 设置
handle_parsing_errors=True
六、 RAG 检索增强生成指南
目标
让 AI 基于你的私有数据(文档、知识库)回答问题,实现"企业知识问答"。
前置要求
- 已完成 Task 5(Tools 与 Agent)
- 理解 Embedding 和向量数据库基本概念
核心概念:什么是 RAG?
RAG = Retrieval(检索)+ Augmented(增强)+ Generation(生成)
为什么需要 RAG?
LLM 的问题是:
- ❌ 不知道你的私有数据(公司内部文档、个人笔记)
- ❌ 知识有截止日期
- ❌ 可能"幻觉"(编造答案)
RAG 解决思路:
用户提问 → 检索相关知识 → 将知识注入 Prompt → LLM 基于知识回答
RAG 流程图
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 用户提问 │────→│ 检索相关文档 │────→│ LLM 基于文档 │
│ "公司年假几天?" │ │ (向量相似度) │ │ 生成答案 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ ┌─────────────────┐ │
└────────→│ 向量数据库 │←─────────────────┘
│ (Chroma/FAISS) │
└─────────────────┘
↑
┌─────────────────┐
│ 文档预处理 │
│ 加载→分割→Embedding
└─────────────────┘
步骤 1:准备文档
创建测试文档:
mkdir -p /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn/docs
创建 docs/company_policy.txt:
公司名称:未来科技有限公司
【考勤制度】
1. 工作时间:周一至周五,9:00-18:00,午休1小时
2. 迟到:每月前3次免责,超过3次每次扣50元
3. 年假:入职满1年享5天,满3年享10天,满5年享15天
4. 请假:需提前1天在系统申请,紧急情况下可后补
【薪资福利】
1. 发薪日:每月10日
2. 五险一金:按全额工资缴纳
3. 年终奖:根据绩效,1-4个月工资
4. 团建:每季度一次,预算人均500元
【报销制度】
1. 差旅:机票经济舱,酒店不超过400元/晚
2. 餐饮:外出就餐每人每餐不超过100元
3. 发票:需在消费后30天内提交
步骤 2:加载和分割文档
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载文档
loader = TextLoader("docs/company_policy.txt", encoding="utf-8")
documents = loader.load()
print(f"加载了 {len(documents)} 个文档")
print(f"文档长度:{len(documents[0].page_content)} 字符")
# 分割文档(避免超过模型上下文限制)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 每块最大字符数
chunk_overlap=50, # 块之间重叠字符数(保持上下文连贯)
separators=["\n\n", "\n", "。", "!", "?"] # 优先在这些位置分割
)
chunks = text_splitter.split_documents(documents)
print(f"分割成 {len(chunks)} 个片段")
for i, chunk in enumerate(chunks[:3]):
print(f"\n片段 {i+1}:")
print(chunk.page_content[:100] + "...")
分割策略说明
| 参数 | 作用 |
|---|---|
chunk_size | 每块最大长度,太小会丢失上下文,太大会超 Token |
chunk_overlap | 块之间重叠,保证语义连贯 |
separators | 优先在句子/段落边界分割,避免切断语义 |
步骤 3:创建向量数据库
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import Chroma
# 初始化 Embedding 模型(使用通义千问的 Embedding)
embeddings = DashScopeEmbeddings(
model="text-embedding-v2", # 通义千问 Embedding 模型
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 创建向量数据库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化存储位置
)
print("向量数据库创建完成!")
print(f"共存储 {vectorstore._collection.count()} 个向量")
# 测试相似度搜索
query = "年假有多少天?"
results = vectorstore.similarity_search(query, k=2)
print(f"\n查询:{query}")
print("最相关的片段:")
for i, doc in enumerate(results):
print(f"{i+1}. {doc.page_content[:100]}...")
Embedding 是什么?
将文本转换为高维向量(一串数字),语义相似的文本向量距离近。
"年假" → [0.1, -0.3, 0.8, ...] (1536维)
"假期" → [0.1, -0.3, 0.7, ...] (距离很近,语义相似)
"工资" → [-0.5, 0.2, 0.1, ...] (距离远,语义不同)
步骤 4:构建 RAG Chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.chat_models import ChatTongyi
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 准备组件
llm = ChatTongyi(model="qwen-turbo", temperature=0)
# 检索器(从向量库中检索相关文档)
retriever = vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3} # 返回最相关的3个片段
)
# RAG 提示词模板
template = """基于以下参考资料回答问题:
参考资料:
{context}
问题:{question}
请根据参考资料回答,如果资料中没有相关信息,请说"根据现有资料无法回答"。
"""
prompt = ChatPromptTemplate.from_template(template)
# 格式化检索结果的函数
def format_docs(docs):
return "\n\n".join([f"[{i+1}] {doc.page_content}" for i, doc in enumerate(docs)])
# 构建 RAG Chain
rag_chain = (
{
"context": retriever | format_docs, # 检索并格式化文档
"question": RunnablePassthrough() # 直接传递用户问题
}
| prompt
| llm
| StrOutputParser()
)
# 使用
response = rag_chain.invoke("公司年假几天?")
print(response)
Chain 结构解析
用户问题
↓
{
"context": retriever | format_docs, → 检索相关文档并格式化
"question": RunnablePassthrough() → 原样传递问题
}
↓
prompt → 填充模板
↓
llm → LLM 生成答案
↓
parser → 解析输出
↓
最终答案
步骤 5:完整问答演示
# 测试问题
test_questions = [
"公司年假几天?",
"迟到怎么扣钱?",
"团建预算多少?",
"报销酒店标准是多少?",
"公司创始人是谁?", # 资料中没有,测试拒答能力
]
print("=" * 60)
print("RAG 问答演示")
print("=" * 60)
for question in test_questions:
print(f"\n问题:{question}")
# 显示检索到的相关片段
docs = retriever.invoke(question)
print("检索到的片段:")
for i, doc in enumerate(docs):
print(f" {i+1}. {doc.page_content[:60]}...")
# 生成答案
answer = rag_chain.invoke(question)
print(f"答案:{answer}")
print("-" * 60)
完整练习代码
运行方式:
cd /Users/zhs/MyFiles/projects/test/test-agent/langchain-learn
source venv/bin/activate
python 06_rag.py
RAG 优化技巧
1. 调整检索数量
retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) # 检索5个片段
2. 使用 MMR 多样化检索
retriever = vectorstore.as_retriever(
search_type="mmr", # 最大边际相关性
search_kwargs={"k": 3, "fetch_k": 10} # 从10个中选3个最 diverse 的
)
3. 添加过滤条件
retriever = vectorstore.as_retriever(
search_kwargs={
"k": 3,
"filter": {"source": "company_policy.txt"} # 只从特定文件检索
}
)
练习任务
练习 1:多文档 RAG
加载多个文档(PDF、Word、TXT),构建统一的知识库。
练习 2:结合 Memory 的 RAG
让 RAG 系统记住用户之前的提问,支持追问(如"刚才说的年假,具体怎么申请?")。
练习 3:RAG + Agent
创建一个 Agent,能判断何时使用 RAG 检索,何时直接回答。
常见问题
Q1: 分割块大小怎么选?
建议:
- 问答场景:200-500 字符
- 长文档摘要:1000-2000 字符
- 代码:按函数/类分割
Q2: 检索不到相关内容怎么办?
- 检查 Embedding 模型是否合适
- 调整
chunk_size和overlap - 尝试不同的
search_type
Q3: 向量数据库可以持久化吗?
可以! Chroma 支持保存到磁盘,下次直接加载:
# 保存
vectorstore.persist()
# 加载
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)