第2周学习笔记

学习时间 :2026年5月(5天) | 核心主题:Model I/O → RAG 系统构建 → Agent 与工具系统 → 中间件架构


一、本周学习路线总览

本周以"从单模型调用到构建完整的智能应用系统"为主线,按以下路径递进:

复制代码
模型抽象与调优 → 文档加载与分割 → 向量嵌入与检索 → 工具定义与 ReAct → Agent 标准入口与中间件
    Day1              Day2              Day3              Day4                    Day5

如果说第 1 周是学会"如何用 LCEL 搭积木",那么第 2 周的核心命题是------如何让模型接入外部世界。RAG 给模型接上知识库,Agent 给模型接上工具,两者叠加让模型从"会说话的百科全书"进化为"能做事、能查资料的智能助手"。


二、Day 1:Model I/O 与模型抽象

2.1 BaseLanguageModel:一套接口,所有模型

本周第一个重要认知:LangChain 通过 BaseLanguageModel 将不同厂商的模型统一在同一套接口下。无论用 OpenAI、Anthropic,还是通过硅基流动接入的国产模型,上层的 Chain 和 Agent 只依赖 invoke() / stream() / batch() 这些方法签名,底层实现可以随意切换。

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic

# 两个不同厂商的模型,调用方式完全一致
llm_openai = ChatOpenAI(model="gpt-5.5", temperature=0.1)
llm_claude = ChatAnthropic(model="claude-3-5-sonnet")

这种设计的哲学是面向接口编程:切换模型只需改一行 import 和初始化参数,管道的其余代码一行不动。这是第 1 周"协议胜于继承"思想在模型层的延续。

2.2 参数调优:四个旋钮的艺术

理解四个核心参数对模型行为的影响,是写出高质量应用的前提:

参数 作用 建议值
temperature 随机性控制 RAG: 0.0, 创作: 0.7-0.9
max_tokens 最大输出长度 按场景设定(成本控制 + 安全兜底)
top_p nucleus sampling 候选词截断 默认 1.0
frequency_penalty 抑制重复 需要时 0.1-0.3

关键理解:

  • temperaturetop_p 二选一调优为主,不建议同时大幅偏离默认值
  • temperature=0 在学习阶段尤为重要------确保每次运行结果可复现,调试时不会因为随机性而困惑
  • frequency_penalty 是解决模型"复读机"问题的利器

2.3 多模型路由:RunnableBranch 的实战应用

RunnableBranch 不仅是第 1 周学到的条件分支组件,它在模型路由场景中找到了最佳的用武之地------根据输入特征(文本长度、任务类型、复杂度)动态选择最合适的模型:

python 复制代码
router = RunnableBranch(
    (is_long_context, llm_powerful),      # 长文本 → 强模型
    (is_code_task, llm_powerful),         # 代码任务 → 强模型
    (is_complex_question, llm_powerful),  # 复杂问题 → 强模型
    llm_fast                                # 默认 → 快速模型
)

RunnableBranch 本身也是 Runnable,可以用 | 接入任何 LCEL 管道------这让原本线性的管道具备了"分叉"能力,是构建非平凡 Agent 的基础。


三、Day 2:RAG 系统构建(上)------文档加载与文本分割

3.1 RAG 六步流程

从今天开始进入 LangChain 最具工程价值的领域------RAG。一个完整的 RAG 系统可以概括为六个字:

复制代码
Source → Load → Transform → Embed → Store → Retrieve
 源       载        转          嵌      存        检

Day 2 聚焦在前两步 Load 和 Transform,这是整个 RAG 系统的地基。

3.2 文档加载:统一接口下的多格式支持

langchain_community.document_loaders 提供了上百种加载器,覆盖 PDF、Markdown、CSV、HTML 等所有常见格式,共享同一套调用契约------load() 返回 List[Document]

