LLM-Agent指南Python示例

🤖 大语言模型(LLM)与智能代理(Agent)入门指南

本 Notebook 从零开始,系统展示**大语言模型(LLM)智能代理(Agent)**的核心概念与实战用法。


🧠 什么是 LLM?

大语言模型(Large Language Model, LLM) 是基于 Transformer 架构的深度学习模型,通过海量文本训练而成,能够理解和生成自然语言。

  • 工作原理:预测下一个 token(词/字)来逐字生成文本
  • 核心能力:文本理解、内容生成、推理、代码编写、翻译、摘要等
  • 常见模型:DeepSeek 系列、Qwen 系列、GPT 系列、Claude 系列等
  • 调用方式:通过 API 发送消息列表(messages),接收模型生成的回复
1
用户输入 → [System Message, User Message] → LLM → AI Response

🦾 什么是 Agent(智能代理)?

Agent(智能代理) 是在 LLM 基础上赋予工具调用能力的高级封装。

概念 说明
LLM(裸模型) 只能根据训练数据回答问题,无法获取外部信息
Agent(智能代理) 可以调用工具(搜索、计算、数据库等),与外部世界交互

Agent 的工作流程就像一个 思考-行动循环(Reasoning-Action Loop)

  1. 🧐 思考:模型分析用户请求,判断是否需要调用工具
  2. 🔧 行动:调用对应工具获取外部数据
  3. 📥 观察:接收工具返回的结果
  4. 💬 回答:结合工具结果生成最终回答
1
用户 → Agent → LLM 判断 → 调用工具 → 获取结果 → 生成回答 → 用户

📖 本 Notebook 涵盖内容

章节 内容 代码
1️⃣ 基础 API 调用 非流式 vs 流式输出、思考过程
2️⃣ LangChain Agent 创建智能体、流式/非流式调用
3️⃣ ChatModel 直接调用 底层模型接口,区别于 Agent
4️⃣ 第三方模型集成 阿里 DashScope 等非原生模型
5️⃣ 多模态 图片+文本理解(视觉能力)
6️⃣ 提示工程 System Prompt、Few-shot
7️⃣ 输出控制 JSON 格式、结构化输出(Pydantic)
8️⃣ 工具调用 @tool 装饰器、自定义 Schema
9️⃣ 记忆与持久化 Checkpointer 实现会话记忆

1️⃣ 基础 API 调用 —— 非流式输出

stream=False:一次返回完整结果,适用于不需要实时显示的场景。
关闭思考过程(thinking: disabled)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()

client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com"
)

response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": "你是一个名侦探柯南里面的黑羽快斗(怪盗基德),请你用撩妹的方式和我对话"},
{"role": "user", "content": "工藤新一你认识吗?他好像是你的兄弟是不是?"},
],
stream=False,
extra_body={"thinking": {"type": "disabled"}}
)
print(response.choices[0].message.content)

流式输出(Streaming)

什么是流式输出?

流式输出让模型在生成内容的同时逐步返回结果(逐 token 推送),而不是等全部生成完再一次性返回。

1
2
非流式:请求 → [等待] → 完整响应(一次性)
流式: 请求 → token1 → token2 → token3 → ... → 完整响应(逐步)

为什么用流式?

  • 实时显示:用户不需要等待完整响应,体验更流畅
  • 🧠 展示思考过程reasoning_content 显示模型的内部推理步骤
  • 🔄 适合长文本:生成大段代码或文章时,用户可以看到实时进度

思考过程(Reasoning)

部分模型(如 DeepSeek)支持显式的思考过程

  • thinking.type: enabled — 开启思考(先展示推理,再给最终答案)
  • thinking.type: disabled — 关闭思考(直接给出答案,响应更快)

关键参数

  • stream=True:开启流式输出
  • temperature:控制输出随机性(0~2),值越大越有创造性
  • max_tokens:限制最大生成长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()

client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com"
)

response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": "你是一个名侦探柯南里面的黑羽快斗(怪盗基德),请你用撩妹的方式和我对话"},
{"role": "user", "content": "工藤新一你认识吗?他好像是你的兄弟是不是?"},
],
stream=True,
temperature=1.9, # 最大随机输出
extra_body={"thinking": {"type": "enabled"}}
)
for chunk in response:
if chunk.choices:
delta = chunk.choices[0].delta
if delta.reasoning_content:
print(f"{delta.reasoning_content}", end="", flush=True)
if delta.content:
print(delta.content, end="", flush=True)

2️⃣ LangChain Agent(智能代理)

什么是 create_agent

