AI Agent 实现全流程:从用户输入到流式响应的 12 个关键步骤

34 阅读15分钟

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 需要:

  1. 理解任务:解析用户想要什么
  2. 判断是否需要工具:是否需要调用外部能力
  3. 选择工具:从可用工具列表中选择最合适的
  4. 生成参数:构造工具调用的参数
// 工具调用决策示例
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 SDKLangChainAutoGen
语言TypeScriptPythonPython
流式响应原生支持支持
多 Agent支持支持优秀
工具生态丰富非常丰富丰富
学习曲线中高
生产级优秀优秀优秀

五、Tool Calling(工具调用)深度解析

5.1 Tool Calling 的工作原理

Tool Calling 是 Agent 系统的核心技术,其本质是:

  1. 工具定义:告诉 LLM 有哪些可用工具及其用途
  2. 参数生成:LLM 根据用户意图生成调用参数
  3. 执行反馈:执行工具并将结果返回给 LLM
  4. 结果整合: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 ↔ ModelMessageconvertToModelMessages()
多模态支持文本 + 图片 + 音频content 数组,多种 type
工具调用流程LLM → 工具 → 结果 → LLMmaxSteps 控制循环次数
状态管理客户端消息状态useChat Hook + React state
增量渲染逐字显示响应parts 数组累积更新
错误处理流中断、工具失败try/catch + 重试机制

九、最佳实践与优化

9.1 工具设计原则

  1. 单一职责:每个工具只做一件事
  2. 清晰描述:工具描述要准确、简洁
  3. 参数精简:只暴露必要参数,使用默认值
  4. 错误处理:工具要返回有意义的错误信息

9.2 提示词优化

// ❌ 糟糕的提示词
const prompt = "帮我查一下";

// ✅ 好的提示词
const prompt = `你是一个专业的旅行助手。请根据用户的以下信息提供帮助:
- 目的地偏好
- 出行日期
- 预算范围
- 特殊需求(如无障碍设施、宠物同行等)

请主动询问缺失信息,并在获取所有必要信息后再调用工具。`;

9.3 性能优化

  1. 流式响应:使用流式输出提升用户体验
  2. 工具并行:独立工具同时执行
  3. 缓存记忆:避免重复检索
  4. 限流保护:防止恶意请求

9.4 安全考虑

  1. 工具权限:最小化工具权限
  2. 输入验证:严格验证用户输入
  3. 执行超时:设置工具执行超时
  4. 审计日志:记录所有工具调用

十、总结与展望

10.1 核心要点回顾

  1. AI Agent = LLM + Planning + Tools + Memory
  2. 核心流程:接收输入 → 推理决策 → 工具执行 → 观察结果 → 迭代优化
  3. 工具调用:通过 JSON Schema 定义,LLM 自主决策调用
  4. 记忆系统:短期记忆 + 长期记忆 + 向量检索
  5. 流式响应:SSE 协议实现实时数据传输
  6. 完整数据流:用户输入 → useChat → API Route → AI SDK → LLM → 工具执行 → SSE 流 → 客户端渲染

10.2 未来趋势

  • 多 Agent 协作:多个专业 Agent 协同工作
  • 持久化状态:Agent 可以在会话间保持状态
  • 自主学习:从交互中持续学习和优化
  • 多模态融合:处理文本、图像、音频、视频等多种输入
  • 边缘计算:在浏览器端运行轻量级 Agent

10.3 学习资源


本文基于 2025-2026 年主流 AI Agent 技术编写,如有更新请以官方文档为准。 欢迎在评论区讨论交流!