加载器 用途 关键细节
TextLoader 纯文本 需要显式指定 encoding="utf-8"
PyPDFLoader PDF 自动按页拆分,metadata 含页码
UnstructuredMarkdownLoader Markdown 识别标题层级并写入 metadata
DirectoryLoader 批量加载 通过 glob 匹配文件,loader_cls 指定加载器

3.3 文本分割:RecursiveCharacterTextSplitter 的设计智慧

分割的核心矛盾:chunk 太大会丢失检索精确性,chunk 太小会割裂语义。RecursiveCharacterTextSplitter 通过递归降级分隔符的设计解决了这个问题:

python 复制代码
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", ",", ";", " ", ""],
)

关键参数理解:

  • separators 的优先级顺序 :先尝试段落边界 \n\n → 行边界 \n → 中文标点 。,; → 空格 → 最终兜底逐字符切割。中文文档必须加入中文标点,否则会退化到字符级切割。
  • chunk_overlap :相邻 chunk 之间的重叠部分(推荐值为 chunk_size 的 10%~20%),解决了"关键信息恰好落在切割边界上"的问题。
  • chunk_size:500 是工程上的常用起点,在精度和上下文之间取得平衡。

四、Day 3:RAG 系统构建(下)------嵌入、存储与检索

4.1 向量嵌入:让机器"理解"文本

向量嵌入的本质是将自然语言映射到高维向量空间,语义相近的文本在高维空间中几何上彼此靠近。LangChain 中所有 embedding 模型遵循统一的 Embeddings 接口,提供两个核心方法:

  • embed_documents(texts) --- 批量嵌入待存储的文档
  • embed_query(text) --- 嵌入用户查询

重要踩坑 :使用硅基流动等非 OpenAI 服务商时,必须设置 check_embedding_ctx_length=False。默认 True 会让 OpenAIEmbeddings 使用 tiktoken 将文本转为 token ID 数组发送,但硅基流动不支持这种格式,会返回 20015 "参数无效" 错误。

4.2 向量存储与检索策略

三种检索策略各有适用的场景:

策略 search_type 原理 适用场景
相似度 similarity(默认) 余弦相似度排序,返回 Top-K 通用场景
MMR mmr fetch_k 候选中做多样性筛选 避免结果同质化
阈值过滤 similarity_score_threshold 只返回相似度超过 score_threshold 的结果 生产环境质量兜底

VectorStore 本身不实现 Runnable 接口,需通过 as_retriever() 包装为 Retriever 对象后才能接入 LCEL 管道。这个设计体现了检索器是比向量存储更通用的抽象------无论数据来自向量库、搜索引擎还是外部 API,都可以统一封装。

4.3 完整 RAG Chain:一条管道串联全部环节

python 复制代码
rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

数据流分析:

  1. RunnablePassthrough() 将用户输入同时传给 retriever(执行检索)和原样透传(保留原始问题)
  2. prompt 模板将检索到的上下文注入 system 消息,原始问题注入 user 消息
  3. llm 在上下文辅助下推理回答
  4. StrOutputParser 提取纯文本

关键习惯:把上下文放在 system 消息中而非 user 消息中,让 system 承载"背景信息",user 保持用户的原始意图不被干扰。


五、Day 4:Agent 系统(上)------工具与 ReAct

5.1 工具定义:给模型装上手脚

工具是 Agent 系统中最基础的单元。@tool 装饰器把一个普通 Python 函数自动转化为模型可理解的结构化接口:

python 复制代码
@tool
def search_database(query: str, limit: int = 10) -> str:
    """搜索客户数据库中的匹配记录。"""
    return f"在数据库中找到了 {limit} 条与 '{query}' 相关的结果。"

@tool 做了三件事:类型标注 → JSON Schema;docstring → 用途描述;函数名 → 全局唯一工具名。

对于复杂参数场景,通过 args_schema 传入 Pydantic 模型可以获得更精细的控制:

python 复制代码
class WeatherInput(BaseModel):
    city: str = Field(description="城市名称")
    units: Literal["celsius", "fahrenheit"] = Field(default="celsius", description="温度单位")

