【LangChain】从模型调用到 Agent 雏形
作为一名 AI 应用开发学习者,在大模型应用开发的浪潮中,LangChain 始终是绕不开的核心工具。起初,我对它的认知仅停留在 “和大模型、RAG、Agent 绑定出现的框架” 这个模糊概念上,直到亲手搭建环境、编写代码、跑通一个个 demo,才真正拆解了 LangChain 的底层逻辑 —— 它不是神秘的智能体,也不是某类大模型,而是一套让 LLM 应用开发标准化、模块化的工具箱。本文将以初学者的视角,完整还原从环境搭建到 Agent 雏形落地的学习全过程,希望能为同样入门 LangChain 的开发者提供一份可落地的参考。
一、环境搭建:从 “能跑起来” 开始
学习任何框架的第一步,都不是死记概念,而是让代码先跑通。大模型应用依赖包繁多,为了避免环境冲突,我选择轻量的 Miniconda 而非完整 Anaconda,为 LangChain 专门创建隔离环境。
1. 环境配置核心要点
Python 版本的选择是第一个需要注意的细节。虽然 Python 3.13 已发布,但 AI 生态包对 3.11/3.12 的兼容性更稳定,因此我最终选择 3.11 版本:
# 创建并激活专属conda环境
conda create -n agent-dev python=3.11
conda activate agent-dev
项目目录规划在D:\code\langchain,随后安装基础依赖包。由于国内开发者常用阿里云百炼(而非 OpenAI 官方 API),核心依赖只需安装 LangChain 核心库、OpenAI 兼容接口包和环境变量管理包:
pip install langchain langchain-openai python-dotenv
2. 模型连接:密钥安全与接口适配
阿里云百炼提供 OpenAI 兼容接口,因此仍可使用 LangChain 的ChatOpenAI类调用模型,但需要配置base_url和 API Key。这里有个关键原则:API Key 绝对不能硬编码在代码中。我在项目根目录创建.env文件存储密钥:
DASHSCOPE_API_KEY=你的百炼APIKey
通过python-dotenv加载环境变量,再初始化模型客户端:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
# 加载环境变量
load_dotenv()
# 初始化百炼模型客户端
model = ChatOpenAI(
model="qwen-plus", # 通义千问增强版
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
3. 第一个最小程序:验证链路通畅性
写一行简单的调用代码,验证环境、依赖、密钥、模型连接是否全部正常:
# 第一次调用模型
response = model.invoke("你好,请用一句话介绍你自己。")
print(response.content)
当终端打印出通义千问的回复时,整个基础链路就通了。这个阶段不需要理解复杂概念,核心目标是确认 “模型能被调用”—— 这是后续所有操作的基础。
二、核心基础:模型、消息、调用与链
环境跑通后,需要拆解 LangChain 的核心调用逻辑。这一阶段的核心可以总结为一条链路:模型 model → 消息 messages → invoke 调用 → AIMessage → 输出解析 → chain。
1. 模型:不是 “模型本身”,而是 “客户端封装”
ChatOpenAI创建的并非大模型本身,而是一个标准化的 Python 客户端。LangChain 的核心价值之一,就是用统一的接口封装不同供应商的大模型(OpenAI、百炼、智谱等),开发者无需为每个平台适配调用逻辑。
2. 消息:聊天模型的输入单元
聊天模型的输入不是简单字符串,而是消息列表。LangChain 定义了多种消息类型,覆盖大模型交互的全场景:
SystemMessage:系统指令,定义模型的身份和行为准则;HumanMessage:用户输入,代表人类的提问或指令;AIMessage:模型输出,是调用模型后的返回结果;ToolMessage:工具执行结果,Agent 场景中会频繁用到。
示例代码清晰展示了消息列表的构建方式:
from langchain_core.messages import SystemMessage, HumanMessage
# 构建消息列表
messages = [
SystemMessage(content="你是一个耐心的中文老师,用通俗的语言解释技术概念。"),
HumanMessage(content="解释一下 LangChain 是什么。"),
]
# 调用模型
result = model.invoke(messages)
print(result.content)
需要注意的是,invoke()返回的不是字符串,而是AIMessage对象,除了content字段,还包含response_metadata(响应元数据)、usage_metadata(用量统计)、tool_calls(工具调用指令)等关键信息 —— 其中tool_calls是实现 Agent 的核心。
3. 输出解析:从 AIMessage 到可用格式
如果只需要字符串结果,无需手动提取content,LangChain 提供StrOutputParser简化操作:
from langchain_core.output_parsers import StrOutputParser
# 初始化解析器
parser = StrOutputParser()
# 构建链:模型调用 → 输出解析
chain = model | parser
# 调用链
result = chain.invoke(messages)
print(result) # 直接输出字符串
这里的|是 LangChain Expression Language(LCEL)的核心语法,代表将多个 “可运行组件(Runnable)” 串联。模型、解析器、链都是 Runnable,因此都能通过.invoke()调用 —— 这是 LangChain 组件化设计的核心体现。
三、工具调用:Agent 的前置核心能力
Agent 的本质是 “模型决策 + 工具执行 + 结果反馈” 的循环,而工具调用是理解这一逻辑的关键。大模型本身只能生成文本,无法执行代码、查询数据库或访问外部接口,工具调用的作用就是让模型学会 “请求外部能力”,再由程序执行并反馈结果。
1. 工具定义:让模型 “看懂” 工具能力
LangChain 通过@tool装饰器快速定义工具,工具的名称、参数类型、文档字符串至关重要 —— 模型不会读取工具内部代码,而是通过这些信息理解工具功能:
from langchain_core.tools import tool
# 定义加法工具
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
# 定义乘法工具
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b
2. 工具绑定:把 “工具说明书” 交给模型
绑定工具并非执行工具,而是将工具的元信息(名称、参数、描述)传递给模型,让模型判断是否需要调用:
# 工具列表
tools = [add, multiply]
# 绑定工具到模型
model_with_tools = model.bind_tools(tools)
3. 工具调用循环:模拟 Agent 的最小逻辑
当用户提问 “9 乘 6 等于多少?5 加 3 等于多少?” 时,模型会返回包含tool_calls的响应,而非直接回答。我们可以手动实现工具执行逻辑,模拟 Agent 的核心循环:
# 用户问题
messages = [HumanMessage(content="9乘6等于多少?5加3等于多少?")]
# 模型生成工具调用指令
ai_msg = model_with_tools.invoke(messages)
# 构建工具映射(方便根据名称查找工具)
tool_map = {tool.name: tool for tool in tools}
# 执行工具调用
for tool_call in ai_msg.tool_calls:
# 获取工具和参数
selected_tool = tool_map[tool_call["name"]]
tool_args = tool_call["args"]
# 执行工具
tool_result = selected_tool.invoke(tool_args)
# 将工具执行结果封装为ToolMessage,加入消息列表
messages.append(ToolMessage(content=str(tool_result), tool_call_id=tool_call["id"]))
# 模型基于工具结果生成最终回答
final_msg = model_with_tools.invoke(messages)
print(final_msg.content) # 输出:9乘6等于54,5加3等于8
这个循环就是 Agent 的最小原理:用户问题 → 模型生成工具调用指令 → 程序执行工具 → 工具结果反馈给模型 → 模型生成最终回答。理解这一逻辑,就掌握了 Agent 的核心本质。
四、LangChain 核心组件:搭建 LLM 应用的 “积木”
如果说工具调用是 Agent 的 “灵魂”,LangChain 的核心组件就是搭建应用的 “积木”。这些组件将模型调用、提示词、消息、解析器等环节抽象化,让复杂应用的开发变得模块化。
1. Messages:对话上下文的载体
Messages 不仅是单次交互的输入,更是多轮对话的上下文容器。模型在多轮交互中看到的是完整的消息列表,而非孤立的一句话。但大模型的上下文窗口有限,后续需处理消息裁剪、过滤、合并等问题,避免上下文溢出。
2. PromptTemplate:提示词的标准化管理
硬编码提示词会导致维护困难,PromptTemplate将提示词结构与变量分离,提升复用性:
from langchain_core.prompts import ChatPromptTemplate
# 定义提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个{role},回答必须用中文,且简洁易懂。"),
("human", "请解释这个概念:{topic}"),
])
# 传入变量生成消息
messages = prompt.invoke({"role": "Python 老师", "topic": "Runnable"})
print(messages)
3. MessagesPlaceholder:历史消息的动态插入
多轮对话中,历史消息需要动态插入提示词,MessagesPlaceholder实现这一需求:
from langchain_core.prompts import MessagesPlaceholder
# 包含历史消息的提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个学习助手,根据历史对话回答问题。"),
MessagesPlaceholder("history"), # 历史消息占位符
("human", "{input}"),
])
# 模拟历史对话
history = [
HumanMessage(content="什么是LangChain?"),
AIMessage(content="LangChain是LLM应用开发的工具箱。"),
]
# 生成消息列表
messages = prompt.invoke({
"history": history,
"input": "它的核心优势是什么?"
})
这一组件是实现多轮对话 Agent 的关键。
4. OutputParser:结构化输出的核心工具
除了StrOutputParser,LangChain 还提供多种解析器满足不同场景:
JsonOutputParser:将模型输出解析为 Python 字典;with_structured_output:让模型直接返回 Pydantic 对象;- 自定义解析器:适配复杂的输出格式。
结构化输出是信息抽取、API 返回、数据库入库等场景的核心需求,示例如下:
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
# 定义结构化输出模型
class ConceptExplain(BaseModel):
name: str = Field(description="概念名称")
definition: str = Field(description="概念定义")
advantage: str = Field(description="核心优势")
# 初始化解析器
parser = JsonOutputParser(pydantic_object=ConceptExplain)
# 构建提示词(包含解析器指令)
prompt = ChatPromptTemplate.from_messages([
("system", f"请按照指定格式输出:{parser.get_format_instructions()}"),
("human", "解释一下LangChain的核心优势"),
])
# 构建链
chain = prompt | model | parser
# 调用链
result = chain.invoke({})
print(result) # 返回ConceptExplain对象
五、RAG:让大模型读懂私有资料
RAG(检索增强生成)是解决大模型 “知识过期” 和 “幻觉” 的核心方案,其本质是 “先检索私有资料,再让模型基于资料回答”。完整的 RAG 流程可拆解为:加载文档 → 切分文本 → 生成向量 → 存入向量库 → 检索相关片段 → 构造提示词 → 调用模型 → 解析输出。
1. 文档加载:把外部文件转为 Document 对象
LangChain 的Document对象是处理私有资料的基础,包含page_content(正文)和metadata(元信息,如来源、页码)。以加载本地 Markdown 文件为例:
from langchain_community.document_loaders import UnstructuredMarkdownLoader
# 加载Markdown文件
loader = UnstructuredMarkdownLoader("langchain_notes.md")
docs = loader.load()
# 查看Document结构
print(docs[0].page_content[:100]) # 正文前100字
print(docs[0].metadata) # 元信息
metadata的价值在于,模型回答时可追溯资料来源,提升回答可信度。
2. 文本切分:平衡检索精度与语义完整性
长文档无法直接输入模型或生成向量,需切分为小块。RecursiveCharacterTextSplitter是最常用的切分工具,核心参数为chunk_size(块大小)和chunk_overlap(块重叠)—— 重叠的作用是避免重要上下文被切断:
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 初始化切分器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块500字符
chunk_overlap=50, # 相邻块重叠50字符
)
# 切分文档
splits = text_splitter.split_documents(docs)
print(f"切分后共{len(splits)}个块")
切块大小需根据场景调整:块太大检索不精确,块太小语义易破碎。
3. 向量生成与存储:让文本 “可检索”
嵌入模型(Embeddings)将文本转为向量,语义相近的文本向量距离更近。学习阶段可使用本地HashEmbeddings(仅用于理解流程),生产环境需用专业模型(如百炼、BGE):
from langchain_community.embeddings import HashEmbeddings
from langchain_community.vectorstores import InMemoryVectorStore
# 初始化嵌入模型
embeddings = HashEmbeddings()
# 生成向量并存入内存向量库
vector_store = InMemoryVectorStore.from_documents(
documents=splits,
embedding=embeddings,
)
学习阶段用InMemoryVectorStore即可,生产环境可选择 Chroma、FAISS、Redis、Milvus 等。
4. 检索器:统一的检索接口
Retriever 封装了向量库的检索逻辑,提供 “输入查询 → 输出相关 Document 列表” 的统一接口:
# 初始化检索器,返回Top3相关文档
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# 检索相关文档
docs = retriever.invoke("RAG的典型流程是什么?")
print(f"检索到{len(docs)}条相关文档")
5. 最小 RAG 闭环:让模型 “基于资料回答”
将检索器、提示词、模型、解析器串联,形成完整 RAG 链路:
from langchain_core.runnables import RunnablePassthrough
# 格式化检索结果为字符串
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 定义RAG提示词
prompt = ChatPromptTemplate.from_messages([
("system", "你只能根据给定资料回答问题,资料不足时明确说明。"),
("human", "问题:{question}\n\n资料:\n{context}"),
])
# 构建RAG链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 调用RAG链
answer = rag_chain.invoke("RAG的典型流程是什么?")
print(answer)
这个闭环的核心价值在于:模型回答完全基于检索到的私有资料,避免幻觉;当问题超出资料范围时,模型会明确说明 “资料中没有足够信息”。
六、从 RAG 到 Agent:让模型自主决策是否检索
固定 RAG 链的问题是 “无论问题是否需要,都先检索”,而 Agent 的思路是 “让模型自主判断是否调用工具(包括检索器)”。只需将 Retriever 包装为工具,即可实现 Agent 雏形。
1. 将 Retriever 封装为工具
@tool
def search_langchain_notes(query: str) -> str:
"""Search local LangChain learning notes to answer questions."""
docs = retriever.invoke(query)
return format_docs(docs)
2. 绑定检索工具到模型
# 绑定检索工具
model_with_rag_tool = model.bind_tools([search_langchain_notes])
3. 模拟 Agent 决策流程
# 用户问题
messages = [HumanMessage(content="RAG的典型流程是什么?")]
# 模型判断是否调用检索工具
ai_msg = model_with_rag_tool.invoke(messages)
print(f"是否调用工具:{bool(ai_msg.tool_calls)}")
# 执行检索工具
if ai_msg.tool_calls:
for tool_call in ai_msg.tool_calls:
if tool_call["name"] == "search_langchain_notes":
# 执行检索
tool_result = search_langchain_notes.invoke(tool_call["args"])
# 反馈结果给模型
messages.append(ToolMessage(content=tool_result, tool_call_id=tool_call["id"]))
# 模型基于检索结果生成回答
final_msg = model_with_rag_tool.invoke(messages)
print(final_msg.content)
此时流程变为:用户问题 → 模型判断是否调用检索工具 → 执行检索 → 反馈结果 → 模型回答 —— 这就是 Agent 的雏形。
七、学习总结与下一步规划
1. 核心认知:LangChain 的本质
经过这段时间的学习,我对 LangChain 的认知从 “抽象名词” 变为 “可落地的组件库”:
- LangChain 不是模型,而是 LLM 应用开发的标准化工具箱;
- 核心组件(模型、消息、提示词、解析器、工具、检索器)都是 Runnable,可通过 LCEL 灵活串联;
- Agent 不是魔法,而是 “模型决策 + 工具执行” 的循环;
- RAG 是 “检索 + 生成” 的组合流程,可封装为 Agent 的一个工具。
2. 已落地的核心 Demo
通过逐个拆解组件,我完成了以下关键 Demo,覆盖 LangChain 核心场景:
main.py:基础模型调用;prompt_demo.py:消息与提示词模板;output_parser_demo.py:输出解析与结构化输出;document_splitter_demo.py:文档加载与文本切分;vector_store_demo.py:向量生成与检索;rag_demo.py:最小 RAG 闭环;retriever_tool_agent_demo.py:Retriever 作为工具的 Agent 雏形。
这些 Demo 验证了一个核心学习方法:先拆解每个组件的输入、输出和场景,再组合成复杂应用,远比直接背概念更有效。
3. 下一步:学习 LangGraph 构建复杂 Agent
手写工具调用循环仅适用于简单场景,复杂 Agent 需要状态管理、工具循环、多步推理、记忆持久化等能力 —— 这正是 LangGraph 的核心价值。如果说 LangChain 的组件是 “积木”,LangGraph 就是 “搭建复杂机器的图纸”,它能实现:
- 对话状态的持久化管理;
- 工具调用的自动循环;
- 多分支的条件执行;
- 人工确认环节的插入;
- 错误恢复与重试;
- 执行轨迹的可视化观察。
八、初学者的学习建议
- 先跑通,再理解:不要一开始就纠结概念,先搭建环境、跑通最小模型调用,建立正向反馈;
- 拆解组件学习:逐个实现模型、提示词、解析器、工具、检索器的小 Demo,搞清楚每个组件的输入输出;
- 重视 LCEL:掌握
|和 Runnable 的核心逻辑,这是 LangChain 组件组合的基础; - 从工具调用理解 Agent:Agent 的核心是工具调用循环,先手动实现这个循环,再学习 LangGraph 的自动化方案;
- RAG 先做最小闭环:先实现 “加载 - 切分 - 向量 - 检索 - 生成” 的基础流程,再优化检索精度、切分策略等细节。
结语
LangChain 的学习过程,是从 “知其然” 到 “知其所以然” 的过程。它不是一套需要死记硬背的 API,而是一套让 LLM 应用开发更高效的方法论。从第一次调用模型,到理解 Agent 的雏形,我最大的收获不是掌握了多少 API,而是理解了 “如何将大模型的能力与外部工具结合,解决实际问题”。后续我会继续深入 LangGraph,将这些零散的 Demo 升级为可维护的 Agent 项目,也会持续记录学习过程,与各位开发者共勉。

更多推荐
所有评论(0)