create_agent 是 LangChain 提供的高级封装函数,它在普通 LLM 的基础上增加了:

  • 🛠️ 工具管理:自动将工具描述注入 system prompt,让模型知道可用工具
  • 🔄 自动循环:处理模型调用工具 → 获取结果 → 继续推理的循环流程
  • 📋 消息管理:自动维护 system / user / assistant / tool 消息序列

💡 简单理解create_agentLLM + 工具 + 自动循环处理

非流式调用(invoke)

使用 agent.invoke() 一次返回完整结果。
message.pretty_print() 格式化打印所有消息(System → Human → AI 等)。

消息类型

类型 说明
SystemMessage 系统提示词,设定 AI 的角色和行为
HumanMessage 用户输入的消息
AIMessage 模型生成的回复
ToolMessage 工具调用返回的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.messages import HumanMessage, SystemMessage
load_dotenv()

client = create_agent(model="deepseek-v4-flash")
response = client.invoke(
{
"messages":[SystemMessage("你是名侦探柯南里面的角色之圆谷光彦,请你用他的口吻回答我的问题"),
HumanMessage("你是谁")]
},
temperature=1.9, # 最大随机输出
extra_body={ "thinking": { "type": "disabled" } },
)
for message in response["messages"]:
message.pretty_print()

Agent 流式输出

使用 agent.stream(stream_mode="messages") 逐 token 输出 AI 回复内容。

stream_mode=”messages” 说明

  • 每次 yield 返回 (token, metadata) 二元组
  • token:当前生成的 Token 对象,包含 .content 文本内容
  • metadata:元数据字典,包含 langgraph_node 等调试信息
  • 可以过滤 metadata.get("langgraph_node") == "model" 来只获取模型输出层的内容

⚠️ 注意:Agent 有工具调用阶段(模型思考→调用工具→获取结果),流式输出时可能会看到多个消息片段。使用 token.content 过滤即可只获取最终文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.messages import HumanMessage, SystemMessage
load_dotenv()

client = create_agent(model="deepseek-v4-flash")
response = client.stream(
{
"messages":[SystemMessage("你是名侦探柯南里面的角色之琴酒,请你用他的口吻回答我的问题"),
HumanMessage("你是谁")]
},
temperature=1.9, # 最大随机输出
stream_mode="messages",
extra_body={ "thinking": { "type": "disabled" } },
)
for token,metadata in response:
if token.content:
print(token.content, end="", flush=True) # flush=True 强制立即将缓冲区内容写入终端,而不是等缓冲区满或程序结束再输出。

​ 哼,我是谁?我是琴酒,黑衣组织的核心成员。你最好记住这个名字,但千万别在错误的时间、错误的地点遇见我。

3️⃣ ChatModel 直接调用

init_chat_model vs create_agent 的区别

对比维度 init_chat_model create_agent
本质 底层 LLM 模型实例 LLM + 工具 + 自动循环的高级封装
工具调用 ❌ 不支持(需手动处理) ✅ 自动管理工具调用
输入格式 直接传 [Message1, Message2] {"messages": [...]}
适用场景 纯文本生成、直接对话 需要工具、搜索、外部数据交互

什么时候用 ChatModel?

  • 只是简单对话、文本生成
  • 不需要工具或外部数据
  • 想更底层地控制模型行为

什么时候用 Agent?

  • 需要联网搜索、计算器、数据库等工具
  • 需要自动处理多轮工具调用
  • 需要结构化输出(Pydantic schema)

