创建第一个智能体

创建第一个智能体

2026年9月21日·#编程学习/langchain学习笔记LangChain/AI·3326 字 17 分钟
浏览量加载中...
AI 摘要

智能体的定义与核心组件、为什么 v1.x 统一成 create_agent、两种模型传入方式,以及 agent.invoke 的输入输出结构

到这里为止,我们一直在”问一句、答一句”地用模型。智能体(Agent) 要解决的是另一个问题:让模型自己决定”要不要用工具、用哪个工具、用几次”,直到把任务做完。

什么是智能体#

在大模型应用开发中,智能体通常指一种以大语言模型为推理与决策核心,结合记忆工具调用环境交互能力,能够进行规划决策执行复杂任务以达成目标的软件系统。

它要具备的关键能力:

能力说人话
理解用户问题先搞清楚你到底要什么
如何拆解任务把”帮我查天气再提醒我带伞”拆成两步
判断是否需要工具闲聊不用工具,查天气就得调
需要调用哪些工具从一堆工具里挑对的那一个
如何利用好工具结果工具返回的数据要能读懂、用上
生成回答 & 推进任务答完这一轮,还要知道下一步做什么

课程给了一句定位:通用人工智能(AGI)是 AI 的终极形态,而构建智能体是 AI 工程应用当下的”终极形态”——Agent 是大模型应用开发的核心。

图:左边「通用人工智能(AGI)」是 AI 的终极形态,右边「构建智能体(Agent)」是 AI 工程应用当下的”终极形态”——通往 AGI 之路要靠基于智能体的工程

核心组件#

组件是否必需说明
行动(Action)必须一切智能体都要能”做事”
工具(Tool)几乎总是存在行动的具体手段
规划决策(Planning)有条件存在简单任务不需要显式规划
记忆(Memory)最容易被省略单轮任务不需要
Note

实际开发中这几个要素并不需要同时出现——先别被”智能体”这个词吓到,最小的 Agent 就是”模型 + 一个工具”。

图:Agent 的完整架构——中间的智能体(Agent)连着工具(日历/计算器/代码解释器/搜索…)、记忆(短期记忆 + 长期记忆向量数据库)、行动与规划决策(反思/自我批评/思维链/子目标分解),外面还可以挂别的智能体

从 v0.x 到 v1.x:为什么统一成 create_agent#

LangChain 0.x 时代是”碎片化”的——针对不同场景设计不同的 Agent 构造函数:

# ❌ v0.x 的复杂方式:要 5 步
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
model = ChatOpenAI(model="gpt-4o-mini")
prompt = PromptTemplate.from_template("""
You are a helpful assistant.
Tools: {tools}
Tool Names: {tool_names}
{agent_scratchpad}
""")
agent = create_react_agent(llm=model, tools=tools, prompt=prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "问题"})

要用思维链推理就找 create_react_agent,要结构化输出就找 create_structured_chat_agent,要工具调用就用 create_tool_calling_agent——灵活,但有三个明显问题:

  1. 心智负担高:每种 Agent 都要单独记忆 API 与参数
  2. 可组合性差:多个 Agent 之间无法统一调度
  3. 生态碎片化:不同模块难以复用或协同演化

LangChain 1.0 之后彻底重构,所有 Agent 的创建方式统一到一个入口——create_agent()

# ✅ v1.x 的简洁方式:3 步
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
# 1. 初始化模型
model = init_chat_model("gpt-4o-mini", model_provider="openai")
# 2. 创建 agent(一步完成)
agent = create_agent(
model=model,
tools=[tool1, tool2],
system_prompt="Agent 的行为指令", # 可选
)
# 3. 调用
result = agent.invoke({"messages": [{"role": "user", "content": "问题"}]})

它取代了旧的 create_react_agentcreate_json_agentcreate_tool_calling_agent 等一堆分支函数,底层通过中间件机制(Middleware)标准模型接口(invoke / stream) 实现全局统一——框架更轻、更稳,也更容易被集成到其他 Agent 平台。

