Agent基本概念

要点

  • Agent 的本质是「LLM + 工具 + 执行循环」,LLM 负责推理决策,工具提供外部能力,执行循环让 Agent 能多步完成任务
  • 普通 Chatbot 只能接收一条输入、返回一条文本回复;Agent 能调用工具、观察结果、继续推理,直到任务完成
  • Agent 具备四个核心能力:感知(接收输入)、推理(制定计划)、行动(调用工具)、记忆(保持上下文)
  • 执行循环(Agent Loop)是 Agent 区别于普通 LLM 调用的关键——一个 while 循环,每轮检查是否需要调用工具
  • Agent 的典型应用场景都有共同特征:任务需要多步完成,每一步可能需要外部数据,后续步骤取决于前面结果
  • 用 TypeScript 实现一个最简 Agent,核心是一个接收工具列表、循环调用 LLM 并执行工具函数的结构

1. 什么是Agent

也许你已经有过这样的经历:问 ChatGPT 一个问题,它给你一段文字回复。再问一个,又是一段回复。每一轮对话都是独立的「输入 → 文本输出」,模型并不主动做任何事情。

Agent 在这个基础上加了一个关键能力:能使用工具,并且能根据工具返回的结果决定下一步做什么。

具体来说,Agent 由三个部分组成:

  1. LLM — 负责理解意图、推理决策、生成回复
  2. Tools — 一组可供调用的外部工具,比如搜索、查数据库、发邮件、执行代码
  3. 执行循环(Agent Loop) — 驱动 Agent 不断「思考 → 行动 → 观察」,直到任务完成

用一个简短的流程来理解:

用户输入 -> LLM 推理 -> 需要工具?
                          ├── 是 -> 调用工具 -> 拿到结果 -> 回到 LLM 推理
                          └── 否 -> 输出最终回复

这个循环是 Agent 的核心。没有循环,LLM 只能回答一次;有了循环,LLM 可以拆解复杂任务、逐步完成。

2. Agent与普通Chatbot的区别

先从一个具体场景出发。

用户说:「帮我查一下明天从上海到东京的航班,挑一个最便宜的,然后把航班信息发邮件给我的同事。」

面对这个请求,普通 Chatbot 和 Agent 的处理方式完全不同。

普通 Chatbot 会怎么做:

  1. 接收用户消息
  2. 根据训练数据生成一段文字:「您可以打开某某航空网站查询……」
  3. 回复结束

它只能给建议,不能真正帮你查航班、发邮件。因为 Chatbot 的边界就是文本输入和文本输出。

Agent 会怎么做:

  1. 接收用户消息
  2. 分析意图:需要查航班 + 比较价格 + 发邮件
  3. 调用航班查询工具,拿到明天的航班列表
  4. 从结果中筛选最便宜的航班
  5. 调用邮件发送工具,把航班信息发给同事
  6. 回复用户:「已查到最便宜的航班是 XX 航空 XX 航班,价格 XXXX 元,已发送邮件给你的同事」

两者的差异可以这样归纳:

  • Chatbot 是单轮的文本生成,Agent 是多轮的任务执行
  • Chatbot 的输出只有文字,Agent 的输出可以是实际行动(查数据、发消息、写文件)
  • Chatbot 无法获取实时信息,Agent 通过工具可以访问外部世界
  • Chatbot 不知道自己说的话对不对,Agent 能通过工具结果验证和修正

下一篇会展开讨论两者的差异,包括在哪些场景下 Chatbot 已经够用、哪些场景必须引入 Agent。

3. 为什么需要Agent

前面已经看到了 Agent 能做什么。现在换个角度:为什么不让 LLM 直接回答所有问题,而要绕一圈引入工具?

原因可以从 LLM 本身的限制说起。

LLM 天然做不到的事

  1. 不知道当下发生的事 — 训练数据有截止日期,LLM 不知道今天的天气、股价、新闻
  2. 不能执行任何操作 — 它不能查数据库、发请求、写文件、调 API,只能生成文本
  3. 不能保证精确计算 — 数学运算、数据统计这类任务,LLM 经常会算错
  4. 每次调用互相独立 — 没有外部存储的话,LLM 不记得上一轮说了什么

Agent 怎么补上这些缺口