@tool(args_schema=WeatherInput)
def get_weather(city: str, units: str = "celsius") -> str:
    """获取指定城市的当前天气信息。"""
    ...

每个字段的 Field(description=...) 是模型决定如何填充参数的关键依据------描述模糊会导致模型在错误的情境下调用工具或填入不合理的参数。

5.2 ReAct 模式:推理与行动交替进行

这是 Agent 系统最经典的思想框架。它的执行流程是一条优雅的循环链条:

复制代码
Question → Thought → Action → Observation → Thought → ... → Final Answer

以一个具体场景为例:用户问"北京今天多少度?华氏度是多少?"

  1. Thought :模型意识到需要先获取温度 → 决定调用 get_weather
  2. Action :调用 get_weather(city="北京")
  3. Observation:收到 "北京:晴,25°C"
  4. Thought :模型判断还需要换算华氏度 → 决定调用 calculate
  5. Action :调用 calculate(expression="25 * 9/5 + 32")
  6. Observation:收到 "77.0"
  7. Final Answer:"北京今天 25°C,约 77°F"

ReAct 的美妙之处在于------把"推理"和"行动"统一为可迭代、可观测的过程。每一步思考都有文本记录,每一次工具调用都有可追溯的输入输出。

5.3 错误处理:当工具出错时

生产环境中的 Agent 不可能一帆风顺。@wrap_tool_call 中间件像一个透明的拦截器,将工具异常转化为模型可理解的 ToolMessage

python 复制代码
@wrap_tool_call
def handle_tool_errors(request, handler):
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"工具执行出错,请检查输入参数后重试。错误详情:{e}",
            tool_call_id=request.tool_call["id"],
        )

这贯彻了 ReAct 的核心理念------把一切(包括失败)都转化为可推理的信息。模型收到错误消息后会像处理正常结果一样分析它,然后决定重试、换参数还是告知用户。


六、Day 5:Agent 系统(下)------Function Calling 与 create_agent

6.1 Function Calling:模型与工具的通信协议

bind_tools() 是连接模型和工具世界的桥梁。在 create_agent 内部,框架自动完成工具绑定------你不需要显式调用 bind_tools()

ReAct vs Function Calling 的本质区别:

维度 ReAct Function Calling
实现方式 提示词工程,模型输出"带格式的文本" 模型原生能力,输出结构化 JSON
解析方式 正则/解析器从文本中提取 确定性 JSON 解析
调用准确率 依赖提示词质量,有格式偏差风险 通常是训练内置能力,更稳定
适用场景 无原生 FC 支持的模型;学术追溯思考过程 有 FC 支持的模型(生产首选)

在 LangChain v1 中,create_agent 自动根据模型能力选择最优策略------支持 tool calling 就走 Function Calling 路径,否则退回文本推理模式。你不需要改变构建代码。

6.2 create_agent:LangChain v1 的 Agent 标准入口

create_agent 统一了过去分散在 create_react_agentAgentExecutorAgentAction 中的功能,把所有复杂性封装进由 LangGraph 驱动的高层抽象。核心参数:

参数 作用
model 模型标识字符串或已初始化的聊天模型实例
tools 工具列表,框架自动完成绑定和注册
system_prompt Agent 的行为基调
response_format Pydantic 模型约束最终输出格式
checkpointer 持久化对话状态(InMemorySaver 用于开发,PostgresSaver 用于生产)
context_schema 定义每次调用的不可变上下文数据类型
middleware 可插拔的中间件列表

checkpointer + thread_id 是实现多轮对话的关键:

python 复制代码
agent = create_agent(model=llm, tools=[...], checkpointer=InMemorySaver())

# 同一 thread_id 下的多次调用自动累积对话历史
result_1 = agent.invoke(
    {"messages": [{"role": "user", "content": "北京天气怎么样?"}]},
    config={"configurable": {"thread_id": "conversation-001"}}
)
result_2 = agent.invoke(
    {"messages": [{"role": "user", "content": "我刚才问了什么?"}]},
    config={"configurable": {"thread_id": "conversation-001"}}  # 同一个 thread_id
)
# Agent 能记住之前的对话!