create_agent 的参数#

参数是否必需说明
model✅ 必需聊天模型,可以是字符串或模型对象
tools✅ 必需工具列表
system_prompt可选系统提示词(Agent 的行为指令)
middleware可选中间件(第 8 章)
response_format可选结构化输出(本篇后面 16 有专门一篇)
interrupt_before / interrupt_after可选在某些工具前/后暂停(人机协作)
debug可选调试模式
name可选Agent 名称(第 15 篇)
Note

实测补充:实际签名里还有 state_schemacontext_schemacheckpointerstorecache 等参数(第 9 章讲记忆时要靠 checkpointer),课程列表只是介绍了最常用的几个。 完整参数见官方文档:https://reference.langchain.com/python/langchain/agents/factory/create_agent

模型的传入方式#

模型是 Agent 的”大脑”,负责决策和推理。传入方式有两种:

方式一:传模型字符串#

Agent 根据字符串自己创建模型对象

from langchain.agents import create_agent
from dotenv import load_dotenv
load_dotenv(override=True)
agent = create_agent("deepseek-v4-flash")
print(type(agent)) # <class 'langgraph.graph.state.CompiledStateGraph'>

方式二:传模型对象#

先自己把模型建好(能精确控制 api_keybase_url 等参数),再交给 Agent:

from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langchain_deepseek import ChatDeepSeek
from dotenv import load_dotenv
import os
load_dotenv(override=True)
# 以 ChatDeepSeek 为例
# model = ChatDeepSeek(model="deepseek-v4-flash")
# 以 init_chat_model 为例
model = init_chat_model(
model="deepseek-v4-flash",
model_provider="openai",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
agent = create_agent(model)
print(type(agent)) # 同上,也是 CompiledStateGraph
Important

两种方式得到的 agent 都是 langgraph.graph.state.CompiledStateGraph 实例——也就是说,Agent 底层是一张”图”(LangGraph 的图结构)。 想亲眼看看这张图,可以画出来:

from IPython.display import Image, display
display(Image(agent.get_graph().draw_mermaid_png()))

实测图的节点是:__start__ → model → tools → __end__——这就是”模型想 → 用工具 → 再想 → 再答”的循环骨架(下一篇会展开)。

图:draw_mermaid_png() 画出来的 agent 图结构——上面这张是没挂工具时的样子(__start__ → model → __end__),下一篇挂上工具就会多出 tools 节点

日常开发推荐方式二:只有传对象才能显式指定 base_urlapi_key 等参数(第 04 篇踩过的坑)。

调用 Agent:invoke#

agent.invoke() 是 Agent 最基本的同步调用方法,它会阻塞程序直到返回最终结果。

方向内容
输入字典类型,通过 messages 字段传消息列表:{"messages": [{"role": "...", "content": "..."}]}
输出也是字典,messages 字段是完整的消息列表(可能包含多轮交互)

为什么输出是一个列表? 因为 Agent 内部可能经历多轮”模型 → 工具 → 模型”,返回的是全过程记录,最终回答只是最后一条:

[
HumanMessage(...), # 用户问题
AIMessage(...), # AI 的工具调用请求
ToolMessage(...), # 工具返回结果
AIMessage(...) # 最终回答 ← 通常取这个
]
response = agent.invoke({"messages": ["你好"]}) # 字符串默认是 HumanMessage
print(type(response)) # <class 'dict'>
final_answer = response["messages"][-1].content # 取最后一条的 content

messages 列表里也支持直接写带角色的消息字典(role 可以是 userassistantsystemtool):

resp = agent.invoke({
"messages": [
{"role": "system", "content": "你是一个小学数学老师,耐心,幽默,讲解深入浅出"},
{"role": "user", "content": "3 个苹果分给 2 个人怎么分?"},
]
})
Tip

每条 AIMessage 上都带着 usage_metadatainput_tokens / output_tokens / total_tokens),想看 Agent 这一轮到底烧了多少 token,直接取最后一条消息的 usage_metadata 就行。

