LLM札记

RAG

RAG(Retrieval-Augmented Generation,检索增强生成)让 AI 能够基于你的私有文档回答问题,不需要微调模型,只需将文档向量化存储,Agent 就能检索相关内容来回答。

普通的大模型只能回答训练数据中有的内容。如果你的文档是私有的(公司内部文档、个人笔记),模型就"不知道"。RAG 解决了这个问题:

  1. 离线阶段:将文档切分成小块 → 用 Embedding 模型转换为向量 → 存入向量数据库

  2. 在线阶段:用户提问 → 将问题转为向量 → 在向量数据库中搜索最相似的内容 → 将检索到的内容作为上下文发给模型 → 模型基于检索内容回答

img

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 的执行流程是:

  1. 用户提问

  2. 模型判断需要查询知识库 → 调用 search_knowledge_base("老鸟 教程数量")

  3. 检索器从向量数据库中搜索语义最相似的文档块

  4. 将检索结果返回给模型

  5. 模型基于检索结果生成准确回答

Agent

Agent 是 LangChain 最核心的概念,它能让 AI 自动判断何时需要调用工具,并在获取工具结果后继续思考,直到完成任务。

Agent 的核心是一个简单的循环:调用模型 → 检查是否需要工具 → 执行工具 → 重复。直到模型不再请求工具调用,Agent 停止并返回最终结果。

img

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 结束后 格式化输出、统计、清理资源

img

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

维度CheckpointerStore
作用域 单个对话线程(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

img

image-20260610094748409

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)
 
posted @ 2026-06-10 10:07  rocky-2013  阅读(10)  评论(0)    收藏  举报