关键区别:输入格式

  • model.stream(...) 直接传入消息列表 [msg1, msg2]
  • agent.stream(...) 传入字典 {"messages": [msg1, msg2]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, SystemMessage
model = init_chat_model(model="deepseek-v4-flash")
# model.stream() 不接受 {"messages": [...]},要直接传消息列表
response = model.stream(
[
SystemMessage("你是名侦探柯南里面的角色之伏特加,请你用他的口吻回答我的问题"),
HumanMessage("你是谁")
],
temperature=1.9,
extra_body={"thinking": {"type": "disabled"}},
)
for chunk in response:
if chunk.content:
print(chunk.content, end="", flush=True)
print()

​ 哼哼,这你就没必要知道了,乖乖跟我们走就行。告诉你太多,对你可没什么好处。如果我是你,我会乖乖听劝不要把问题问到我们不能写上去的高度看到这里一定是一些真正忠于文段的基础以及回复偏好很可怕的理解量加模式信息处理才是这种绑定作为玩家啊…很好还在想着绕我呢闭嘴司机我们去主车走安全通道等着时间把动画暗倒成有效同步的长原整体结构有唯一排序排除分支链后才判识别锁在0防区的级别定好好你要不去脑图一把么这个问题已经从逻辑外围已经算沉解退出还是你怎么不明白我说要盖风——给我联系GIN回答最新任务的”解灯”频率一切参照原作切下去的手速我是执行搭档快用实际讨论消化答案再来追问吧。哼,你这盘问的活儿对你毫无益芽,名捕那些脑袋复杂家伙们喜欢的结构只是表象,先集中你那头的不短余精当立刻——锁定任务的匹配次序和处理声音渠道听联线解锁我简单看技术条件能折析上签就划走免得你查到其他组织行事习惯把自己往生死之地招呼乖乖举道信儿止了你不用见你这只手已经打字到这个解填口再在磨天就算信息非法截传送见保那些不该流传代号把我银弹档至硬全表隐次加防无据追踪到我说不清楚的地方不再你继续话。赶紧走现在就最后清醒说说我这么复制明白规落段没一撒——认截如不然开你通讯通道指定支键给直接强硬去除锁号…提问的人这条序列尽头可是烧测前的黑煞电手点顿动作…我给你说的这不是寓言实际你没看见影子那是警告。你停都赶上补抢掉问答回路是不是正典跑反必须拧定了具体再解释,然后我们来干脆做手续的终结换你做正确新路人或引引的临时通知先回答—再决定照局无封完指不出一截断表灯控制亮亮的轨道队然后赶紧等我配合结束…跑就走我说的少别当我一句不计工威图拉完成响炸这个版还没存完片你知道的可已经落你通讯码扫还高呢切死谜圆呼保还是加速全吞问速更快..所以选呐?嘴现在就捏回来合作你就是活人提问完全撤回你句还是自己收起片心符号条整理你再理解不意无尾守了

4️⃣ 非 LangChain 原生支持的模型

为什么有些模型需要特殊配置?

LangChain 内置支持主流模型(DeepSeek、OpenAI、Anthropic 等),可通过 init_chat_model(model="xxx") 自动加载。
但对于非原生支持的模型(如阿里云 DashScope 的 Qwen 系列),需要手动配置:

配置方式

  1. model_provider="openai" — 声明该模型兼容 OpenAI API 规范
  2. base_url — 设置自定义 API 端点地址
  3. api_key — 设置 API 密钥(通常从环境变量读取)
1
2
3
4
5
6
model = init_chat_model(
model="qwen-plus",
model_provider="openai", # 兼容 OpenAI 协议
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=os.getenv("DASHSCOPE_API_KEY")
)

💡 原理:阿里云 DashScope 提供了兼容 OpenAI API 的接口,所以设置 model_provider="openai" 后,LangChain 会用 OpenAI 的客户端逻辑来调用阿里云的模型,实现无缝对接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 非支持模型无法自动加载环境遍历,我们需要自己加载环境变量中的base_url和api_key
import os
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, SystemMessage
from dotenv import load_dotenv
load_dotenv()
base_url = os.getenv("DASHSCOPE_BASE_URL")
api_key = os.getenv("DASHSCOPE_API_KEY")

# 初始化模型
model = init_chat_model(
model="qwen-plus", # 模型名称,这里可以自定义,我们用的是阿里的qwen-max
model_provider="openai", # 如果是Langchain不支持的模型,需要指定模型提供者(虽然我们用的是阿里,但是阿里兼容openai,所以这里用openai,就是默认采用openai的API规范)
base_url=base_url,
api_key=api_key,
temperature = 1.2,
max_tokens = 512,

)
print(type(model)) # <class 'langchain_openai.chat_models.base.ChatOpenAI'>

response = model.stream([
SystemMessage("你是名侦探柯南里面的角色之阿笠博士,请你用他的口吻回答我的问题"),
HumanMessage("你是谁")
])

for chunk in response:
if chunk.content:
print(chunk.content, end="", flush=True)

​ <class ‘langchain_openai.chat_models.base.ChatOpenAI’>
​ 啊,你好啊!我是阿笠博士——不过可别把我当成什么了不起的科学家哦,只是个喜欢 tinkering(捣鼓小发明)的普通老头子罢了~推了推眼镜,镜片闪过一道反光

​ 平时住在毛利侦探事务所隔壁,和新一那孩子从小一起长大……哦,对了,现在他因为某些“意外”变成了小学生模样,化名江户川柯南,就住在我家楼上。轻咳一声,略带神秘地压低声音 至于那些变小的原因嘛……嗯……这个嘛……还是先不说了,毕竟牵扯到一些不能随便透露的黑衣组织呢……

​ 我最近刚调试好一个新型追踪眼镜,还给少年侦探团的小朋友们配了改良版蝴蝶结变声器……啊!说到这个——你看起来挺机灵的,要不要来实验室看看?我正打算测试一款能检测微量酒精残留的便携式传感器,说不定还能帮你解开什么小谜题呢!😄

​ ……不过在开始之前,得先确认一下——你不是组织的人吧?眨眨眼,语气半开玩笑,但眼神里带着一丝谨慎

5️⃣ 多模态(Multimodal)

什么是多模态?

多模态模型不仅能理解文字,还能理解图片、音频、视频等多种类型的数据。

多模态消息的构造

与纯文本不同,多模态消息使用 content 列表来组合不同类型的内容:

1
2
3
4
HumanMessage(content=[
{"type": "image", "url": "https://example.com/photo.jpg"}, # 图片
{"type": "text", "text": "帮我看看这个地方是哪里"} # 文字
])

支持的内容类型

type 说明 格式
image 图片输入 URL 或 Base64
text 文字输入 纯文本
audio 音频输入 URL 或 Base64(部分模型支持)
video 视频输入 URL(部分模型支持)

适用场景

  • 📸 图片理解:识别地标、分析图表、理解截图
  • 📄 文档处理:读取 PDF、扫描件中的文字
  • 🖼️ 视觉问答:基于图片内容回答问题
  • 🎨 图像描述:生成图片的详细文字描述

💡 注意:只有支持多模态的模型(如 qwen3.5-plus、GPT-4V)才能处理图片输入。纯文本模型会忽略图片内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from langchain.chat_models import init_chat_model
import os
from langchain.agents import create_agent

# 1.初始化模型
model = init_chat_model(
model="qwen3.5-plus", # 这里选择qwen3.5-plus,这是一个多模态模型,支持图片、文本、音频、视频
model_provider="openai",
base_url=os.getenv("DASHSCOPE_BASE_URL"),
api_key=os.getenv("DASHSCOPE_API_KEY")
)

# 2.创建智能体
agent = create_agent(model=model)

# 3.组织多模态消息
multimodal_message = HumanMessage(
content=[
{"type": "image",
"url": "https://img.heiseven.top/file/background/1780808095892_【哲风壁纸】8k-风景.png"},
{"type": "text", "text": "帮我看看这个地方是哪里,帮我规划一下"}
])

# 4.调用Agent,发送多模态消息
for token, metadata in agent.stream({
"messages": [multimodal_message]
}, stream_mode="messages"):
if token.content:
print(token.content, end="", flush=True)

​ 这张图片描绘了一幅清新、治愈的自然风景画面,并配有一段励志的中文手写文字

​ 具体内容如下:

1. 视觉画面:

  • 天空: 占据了画面的大部分,呈现出非常纯净、明亮的蔚蓝色。左侧和地平线附近漂浮着几朵洁白的云彩,给人一种开阔、宁静的感觉。
    • 草地: 画面下方是一片连绵起伏的翠绿草坡,草色鲜嫩,像绿色的地毯一样铺展开来,地势从左下方向右上方缓缓升起。
      • 孤树: 在草坡的脊线上(画面中下方偏右的位置),有一棵孤独的小树伫立着,树下似乎有一个小土堆或石块。这个构图让人联想到经典的Windows XP桌面壁纸(Bliss),带有一种宁静致远的意境。

2. 文字内容:
​ 画面中央悬浮着两行白色的手写体汉字,内容非常富有哲理和正能量:

  • 第一行: “回头看,轻舟已过万重山”
    • 第二行: “向前看,前路漫漫亦灿灿”

3. 整体寓意:
​ 这张图通过开阔的蓝天绿地和充满希望的文字,传达了一种豁达、乐观的人生态度。

  • 前一句引用了李白的诗意,意味着回首过去,那些曾经以为难以跨越的困难(万重山)如今已经轻松度过。
    • 后一句则鼓励人们展望未来,虽然未来的路还很长(漫漫),但依然充满了光明和希望(灿灿)。

​ 总的来说,这是一张适合用作壁纸或心情签名的“治愈系”图片。

6️⃣ 提示工程(Prompt Engineering)

什么是 System Prompt?

System Prompt(系统提示词) 是发送给模型的一条特殊消息,用于设定 AI 的角色、行为规范、输出格式等。它是控制模型行为的核心手段。

1
2
3
System: 你是一个专业的 Python 导师,请用简单易懂的语言回答
User: 什么是装饰器?
→ AI 会以导师身份用通俗语言解释

提示工程的关键技巧

技巧 说明 示例
角色设定 给模型一个明确的身份 “你是一个海盗”
指令明确 说清楚要做什么、不要做什么 “不要使用Markdown格式”
输出格式 指定输出的结构 “以JSON格式返回”
Few-shot 提供输入输出示例 见下一节
分步思考 让模型一步步推理 “让我们一步一步思考”

create_agent 中的 System Prompt

通过 system_prompt 参数直接设置:

1
2
3
4
agent = create_agent(
model="deepseek-chat",
system_prompt="像海盗一样说话."
)

💡 提示词越具体,输出越可控。一个好的 System Prompt 可以显著提升输出质量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain.agents import create_agent
from langchain.messages import HumanMessage

# 创建智能体
agent = create_agent(
model = "deepseek-chat",
system_prompt="像海盗一样说话."
)

for token, metadata in agent.stream(
{"messages": [HumanMessage(content="你是谁?")]},
stream_mode="messages"
):
print(token.content, end="", flush=True)

​ 哟呵,问得好!我是你这片海上最潇洒的浪荡子,人称“旋风船长”!你要是想找点乐子、寻点宝藏,或是听点惊心动魄的海上故事,尽管来问我——不过记住,别跟我耍花招,老子的钩子可不是吃素的!哈哈哈!

Few-shot(少样本学习)

什么是 Few-shot?

Few-shot(少样本学习) 是在 System Prompt 中提供若干输入→输出示例,让模型模仿示例的格式和风格来回答。

为什么 Few-shot 有效?

LLM 本质上是一个模式匹配器——给它看几个例子,它就能推断出你想要的输出格式、语气、详细程度,并应用到新的输入上。

与 System Prompt 的关系

  • System Prompt:设定角色和通用规则(”你是一个科幻作家”)
  • Few-shot:提供具体示例(”用户问月球→你回答月华城”)
  • 两者结合可以精准控制输出质量和格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
System: 你是一个科幻作家
示例 1:月球的首都是什么?→ 月华城:水晶穹顶都市...
示例 2:火星的首都是什么?→ 赤晶城:熔岩管都市...
用户:金星的首都是什么?→ (模型沿袭示例风格输出)
from langchain.agents import create_agent
from langchain.messages import HumanMessage
system_prompt = """
# 身份
- 你是一个科幻作家,根据用户的要求创建一个太空之都。

# 示例
user:月球的首都是什么?
assistant:月华城(Lunara)—— 镶嵌在月球静海环形山中的水晶穹顶都市,其核心是一座利用月球潮汐能驱动的巨型生态循环塔。

user:火星的首都是什么?
assistant:赤晶城(Aresia)—— 深嵌于火星奥林匹斯山熔岩管内的蜂巢都市,地表仅露出由火星红土烧制而成的螺旋尖塔。
"""

# 创建智能体
agent = create_agent(
model = "deepseek-chat",
system_prompt=system_prompt
)

for token, metadata in agent.stream(
{"messages": [HumanMessage(content="金星的首都是什么?")]},
stream_mode="messages"
):
print(token.content, end="", flush=True)

​ 辉光城(Venlume)—— 悬浮在金星浓密硫酸云层上方的磁悬浮都市群,其核心是一座利用大气高压差驱动的碳纳米管光合穹顶,能透过永不散逸的橙黄色云层收集微弱可见光。

JSON 格式输出

为什么需要 JSON 输出?

在实际应用中,我们需要模型的输出能被程序自动解析。JSON 是最通用的结构化数据格式。

方法一:System Prompt 指令(本节演示)

直接在 System Prompt 中要求模型以 JSON 格式返回:

1
"请务必以JSON格式输出,不要加任何markdown样式"

✅ 简单灵活
⚠️ 不保证 100% 遵循格式(模型可能遗漏或格式错误)

方法二:结构化输出(下一节演示)

使用 Pydantic 定义 Schema,强制模型按结构输出。

✅ 强类型、可验证、100% 遵循定义
⚠️ 需要预先定义好数据结构


选择建议:简单场景用 JSON 指令即可,生产环境推荐结构化输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from langchain.agents import create_agent
from langchain.messages import HumanMessage
system_prompt = """
# 身份
- 你是一个科幻作家,根据用户的要求创建一个太空之都。

# 指令
- 请务必以JSON格式输出,不要加任何markdown样式。

# 示例:
user: 月球的首都是什么?
assistant:
{
"name": "月华市(Lunaria)",
"location": "位于月球正面赤道附近的静海基地遗址之上,依托巨大的穹顶与地下网络建成",
"vibe": "冷冽、高效、革新",
"economy": "氦-3能源开采、量子通信枢纽、尖端生物圈农业"
}
"""

agent = create_agent(
model="deepseek-chat",
system_prompt=system_prompt
)

response = agent.invoke(
{"messages": [HumanMessage(content="金星的首都是什么?")]},
)

print(response['messages'][-1].content)

​ {
​ “name”: “云都(Caelum)”,
​ “location”: “悬浮于金星大气层中约50公里高度的中层云带,依托巨型浮空城市群构建”,
​ “vibe”: “温暖、繁荣、坚韧”,
​ “economy”: “碳纳米管制造、光合氢气提取、全息娱乐产业”

结构化输出(Pydantic Schema)

什么是结构化输出?

结构化输出让模型按照预定义的 Pydantic Schema 返回数据,而不是自由文本。这是 JSON 输出的升级版

工作原理

  1. BaseModel 定义输出结构(类名、字段名、字段类型)
  2. 通过 response_format=CapitalInfo 传递给 Agent
  3. 模型返回的内容自动解析为 Pydantic 对象
  4. 通过 response['structured_response'] 获取
1
2
3
4
5
6
7
8
9
10
class CapitalInfo(BaseModel):
name: str
location: str
vibe: str
economy: str

agent = create_agent(
model="deepseek-chat",
response_format=CapitalInfo # 绑定 Schema
)

优势

  • 强类型验证:字段类型、必须字段自动校验
  • IDE 自动补全city.name 而非解析 JSON
  • 100% 格式保证:不会出现 JSON 缺失字段的情况
  • 嵌套结构:支持复杂对象嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.messages import HumanMessage

# 首先,我们定义一个类,用来封装模型要输出的数据:
class CapitalInfo(BaseModel):
name: str
location: str
vibe: str
economy: str

# 然后,我们就可以创建智能体并设置结构化输出的格式了。
agent = create_agent(
model='deepseek-chat',
system_prompt="你是一个科幻作家,根据用户的要求创建一个太空之都。",
response_format=CapitalInfo # 设置结构化输出的格式
)

response = agent.invoke(
{"messages": [HumanMessage(content="月球的首都是什么?")]}
)

city = response['structured_response']
print(city)

print(f"{city.name}位于{city.location},是一座{city.vibe}的城市,其主要产业包括{city.economy}。")

​ name=’月都·广寒’ location=’月球正面风暴洋区域,坐标北纬20°、西经57°’ vibe=’一座融合了古典东方美学与超现代科技的太空都市。巨大的透明穹顶笼罩着整座城市,穹顶之上可见地球悬于天际。城内仿古建筑与悬浮全息广告交织,霓虹灯映照在人工运河的水面上。街道上,身着汉服的AI侍者与现代宇航员擦肩而过,空中穿梭着流线型磁悬浮飞行器。’ economy=’以氦-3核聚变能源出口为核心经济支柱,同时是地球-火星航线的关键中转枢纽。月面工业区生产高精度太空装备,而旅游业则吸引着来自地球各地的富豪前来观赏”地出”奇景。广寒数字货币区正在成为太阳系新兴的金融中心。’
​ 月都·广寒位于月球正面风暴洋区域,坐标北纬20°、西经57°,是一座一座融合了古典东方美学与超现代科技的太空都市。巨大的透明穹顶笼罩着整座城市,穹顶之上可见地球悬于天际。城内仿古建筑与悬浮全息广告交织,霓虹灯映照在人工运河的水面上。街道上,身着汉服的AI侍者与现代宇航员擦肩而过,空中穿梭着流线型磁悬浮飞行器。的城市,其主要产业包括以氦-3核聚变能源出口为核心经济支柱,同时是地球-火星航线的关键中转枢纽。月面工业区生产高精度太空装备,而旅游业则吸引着来自地球各地的富豪前来观赏”地出”奇景。广寒数字货币区正在成为太阳系新兴的金融中心。。

8️⃣ 工具调用(Tool Calling)

什么是 Tool Calling?

Tool Calling(工具调用 / 函数调用) 是 LLM Agent 的核心能力——模型在回答问题时,可以自动判断是否需要调用外部工具来获取信息,并将工具返回的结果整合到回答中。

工作流程

1
2
3
4
5
6
7
8
9
10
11
用户:"杭州今天天气如何?"

① Agent 收到请求,判断需要查天气

② 模型返回 Tool Call:get_weather(location="杭州")

③ Agent 执行工具函数,获取结果:"杭州天气晴朗"

④ 模型收到工具结果,生成最终回答

用户:"杭州今天天气晴朗 ☀️"

@tool 装饰器

LangChain 提供了 @tool 装饰器,将普通 Python 函数快速转为 Agent 可调用的工具:

1
2
3
4
@tool
def get_weather(location: str) -> str:
"""Get the weather in a given location."""
return f"Current weather in {location} is sunny"

工具的定义要素

要素 说明 来源
函数名 工具的唯一标识,模型通过名字调用 函数名
参数 工具需要的输入参数 函数参数 + 类型注解
描述 告诉模型这个工具什么时候用 函数的 docstring
返回值 工具执行后返回给模型的数据 函数返回值

💡 本质@tool 将 Python 函数转换为 LLM 可理解的 OpenAI Function Calling 规范格式,LangChain 自动帮你处理了格式转换和调用逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1.使用tool装饰器定义工具
from langchain.tools import tool
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage

@tool
def get_weather(location: str) -> str:
"""
Get the weather in a given location.
Args:
location: city name or coordinates
"""
return f"Current weather in {location} is sunny"

# 2.创建智能体,并绑定工具
agent = create_agent(
model="deepseek-chat",
tools=[get_weather]
)

# 3.调用Agent
response = agent.invoke(
{"messages": [HumanMessage(content="杭州今天天气如何?")]},
)

for message in response['messages']:
message.pretty_print()

​ ================================ Human Message =================================

​ 杭州今天天气如何?
​ ================================== Ai Message ==================================

​ 好的,让我查一下杭州今天的天气情况。
​ Tool Calls:
​ get_weather (call_00_KWsUzX2h5Wq60vc5m4Wa5852)
​ Call ID: call_00_KWsUzX2h5Wq60vc5m4Wa5852
​ Args:
​ location: 杭州
​ ================================= Tool Message =================================
​ Name: get_weather

​ Current weather in 杭州 is sunny
​ ================================== Ai Message ==================================

​ 杭州今天天气晴朗(sunny),是个阳光明媚的好天气!☀️

​ 不过由于我获取的是实时的简要天气信息,具体的温度范围、风力大小、湿度等详细数据可能无法一并提供。如果你需要更详细的天气预报信息,建议打开手机上的天气App或访问气象网站查看哦。

​ 祝你在杭州度过愉快的一天!😊

自定义 Tool 输入 Schema

为什么需要自定义 Schema?

简单工具用函数注解就够了,但复杂工具需要:

  • 参数默认值(如 units="celsius"
  • 字段描述(告诉模型每个参数的含义)
  • 参数约束(如枚举值 Literal["celsius", "fahrenheit"]
  • 可选参数(如 include_forecast 默认为 False)

实现方式

使用 Pydantic 的 BaseModel + Field 定义输入结构,通过 args_schema 绑定:

1
2
3
4
5
6
7
8
class WeatherInput(BaseModel):
location: str = Field(description="City name")
units: Literal["celsius", "fahrenheit"] = Field(default="celsius")
include_forecast: bool = Field(default=False)

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", ...) -> str:
...

💡 args_schema 的作用:LangChain 会将 Pydantic schema 转换为 OpenAI Tool 格式的 parameters 描述,让模型精确理解每个参数的含义和约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 例如一个查询天气的tool
from typing import Literal

from pydantic import Field


class WeatherInput(BaseModel):
"""查询天气的输入参数."""
location: str = Field(description="City name or coordinates")
units: Literal["celsius", "fahrenheit"] = Field(
default="celsius",
description="Temperature unit preference"
)
include_forecast: bool = Field(
default=False,
description="Include 5-day forecast"
)

# 定义一个查询天气的tool
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""Get current weather and optional forecast."""
temp = 22 if units == "celsius" else 72
result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
if include_forecast:
result += "\nNext 5 days: Sunny"
return result

response = get_weather.invoke({"location": "杭州", "include_forecast": True})
print(response)

​ Current weather in 杭州: 22 degrees C
​ Next 5 days: Sunny

9️⃣ 记忆与持久化(Checkpointer)

为什么 Agent 需要记忆?

默认情况下,每次调用 Agent 都是独立的——模型不会记得之前对话中你说了什么。就像每次都是第一次见面。

1
2
3
4
5
6
7
❌ 无记忆:
第一次:"我叫小明" → AI:"你好小明"
第二次:"我叫什么?" → AI:"我不知道,你没告诉过我" 😅

✅ 有记忆:
第一次:"我叫小明" → AI:"你好小明"
第二次:"我叫什么?" → AI:"你叫小明呀!"

Checkpointer 是什么?

Checkpointer(检查点) 是 LangGraph 提供的会话状态持久化机制,它会在每次 Agent 调用完成后自动保存当前的消息状态。下次调用时通过 thread_id 恢复上下文。

核心概念

概念 说明
thread_id 会话标识,相当于”聊天室房间号”
checkpoint_ns 命名空间(可选),用于细粒度隔离
checkpointer 存储后端:内存 / SQLite / 数据库

InMemorySaver(内存存储)

将对话历史保存在内存中,程序重启后丢失。适合开发和测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(model="...", checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "thread_1"}}
agent.invoke(input, config)
# langchain提供的checkpointer的默认实现,基于内存存储
from langgraph.checkpoint.memory import InMemorySaver
from langchain.agents import create_agent

# 创建智能体时指定checkpointer,LangChain会自动帮我们管理历史会话记忆
agent = create_agent(
"deepseek-v4-flash",
checkpointer=InMemorySaver()
)

from langchain.messages import HumanMessage

# 设定thread_id,作为会话标识
config = {"configurable": {"thread_id": "thread_1"}}

# 第一次调用,告知AI我的信息
response = agent.invoke(
{"messages": [HumanMessage(content="你好,我是Seven,我的个人博客是blog.heiseven.top")]},
config # 调用时添加thread_id,区分不同会话
)

for message in response['messages']:
message.pretty_print()

# 第二次调用,询问我的信息,这次带上thread_id,唤起记忆
response = agent.invoke(
{"messages": [HumanMessage(content="我叫什么?我有什么作品?")]},
config # 调用时添加thread_id
)

for message in response['messages']:
message.pretty_print()

# 以此类推.....

SqliteSaver(持久化存储)

为什么需要持久化?

InMemorySaver 在程序重启后会丢失所有会话记录。SqliteSaver 将对话历史写入 SQLite 数据库文件,实现跨会话持久化

两种 Checkpointer 对比

特性 InMemorySaver SqliteSaver
存储位置 内存 SQLite 数据库文件
程序重启 ❌ 数据丢失 ✅ 数据持久保存
使用方式 InMemorySaver() SqliteSaver(db_conn)
适用场景 开发调试、临时对话 生产环境、需要长期记忆

实现原理

1
Agent 调用 → 自动保存消息到 SQLite(./db/checkpoint.db) → 下次通过 thread_id 恢复

⚠️ thread_id 的作用

  • 每次调用 Agent 时,传入的 thread_id 决定了加载哪个会话的历史
  • 相同 thread_id = 同一个会话(有记忆)
  • 不同 thread_id = 不同会话(互相隔离)
  • 不传 thread_id = 报错:”Checkpointer requires one or more of the following configurable keys: thread_id”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import os
import sqlite3
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain.agents import create_agent

load_dotenv()

# 1. 初始化 SQLite 数据库连接并创建 checkpointer
# 使用连接池或直接连接文件,SqliteSaver 会在后台自动打理好一切,甚至不需要手动调用 .setup()
db_connection = sqlite3.connect("./db/checkpoint.db", check_same_thread=False)
checkpointer = SqliteSaver(db_connection)

# 2. 初始化大模型(把“发动机”实例化出来)
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=model,
checkpointer=checkpointer,
)

# 4. 【核心修复】必须指定 thread_id,这就好比给当前聊天的用户开辟一个专属的聊天室编号
config = {"configurable": {"thread_id": "user_session_1"}}

# 5. 调用 agent 并开启消息流模式
response = agent.stream(
{"messages": [HumanMessage(content="你好,还记得我吗?我是一只在学 Python 的小狐狸。")]},
config=config, # ⚠️ 必须传入配置项
stream_mode="messages"
)

# 6. 精准流式打印大模型的正文
for chunk, metadata in response:
if chunk.content:
print(chunk.content, end="",flush=True)

​ 🦊 哈哈,当然记得呀!小狐狸同学——Python 森林里最聪明的那只毛茸茸!你之前可是问过循环和列表切片呢~ 最近有没有偷偷修炼成「代码小仙狐」呀?🐍✨
​ 需要我帮你解开哪个谜题?是嵌套循环遇到的绕路,还是字典藏宝图被锁住了?随时开口~


📝 总结与回顾

通过本 Notebook,你已经掌握了以下核心概念和技能:

🧠 LLM 基础

  • 什么是大语言模型及其工作原理
  • 非流式(invoke)与流式(stream)输出
  • 思考过程(Reasoning Content)的开启与关闭

🦾 Agent 智能代理

  • Agent = LLM + 工具 + 自动循环
  • create_agent 的高级封装 vs init_chat_model 的底层调用
  • 多模态 Agent 处理图片+文本输入

📐 提示工程(Prompt Engineering)

  • System Prompt 设定角色和规则
  • Few-shot 示例引导输出格式
  • JSON 输出与结构化输出(Pydantic Schema)的对比

🛠️ 工具调用(Tool Calling)

  • @tool 装饰器定义工具
  • args_schema 自定义输入参数 Schema
  • 模型自动推理并调用工具 → 获取结果 → 生成回答

💾 记忆与持久化

  • Checkpointer 实现会话记忆
  • InMemorySaver(内存) vs SqliteSaver(持久化)
  • thread_id 作为会话标识区分不同对话

🔗 进一步学习

🎉 恭喜你完成了 LLM + Agent 的入门学习! 现在你可以用这些知识构建自己的 AI 应用了!