Agent 通过给 LLM 接入工具,把上面这些限制逐个补上:

  • HTTP 客户端工具 — 让 Agent 能查天气、查航班、查任何有 API 的服务
  • 数据库查询工具 — 让 Agent 能读写数据库,获取持久化数据
  • 代码执行工具 — 让 Agent 能运行代码、做精确计算
  • 文件系统工具 — 让 Agent 能读写文件
  • 消息通知工具 — 让 Agent 能发邮件、发 Slack 消息、推送通知
  • 认证鉴权工具 — 让 Agent 能代表用户访问需要登录的服务

可以把 Agent 理解成 LLM 和真实世界之间的桥梁:LLM 负责理解语言和做决策,工具负责和外部交互,执行循环把两者串起来。

4. 一个具体的Agent执行流程

在看代码之前,先用一个完整的例子把 Agent 的工作流程走一遍。

用户请求:「帮我查一下北京今天的天气,如果空气质量差就提醒我带口罩。」

Agent 内部发生了这些事:

第一轮:
  LLM 收到用户请求
  -> 判断需要先查天气
  -> 调用工具:get_weather(city="北京")
  -> 工具返回:{ temperature: "28°C", aqi: 180, description: "轻度污染" }

第二轮:
  LLM 看到天气结果
  -> 判断 AQI 180 属于「轻度污染」,满足用户说的「空气质量差」条件
  -> 调用工具:send_reminder(message="北京今天 AQI 180,轻度污染,建议戴口罩")
  -> 工具返回:{ success: true }

第三轮:
  LLM 看到提醒已发送
  -> 判断任务完成
  -> 生成最终回复:已查询北京天气,当前 AQI 180(轻度污染),已发送提醒建议你戴口罩。

三轮循环,每一步都依赖上一步的结果。这就是 Agent Loop 在解决的问题:让 LLM 有能力根据中间结果调整后续行动。

5. 用代码实现一个最简Agent

理解了流程之后,看看代码怎么写。一个最简 Agent 只需要几个部分。

工具定义

工具需要告诉 LLM 两件事:这个工具能做什么(描述),以及调用它需要传什么参数(参数格式)。LLM 根据这些信息决定何时调用、怎么调用。

// src/agent/tools.ts
import type { ToolDefinition } from './types'
 
export const tools: ToolDefinition[] = [
  {
    name: 'get_weather',
    description: '查询指定城市的当前天气信息,包括温度、空气质量',
    parameters: {
      type: 'object',
      properties: {
        city: {
          type: 'string',
          description: '城市名称,例如:北京、上海',
        },
      },
      required: ['city'],
    },
  },
  {
    name: 'send_reminder',
    description: '给用户发送一条提醒消息',
    parameters: {
      type: 'object',
      properties: {
        message: {
          type: 'string',
          description: '提醒内容',
        },
      },
      required: ['message'],
    },
  },
]

Agent 执行循环

这是整个 Agent 最核心的部分。一个 while 循环,每轮把对话历史发给 LLM,LLM 要么直接回复,要么返回要调用的工具列表。如果有工具调用,执行工具、把结果追加到对话历史,然后进入下一轮。

// src/agent/loop.ts
import type { Message, ToolCall, ToolResult } from './types'
import { callLLM } from './llm'
import { executeTool } from './executor'
 
const MAX_ITERATIONS = 10
 
export async function runAgent(
  userInput: string,
  systemPrompt: string,
): Promise<string> {
  // 初始化对话历史
  const messages: Message[] = [
    { role: 'system', content: systemPrompt },
    { role: 'user', content: userInput },
  ]
 
  for (let i = 0; i < MAX_ITERATIONS; i++) {
    // 把对话历史发给 LLM,拿到回复
    const response = await callLLM(messages)
 
    // 如果 LLM 没有调用任何工具,说明它已经有了最终回复
    if (response.toolCalls.length === 0) {
      return response.text
    }
 
    // 把 LLM 的工具调用请求追加到对话历史
    messages.push({
      role: 'assistant',
      content: response.text,
      toolCalls: response.toolCalls,
    })
 
    // 逐个执行工具,把结果追加到对话历史
    for (const toolCall of response.toolCalls) {
      const result = await executeTool(toolCall)
      messages.push({
        role: 'tool',
        toolCallId: toolCall.id,
        content: JSON.stringify(result),
      })
    }
  }
 
  return 'Agent 已达到最大执行轮次,任务未能完成。'
}