一次 invoke 的完整输出长什么样#

课程里用 richrprint 把返回的字典整个打印出来,结构一览无余(print 也行,但 rprint 会着色、换行更好看):

from rich import print as rprint
response = agent.invoke({"messages": ["你好"]}) # 默认是 HumanMessage
print(type(response)) # <class 'dict'>
rprint(response)

输出(只保留关键字段,省略号是省略掉的部分):

{
'messages': [
HumanMessage(
content='你好',
additional_kwargs={},
response_metadata={}, # 用户消息没有这些元数据
id='93ffcb22-179a-4cab-81a7-a3bd3a1795d4'
),
AIMessage(
content='你好!有什么我可以帮你的吗?',
additional_kwargs={'refusal': None},
response_metadata={
'token_usage': { # ← 厂商返回的原始用量
'completion_tokens': 13,
'prompt_tokens': 7,
'total_tokens': 20,
'completion_tokens_details': {...},
'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0},
'latency_checkpoint': { # ← 延迟指标(下面单独讲)
'engine_tbt_ms': 3, # 引擎侧每 token 平均耗时
'engine_ttft_ms': 48, # 引擎侧首 token 时间
'engine_ttlt_ms': 90, # 引擎侧整段输出总耗时
'pre_inference_ms': 95, # 进入推理前的排队/预处理耗时
'service_tbt_ms': 4, # 服务侧每 token 平均耗时
'service_ttft_ms': 673, # 服务侧首 token 时间
'service_ttlt_ms': 712, # 服务侧总耗时
'total_duration_ms': 626,
'user_visible_ttft_ms': 578, # 用户真正看到第一个字的延迟 ← 最该看这个
}
},
'model_provider': 'openai',
'model_name': 'gpt-5.4-mini-2026-03-17', # 实际落到哪个模型版本
'system_fingerprint': None,
'id': 'chatcmpl-DlUJdEGrlJtKPHQ9wgdp1KsrEjIDc',
'service_tier': 'default',
'finish_reason': 'stop', # 'stop' = 说完了;'tool_calls' = 要去调工具
'logprobs': None
},
id='lc_run--019e7ccd-c08b-7f60-8b07-32324cccf2c2-0',
tool_calls=[], # 没调工具,所以是空列表
invalid_tool_calls=[],
usage_metadata={ # ← LangChain 统一命名后的用量
'input_tokens': 7,
'output_tokens': 13,
'total_tokens': 20,
'input_token_details': {'audio': 0, 'cache_read': 0},
'output_token_details': {'audio': 0, 'reasoning': 0}
}
)
]
}
字段说什么
content消息正文;发起工具调用时是空字符串(第 14 篇)
response_metadata['token_usage']厂商返回的原始用量(OpenAI 风格命名:prompt / completion / total)
response_metadata['latency_checkpoint']各家网关的延迟指标(详见下表)
response_metadata['model_name']实际命中的模型版本号,排查”是不是被换了模型”看它
response_metadata['finish_reason']'stop' 正常说完;'tool_calls' 还要去调工具
tool_calls / invalid_tool_calls这一轮的工具调用计划(没调就是空列表)
usage_metadataLangChain 归一化后的用量(input_tokens / output_tokens / total_tokens),跨厂商命名一致,比 token_usage 更好用

延迟指标分三个”视角”(引擎 → 服务 → 用户),另外还有几个配套的耗时项:

指标含义
engine_ttft_ms引擎侧首 token(TTFT = Time To First Token),模型自己算得有多快
service_ttft_ms服务侧首 token,含网关排队、鉴权、转发
user_visible_ttft_ms用户可见首 token 延迟——真正决定”手感”的就是它
engine_tbt_ms / service_tbt_ms平均每个 token 的间隔(TBT = Time Between Tokens),决定吐字是否均匀
engine_ttlt_ms / service_ttlt_ms整段输出的总耗时(TTLT = Time To Last Token)
pre_inference_ms进入推理前的预处理/排队耗时
Note

