LLM札记
RAG(Retrieval-Augmented Generation,检索增强生成)让 AI 能够基于你的私有文档回答问题,不需要微调模型,只需将文档向量化存储,Agent 就能检索相关内容来回答。
普通的大模型只能回答训练数据中有的内容。如果你的文档是私有的(公司内部文档、个人笔记),模型就"不知道"。RAG 解决了这个问题:
-
离线阶段:将文档切分成小块 → 用 Embedding 模型转换为向量 → 存入向量数据库
-
在线阶段:用户提问 → 将问题转为向量 → 在向量数据库中搜索最相似的内容 → 将检索到的内容作为上下文发给模型 → 模型基于检索内容回答

from dotenv import load_dotenv
load_dotenv()
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
# ----- 步骤 1:准备知识库 -----
# 模拟老鸟 的知识文档
knowledge_docs = [
"老鸟()创立于 2013 年,是一个完全免费的编程学习平台。",
"平台已上线 300+ 套教程,涵盖前端、后端、数据库、移动开发等领域。",
"Python3 基础教程是平台最受欢迎的课程,累计学习人次超过 500 万。",
"Python3 基础教程共 30 章,包含环境搭建、基本语法、函数、类、异常处理等内容。",
"HTML 基础教程共 25 章,从 HTML 基本结构讲到表单与多媒体元素。",
"老鸟支持在线运行代码,学习者无需安装任何软件即可编写和运行代码。",
"平台提供移动端适配,用户可以在手机上随时随地学习编程。",
"老鸟的会员服务提供视频课程、项目实战、一对一答疑等增值服务。",
]
# 切分
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, chunk_overlap=30
)
chunks = text_splitter.create_documents(knowledge_docs)
# 向量化存储
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# ----- 步骤 2:创建检索工具 -----
@tool
def search_knowledge_base(query: str) -> str:
"""在老鸟 知识库中搜索相关信息。
当用户询问关于老鸟的具体信息时(如课程数量、平台历史、功能特性等),
必须使用此工具查询知识库获取准确信息。
Args:
query: 搜索关键词或问题
"""
docs = retriever.invoke(query)
if not docs:
return "知识库中未找到相关信息。"
results = []
for i, doc in enumerate(docs, 1):
results.append(f"[{i}] {doc.page_content}")
return "\n\n".join(results)
# ----- 步骤 3:创建 RAG Agent -----
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_knowledge_base],
system_prompt="""你是老鸟 的智能客服助手。
## 规则
1. 当用户询问关于老鸟的具体信息时,必须使用 search_knowledge_base 工具查询
2. 基于检索到的信息回答,不要编造知识库中没有的内容
3. 如果知识库中没有相关信息,诚实地告诉用户
4. 回答要友好、简洁、准确""",
)
# ----- 步骤 4:测试 -----
questions = [
"老鸟是什么时候创立的?",
"Python3 基础教程有多少章?",
"老鸟一共有多少套教程?",
]
for q in questions:
result = agent.invoke({"messages": [HumanMessage(content=q)]})
print(f"Q: {q}")
print(f"A: {result['messages'][-1].content}")
print("-" * 60)
RAG Agent 的执行流程
对于上面的第三个问题"老鸟一共有多少套教程?",Agent 的执行流程是:
-
用户提问
-
模型判断需要查询知识库 → 调用 search_knowledge_base("老鸟 教程数量")
-
检索器从向量数据库中搜索语义最相似的文档块
-
将检索结果返回给模型
-
模型基于检索结果生成准确回答
Agent
Agent 是 LangChain 最核心的概念,它能让 AI 自动判断何时需要调用工具,并在获取工具结果后继续思考,直到完成任务。
Agent 的核心是一个简单的循环:调用模型 → 检查是否需要工具 → 执行工具 → 重复。直到模型不再请求工具调用,Agent 停止并返回最终结果。