6.3 Middleware:Agent 的可插拔扩展层

这是 create_agent 最令人惊艳的设计。中间件分为节点式和包裹式两种风格:

节点式钩子(在特定时间点运行):

  • @before_agent --- Agent 调用开始前(全局状态初始化)
  • @before_model --- 每次模型调用前(注入动态上下文,如当前时间戳)
  • @after_model --- 每次模型响应后(日志记录、响应校验)
  • @after_agent --- Agent 调用结束后(清理、汇总)

包裹式钩子(环绕调用,控制执行零次/一次/多次):

  • @wrap_model_call --- 环绕模型调用(重试、缓存、动态切换模型)
  • @wrap_tool_call --- 环绕工具调用(错误转换、日志、限流)

一个经典的 @before_model 中间件------为 Agent 注入时间感知能力:

python 复制代码
@before_model
def add_timestamp(state: AgentState, runtime: Runtime):
    now = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
    state["messages"].append(HumanMessage(content=f"[系统信息] 当前时间:{now}"))
    return None

中间件执行顺序遵循洋葱模型before_* 按列表正序执行,after_* 按列表反序执行,wrap_* 形成嵌套结构(列表前面的包裹在最外层)。

官方预置中间件覆盖了完整场景:ModelRetryMiddleware(模型重试)、ToolRetryMiddleware(工具重试)、ModelFallbackMiddleware(模型降级)、HumanInTheLoopMiddleware(人工审批)、SummarizationMiddleware(对话历史压缩)、TodoListMiddleware(任务规划追踪)等。


七、核心概念的个性化理解

7.1 对"文档 → 向量 → 检索 → 生成"管道的感悟

RAG 本质上就是在 LLM 管道的前端插入了一个检索步骤:retriever | prompt | llm | parser。第 1 周学到的 LCEL 知识在这里被完整复用------管道的可组合性让"插入新组件"极其自然。这印证了一个设计理念:好的抽象能经得起场景扩展的考验

7.2 对 Agent = Model + Harness 的理解

create_agent 的官方定义 "Agent = Model + Harness" 精准概括了 Agent 的本质。Model 负责思考(推理是否调用工具、如何填充参数、何时给出最终答案),Harness(包括工具绑定、状态管理、中间件、循环控制)负责为模型提供正确的上下文和行动框架。这种分离让 Agent 的"大脑"和"身体"可以独立演进而互不影响。

7.3 ReAct 与 Function Calling 的关系

两者不是"替代"关系,而是分层关系。ReAct 定义的是逻辑范式 (推理-行动-观察的循环),Function Calling 提供的是底层实现 (用结构化 JSON 替代文本解析)。在 LangChain v1 中,create_agent 为上层提供了统一的抽象,底层的选择只影响执行效率而不影响功能接口------这正是优秀的框架设计。

7.4 Middleware 与 Runnable 的设计共性

中间件系统和 LCEL 的 Runnable 接口共享同一个设计哲学:协议胜于继承Runnable 只要实现 invoke 就能接入管道,Middleware 只要实现 @before_model@wrap_tool_call 就能插入 Agent 生命周期。两者都通过"定义协议 + 自由组合"的方式实现了极高的扩展性。