实测补充response_metadata 里的内容是模型服务商返回什么、LangChain 就记什么,所以字段随平台而变:

  • latency_checkpoint 这一组是课程用的 CloseAI 网关(OpenAI 兼容)返回的;换平台不一定有
  • 本机用假服务端(自建 OpenAI 兼容响应)实测时,response_metadata 只有 finish_reason / id / model_name / model_provider / token_usage / logprobs / system_fingerprint 这几个基本字段,没有 latency_checkpoint
  • 想写”跨平台都能跑”的代码,优先读 usage_metadata(LangChain 归一化过),别去解析 token_usage 或延迟指标。

相关#

练习题#

一、回忆填空(写完再展开对答案)#

  1. 智能体 = 以____为推理与决策核心,结合记忆、____与环境交互能力,能进行规划决策并执行复杂任务以达成目标的软件系统
  2. 核心组件里,必须的是____,几乎总是存在的是____,最容易被省略的是____
  3. v0.x 的问题是心智负担高、差、生态;v1.x 把创建方式统一成一个入口:____()
  4. create_agent 的两个必需参数是____和____;系统提示词通过 ____ 参数传入
  5. 模型传入有两种方式:直接传____,或传____对象(推荐后者,因为能显式指定 base_urlapi_key
  6. 不管哪种方式,agent 的本质都是 LangGraph 的____实例——所以它底层是一张____
  7. agent.invoke() 的输入是字典,消息列表放在 ____ 字段里;字符串会被当成____消息
  8. invoke 返回的是完整的____(可能含多轮交互),最终回答通常取 response["messages"][____]
  9. 想看 Agent 的 token 消耗,可以读消息上的 ____ 属性
填空答案(做完再点开)
  1. 大语言模型 / 工具调用 2. 行动(Action) / 工具(Tool) / 记忆(Memory) 3. 可组合性 / 碎片化 / create_agent 4. model / tools / system_prompt 5. 模型字符串 / 模型 6. CompiledStateGraph / 图 7. messages / HumanMessage 8. 消息列表 / -1 9. usage_metadata

二、裸写题#

  • 2-1 用字符串方式创建第一个 Agentcreate_agent("deepseek-v4-flash") 创建 Agent,打印 type(agent),然后 invoke 一句”你好”,取出最后一条消息的 content 打印出来。

    提示(先自己想,实在想不出再点开)

    一级 · 思路:字符串方式最省事,但依赖环境变量里的密钥 二级 · 方法agent = create_agent("deepseek-v4-flash") + agent.invoke({"messages": ["你好"]}) 三级 · 骨架:先把 load_dotenv(override=True) 写上,否则密钥读不到

  • 2-2 用模型对象方式创建,并带上 system 消息init_chat_model 建好模型(带 base_url/api_key),create_agent(model=model) 创建 Agent,invoke 时在消息列表开头加一条 {"role": "system", ...}(比如”你是一个小学数学老师,耐心幽默”),观察回答风格。

    提示

    一级 · 思路:system 消息是用来”定人设”的 二级 · 方法{"messages": [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}]} 三级 · 骨架:对比一下不加 system 消息时的回答风格

  • 2-3 把返回的消息流打印清楚 调用一次 Agent 后,遍历 response["messages"],打印每条消息的类型HumanMessage / AIMessage / ToolMessage)和内容,并单独打印最后一条的 contentusage_metadata

    提示

    一级 · 思路:Agent 的返回是”全过程记录”,先看清有几条、分别是什么 二级 · 方法for msg in response["messages"]: print(type(msg).__name__, msg.content) 三级 · 骨架:消息对象还有 pretty_print() 方法,输出更漂亮