工具执行器

根据 LLM 返回的工具名和参数,找到对应的函数并执行。

// src/agent/executor.ts
import type { ToolCall, ToolResult } from './types'
 
export async function executeTool(toolCall: ToolCall): Promise<ToolResult> {
  const { name, arguments: args } = toolCall
 
  try {
    let result: unknown
 
    switch (name) {
      case 'get_weather':
        result = await fetchWeather(args.city)
        break
      case 'send_reminder':
        result = await sendNotification(args.message)
        break
      default:
        result = { error: `未知工具:${name}` }
    }
 
    return { toolCallId: toolCall.id, result }
  } catch (error) {
    // 工具执行失败时,把错误信息也返回给 LLM
    // LLM 可以据此决定重试还是换一种方式
    return {
      toolCallId: toolCall.id,
      result: { error: `工具执行失败:${error instanceof Error ? error.message : '未知错误'}` },
    }
  }
}

在 Hono 中接入 Agent

把 Agent 包装成 HTTP 接口,前端就可以通过 API 调用。

// src/routes/agent.ts
import { Hono } from 'hono'
import { runAgent } from '../agent/loop'
import type { AppEnv } from '../types'
 
const agentApp = new Hono<AppEnv>()
 
agentApp.post('/chat', async (c) => {
  const { message } = await c.req.json<{ message: string }>()
 
  if (!message) {
    return c.json({ error: 'message 不能为空' }, 400)
  }
 
  const systemPrompt = `你是一个智能助手,可以查询天气、发送提醒。
根据用户的需求,合理使用工具来完成任务。
如果用户的请求不需要工具,直接回复即可。`
 
  const reply = await runAgent(message, systemPrompt)
 
  return c.json({ reply })
})
 
export default agentApp

入口文件挂载路由:

// src/index.ts
import { Hono } from 'hono'
import agentApp from './routes/agent'
 
const app = new Hono()
app.route('/api/agent', agentApp)
 
export default app

前端发送 &#123; "message": "查一下北京天气" &#125;POST /api/agent/chat,Agent 就会自动走完查天气、返回结果的整个流程。

6. 典型应用场景

什么样的任务适合用 Agent?可以关注一个判断标准:任务是否需要多步完成,每一步可能需要外部数据,且后续步骤取决于前面的结果。

满足这个条件的场景包括:

  • 智能客服 — 用户问「我的订单到哪了」,Agent 查订单系统、查物流 API,根据物流状态给出不同回复
  • 数据分析助手 — 用户说「帮我看一下上个月的销售数据,哪些产品下降了」,Agent 执行 SQL 查询、分析结果、生成报告
  • 代码助手 — 用户说「这个函数有 bug,帮我修一下」,Agent 读代码、定位问题、生成修复代码、跑测试验证
  • 工作流自动化 — 用户说「每周一早上把上周的数据报告发到 Slack」,Agent 查数据库、生成报表、调 API 发送
  • 个人助理 — 用户说「帮我约明天下午的会议室,然后通知团队成员」,Agent 查日历空闲时间、预订会议室、发通知

不满足这个条件的场景——比如翻译一段文字、回答一个常识问题、总结一篇文章——用普通 Chatbot 就够了,引入 Agent 反而增加了复杂度和延迟。

7. 核心架构:感知、推理、行动、记忆

把前面的代码拆开看,Agent 由四个核心能力组成。

感知(Perception)

Agent 需要接收和理解外部输入。最基本的输入是用户的自然语言消息,但它也可以接收结构化的 API 响应、系统告警、文件变更事件等。感知层的工作是把各种输入统一转换成 LLM 能处理的消息格式——也就是前面 messages 数组里的 Message 对象。不同来源的输入,最终都会被转成同一种结构:&#123; role, content &#125;&#123; role: 'tool', toolCallId, content &#125;

这一层的代码很简单,就是几个转换函数,这里不再单独列出。

推理(Reasoning)

推理是 Agent 的大脑,由 LLM 承担。它负责分析当前状态和目标,决定下一步要做什么:选哪个工具、传什么参数、什么时候任务已经完成可以停下来。