八、踩坑记录与经验

  1. check_embedding_ctx_length=False 的重要性 :使用硅基流动等非 OpenAI 服务商时,必须显式设置此参数为 False。默认 True 会让 OpenAIEmbeddings 使用 tiktoken 将文本转换为 token ID 数组发送给 API,但硅基流动不支持 token 化输入,会返回 20015 "参数无效" 错误。这个问题 debug 了很久才定位到。

  2. langchain-community 被标记为 deprecated :在使用 langchain_community.document_loaderslangchain_community.vectorstores 时,会收到 DeprecationWarning,提示该包正在被逐步淘汰。官方建议迁移到独立的集成包(如 langchain-chromalangchain-pdf 等)。学习阶段不影响使用,但生产项目需要注意迁移路径。

  3. 中文文档的 separators 必须定制RecursiveCharacterTextSplitter 默认分隔符是英文导向的(\n\n, \n, , "")。处理中文文档时,如果不在 separators 中加入 ,分割器会跳过所有中文标点直接退化到空格或字符级切割,导致语义碎片化。

  4. VectorStore 不是 Runnable :向量存储对象不能直接用 | 接入管道,必须通过 as_retriever() 包装。这个细节容易忽略,直接写 vectorstore | prompt 会报类型错误。

  5. 工具名应使用 snake_case :部分模型提供商会拒绝包含空格或特殊字符的函数名。@tool 装饰器默认使用函数名作为工具名,保持 Python 原生命名习惯即可。

  6. @before_model 返回 None 的含义 :当中间件通过修改 state["messages"] 原地生效后,返回 None 表示不需要额外更新 state。如果返回一个 dict,框架会用它来部分更新 state。


九、下周展望

第 3 周的主题是高级特性与生产级应用。从本周的认知出发:

  • RAG 的检索质量可以通过多路检索融合、重排序(Re-ranking)、HyDE 假设文档嵌入等高级策略进一步提升
  • Agent 的可靠性可以通过更多的中间件组合 (如 SummarizationMiddleware 自动压缩长对话历史、HumanInTheLoopMiddleware 在关键操作前暂停审批)来保障
  • checkpointer + thread_id 的多轮对话能力将在实际应用中发挥核心作用
  • context_schema 让 Agent 能感知用户身份、权限级别等运行时上下文,是构建多租户应用的基础

十、本周学习成果自检

  • 理解 BaseLanguageModel 的统一抽象设计,能对比不同模型在同一 prompt 下的输出差异
  • 掌握 temperature / max_tokens / top_p / frequency_penalty 四个参数的作用与调优哲学
  • 能使用 RunnableBranch 实现基于任务特征的模型路由器
  • 能使用至少 3 种文档加载器加载不同格式的文档
  • 理解 RecursiveCharacterTextSplitter 的递归降级分割机制,能为中文文档定制 separators
  • 掌握 embedding 模型的使用,理解 check_embedding_ctx_length 参数的含义
  • 能构建完整的 RAG Chain(Load → Split → Embed → Store → Retrieve → Generate)
  • 理解三种检索策略(similarity / MMR / threshold)的原理与适用场景
  • 能使用 @tool 装饰器和 args_schema 定义结构化的工具接口
  • 理解 ReAct 循环(Thought → Action → Observation)的运作机制
  • 能使用 create_agent() 构建完整的 Agent 系统
  • 理解 checkpointer + thread_id 实现多轮对话的原理
  • 能编写自定义 middleware(@before_model / @wrap_tool_call
  • 理解 ReAct 与 Function Calling 的异同及 create_agent 的自动适配策略
相关推荐
ZC跨境爬虫1 小时前
跟着 MDN 学JavaScript day_6:JavaScript 中的基础数学——数字与运算符
开发语言·前端·javascript·学习·ecmascript
copyer_xyf1 小时前
Python 迭代器与生成器
前端·后端·python
MartinYeung51 小时前
[论文学习]网路知识产权面临风险:防止大型语言模型未经授权即时检索
人工智能·学习·语言模型
数智工坊2 小时前
周志华《Machine Learning》学习笔记--第十二章--计算学习理论
笔记·学习·机器学习
小小测试开发8 小时前
安装 Python 3.10+
开发语言·人工智能·python
梦想不只是梦与想9 小时前
Python 中的装饰器
python·装饰器
我叫唧唧波9 小时前
Python+AI 全栈学习笔记
人工智能·python·学习
copyer_xyf10 小时前
Python 异常处理
前端·后端·python
城北徐宫10 小时前
Linux信号深度解剖:5种产生、3张表、4次切换
linux·c++·学习