参考答案(做完再点开)
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
load_dotenv(override=True)
# ---------- 2-1 字符串方式 ----------
agent = create_agent("deepseek-v4-flash")
print(type(agent)) # <class 'langgraph.graph.state.CompiledStateGraph'>
response = agent.invoke({"messages": ["你好"]})
print(response["messages"][-1].content)
# ---------- 2-2 模型对象 + system 消息 ----------
model = init_chat_model(
model="deepseek-v4-flash",
model_provider="openai",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL"),
)
agent2 = create_agent(model=model)
resp = agent2.invoke({
"messages": [
{"role": "system", "content": "你是一个小学数学老师,耐心,幽默,讲解深入浅出"},
{"role": "user", "content": "3 个苹果分给 2 个人怎么分?"},
]
})
# ---------- 2-3 打印消息流 ----------
for msg in resp["messages"]:
print(type(msg).__name__, "→", msg.content[:50])
last = resp["messages"][-1]
print("最终回答:", last.content)
print("token 用量:", last.usage_metadata)
# 也可以更漂亮地打印:
# for msg in resp["messages"]:
# msg.pretty_print()

评论区

[ 标签 ]
# AI37# AI 编程2# AI工具1# Ajax2# Apifox1# AstrBot3# Astro2# CC Switch1# CDN2# Claude Code1# claudecode2# ClaudeCode1# Cloudflare2# CloudFlare2# CloudFlare-ImgBed3# coc3# CSS6# DeepSeek6# deepseek2# DELETE1# Docker1# EdgeOne3# Gist1# git1# GitHub1# hexo-circle-of-friends1# HTML6# HTTP5# ImageManager1# Java23# java13# JavaScript5# JDBC3# JSON2# JUnit1# LangChain25# Logback1# Maven6# Muse Spark1# Mybatis1# MyBatis4# MySQL28# MySql1# NapCat1# Node.js1# obsidian2# Obsidian5# OpenCode4# ORM1# PathVariable1# PicGo1# PyCharm1# Python65# RequestBody1# RequestMapping1# RESTful风格1# skills1# Slf4j1# SpringBoot11# SQL2# Streamlit5# Svelte2# TailwindCSS1# Telegram3# Tlias2# Vercel1# vscode2# Vue7# Waline3# WebDAV1# Web基础6# Web开发6# WinSCP1# YAML1# 三层架构1# 中二宣言1# 书籍1# 使用文档10# 写作1# 函数2# 刷步数1# 前端32# 动态1# 动漫1# 包1# 单词2# 博客7# 博客工作流1# 博客开发2# 参数接收1# 友链1# 反思2# 图床6# 地图1# 备份2# 大模型1# 奇思妙想1# 存储1# 学习方法6# 学校1# 宝塔面板3# 宝宝10# 对象1# 导航栏1# 工具2# 开发1# 开发工具1# 开发规范1# 开心1# 异常处理1# 影视2# 微信1# 性能优化2# 总结1# 想法15# 感受1# 感悟11# 指南1# 提示词工程1# 插件5# 故障排除1# 效率工具2# 教程10# 数据分析9# 数据库27# 数据结构1# 文件操作2# 斩神1# 日常92# 日志框架1# 朋友圈1# 朱元璋1# 模块1# 模板1# 正则表达式2# 测试1# 游戏2# 爬虫7# 生活迁移1# 电影2# 电脑1# 碎碎念1# 视觉识别1# 类1# 类型注解1# 网络基础2# 网络教室1# 羊毛2# 脚本2# 脚本工具1# 自动化2# 蓝奏云1# 订阅推荐2# 记录2# 评论系统1# 词根1# 词缀1# 说说1# 足迹1# 跑步2# 路径参数1# 转载2# 运动1# 部落冲突1# 配置1# 随机图1# 面向对象5# 音乐3# 音标1# 饮食1# 驼峰命名1# 高德地图1
[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

暂未播放

0:000:00
暂无歌词
找不到相关结果。
[ contents ]
[ 全部文章 ]