from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool
def get_stock_price(symbol: str) -> str:
"""查询股票当前价格。
Args:
symbol: 股票代码,如 AAPL、GOOGL、TSLA
"""
# 模拟股票数据
prices = {"AAPL": 185.50, "GOOGL": 142.30, "TSLA": 245.80}
price = prices.get(symbol.upper())
if price is None:
return f"未找到股票代码 {symbol}"
return f"{symbol.upper()} 当前价格:${price}"
@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
"""货币换算。
Args:
amount: 金额
from_currency: 原货币代码,如 USD、CNY
to_currency: 目标货币代码,如 CNY、USD
"""
# 模拟汇率
rates = {
("USD", "CNY"): 7.25,
("CNY", "USD"): 0.138,
}
rate = rates.get((from_currency.upper(), to_currency.upper()))
if rate is None:
return f"不支持 {from_currency} → {to_currency} 的换算"
result = round(amount * rate, 2)
return f"{amount} {from_currency} = {result} {to_currency}"
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[get_stock_price, convert_currency],
system_prompt="你是一个金融助手,帮助用户查询股价和换算货币。",
)
# Agent 会自动决定调用哪个工具
result = agent.invoke({
"messages": [HumanMessage(content="苹果股价是多少?换算成人民币是多少?")]
})
for msg in result["messages"]:
if msg.type == "tool":
print(f"[调用工具 {msg.name}] {msg.content}")
elif msg.type == "ai" and msg.content:
print(f"\n最终回答: {msg.content}")
LangChain 中间件(Middleware)
LangChain Middleware(中间件)是 LangChain 最强大的特性。它让你在 Agent 执行的各个环节插入自定义逻辑,实现重试、降级、缓存、内容过滤、日志记录等功能——而不需要修改 Agent 本身的代码。
| 钩子 | 执行频率 | 执行位置 | 主要用途 |
|---|---|---|---|
| before_agent | 一次 | Agent 开始前 | 初始化、权限检查、输入预处理 |
| before_model | 每次循环 | 模型调用前 | 消息预处理、动态上下文注入 |
| wrap_model_call | 每次循环 | 包裹模型调用 | 重试、降级、缓存、请求改写 |
| after_model | 每次循环 | 模型调用后 | 内容审核、响应过滤、日志 |
| wrap_tool_call | 每次工具调用 | 包裹工具执行 | 工具重试、结果缓存、参数改写 |
| after_agent | 一次 | Agent 结束后 | 格式化输出、统计、清理资源 |

