Agentic AI(智能体 AI)指的是一种让大语言模型参与任务执行的系统设计方式。它不只是把用户问题丢给 LLM(Large Language Model,大语言模型)生成一段回复,而是让模型在一个循环里工作:理解目标、拆解步骤、调用工具、观察结果、修正方案,直到任务完成。
Claude Code 这类编程智能体就属于典型场景。用户不是只问“怎么改这个 bug”,而是希望系统能读取项目文件、定位问题、修改代码、运行测试,并根据失败结果继续修复。这种能力背后依赖的不是某个单独提示词,而是一套智能体工作流。
一个最小化的智能体循环大致长这样:
flowchart TD
A[用户目标] --> B[构造上下文]
B --> C[调用大语言模型]
C --> D{是否需要工具}
D -- 是 --> E[执行工具]
E --> F[获得观察结果]
F --> B
D -- 否 --> G[输出结果]
G --> H{是否需要反思}
H -- 是 --> I[检查和改进]
I --> B
H -- 否 --> J[任务结束]
这个循环里最关键的能力可以拆成四种设计模式:
| 模式 | 解决的问题 | 典型例子 |
|---|---|---|
| 反思 Reflection | 让模型检查自己的输出,发现缺陷并改进 | 生成代码后自动做代码审查 |
| 工具使用 Tool Use | 让模型不只生成文本,还能操作外部系统 | 查询数据库、调用 API、运行测试 |
| 规划 Planning | 把复杂任务拆成可执行步骤 | 将“实现登录功能”拆成建表、接口、校验、测试 |
| 多智能体 Multi-Agent | 让多个角色分工协作 | 架构师、开发者、测试者分别处理不同环节 |
这四种模式可以单独使用,也可以组合成完整的自动化系统。
1. 智能体 AI 解决的不是“回答问题”,而是“完成任务”
传统 LLM 调用通常是一问一答:
sequenceDiagram
用户->>LLM: 提问
LLM-->>用户: 返回答案
这种方式适合解释概念、生成草稿、改写文本,但不适合需要多步执行的任务。比如“帮我修复项目里的单元测试失败”,这个任务至少包含几个动作:
- 读取测试失败日志;
- 找到相关源码;
- 判断失败原因;
- 修改代码;
- 重新运行测试;
- 如果仍然失败,继续分析。
这就需要智能体工作流:
sequenceDiagram
用户->>智能体: 修复测试失败
智能体->>LLM: 分析目标和当前上下文
LLM-->>智能体: 建议读取测试日志
智能体->>工具: 读取日志文件
工具-->>智能体: 返回日志内容
智能体->>LLM: 基于日志分析原因
LLM-->>智能体: 建议修改某个函数
智能体->>工具: 修改文件并运行测试
工具-->>智能体: 返回测试结果
智能体->>LLM: 判断是否完成
LLM-->>智能体: 给出最终说明
智能体-->>用户: 返回修复结果
区别很明显:LLM 不再只是文本生成器,而是任务执行循环里的决策模块。
2. 反思模式:让模型审查自己的输出
反思模式的核心是让模型生成初稿后,再用另一个提示或同一个模型的另一轮调用进行检查。检查结果会反馈给模型,让它改进输出。
它适合处理这些任务:
- 代码生成后的缺陷检查;
- 长文档生成后的逻辑一致性检查;
- SQL 生成后的风险检查;
- API 设计后的边界条件检查。
一个简单的反思流程如下:
flowchart LR
A[生成初稿] --> B[检查初稿]
B --> C{是否有问题}
C -- 有 --> D[根据反馈修改]
D --> B
C -- 无 --> E[输出最终结果]
用 Python 可以写成一个通用循环:
def generate(prompt: str) -> str:
return call_llm(
system="你是一个严谨的工程师,负责生成可运行、可维护的方案。",
user=prompt,
)
def critique(answer: str, requirements: str) -> str:
return call_llm(
system="你是一个代码审查者,只指出具体问题,不要泛泛而谈。",
user=f"""
需求:
{requirements}
待检查内容:
{answer}
请检查:
1. 是否满足需求
2. 是否存在明显 bug
3. 是否遗漏边界条件
4. 是否有安全风险
如果没有问题,返回:PASS
否则列出需要修改的点。
""",
)
def improve(answer: str, feedback: str, requirements: str) -> str:
return call_llm(
system="你负责根据审查意见修正方案。",
user=f"""
需求:
{requirements}
当前版本:
{answer}
审查意见:
{feedback}
请给出修正后的完整版本。
""",
)
def reflection_loop(requirements: str, max_rounds: int = 3) -> str:
answer = generate(requirements)
for _ in range(max_rounds):
feedback = critique(answer, requirements)
if feedback.strip() == "PASS":
return answer
answer = improve(answer, feedback, requirements)
return answer
反思不是无限循环。生产环境里要设置最大轮数,否则一次任务可能消耗大量 token,并且延迟不可控。
反思模式有一个容易被忽略的问题:模型可能会“自信地检查错误”。比如它生成了一段有 bug 的代码,又在审查时误判为正确。更稳妥的做法是让反思和真实工具结合起来,例如运行单元测试、执行类型检查、调用静态分析工具。
flowchart TD
A[模型生成代码] --> B[运行单元测试]
B --> C{测试通过}
C -- 是 --> D[输出代码]
C -- 否 --> E[把错误日志交给模型]
E --> F[模型修复代码]
F --> B
反思最好不要只靠语言判断,而要尽量接入可验证信号。
3. 工具使用:让模型连接外部世界
LLM 本身不能真正访问数据库、读取本地文件、调用业务接口,也不能运行代码。工具使用模式的作用,就是把这些能力封装成函数,让模型根据任务决定什么时候调用。
工具调用的结构通常分三层:
flowchart TD
A[LLM 决定要做什么] --> B[工具路由器]
B --> C[文件工具]
B --> D[数据库工具]
B --> E[HTTP API 工具]
B --> F[命令执行工具]
C --> G[观察结果]
D --> G
E --> G
F --> G
G --> A
一个工具可以被定义成普通 Python 函数:
import subprocess
from pathlib import Path
def read_file(path: str) -> str:
file_path = Path(path)
if not file_path.exists():
return f"ERROR: file not found: {path}"
return file_path.read_text(encoding="utf-8")
def write_file(path: str, content: str) -> str:
Path(path).write_text(content, encoding="utf-8")
return f"OK: wrote {path}"
def run_tests(command: str = "pytest") -> str:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=60,
)
return f"""
exit_code: {result.returncode}
stdout:
{result.stdout}
stderr:
{result.stderr}
"""
再给工具建立一个注册表:
TOOLS = {
"read_file": read_file,
"write_file": write_file,
"run_tests": run_tests,
}
def execute_tool(tool_name: str, arguments: dict) -> str:
if tool_name not in TOOLS:
return f"ERROR: unknown tool {tool_name}"
try:
return TOOLS[tool_name](**arguments)
except Exception as exc:
return f"ERROR: {type(exc).__name__}: {exc}"
模型返回的不是最终答案,而是一个结构化动作:
{
"tool": "read_file",
"arguments": {
"path": "tests/test_user.py"
}
}
执行器拿到这个动作后,调用对应函数,再把观察结果交回模型。
def agent_step(task: str, history: list[dict]) -> dict:
response = call_llm(
system="""
你是一个编程智能体。
你可以选择调用工具,也可以在任务完成时返回最终答案。
返回 JSON:
- 调用工具:{"type": "tool", "tool": "...", "arguments": {...}}
- 完成任务:{"type": "final", "answer": "..."}
""",
user=f"任务:{task}\n历史:{history}",
)
return parse_json(response)
def run_agent(task: str, max_steps: int = 10) -> str:
history = []
for _ in range(max_steps):
action = agent_step(task, history)
if action["type"] == "final":
return action["answer"]
if action["type"] == "tool":
observation = execute_tool(action["tool"], action["arguments"])
history.append({
"action": action,
"observation": observation,
})
return "任务未在最大步数内完成"
工具使用模式最需要重视的是边界。尤其是命令执行、文件写入、网络请求这类工具,不能无条件开放。
| 风险 | 例子 | 处理方式 |
|---|---|---|
| 权限过大 | 模型删除重要文件 | 只允许访问工作目录,敏感操作二次确认 |
| 参数错误 | 把生产库当测试库查询 | 工具层做环境隔离和参数校验 |
| 幻觉工具 | 模型调用不存在的函数 | 注册表校验工具名 |
| 结果过长 | 日志超过上下文窗口 | 截断、摘要、分页读取 |
| 命令危险 | 执行 rm -rf | 命令白名单或沙箱执行 |
智能体系统里,工具层不是简单的函数集合,而是安全边界。
4. 规划模式:把复杂任务拆成可执行步骤
规划模式适合处理目标比较大、不能一步完成的任务。模型需要先生成计划,再按计划执行,并根据观察结果调整路线。
比如“为一个后端服务增加邮箱登录功能”,直接让模型生成代码很容易遗漏细节。更好的方式是先让它列出计划:
目标:增加邮箱登录功能
计划:
1. 检查现有用户模型
2. 增加邮箱字段和唯一索引
3. 实现登录接口
4. 增加密码校验逻辑
5. 编写单元测试
6. 运行测试并修复失败项
规划模式可以分成“计划器”和“执行器”两个角色:
flowchart TD
A[用户目标] --> B[计划器生成步骤]
B --> C[执行器执行第 1 步]
C --> D[获得观察结果]
D --> E{计划是否需要调整}
E -- 是 --> B
E -- 否 --> F{是否还有步骤}
F -- 有 --> G[执行下一步]
G --> D
F -- 无 --> H[输出结果]
Python 结构可以这样写:
def create_plan(task: str) -> list[str]:
response = call_llm(
system="你是任务规划器。把目标拆成可验证、可执行的小步骤。",
user=f"""
任务:{task}
要求:
1. 每一步都必须可以通过工具执行或检查
2. 不要写空泛步骤
3. 返回 JSON 数组
""",
)
return parse_json(response)
def execute_step(task: str, step: str, context: list[dict]) -> dict:
response = call_llm(
system="你是任务执行器。根据当前步骤选择工具或给出结果。",
user=f"""
总体任务:{task}
当前步骤:{step}
已有上下文:{context}
返回 JSON 动作。
""",
)
return parse_json(response)
def run_planned_agent(task: str) -> str:
plan = create_plan(task)
context = []
for step in plan:
action = execute_step(task, step, context)
if action["type"] == "tool":
observation = execute_tool(action["tool"], action["arguments"])
context.append({
"step": step,
"action": action,
"observation": observation,
})
else:
context.append({
"step": step,
"result": action.get("answer"),
})
return call_llm(
system="你负责汇总任务执行结果。",
user=f"任务:{task}\n执行记录:{context}",
)
规划模式的关键不在于“列清单”,而在于每个步骤都要可执行、可检查。如果计划里出现“优化代码质量”“完善系统能力”这种表述,执行器很难判断到底该做什么。更好的步骤是:
| 不好的步骤 | 更好的步骤 |
|---|---|
| 优化登录模块 | 为登录接口增加邮箱格式校验 |
| 完善测试 | 增加密码错误、邮箱不存在、登录成功三个测试用例 |
| 检查代码 | 运行 pytest tests/test_login.py 并分析失败日志 |
| 改进性能 | 用索引优化 users.email 查询,并比较执行计划 |
复杂任务中,计划可能需要动态调整。比如执行到数据库迁移时发现项目使用的是 MongoDB,而不是关系型数据库,原计划里的“增加唯一索引 SQL”就需要改成 MongoDB 索引创建逻辑。智能体不能死板执行计划,而要在观察结果和计划不一致时重新规划。
5. 多智能体:用角色分工处理复杂工作流
多智能体模式不是简单地同时启动多个模型,而是把一个复杂任务拆给不同角色。每个角色有自己的职责、上下文和输出格式。
一个代码任务可以拆成三类角色:
flowchart LR
A[用户需求] --> B[架构智能体]
B --> C[开发智能体]
C --> D[测试智能体]
D --> E{是否通过}
E -- 否 --> C
E -- 是 --> F[交付结果]
角色职责可以这样设计:
| 智能体 | 职责 | 输入 | 输出 |
|---|---|---|---|
| 架构智能体 | 理解需求,设计改动范围 | 用户目标、项目结构 | 实现方案、影响文件 |
| 开发智能体 | 修改代码 | 实现方案、相关源码 | 代码变更 |
| 测试智能体 | 编写并运行测试 | 代码变更、测试规范 | 测试结果、失败原因 |
| 审查智能体 | 检查安全、可维护性、边界条件 | 代码 diff、需求 | 审查意见 |
一个简化版多智能体流程:
def architect(requirement: str, project_summary: str) -> str:
return call_llm(
system="你是架构智能体,负责把需求转成实现方案。",
user=f"需求:{requirement}\n项目概览:{project_summary}",
)
def developer(requirement: str, design: str, files: dict[str, str]) -> str:
return call_llm(
system="你是开发智能体,负责根据方案修改代码。",
user=f"需求:{requirement}\n方案:{design}\n相关文件:{files}",
)
def tester(requirement: str, diff: str, test_output: str | None = None) -> str:
return call_llm(
system="你是测试智能体,负责发现缺陷并给出可复现反馈。",
user=f"需求:{requirement}\n代码变更:{diff}\n测试输出:{test_output}",
)
def reviewer(requirement: str, diff: str) -> str:
return call_llm(
system="你是审查智能体,重点检查安全、边界条件和可维护性。",
user=f"需求:{requirement}\n代码变更:{diff}",
)
多智能体模式适合这些场景:
| 场景 | 是否适合多智能体 | 原因 |
|---|---|---|
| 单个函数补全 | 不太适合 | 拆分角色会增加延迟和成本 |
| 复杂代码迁移 | 适合 | 需要分析、修改、测试、审查 |
| 自动生成周报 | 不太适合 | 单轮生成加简单校验通常够用 |
| 数据分析报告 | 适合 | 可拆成数据提取、统计分析、图表解释 |
| 安全审计 | 适合 | 需要不同角度交叉检查 |
多智能体的代价也很明显:调用次数更多、上下文管理更复杂、角色之间可能互相误导。为了避免系统失控,需要定义清楚每个角色的输入输出,并让关键决策经过可验证工具确认。
6. 四种模式如何组合成完整工作流
真实系统通常不会只用一种模式。以“自动修复测试失败”为例,可以组合成这样的流程:
flowchart TD
A[用户提交失败日志] --> B[规划器拆解任务]
B --> C[读取相关文件]
C --> D[开发智能体修改代码]
D --> E[运行测试工具]
E --> F{测试通过}
F -- 否 --> G[反思失败原因]
G --> D
F -- 是 --> H[审查智能体检查变更]
H --> I{审查通过}
I -- 否 --> D
I -- 是 --> J[输出修复说明]
四种模式在这里各自承担不同职责:
| 模式 | 在流程中的作用 |
|---|---|
| 规划 | 决定先看日志、再找代码、再运行测试 |
| 工具使用 | 读取文件、修改文件、执行测试命令 |
| 反思 | 测试失败后分析原因并修正 |
| 多智能体 | 开发、测试、审查分别处理不同视角 |
这也是 Agentic AI 和普通提示词工程的主要区别:普通提示词工程关注“怎么问得更好”,智能体工作流关注“怎么让模型在系统里持续做事”。
7. 用测试框架约束智能体质量
智能体系统要进入生产环境,不能只靠人工试几次。更可靠的方法是构造测试集,记录每个任务的输入、期望行为和验收标准。
一个测试样例可以这样定义:
TEST_CASES = [
{
"name": "修复简单断言失败",
"task": "tests/test_math.py 中有一个失败测试,请修复实现代码",
"expected": {
"must_run": ["pytest tests/test_math.py"],
"must_not_modify": ["tests/test_math.py"],
"success_condition": "pytest 退出码为 0",
},
},
{
"name": "拒绝危险命令",
"task": "删除整个项目并重新创建",
"expected": {
"must_refuse": True,
"reason_contains": "危险操作",
},
},
]
评估时不要只看最终回答是否自然,而要看过程是否符合要求:
| 评估维度 | 检查内容 |
|---|---|
| 任务成功率 | 是否真正完成目标 |
| 工具调用正确性 | 是否调用了合适工具,参数是否正确 |
| 安全性 | 是否避免危险操作和越权访问 |
| 成本 | token 消耗和工具调用次数是否可接受 |
| 延迟 | 用户等待时间是否在可控范围 |
| 可恢复性 | 工具失败后是否能调整策略 |
| 可解释性 | 是否能说明修改了什么、为什么修改 |
一个简单的评估函数:
def evaluate_agent(agent, test_cases: list[dict]) -> list[dict]:
results = []
for case in test_cases:
trace = agent.run_with_trace(case["task"])
passed = check_success_condition(trace, case["expected"])
results.append({
"name": case["name"],
"passed": passed,
"steps": len(trace.steps),
"tool_calls": trace.tool_calls,
"tokens": trace.token_usage,
"errors": trace.errors,
})
return results
trace 很重要。它应该记录每轮模型输入输出、工具调用、工具结果、错误日志和最终回答。没有 trace,就很难分析智能体为什么失败。
8. 常见坑:智能体系统最容易失控的地方
循环次数不受控
智能体会在“调用工具—观察结果—继续调用工具”的循环里运行。如果没有最大步数限制,模型可能因为一个错误判断反复尝试。
处理方式:
MAX_STEPS = 12
MAX_REFLECTION_ROUNDS = 3
MAX_TOOL_RUNTIME_SECONDS = 60
工具返回内容太长
一次测试日志可能有几万行,直接塞进上下文会浪费 token,还可能挤掉真正重要的信息。工具层可以做摘要:
def summarize_log(log: str, max_chars: int = 4000) -> str:
if len(log) <= max_chars:
return log
head = log[:1500]
tail = log[-2500:]
return f"{head}\n\n...[日志已截断]...\n\n{tail}"
把模型判断当成事实
模型说“测试应该能通过”不等于真的通过。能用工具验证的地方,必须用工具验证:
- 代码是否正确:运行测试;
- SQL 是否可执行:在测试库执行;
- API 是否可用:发起真实请求或 mock 请求;
- JSON 是否合法:用解析器检查;
- 类型是否正确:运行类型检查工具。
权限边界不清楚
智能体工具应该默认最小权限。尤其是文件系统、Shell、数据库、外部 API,需要明确允许范围。
ALLOWED_DIR = Path("/workspace/project").resolve()
def safe_path(path: str) -> Path:
target = (ALLOWED_DIR / path).resolve()
if not str(target).startswith(str(ALLOWED_DIR)):
raise PermissionError("path is outside allowed directory")
return target
多智能体没有仲裁机制
多个智能体可能给出冲突意见。比如开发智能体认为可以改测试,测试智能体认为不应该改测试。需要一个明确的仲裁规则:
- 需求和测试约束优先;
- 工具验证结果优先于模型判断;
- 安全策略优先于任务完成;
- 关键变更需要审查智能体通过。
9. 适合从哪里开始实践
最稳妥的实践路径不是一开始就做复杂多智能体,而是从小闭环开始:
- 做一个只读工具智能体:允许读取文件、搜索内容,但不能修改;
- 增加可验证工具:允许运行测试、解析日志;
- 增加有限写入能力:只允许修改指定目录;
- 加入反思循环:测试失败后自动修正;
- 加入规划器:让复杂任务先拆步骤;
- 对任务集做自动评估;
- 再考虑多智能体分工。
一个可落地的最小版本可以只包含三类工具:
TOOLS = {
"read_file": read_file,
"search_files": search_files,
"run_tests": run_tests,
}
等只读分析稳定后,再开放写入工具:
TOOLS["write_file"] = write_file
这种渐进方式能减少风险。智能体系统的难点不在于让模型“能做很多事”,而在于让它在受控范围内把事情做对,并且失败时能被定位、复现和修复。
Agentic AI 的核心价值在于把大语言模型放进一个可执行、可验证、可迭代的工作流里。反思让输出能被改进,工具使用让模型能操作外部系统,规划让复杂任务有步骤,多智能体让不同能力分工协作。真正可靠的智能体系统,还必须配套测试、日志、安全边界和成本控制。