AI Agent 实现全流程:从用户输入到流式响应的 12 个关键步骤
本文将系统性地梳理 AI Agent 的核心概念、技术架构、实现流程以及最佳实践,帮助开发者全面理解如何构建智能代理系统。
一、什么是 AI Agent?
1.1 从 LLM 到 Agent 的演进
大型语言模型(LLM)本身只是被动响应的工具——用户输入提示,模型输出回答。而 AI Agent(人工智能代理)则赋予了模型主动思考、规划和使用工具的能力,使其能够:
- 自主规划:将复杂任务分解为可执行的步骤
- 工具使用:调用外部 API、数据库、文件系统等完成实际工作
- 持续迭代:根据执行结果动态调整下一步行动
- 长期记忆:在多轮对话中保持上下文连贯性
简单来说:LLM 是大脑,Agent 是大脑 + 手脚 + 感官。
1.2 Agent 的核心定义
AI Agent = LLM(推理引擎) + Planning(规划能力) + Tools(工具集) + Memory(记忆机制)
这四个要素构成了现代 Agent 系统的基础架构。
二、AI Agent 的技术架构
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ User Interface │
│ (Chat / API / Webhook) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Agent Controller │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Planner │ │ Executor │ │ Memory Manager │ │
│ │ (思考/规划) │ │ (工具调用) │ │ (短期/长期记忆) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ LLM (Language Model) │
│ (推理、决策、内容生成) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Tool Ecosystem │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │Web Search│ │ API │ │ Database │ │ File System │ │
│ │ 搜索 │ │ 调用 │ │ 查询 │ │ 读写 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 核心组件详解
2.2.1 LLM 推理引擎
LLM 是 Agent 的"大脑",负责:
- 理解用户意图:解析自然语言输入
- 推理决策:判断需要采取什么行动
- 生成内容:输出文本响应或工具调用参数
主流模型选择:
- OpenAI: GPT-5 系列(推荐通过 AI Gateway 使用)
- Anthropic: Claude 4 系列(Sonnet 4.6 性价比最高)
- Google: Gemini 2.0/3.0 系列
- 国内: 通义千问、文心一言、DeepSeek 等
2.2.2 Planning(规划模块)
规划模块决定 Agent 如何完成复杂任务,主要模式包括:
1. ReAct(Reasoning + Acting)
思考 → 行动 → 观察结果 → 继续思考 → ...
2. Chain of Thought(思维链)
问题 → 逐步推理 → 最终答案
3. Tree of Thought(思维树)
问题
/ | \
分支1 分支2 分支3
↓ ↓ ↓
评估 评估 评估
\ | /
最优路径
2.2.3 Tools(工具系统)
工具是 Agent 与外部世界交互的桥梁:
| 工具类型 | 用途 | 示例 |
|---|---|---|
| 搜索工具 | 获取实时信息 | Web Search、Wikipedia |
| API 工具 | 调用外部服务 | REST API、GraphQL |
| 数据库工具 | 数据持久化 | SQL 查询、向量检索 |
| 文件工具 | 读写文件 | 读取文档、生成报告 |
| 计算工具 | 精确计算 | 数学运算、代码执行 |
2.2.4 Memory(记忆机制)
短期记忆(Working Memory):
- 当前对话的上下文
- 最近 N 轮对话历史
- 工具执行结果
长期记忆(Long-term Memory):
- 用户偏好设置
- 历史交互模式
- 知识库检索
三、AI Agent 的实现流程
3.1 单轮交互流程
用户输入 → 意图识别 → LLM 推理 → 响应生成 → 返回结果
3.2 多轮 Agent 循环(核心流程)
这是 Agent 系统的精髓所在:
┌──────────────────────────────────────────────────────────────┐
│ START │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 1. Receive User Input (接收用户输入) │
│ - 原始文本 / API 请求 │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 2. Retrieve Context (获取上下文) │
│ - 从 Memory 加载历史对话 │
│ - 检索相关知识库 │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 3. LLM Reasoning (LLM 推理) │
│ - 分析用户意图 │
│ - 决定是否需要调用工具 │
│ - 生成工具调用参数 │
└──────────────────────────────────────────────────────────────┘
│
▼
┌───────────────┴───────────────┐
│ Decision Point │
│ (决策分支点) │
└───────────────┬───────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌──────────┐
│需要工具 │ │ 直接响应 │ │ 结束 │
│ 调用 │ │ 用户 │ │ 循环 │
└────┬────┘ └─────┬─────┘ └──────────┘
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────────────────┐
│ 4. Execute Tool │ │ 7. Generate Final Response │
│ (执行工具) │ │ (生成最终响应) │
│ - 调用外部 API │ │ - 整合所有信息 │
│ - 获取结果 │ │ - 生成自然语言回答 │
│ - 观察执行结果 │ │ - 返回给用户 │
└────────────────────┘ └────────────────────────────────┘
│ ▲
│ │
└──────────┬──────────┘
│
▼
┌─────────────────┐
│ 5. Observe Result│
│ (观察结果) │
└─────────────────┘
│
▼
┌─────────────────┐
│ 6. Store Memory │
│ (存储记忆) │
└─────────────────┘
│
└──────────┐
│
▼
┌─────────────────┐
│ 3. LLM Reasoning │
│ (回到步骤3) │
└─────────────────┘
3.3 关键步骤详解
步骤 1:接收用户输入
// 典型的用户输入处理
interface UserInput {
message: string; // 用户消息
sessionId: string; // 会话 ID
userId?: string; // 用户 ID
metadata?: Record<string, any>; // 附加元数据
}
步骤 2:获取上下文
// 上下文检索伪代码
async function retrieveContext(input: UserInput) {
// 1. 获取短期记忆(最近对话)
const recentMessages = await memory.getRecent(input.sessionId, 10);
// 2. 检索长期记忆(知识库)
const relevantKnowledge = await knowledgeBase.retrieve(
input.message,
{ topK: 5 }
);
// 3. 合并上下文
return {
messages: recentMessages,
knowledge: relevantKnowledge,
};
}
步骤 3:LLM 推理与决策
这是 Agent 的核心,LLM 需要:
- 理解任务:解析用户想要什么
- 判断是否需要工具:是否需要调用外部能力
- 选择工具:从可用工具列表中选择最合适的
- 生成参数:构造工具调用的参数
// 工具调用决策示例
const tools = [
{
name: 'web_search',
description: '搜索互联网获取最新信息',
inputSchema: z.object({
query: z.string().describe('搜索关键词'),
}),
},
{
name: 'calculate',
description: '执行数学计算',
inputSchema: z.object({
expression: z.string().describe('数学表达式'),
}),
},
];
// LLM 决定调用哪个工具
const result = await generateText({
model: 'anthropic/claude-sonnet-4.6',
messages: [...context, { role: 'user', content: input.message }],
tools: tools, // 注入工具定义
});
步骤 4:执行工具
// 工具执行器
async function executeTool(toolCall: ToolCall) {
const { name, args } = toolCall;
switch (name) {
case 'web_search':
return await webSearch(args.query);
case 'calculate':
return await evaluateMath(args.expression);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
步骤 5:观察结果
工具执行完成后,需要将结果反馈给 LLM 进行下一轮推理:
// 将工具结果加入上下文
const toolResult = await executeTool(toolCall);
const updatedMessages = [
...messages,
{ role: 'assistant', content: toolCallText },
{ role: 'tool', toolCallId, content: JSON.stringify(toolResult) },
];
步骤 6:存储记忆
// 记忆存储
async function storeMemory(sessionId: string, data: MemoryData) {
// 短期记忆:存入 Redis 或内存
await shortTermMemory.push(sessionId, data);
// 长期记忆:向量数据库存储(用于后续检索)
if (data.important) {
await longTermMemory.embed(sessionId, data);
}
}
四、主流 Agent 框架对比
4.1 Vercel AI SDK (推荐)
特点:
- 现代化 TypeScript 设计
- 流式响应原生支持
- 与 Vercel 平台深度集成
- AI Gateway 统一路由
适用场景:Web 应用、Next.js 项目、需要快速原型开发
import { generateText, tool } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
// 定义工具
const weatherTool = tool({
description: '获取指定城市的天气信息',
parameters: z.object({
city: z.string().describe('城市名称'),
}),
execute: async ({ city }) => {
const response = await fetch(`https://api.weather.com/?city=${city}`);
return response.json();
},
});
// 使用 Agent
const result = await generateText({
model: anthropic('claude-sonnet-4-6-20250514'),
messages: [{ role: 'user', content: '北京天气怎么样?' }],
tools: [weatherTool],
});
4.2 LangChain / LangGraph
特点:
- Python 生态主导
- 丰富的预置组件
- LangGraph 支持有状态工作流
- 社区活跃、文档完善
适用场景:Python 项目、复杂工作流、需要高度定制
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
# 创建 Agent
agent = create_react_agent(
llm=ChatOpenAI(model="gpt-5"),
tools=[web_search, calculator],
)
# 执行
result = agent.invoke({
"messages": [("user", "北京天气怎么样?")]
})
4.3 AutoGen (Microsoft)
特点:
- 多 Agent 协作
- 强大的代码执行能力
- 企业级特性支持
- 微软生态集成
适用场景:企业应用、多 Agent 协作场景、需要代码执行
4.4 框架对比表
| 特性 | AI SDK | LangChain | AutoGen |
|---|---|---|---|
| 语言 | TypeScript | Python | Python |
| 流式响应 | 原生 | 支持 | 支持 |
| 多 Agent | 支持 | 支持 | 优秀 |
| 工具生态 | 丰富 | 非常丰富 | 丰富 |
| 学习曲线 | 低 | 中 | 中高 |
| 生产级 | 优秀 | 优秀 | 优秀 |
五、Tool Calling(工具调用)深度解析
5.1 Tool Calling 的工作原理
Tool Calling 是 Agent 系统的核心技术,其本质是:
- 工具定义:告诉 LLM 有哪些可用工具及其用途
- 参数生成:LLM 根据用户意图生成调用参数
- 执行反馈:执行工具并将结果返回给 LLM
- 结果整合:LLM 整合工具结果生成最终响应
5.2 工具定义规范
现代 Agent 框架使用 JSON Schema 定义工具:
const tools = [
{
name: 'search_hotels',
description: '搜索指定城市的酒店信息',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称,如北京、上海',
},
checkInDate: {
type: 'string',
description: '入住日期,格式 YYYY-MM-DD',
},
checkOutDate: {
type: 'string',
description: '退房日期,格式 YYYY-MM-DD',
},
guests: {
type: 'integer',
description: '入住人数',
minimum: 1,
maximum: 10,
},
},
required: ['city', 'checkInDate', 'checkOutDate'],
},
},
];
5.3 工具执行策略
同步执行:等待工具执行完成后再继续
// 顺序执行
for (const toolCall of toolCalls) {
const result = await executeTool(toolCall);
messages.push({ role: 'tool', content: result });
}
并行执行:同时执行多个独立的工具调用
// 并行执行
const results = await Promise.all(
toolCalls.map(executeTool)
);
条件执行:根据前置工具结果决定是否执行
// 条件链
const searchResult = await executeTool(searchTool);
if (searchResult.needsMoreDetails) {
const detailResult = await executeTool(detailTool);
// 合并结果
}
六、Memory(记忆系统)设计
6.1 记忆层次结构
┌─────────────────────────────────────────────┐
│ Long-term Memory │
│ (长期记忆 - 向量数据库) │
│ - 用户偏好 - 历史交互 - 知识库 │
└─────────────────────────────────────────────┘
▲
│ 检索
▼
┌─────────────────────────────────────────────┐
│ Working Memory │
│ (工作记忆 - 当前会话) │
│ - 当前对话 - 工具结果 - 临时变量 │
└─────────────────────────────────────────────┘
▲
│ 更新
▼
┌─────────────────────────────────────────────┐
│ Short-term Memory │
│ (短期记忆 - Redis/内存) │
│ - 最近 N 轮对话 - 会话状态 │
└─────────────────────────────────────────────┘
6.2 记忆检索策略
1. 最近邻检索(Similarity Search)
// 向量相似度检索
const results = await vectorStore.similaritySearch(
queryEmbedding, // 用户问题的向量
{ topK: 5 } // 返回最相似的 5 条
);
2. 时间衰减检索
// 越近的记忆权重越高
const weightedScore = similarityScore * recencyWeight;
3. 重要性加权
// 标记重要信息提高权重
if (message.isImportant) {
score *= 1.5;
}
七、实战:构建一个 AI Agent
7.1 项目初始化
# 使用 Vercel AI SDK
npm install ai @ai-sdk/react @ai-sdk/anthropic
7.2 完整代码示例
import { generateText, tool } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
// ========== 1. 定义工具 ==========
const searchTool = tool({
description: '搜索互联网获取最新信息',
parameters: z.object({
query: z.string().describe('搜索关键词'),
}),
execute: async ({ query }) => {
const response = await fetch(
`https://api.search.com?q=${encodeURIComponent(query)}`
);
return response.json();
},
});
const calculatorTool = tool({
description: '执行数学计算',
parameters: z.object({
expression: z.string().describe('数学表达式'),
}),
execute: async ({ expression }) => {
// 安全计算(实际使用需更严格的防护)
const result = Function(`"use strict"; return (${expression})`)();
return { result };
},
});
// ========== 2. 创建 Agent ==========
class SimpleAgent {
private model: any;
private tools: any[];
private memory: Message[] = [];
constructor() {
this.model = anthropic('claude-sonnet-4-6-20250514');
this.tools = [searchTool, calculatorTool];
}
// ========== 3. 处理用户输入 ==========
async chat(userMessage: string) {
// 添加用户消息到记忆
this.memory.push({ role: 'user', content: userMessage });
// 调用 LLM
const result = await generateText({
model: this.model,
messages: this.memory,
tools: this.tools,
maxSteps: 10, // 最多 10 步推理
});
// 处理工具调用
for (const toolCall of result.toolCalls) {
const toolResult = await toolCall.execute();
// 添加工具调用和结果到记忆
this.memory.push({
role: 'assistant',
content: `[Tool Call: ${toolCall.toolName}]`,
});
this.memory.push({
role: 'tool',
toolCallId: toolCall.toolCallId,
content: JSON.stringify(toolResult),
});
}
// 如果有新的 LLM 响应,继续处理
if (result.toolCalls && result.toolCalls.length > 0) {
const continuedResult = await generateText({
model: this.model,
messages: this.memory,
});
this.memory.push({
role: 'assistant',
content: continuedResult.text,
});
return continuedResult.text;
}
// 直接返回文本响应
const response = result.text;
this.memory.push({ role: 'assistant', content: response });
return response;
}
// ========== 4. 记忆管理 ==========
clearMemory() {
this.memory = [];
}
getHistory() {
return this.memory;
}
}
// ========== 5. 使用 Agent ==========
const agent = new SimpleAgent();
const response = await agent.chat('北京现在的天气怎么样?');
console.log(response);
八、完整数据流:从用户输入到流式响应
8.1 整体数据流架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ UI 组件 │ │ useChat Hook │ │ Message 渲染器 │ │
│ │ (输入框/上传) │ │ (状态管理) │ │ (文本/图片/工具/思考) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────────────────────┘ │
└─────────┼──────────────────┼───────────────────────────────────────────┘
│ │
│ 1. 用户输入 │ 2. 流式数据到达
│ (文本/图片) │ (SSE 数据流)
▼ ▼
┌─────────┴──────────────────┴───────────────────────────────────────────┐
│ HTTP 请求层 │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Content-Type: application/json │ │
│ │ Body: { messages: [ {...}, {...} ] } │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Next.js API Route Handler │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ app/api/chat/route.ts │ │
│ │ │ │
│ │ 1. 接收请求 → 解析消息 │ │
│ │ 2. 处理图片 → 转换为 base64 │ │
│ │ 3. 调用 streamText → 启动流式响应 │ │
│ │ 4. 返回 toUIMessageStreamResponse() │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ AI SDK streamText 核心 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ convertToModelMessages() → 转换消息格式 │ │
│ │ streamText() → 启动流式生成 │ │
│ │ ├── 处理文本流 (text delta) │ │
│ │ ├── 处理工具调用 (tool call) │ │
│ │ ├── 处理工具结果 (tool result) │ │
│ │ └── 处理推理过程 (reasoning) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ LLM Provider (Claude/GPT) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ AI Gateway → Provider API │ │
│ │ │ │
│ │ 请求格式: │ │
│ │ { │ │
│ │ model: "anthropic/claude-sonnet-4.6", │ │
│ │ messages: [...], │ │
│ │ tools: [...], │ │
│ │ stream: true │ │
│ │ } │ │
│ │ │ │
│ │ 响应格式 (SSE 流): │ │
│ │ data: {"type":"text_delta","delta":{"text":"你好"}} │ │
│ │ data: {"type":"content_block_delta","delta":{"text":",我是"}} │ │
│ │ data: {"type":"content_block_stop"} │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 工具执行层 (可选) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 当 LLM 决定调用工具时: │ │
│ │ │ │
│ │ 1. 解析工具调用参数 │ │
│ │ 2. 执行工具 (fetch / API 调用) │ │
│ │ 3. 获取工具结果 │ │
│ │ 4. 将结果注入 LLM 上下文 │ │
│ │ 5. 继续流式生成 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SSE 流式响应层 (Server-Sent Events) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Content-Type: text/event-stream │ │
│ │ Cache-Control: no-cache │ │
│ │ Connection: keep-alive │ │
│ │ │ │
│ │ 流式数据块: │ │
│ │ data: 0:"你好" │ │
│ │ data: 0:",我是" │ │
│ │ data: 0:"AI助手" │ │
│ │ data: 1:{"type":"tool-call",...} │ │
│ │ data: d:{"finishReason":"stop"} │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 客户端接收与渲染 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 1. DefaultChatTransport 解析 SSE 流 │ │
│ │ 2. useChat 更新 messages 状态 │ │
│ │ 3. Message 组件根据 parts 类型渲染: │ │
│ │ - text: 渲染文本 │ │
│ │ - image: 渲染图片 │ │
│ │ - tool-call: 渲染工具调用卡片 │ │
│ │ - tool-result: 渲染工具结果 │ │
│ │ - reasoning: 渲染思考过程 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
8.2 完整流程详解
步骤 1:用户在浏览器输入内容
场景 A:纯文本输入
// 用户在输入框输入
用户输入: "北京今天天气怎么样?"
// UI 组件捕获输入
const [input, setInput] = useState('');
function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
setInput(e.target.value);
}
// 用户点击发送按钮
async function handleSend() {
if (!input.trim()) return;
// useChat hook 处理发送
append({
role: 'user',
content: input,
});
setInput('');
}
场景 B:图片 + 文本输入
// 用户上传图片并输入文字
用户输入: "这张图片里是什么?"
用户上传: image.png (2.3MB)
// 图片处理流程
async function handleImageUpload(file: File) {
// 1. 读取文件
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const base64 = reader.result as string;
// 2. 构建多模态消息
append({
role: 'user',
content: [
{ type: 'text', text: "这张图片里是什么?" },
{ type: 'image', image: base64 },
],
});
};
}
步骤 2:useChat Hook 构建请求
// useChat 内部处理
import { useChat } from '@ai-sdk/react';
function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, status } = useChat({
api: '/api/chat', // API 端点
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
// messages 状态示例
// [
// { id: '1', role: 'user', content: '北京今天天气怎么样?' },
// { id: '2', role: 'assistant', content: '我来帮您查询...', parts: [...] },
// ]
return (
<div>
{/* 消息列表 */}
{messages.map(message => (
<Message key={message.id} message={message} />
))}
{/* 输入框 */}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button disabled={status === 'streaming'}>发送</button>
</form>
</div>
);
}
步骤 3:HTTP 请求发送到服务器
// 实际发送的 HTTP 请求
POST /api/chat HTTP/1.1
Host: localhost:3000
Content-Type: application/json
{
"messages": [
{
"id": "msg_1",
"role": "user",
"content": "北京今天天气怎么样?"
}
]
}
带图片的请求:
{
"messages": [
{
"id": "msg_1",
"role": "user",
"content": [
{
"type": "text",
"text": "这张图片里是什么?"
},
{
"type": "image",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}
]
}
]
}
步骤 4:API Route Handler 处理请求
// app/api/chat/route.ts
import { streamText, convertToModelMessages } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { toUIMessageStreamResponse } from '@ai-sdk/react';
export async function POST(req: Request) {
// ========== 1. 解析请求 ==========
const { messages } = await req.json();
// ========== 2. 转换消息格式 ==========
// 将 UIMessage 格式转换为 ModelMessage 格式
const modelMessages = await convertToModelMessages(messages);
// ========== 3. 定义工具 ==========
const tools = {
getWeather: {
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名称' },
},
required: ['city'],
},
execute: async ({ city }: { city: string }) => {
const response = await fetch(
`https://api.weather.com/v1/current?city=${encodeURIComponent(city)}`
);
return response.json();
},
},
};
// ========== 4. 启动流式生成 ==========
const result = await streamText({
model: anthropic('claude-sonnet-4-6-20250514'),
messages: modelMessages,
tools: [tools.getWeather],
maxSteps: 5, // 最多 5 步推理(包括工具调用)
});
// ========== 5. 返回流式响应 ==========
return toUIMessageStreamResponse(result.toDataStream());
}
步骤 5:AI SDK streamText 内部处理
// streamText 内部简化流程
async function streamText(params) {
const { model, messages, tools, maxSteps } = params;
let currentMessages = [...messages];
let stepCount = 0;
// ========== 循环处理多步推理 ==========
while (stepCount < maxSteps) {
// 1. 调用 LLM API
const llmResponse = await callLLM({
model,
messages: currentMessages,
tools,
stream: true, // 启用流式
});
// 2. 返回流式响应生成器
return createStreamGenerator({
async *[Symbol.asyncIterator]() {
// 3. 逐个处理 LLM 返回的流式数据块
for await (const chunk of llmResponse) {
// 处理文本增量
if (chunk.type === 'text_delta') {
yield { type: 'text-delta', content: chunk.delta.text };
}
// 处理工具调用
if (chunk.type === 'tool_call') {
yield { type: 'tool-call', toolCall: chunk.toolCall };
// 执行工具
const toolResult = await tools[chunk.toolCall.name].execute(
chunk.toolCall.args
);
// 更新消息历史
currentMessages.push({
role: 'assistant',
content: chunk.toolCallText,
});
currentMessages.push({
role: 'tool',
toolCallId: chunk.toolCall.id,
content: JSON.stringify(toolResult),
});
// 继续下一轮 LLM 调用
stepCount++;
continue;
}
// 处理思考过程
if (chunk.type === 'reasoning') {
yield { type: 'reasoning', content: chunk.reasoning };
}
}
// 4. 流结束
yield { type: 'finish', finishReason: 'stop' };
},
});
}
}
步骤 6:LLM Provider 返回 SSE 流
Claude API 返回的 SSE 流示例:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
event: message_start
data: {"type":"message_start","message":{"id":"msg_123","type":"message","role":"assistant","content":[],"model":"claude-sonnet-4-6-20250514","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":15,"output_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"我"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"来"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"帮"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"您"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"查询"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"北京"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"的"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"天气"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"信息"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"。"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_123","name":"getWeather","input":{"city":"北京"}}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":15}}
event: message_stop
data: {"type":"message_stop"}
步骤 7:工具执行(如果需要)
// 当 LLM 返回工具调用时
const toolCall = {
id: 'toolu_123',
name: 'getWeather',
input: { city: '北京' },
};
// 执行工具
async function executeTool(toolCall) {
const { name, input } = toolCall;
switch (name) {
case 'getWeather':
// 调用天气 API
const response = await fetch(
`https://api.weather.com/v1/current?city=${encodeURIComponent(input.city)}`
);
const weatherData = await response.json();
return {
city: '北京',
temperature: 22,
condition: '晴',
humidity: 45,
wind: '东南风 3级',
timestamp: new Date().toISOString(),
};
}
}
// 获取工具结果
const toolResult = await executeTool(toolCall);
// {
// city: '北京',
// temperature: 22,
// condition: '晴',
// humidity: 45,
// wind: '东南风 3级',
// timestamp: '2025-03-25T10:30:00.000Z'
// }
步骤 8:将工具结果注入 LLM 继续生成
// 更新消息历史
const updatedMessages = [
...currentMessages,
{
role: 'assistant',
content: `<tool_calls>[{"id":"toolu_123","name":"getWeather","args":{"city":"北京"}}]</tool_calls>`,
},
{
role: 'tool',
tool_call_id: 'toolu_123',
content: JSON.stringify(toolResult),
},
];
// 继续调用 LLM
const continuedResponse = await streamText({
model: anthropic('claude-sonnet-4-6-20250514'),
messages: updatedMessages,
});
// LLM 继续流式输出最终回答
// "根据查询结果,北京今天天气晴朗,气温 22°C,湿度 45%,东南风 3 级。"
步骤 9:toUIMessageStreamResponse 转换为客户端可读格式
// AI SDK 将 LLM 流转换为 UIMessage 流
async function* toUIMessageStreamResponse(stream) {
for await (const chunk of stream) {
// 文本增量
if (chunk.type === 'text-delta') {
yield `0:${JSON.stringify(chunk.content)}\n`;
}
// 工具调用
if (chunk.type === 'tool-call') {
yield `1:${JSON.stringify({
type: 'tool-call',
toolCallId: chunk.toolCall.id,
toolName: chunk.toolCall.name,
args: chunk.toolCall.args,
})}\n`;
}
// 工具结果
if (chunk.type === 'tool-result') {
yield `1:${JSON.stringify({
type: 'tool-result',
toolCallId: chunk.toolCallId,
result: chunk.result,
})}\n`;
}
// 思考过程
if (chunk.type === 'reasoning') {
yield `1:${JSON.stringify({
type: 'reasoning',
reasoning: chunk.content,
})}\n`;
}
// 流结束
if (chunk.type === 'finish') {
yield `d:${JSON.stringify({
finishReason: chunk.finishReason,
usage: chunk.usage,
})}\n`;
}
}
}
实际返回给客户端的 SSE 流:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: 0:"我"
data: 0:"来"
data: 0:"帮"
data: 0:"您"
data: 0:"查询"
data: 0:"北京"
data: 0:"的"
data: 0:"天气"
data: 0:"信息"
data: 0:"。"
data: 1:{"type":"tool-call","toolCallId":"toolu_123","toolName":"getWeather","args":{"city":"北京"}}
data: 1:{"type":"tool-result","toolCallId":"toolu_123","result":{"city":"北京","temperature":22,"condition":"晴","humidity":45,"wind":"东南风 3级"}}
data: 0:"根据"
data: 0:"查询"
data: 0:"结果"
data: 0:","
data: 0:"北京"
data: 0:"今天"
data: 0:"天气"
data: 0:"晴"
data: 0:"朗"
data: 0:","
data: 0:"气温"
data: 0:"22"
data: 0:"°"
data: 0:"C"
data: 0:"
data: d:{"finishReason":"stop","usage":{"promptTokens":25,"completionTokens":35}}
步骤 10:客户端接收并渲染流式响应
// DefaultChatTransport 内部处理
class DefaultChatTransport {
async* streamMessages(messages: CoreMessage[]) {
// 发送 POST 请求
const response = await fetch(this.api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages }),
});
// 解析 SSE 流
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 处理 SSE 数据行
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6);
const [type, content] = data.split(':');
if (type === '0') {
// 文本增量
yield { type: 'text-delta', content: JSON.parse(content) };
} else if (type === '1') {
// 结构化数据(工具调用、思考等)
yield JSON.parse(content);
} else if (type === 'd') {
// 流结束
yield JSON.parse(content);
}
}
}
}
}
步骤 11:useChat 更新状态并触发渲染
// useChat 内部状态更新
function useChat({ api, transport }) {
const [messages, setMessages] = useState<UIMessage[]>([]);
const [status, setStatus] = useState<'idle' | 'streaming'>('idle');
const append = async (message: UIMessage) => {
// 1. 添加用户消息
setMessages(prev => [...prev, message]);
// 2. 创建空的助手消息
const assistantMessage: UIMessage = {
id: generateId(),
role: 'assistant',
content: '',
parts: [],
};
setMessages(prev => [...prev, assistantMessage]);
setStatus('streaming');
// 3. 接收流式数据
const stream = transport.streamMessages(messages);
for await (const chunk of stream) {
// 更新助手消息
setMessages(prev => prev.map(msg => {
if (msg.id !== assistantMessage.id) return msg;
if (chunk.type === 'text-delta') {
return {
...msg,
content: msg.content + chunk.content,
parts: [...msg.parts, { type: 'text', text: chunk.content }],
};
}
if (chunk.type === 'tool-call') {
return {
...msg,
parts: [...msg.parts, {
type: 'tool-call',
toolCallId: chunk.toolCallId,
toolName: chunk.toolName,
args: chunk.args,
}],
};
}
if (chunk.type === 'tool-result') {
return {
...msg,
parts: [...msg.parts, {
type: 'tool-result',
toolCallId: chunk.toolCallId,
result: chunk.result,
}],
};
}
if (chunk.type === 'reasoning') {
return {
...msg,
parts: [...msg.parts, {
type: 'reasoning',
reasoning: chunk.reasoning,
}],
};
}
return msg;
}));
}
setStatus('idle');
};
return { messages, append, status, ... };
}
步骤 12:Message 组件根据 parts 渲染
// Message 组件渲染逻辑
function Message({ message }: { message: UIMessage }) {
return (
<div className="message">
<div className="message-header">
<span className="role">{message.role}</span>
</div>
<div className="message-content">
{message.parts.map((part, index) => {
switch (part.type) {
case 'text':
// 渲染文本
return <TextPart key={index} text={part.text} />;
case 'image':
// 渲染图片
return <ImagePart key={index} image={part.image} />;
case 'tool-call':
// 渲染工具调用卡片
return (
<ToolCallCard
key={index}
toolName={part.toolName}
args={part.args}
/>
);
case 'tool-result':
// 渲染工具结果
return (
<ToolResult
key={index}
result={part.result}
/>
);
case 'reasoning':
// 渲染思考过程(可折叠)
return (
<ReasoningBlock
key={index}
reasoning={part.reasoning}
/>
);
default:
return null;
}
})}
</div>
</div>
);
}
8.3 完整时间线示例
时间轴从用户输入到最终显示:
0ms ┌─────────────────────────────────────────────┐
│ 用户在输入框输入: "北京今天天气怎么样?" │
└─────────────────────────────────────────────┘
│
▼
50ms ┌─────────────────────────────────────────────┐
│ useChat 拦截输入,构建 messages 数组 │
│ [{ role: 'user', content: '...' }] │
└─────────────────────────────────────────────┘
│
▼
100ms ┌─────────────────────────────────────────────┐
│ 发送 POST /api/chat 请求 │
└─────────────────────────────────────────────┘
│
▼
150ms ┌─────────────────────────────────────────────┐
│ API Route 接收请求,调用 streamText │
└─────────────────────────────────────────────┘
│
▼
200ms ┌─────────────────────────────────────────────┐
│ AI SDK 调用 Claude API (stream: true) │
└─────────────────────────────────────────────┘
│
▼
250ms ┌─────────────────────────────────────────────┐
│ Claude 返回第一个 SSE 数据块: "我" │
└─────────────────────────────────────────────┘
│
▼
300ms ┌─────────────────────────────────────────────┐
│ 客户端收到 "我",立即渲染到屏幕 │
│ 用户看到: "我" │
└─────────────────────────────────────────────┘
│
▼
350ms ┌─────────────────────────────────────────────┐
│ Claude 返回: "来" │
│ 用户看到: "我来" │
└─────────────────────────────────────────────┘
│
▼
400ms ┌─────────────────────────────────────────────┐
│ Claude 返回: "帮您查询天气信息。" │
│ 用户看到: "我来帮您查询天气信息。" │
└─────────────────────────────────────────────┘
│
▼
450ms ┌─────────────────────────────────────────────┐
│ Claude 返回工具调用: getWeather(city="北京") │
│ 渲染工具调用卡片 │
└─────────────────────────────────────────────┘
│
▼
500ms ┌─────────────────────────────────────────────┐
│ 服务器执行 getWeather 工具 │
│ fetch('https://api.weather.com/...') │
└─────────────────────────────────────────────┘
│
▼
800ms ┌─────────────────────────────────────────────┐
│ 工具返回结果: { temp: 22, condition: "晴" } │
│ 渲染工具结果卡片 │
└─────────────────────────────────────────────┘
│
▼
850ms ┌─────────────────────────────────────────────┐
│ 将工具结果注入 LLM,继续生成 │
└─────────────────────────────────────────────┘
│
▼
900ms ┌─────────────────────────────────────────────┐
│ Claude 返回: "根据查询结果," │
│ 用户看到: "...根据查询结果," │
└─────────────────────────────────────────────┘
│
▼
950ms ┌─────────────────────────────────────────────┐
│ Claude 返回: "北京今天天气晴朗," │
└─────────────────────────────────────────────┘
│
▼
1000ms ┌─────────────────────────────────────────────┐
│ Claude 返回: "气温 22°C,湿度 45%。" │
└─────────────────────────────────────────────┘
│
▼
1050ms ┌─────────────────────────────────────────────┐
│ 流结束标记: finishReason: "stop" │
└─────────────────────────────────────────────┘
│
▼
1100ms ┌─────────────────────────────────────────────┐
│ 完整消息渲染完成 │
│ 用户看到完整的对话和工具调用过程 │
└─────────────────────────────────────────────┘
总耗时: 约 1.1 秒
用户体验: 实时看到文字逐字出现,流畅自然
8.4 多模态输入处理(图片)
// 当用户上传图片时的处理流程
// 1. 客户端:转换为 base64
async function handleImageUpload(file: File) {
const base64 = await fileToBase64(file);
append({
role: 'user',
content: [
{ type: 'text', text: '这张图片里是什么?' },
{ type: 'image', image: base64 },
],
});
}
// 2. API Route:处理多模态消息
export async function POST(req: Request) {
const { messages } = await req.json();
// 转换消息格式(AI SDK 自动处理图片)
const modelMessages = await convertToModelMessages(messages);
// 结果:
// [
// {
// role: 'user',
// content: [
// { type: 'text', text: '这张图片里是什么?' },
// { type: 'image', image: { type: 'base64', data: '...' } },
// ],
// },
// ]
// 3. 调用 LLM(支持多模态的模型)
const result = await streamText({
model: anthropic('claude-sonnet-4-6-20250514'), // 支持图片
messages: modelMessages,
});
return toUIMessageStreamResponse(result.toDataStream());
}
// 4. LLM 处理图片并流式返回描述
// 返回的流可能包含:
// data: 0:"这张"
// data: 0:"图片"
// data: 0:"显示"
// data: 0:"的是"
// data: 0:"一只"
// data: 0:"可爱"
// data: 0:"的"
// data: 0:"猫咪"
// data: 0:"。"
8.5 关键技术点总结
| 技术点 | 说明 | 实现方式 |
|---|---|---|
| SSE 流式传输 | 服务器主动推送数据 | Server-Sent Events 协议 |
| 消息格式转换 | UIMessage ↔ ModelMessage | convertToModelMessages() |
| 多模态支持 | 文本 + 图片 + 音频 | content 数组,多种 type |
| 工具调用流程 | LLM → 工具 → 结果 → LLM | maxSteps 控制循环次数 |
| 状态管理 | 客户端消息状态 | useChat Hook + React state |
| 增量渲染 | 逐字显示响应 | parts 数组累积更新 |
| 错误处理 | 流中断、工具失败 | try/catch + 重试机制 |
九、最佳实践与优化
9.1 工具设计原则
- 单一职责:每个工具只做一件事
- 清晰描述:工具描述要准确、简洁
- 参数精简:只暴露必要参数,使用默认值
- 错误处理:工具要返回有意义的错误信息
9.2 提示词优化
// ❌ 糟糕的提示词
const prompt = "帮我查一下";
// ✅ 好的提示词
const prompt = `你是一个专业的旅行助手。请根据用户的以下信息提供帮助:
- 目的地偏好
- 出行日期
- 预算范围
- 特殊需求(如无障碍设施、宠物同行等)
请主动询问缺失信息,并在获取所有必要信息后再调用工具。`;
9.3 性能优化
- 流式响应:使用流式输出提升用户体验
- 工具并行:独立工具同时执行
- 缓存记忆:避免重复检索
- 限流保护:防止恶意请求
9.4 安全考虑
- 工具权限:最小化工具权限
- 输入验证:严格验证用户输入
- 执行超时:设置工具执行超时
- 审计日志:记录所有工具调用
十、总结与展望
10.1 核心要点回顾
- AI Agent = LLM + Planning + Tools + Memory
- 核心流程:接收输入 → 推理决策 → 工具执行 → 观察结果 → 迭代优化
- 工具调用:通过 JSON Schema 定义,LLM 自主决策调用
- 记忆系统:短期记忆 + 长期记忆 + 向量检索
- 流式响应:SSE 协议实现实时数据传输
- 完整数据流:用户输入 → useChat → API Route → AI SDK → LLM → 工具执行 → SSE 流 → 客户端渲染
10.2 未来趋势
- 多 Agent 协作:多个专业 Agent 协同工作
- 持久化状态:Agent 可以在会话间保持状态
- 自主学习:从交互中持续学习和优化
- 多模态融合:处理文本、图像、音频、视频等多种输入
- 边缘计算:在浏览器端运行轻量级 Agent
10.3 学习资源
- Vercel AI SDK 官方文档
- LangChain 官方文档
- Anthropic Tool Use 指南
- OpenAI Function Calling 文档
- Server-Sent Events MDN 文档
本文基于 2025-2026 年主流 AI Agent 技术编写,如有更新请以官方文档为准。 欢迎在评论区讨论交流!