from langchain.agents.middleware import before_model
SENSITIVE_WORDS = ["密码", "银行卡号", "身份证号"]
@before_model
def content_filter(state, runtime):
"""检查用户消息是否包含敏感词,如果包含则拦截"""
messages = state.get("messages", [])
if not messages:
return None
last_msg = messages[-1]
content = str(last_msg.content) if hasattr(last_msg, 'content') else ""
for word in SENSITIVE_WORDS:
if word in content:
print(f"[拦截] 检测到敏感词: {word}")
# jump_to="end" 直接结束,不让模型回复
return {
"jump_to": "end",
"messages": [
HumanMessage(content=f"抱歉,为了安全,不能处理包含「{word}」的请求。")
]
}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[content_filter],
system_prompt="你是老鸟 的助手。",
)
# 正常问题
result = agent.invoke({
"messages": [HumanMessage(content="Python 怎么入门?")]
})
print(f"正常问题: {result['messages'][-1].content[:80]}")
# 敏感问题
result = agent.invoke({
"messages": [HumanMessage(content="我的银行卡号是多少?你能帮我查吗?")]
})
print(f"\n敏感问题: {result['messages'][-1].content}")
LangChain 对话记忆 -- Checkpointer
默认情况下,每次 agent.invoke() 都是独立的——Agent 不记得之前聊过什么。Checkpointer(检查点保存器)让 Agent 能够记住对话历史,实现真正的多轮对话。
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
# 创建一个内存 Checkpointer
checkpointer = InMemorySaver()
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
checkpointer=checkpointer, # 传入 Checkpointer
system_prompt="你是老鸟 的助手。",
)
# 使用 thread_id 来标识对话线程
config = {"configurable": {"thread_id": "user-001"}}
# 第一轮
result1 = agent.invoke(
{"messages": [HumanMessage(content="我叫小明,我在学 Python")]},
config=config,
)
print(f"第一轮: {result1['messages'][-1].content}")
# 第二轮——使用相同的 thread_id,Agent 记住了!
result2 = agent.invoke(
{"messages": [HumanMessage(content="我叫什么名字?我在学什么?")]},
config=config,
)
print(f"第二轮: {result2['messages'][-1].content}")
Checkpointer 类型
| 类型 | 存储位置 | 持久化 | 适用场景 |
|---|---|---|---|
| InMemorySaver | 内存 | 否(重启丢失) | 开发调试、测试 |
| SqliteSaver | SQLite 文件 | 是 | 单机部署、小规模应用 |
| PostgresSaver | PostgreSQL | 是 | 生产环境、多实例共享 |
LangChain 跨会话存储 —— Store
Checkpointer vs Store
| 维度 | Checkpointer | Store |
|---|---|---|
| 作用域 | 单个对话线程(thread_id) | 跨所有对话线程 |
| 数据类型 | Agent 状态快照(自动管理) | 任意键值数据(手动管理) |
| 典型用途 | 多轮对话记忆 | 用户偏好、知识库、配置 |
| 数据组织 | thread_id → checkpoint | (namespace, key) → value |
from dotenv import load_dotenv
load_dotenv()
from typing import Annotated
from langgraph.store.base import BaseStore
from langgraph.store.memory import InMemoryStore
from langchain.tools import tool, InjectedStore
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
# 创建 Store 并预置数据
store = InMemoryStore()
store.put(("", "courses"), "catalog", {
"Python3 基础教程": {"price": "免费", "hours": 20, "level": "入门"},
"Python 数据分析": {"price": "会员", "hours": 30, "level": "进阶"},
"Java 面向对象": {"price": "免费", "hours": 25, "level": "进阶"},
})
store.put(("", "users"), "user_vip_001", {
"name": "小明",
"membership": "VIP",
"joined": "2024-01-15",
})
@tool
def query_course_info(
course_name: str,
store: Annotated[BaseStore, InjectedStore()],
) -> str:
"""查询老鸟 中课程的详细信息。
Args:
course_name: 课程名称
"""
item = store.get(("", "courses"), "catalog")
catalog = item.value if item else {}
if course_name in catalog:
info = catalog[course_name]
return (
f"《{course_name}》- 价格:{info['price']},"
f"时长:{info['hours']}小时,难度:{info['level']}"
)
return f"未找到课程《{course_name}》"
@tool
def get_user_membership(
user_id: str,
store: Annotated[BaseStore, InjectedStore()],
) -> str:
"""查询用户会员信息。
Args:
user_id: 用户 ID
"""
item = store.get(("", "users"), user_id)
if item is None:
return f"未找到用户 {user_id}"
user = item.value
return (
f"用户 {user['name']},{user['membership']} 会员,"
f"注册日期 {user['joined']}"
)
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[query_course_info, get_user_membership],
store=store,
system_prompt="你是老鸟 的课程顾问。",
)
# 查询课程信息(数据来自 Store)
result = agent.invoke({
"messages": [HumanMessage(content="Python3 基础教程多少钱?")]
})
print(f"查询课程: {result['messages'][-1].content}")
# 查询用户信息(数据来自 Store)
result = agent.invoke({
"messages": [HumanMessage(content="帮我查一下用户 user_vip_001 的信息")]
})
print(f"查询用户: {result['messages'][-1].content}")
LangGraph
LangGraph 是由 LangChain 团队开发的一个低层级 Agent 编排框架,专为构建有状态(Stateful)、长时运行的 AI 工作流而设计。
与传统的线性 LLM 调用链不同,LangGraph 将工作流建模为有向图(Directed Graph):
| 概念 | 说明 | 关键代码 |
|---|---|---|
| StateGraph | 有向图工作流引擎,LangGraph 的核心 | StateGraph(MyState) |
| State | 节点间共享的状态数据结构 | TypedDict + Annotated |
| Nodes | 执行具体操作的函数节点/子图(Subgraph) | builder.add_node() |
| Edges | 节点间的流转路径,支持条件分支 | add_edge() / add_conditional_edges() |
| Human-in-Loop | 人工审批与介入机制 | interrupt() + Command(resume=) |
| 持久化 | 会话记忆与状态保存 | MemorySaver / SqliteSaver |

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# 初始化 LLM
llm = ChatOpenAI(
model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
temperature=0
)
# 定义专家 Agent
def research_agent(state: MessagesState) -> dict:
"""研究 Agent:负责信息收集"""
system = SystemMessage(content="你是一个专业的研究员,负责收集和整理信息。请简洁地总结关键信息。")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def writing_agent(state: MessagesState) -> dict:
"""写作 Agent:负责内容创作"""
system = SystemMessage(content="你是一个专业的写作者,负责根据已有信息撰写内容。请保持内容清晰流畅。")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def review_agent(state: MessagesState) -> dict:
"""审校 Agent:负责质量控制"""
system = SystemMessage(content="你是一个专业的编辑,负责审核和改进内容质量。请指出问题并给出改进建议。")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
# 主管 Agent 决定流程
def supervisor_node(state: MessagesState) -> dict:
"""主管:协调各专家 Agent 的工作"""
system = SystemMessage(content="""你是一个工作流主管。
根据任务进度决定下一步应该由哪个 Agent 处理。
分析对话历史,只返回以下之一:RESEARCH、WRITING、REVIEW、FINISH
- RESEARCH:需要收集更多信息
- WRITING:信息充足,可以开始写作
- REVIEW:写作完成,需要审核
- FINISH:任务已完成
""")
response = llm.invoke([system] + state["messages"])
return {"messages": [response]}
def route_by_supervisor(state: MessagesState) -> str:
"""根据主管决策路由"""
last_msg = state["messages"][-1].content.strip().upper()
if "RESEARCH" in last_msg:
return "research"
elif "WRITING" in last_msg:
return "writing"
elif "REVIEW" in last_msg:
return "review"
else:
return END
# 构建多 Agent 图
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("research", research_agent)
builder.add_node("writing", writing_agent)
builder.add_node("review", review_agent)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_by_supervisor)
# 每个专家完成后返回主管
for agent in ["research", "writing", "review"]:
builder.add_edge(agent, "supervisor")
graph = builder.compile()
# 测试多 Agent 协作
result = graph.invoke({
"messages": [HumanMessage(content="请帮我写一篇关于 Python 装饰器的简短介绍文章")]
})
print("=== 多 Agent 协作完成 ===")
for i, msg in enumerate(result["messages"]):
print(f"\n[{i+1}] {msg.type}: {msg.content[:150]}...")
# LangGraph 提供了内置的状态持久化机制,让 Agent 能够跨会话记住对话历史。
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# 使用 thread_id 区分不同会话
config_user_a = {"configurable": {"thread_id": "user-alice"}}
config_user_b = {"configurable": {"thread_id": "user-bob"}}
# Alice 的对话
graph.invoke({"messages": [HumanMessage(content="我叫 Alice")]}, config=config_user_a)
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_a)
# Agent 能记住:你叫 Alice
# Bob 的对话完全独立
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_b)
# Agent 不知道 Bob 的名字(不同 thread_id)

浙公网安备 33010602011771号