推理的质量很大程度取决于系统 Prompt 的引导。写得越具体,Agent 的行为越可控:

// src/agent/prompts.ts
 
// 反面:太模糊,Agent 容易乱调用工具
const badPrompt = '你是一个助手,帮用户做事。'
 
// 正面:明确了能力边界、决策规则、输出要求
const goodPrompt = `你是一个任务执行助手。
你可以使用以下工具:
- get_weather:查询天气,参数为城市名
- send_reminder:发送提醒消息,参数为提醒内容
 
决策规则:
1. 先理解用户意图,判断是否需要调用工具
2. 如果需要多个工具,按依赖顺序逐个调用
3. 工具返回错误时,尝试换一种方式;重试一次仍失败则告知用户
4. 所有任务完成后,用一句话总结结果
 
不要编造工具返回的数据,所有事实必须来自工具结果。`

行动(Action)

行动能力就是工具调用。Agent 通过工具与外部世界交互——查 API、写数据库、发消息、执行代码。每个工具是一个独立的函数,接收参数、返回结果。工具之间互不感知,由 LLM 负责编排调用顺序。

// src/agent/actions.ts
import type { ToolCall, ToolResult } from './types'
 
type ActionHandler = (args: Record<string, unknown>) => Promise<unknown>
 
// 工具注册表:把所有 action 集中在一个地方管理
const actionRegistry = new Map<string, ActionHandler>()
 
actionRegistry.set('get_weather', async (args) => {
  const res = await fetch(`https://api.weather.com/v1/${args.city}`)
  return res.json()
})
 
// 统一的 action 执行入口
export async function executeAction(toolCall: ToolCall): Promise<ToolResult> {
  const handler = actionRegistry.get(toolCall.name)
  if (!handler) {
    return { toolCallId: toolCall.id, result: { error: `未知工具:${toolCall.name}` } }
  }
  const result = await handler(toolCall.arguments)
  return { toolCallId: toolCall.id, result }
}

记忆(Memory)

Agent 需要记住两类信息:

  1. 短期记忆 — 当前任务的对话历史和工具调用结果,让 Agent 能在多轮循环中保持上下文
  2. 长期记忆 — 跨会话的用户偏好、历史任务、积累的知识

短期记忆就是前面 runAgent 函数里的 messages 数组,在 Agent 执行期间一直存在,任务结束后就丢弃。

长期记忆需要持久化存储,可以用 Cloudflare KV 存简单的键值对:

// src/agent/memory.ts
import type { AppEnv } from '../types'
 
// 保存用户偏好到 KV
export async function saveUserPreference(
  env: AppEnv['Bindings'],
  userId: string,
  preference: Record<string, unknown>,
) {
  await env.AGENT_MEMORY.put(
    `user:${userId}:preference`,
    JSON.stringify(preference),
  )
}
 
// 从 KV 读取用户偏好
export async function loadUserPreference(
  env: AppEnv['Bindings'],
  userId: string,
): Promise<Record<string, unknown> | null> {
  const data = await env.AGENT_MEMORY.get(`user:${userId}:preference`)
  return data ? JSON.parse(data) : null
}

短期记忆保证单次任务内的连贯性,长期记忆让 Agent 在多次会话之间积累对用户的了解。

总结

回顾一下这篇的要点:

  • Agent 的本质是「LLM + 工具 + 执行循环」。LLM 负责推理决策,工具提供外部能力,执行循环把两者串起来
  • Agent 和 Chatbot 的核心区别在于:Chatbot 只生成文本,Agent 能调用工具、执行操作、多步完成任务
  • Agent 具备四个核心能力:感知(接收输入)、推理(制定计划)、行动(调用工具)、记忆(保持上下文)
  • 实现一个最简 Agent,核心是一个 while 循环:每轮把对话历史发给 LLM,如果有工具调用就执行并追加结果,直到 LLM 给出最终回复
  • 适合 Agent 的场景有一个共同特征:任务需要多步完成,每一步可能需要外部数据,后续步骤取决于前面结果
  • 这篇的代码只是最简骨架,生产环境的 Agent 还需要考虑错误恢复、超时控制、流式输出、权限管理

下一篇展开聊 Agent 与普通 Chatbot 的详细区别,包括在什么场景下用 Chatbot 就够了、什么场景必须引入 Agent。