Agent基本概念
要点
- Agent 的本质是「LLM + 工具 + 执行循环」,LLM 负责推理决策,工具提供外部能力,执行循环让 Agent 能多步完成任务
- 普通 Chatbot 只能接收一条输入、返回一条文本回复;Agent 能调用工具、观察结果、继续推理,直到任务完成
- Agent 具备四个核心能力:感知(接收输入)、推理(制定计划)、行动(调用工具)、记忆(保持上下文)
- 执行循环(Agent Loop)是 Agent 区别于普通 LLM 调用的关键——一个 while 循环,每轮检查是否需要调用工具
- Agent 的典型应用场景都有共同特征:任务需要多步完成,每一步可能需要外部数据,后续步骤取决于前面结果
- 用 TypeScript 实现一个最简 Agent,核心是一个接收工具列表、循环调用 LLM 并执行工具函数的结构
1. 什么是Agent
也许你已经有过这样的经历:问 ChatGPT 一个问题,它给你一段文字回复。再问一个,又是一段回复。每一轮对话都是独立的「输入 → 文本输出」,模型并不主动做任何事情。
Agent 在这个基础上加了一个关键能力:能使用工具,并且能根据工具返回的结果决定下一步做什么。
具体来说,Agent 由三个部分组成:
- LLM — 负责理解意图、推理决策、生成回复
- Tools — 一组可供调用的外部工具,比如搜索、查数据库、发邮件、执行代码
- 执行循环(Agent Loop) — 驱动 Agent 不断「思考 → 行动 → 观察」,直到任务完成
用一个简短的流程来理解:
用户输入 -> LLM 推理 -> 需要工具?
├── 是 -> 调用工具 -> 拿到结果 -> 回到 LLM 推理
└── 否 -> 输出最终回复
这个循环是 Agent 的核心。没有循环,LLM 只能回答一次;有了循环,LLM 可以拆解复杂任务、逐步完成。
2. Agent与普通Chatbot的区别
先从一个具体场景出发。
用户说:「帮我查一下明天从上海到东京的航班,挑一个最便宜的,然后把航班信息发邮件给我的同事。」
面对这个请求,普通 Chatbot 和 Agent 的处理方式完全不同。
普通 Chatbot 会怎么做:
- 接收用户消息
- 根据训练数据生成一段文字:「您可以打开某某航空网站查询……」
- 回复结束
它只能给建议,不能真正帮你查航班、发邮件。因为 Chatbot 的边界就是文本输入和文本输出。
Agent 会怎么做:
- 接收用户消息
- 分析意图:需要查航班 + 比较价格 + 发邮件
- 调用航班查询工具,拿到明天的航班列表
- 从结果中筛选最便宜的航班
- 调用邮件发送工具,把航班信息发给同事
- 回复用户:「已查到最便宜的航班是 XX 航空 XX 航班,价格 XXXX 元,已发送邮件给你的同事」
两者的差异可以这样归纳:
- Chatbot 是单轮的文本生成,Agent 是多轮的任务执行
- Chatbot 的输出只有文字,Agent 的输出可以是实际行动(查数据、发消息、写文件)
- Chatbot 无法获取实时信息,Agent 通过工具可以访问外部世界
- Chatbot 不知道自己说的话对不对,Agent 能通过工具结果验证和修正
下一篇会展开讨论两者的差异,包括在哪些场景下 Chatbot 已经够用、哪些场景必须引入 Agent。
3. 为什么需要Agent
前面已经看到了 Agent 能做什么。现在换个角度:为什么不让 LLM 直接回答所有问题,而要绕一圈引入工具?
原因可以从 LLM 本身的限制说起。
LLM 天然做不到的事
- 不知道当下发生的事 — 训练数据有截止日期,LLM 不知道今天的天气、股价、新闻
- 不能执行任何操作 — 它不能查数据库、发请求、写文件、调 API,只能生成文本
- 不能保证精确计算 — 数学运算、数据统计这类任务,LLM 经常会算错
- 每次调用互相独立 — 没有外部存储的话,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前端发送 { "message": "查一下北京天气" } 到 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 对象。不同来源的输入,最终都会被转成同一种结构:{ role, content } 或 { role: 'tool', toolCallId, content }。
这一层的代码很简单,就是几个转换函数,这里不再单独列出。
推理(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 需要记住两类信息:
- 短期记忆 — 当前任务的对话历史和工具调用结果,让 Agent 能在多轮循环中保持上下文
- 长期记忆 — 跨会话的用户偏好、历史任务、积累的知识
短期记忆就是前面 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。