白帽小阳头像
AI Agent时代来了!程序员必须掌握的大模型智能体开发完整路线封面图

AI Agent时代来了!程序员必须掌握的大模型智能体开发完整路线

引言:从"会调 API"到"会造 Agent"

过去两年,大模型应用的形态经历了三次明显的跃迁:2023 年是 Prompt Engineering,大家比谁的提示词写得好;

请添加图片描述

2024 年是 RAG + Function Calling,模型开始能读私有数据、能调外部接口;到 2025 年,Agent 已经成为默认范式——模型不再只是"回答问题",而是"拆解目标、调用工具、观察结果、自我修正"。

但很多程序员的认知还停留在"调个 OpenAI API 就是 AI 开发"。真正落地时会撞上四堵墙:模型没有持久记忆、无法操作外部世界、多步任务成功率断崖式下跌、成本和延迟不可控。Agent 的本质,就是用工程手段把这四堵墙拆掉:Agent = LLM + 规划(Planning)+ 记忆(Memory)+ 工具(Tools)+ 执行循环(Execution Loop)。

这篇文章给出一条完整的工程路线:核心原理 → 最小可运行代码 → 实战案例 → 踩坑与优化。

这三阶段不是替代关系,而是层层叠加。2023 年的 Prompt Engineering 解决的是“单轮输出质量”,你写一段更好的提示词,模型回答得更准;2024 年的 RAG 解决“知识边界”,让模型能读你的文档、数据库、代码库;Function Calling 解决“行动边界”,让模型能调用搜索、发邮件、查订单、跑脚本。到 2025 年,Agent 把这些能力串成一个闭环:它先理解目标,再规划步骤,再选择工具,再观察结果,如果失败就反思重试,直到交付结果。

很多 Demo 看起来很惊艳,一上生产就翻车,原因通常不是模型不够强,而是工程没跟上。单步准确率 90% 听起来很高,但如果一个任务要 10 步,0.9 的 10 次方只有约 35%。如果每一步还涉及工具调用、参数解析、网络超时、权限校验,实际成功率会更低。Agent 开发的核心,不是“写一个更聪明的提示词”,而是用软件工程把不确定性关进笼子里:最大步数、重试、回滚、状态持久化、上下文压缩、工具幂等、可观测性、评估集,一个都不能少。

一、核心原理:Agent 的四个支柱

Agent 不是新模型,而是新架构。你可以把 LLM 当作 CPU,Prompt 是指令集,工具是外设,记忆是内存和硬盘,编排是操作系统。四个支柱缺一不可:没有规划,模型会迷路;没有记忆,模型会失忆;没有工具,模型只能空谈;没有执行循环,模型只能单轮问答。

1.1 认知循环:ReAct、Plan-and-Execute、Reflexion

ReAct(Yao et al., 2022)是最经典的范式:模型交替输出 Thought(推理)和 Action(工具调用),环境返回 Observation,循环直到任务完成。优点是简单、可解释、轨迹可调试;缺点是容易陷入循环、Token 消耗大。

一个典型的 ReAct 轨迹是这样的:

用户:北京今天热吗?适合穿什么?
Thought:我需要查询北京实时天气。
Action:get_weather(city="北京")
Observation:{"temp": 31, "condition": "晴", "humidity": 40}
Thought:31 度且晴天,属于偏热天气。
Final:北京今天 31 度、晴天,建议穿短袖、注意防晒。

ReAct 的关键在于“交替”二字:模型不能一口气把 10 步全想完,而是走一步、看一步、再决定下一步。这样做的好处是能根据工具返回动态调整;坏处是如果工具返回慢,整个链路就慢,而且每一步都带着历史上下文,Token 会迅速膨胀。工程上要加三个护栏:最大步数、重复动作检测、工具错误恢复。比如连续两次调用同一个工具且参数相同,就应该中断或强制换策略;工具超时后,应返回结构化错误让模型决定重试还是换工具。

Plan-and-Execute:先用强模型把任务拆成 DAG,再逐节点执行。适合长链路任务,但计划容易僵化,需要 replan 机制兜底。比如“分析销售数据并生成周报”,Planner 会拆成:查询订单表 → 按地区聚合 → 计算同比 → 找出异常 → 生成图表 → 写总结。Executor 逐个执行。如果执行到“计算同比”时发现数据缺失,就需要 replan,而不是硬着头皮往下走。Plan-and-Execute 的优点是全局感强、Token 更省;缺点是初始计划依赖模型对环境的理解,如果工具能力描述不清,计划会直接跑偏。

Reflexion(Shinn et al., 2023):执行失败后让模型自我反思,把教训写入长期记忆,下次重试时复用。本质是"带记忆的试错",对代码生成、运维排障类任务收益明显。比如 Agent 写了一段 Python 代码,运行测试失败,Reflexion 会生成一句反思:“失败原因是没有处理空列表输入,下次应在函数开头加 if not items: return []。”这条反思写入记忆后,下一次生成类似函数时会被检索出来,从而避免重复犯错。

生产系统通常是混合的:外层 Plan-and-Execute 控全局,内层 ReAct 做具体步骤,失败时 Reflexion 复盘。不要迷信单一范式。短任务用 ReAct 就够;长任务用 Plan-and-Execute 加 ReAct;高风险任务再加 Critic 节点做人工规则校验。常见误区是“把所有任务都做成复杂多 Agent”,结果调试成本暴涨,收益却有限。先从单 Agent 加工具开始,跑通评估集,再考虑拆分。

1.2 工具调用:Function Calling 与 MCP

Function Calling 把工具描述成 JSON Schema,模型输出结构化的 tool_calls,你的代码负责执行并把结果回填。这里有一个关键认知:工具描述就是写给模型的 API 文档,它的重要性往往超过系统提示词。描述里必须写清"做什么、什么时候用、什么时候不要用、参数格式、返回示例"。

对比一个坏描述和一个好描述:

{
  "name": "get_weather",
  "description": "获取天气",
  "parameters": {"city": "string"}
}

上面这个描述缺少参数类型、必填项、使用边界,模型很容易在闲聊时也调用,或者把“北京”写成“北京市朝阳区”导致工具报错。更好的写法是:

{
  "name": "get_weather",
  "description": "查询指定城市的实时天气。仅当用户明确询问天气、温度、降雨、穿衣建议时调用;不要用于闲聊、新闻、股票查询。返回 JSON,包含 temp、condition、humidity。",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名,如北京、上海、广州,不要带省市区后缀"
      }
    },
    "required": ["city"]
  }
}

工具调用的完整链路是:用户输入 → 模型返回 tool_calls → 你的代码解析参数 → 执行真实函数 → 把结果以 role=tool 和对应 tool_call_id 回填 → 模型继续推理或生成最终答案。这里有几个高频坑:第一,模型可能同时返回多个工具调用,必须用循环处理,不能只取第一个;第二,tool_call_id 必须严格对应,否则 API 会报错;第三,参数是 JSON 字符串,需要 json.loads,并且要做异常捕获;第四,工具返回不要塞入无关大字段,否则上下文会爆炸;第五,工具本身要幂等,尤其是写操作,避免重试导致重复下单、重复发邮件。

MCP(Model Context Protocol)解决的是工具生态碎片化问题:把文件系统、数据库、浏览器、Git 等能力统一封装成 MCP Server,Agent 作为 Client 动态发现和调用工具。Anthropic 在 2024 年底开源后,它已经成为事实标准,Claude、Cursor、各类 IDE 都在接入。MCP 的架构通常包括 Host(宿主应用)、Client(连接器)、Server(能力提供方)。传输层支持 stdio、SSE 等。它的价值在于“一次封装,多处复用”:你写一个数据库 MCP Server,Claude Desktop、Cursor、自己的 Agent 都能接。但安全边界必须重视:MCP Server 权限过大,可能被提示词注入攻击利用。生产环境要加白名单、沙箱、审计日志、人工确认,尤其是文件写入、命令执行、支付类工具。

1.3 记忆:短期、长期与压缩

短期记忆就是消息列表 + scratchpad;长期记忆通常是向量库、关系库或知识图谱。工程上最关键的不是"存",而是上下文压缩策略:滑动窗口、递归摘要、工具结果只保留关键字段、用结构化事件流替代自然语言历史。一个不做压缩的多轮 Agent,很容易在 10 轮内烧掉 10 万 Token。

短期记忆可以理解为一个状态对象:messages 保存对话,scratchpad 保存中间推理,state 保存任务变量,比如“已查城市=上海”“天气=小雨”“下一步=推荐室内景点”。长期记忆则要回答三个问题:什么值得记?怎么检索?什么时候遗忘?值得记的通常包括用户偏好、稳定事实、任务教训、成功模板;不值得记的是临时工具返回、一次性闲聊、敏感隐私。检索时不能只看向量相似度,还要加时间衰减、重要性权重、来源可信度。比如用户三年前说“喜欢川菜”,现在可能已经变了,时间衰减能降低旧记忆权重。

上下文压缩有几种常用手段。滑动窗口:只保留最近 N 条消息,旧消息丢弃,简单但会丢关键信息。递归摘要:把旧对话分段总结成摘要,保留摘要加最近消息。结构化事件流:不保存自然语言历史,而是保存 {event: "weather_checked", city: "上海", result: "rain"},模型需要时再格式化。工具结果裁剪:工具返回 100 行 JSON,只保留 status、data.items[0..3]、error 等关键字段。Token 预算也要动态分配:系统提示、工具描述、历史、工具结果、当前输入各占一部分,超出就压缩。常见问题包括记忆污染(把错误信息写进长期记忆)、记忆矛盾(新偏好和旧偏好冲突)、隐私泄露(把用户敏感信息写入向量库)。生产环境要有记忆写入审核、加密、删除机制。

1.4 编排:从 while 循环到状态机

最小 Agent 就是一个 while 循环,但生产级需要状态机——LangGraph、Temporal、Dify 都是这个思路。核心目标只有一个:把不可控的模型,关进可控的图里。

while 循环的问题在于:它不可控、难恢复、难观测。模型说走 5 步,实际可能走 50 步;执行到第 3 步服务重启,状态全丢;出了问题只能看日志,没有可视化轨迹。状态机把流程拆成节点和边:节点可以是 planner、executor、tool、critic、human_review;边可以是普通边,也可以是条件边,比如“如果工具调用成功 → executor;如果失败 → reflexion;如果涉及支付 → human_review”。LangGraph 的 StateGraph 就是典型实现,它支持持久化 checkpointer,能把每一步状态存入数据库,实现断点续跑和人工介入。Temporal 更适合长时任务,比如等待三天后的审批结果。Dify 则适合低代码快速搭原型。

可观测性是编排的眼睛。你需要记录 trace、span、token 消耗、工具调用参数、返回耗时、错误堆栈。没有可观测性,你根本不知道 Agent 为什么失败:是模型选错工具,还是工具超时,还是上下文太长导致模型“遗忘”了目标。生产系统还要有评估集:准备 50 到 200 条真实任务,定期跑回归,比较成功率、平均步数、平均成本、P95 延迟。只有把 Agent 当作一个软件系统来度量,它才可能稳定。

二、实战:最小 ReAct Agent

先不依赖任何框架,用官方 SDK 写一个 100 行以内的 Agent,理解底层机制。

import json
from openai import OpenAI

client = OpenAI()

TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的实时天气。仅当用户明确询问天气时调用,不要用于闲聊。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名,如北京、上海、广州"
                }
            },
            "required": ["city"]
        }
    }
}]

def get_weather(city: str) -> str:
    """模拟天气工具。真实场景替换为 API 调用,并加上超时、重试、缓存。"""
    fake_db = {
        "北京": {"temp": 31, "condition": "晴", "humidity": 40},
        "上海": {"temp": 28, "condition": "小雨", "humidity": 80},
        "广州": {"temp": 33, "condition": "多云", "humidity": 75},
    }
    data = fake_db.get(city, {"temp": 25, "condition": "未知", "humidity": 50})
    return json.dumps(data, ensure_ascii=False)

def execute_tool(name: str, args: dict) -> str:
    """根据模型返回的工具名路由到具体函数。"""
    try:
        if name == "get_weather":
            return get_weather(args["city"])
        return json.dumps({"error": f"未知工具: {name}"}, ensure_ascii=False)
    except Exception as e:
        return json.dumps({"error": str(e)}, ensure_ascii=False)

def run_agent(user_input: str, max_steps: int = 5) -> str:
    """最小 ReAct 循环:模型思考 -> 调工具 -> 观察结果 -> 继续。"""
    messages = [
        {
            "role": "system",
            "content": "你是一个助手。需要天气时调用 get_weather。不要编造工具结果。"
        },
        {"role": "user", "content": user_input},
    ]

    for step in range(max_steps):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto",
        )
        msg = response.choices[0].message
        messages.append(msg)

        if not msg.tool_calls:
            return msg.content or "任务结束,但模型没有返回内容。"

        for tool_call in msg.tool_calls:
            name = tool_call.function.name
            try:
                args = json.loads(tool_call.function.arguments)
            except json.JSONDecodeError:
                args = {}
            result = execute_tool(name, args)
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result,
            })

    return "达到最大步数,任务未完成。"

if __name__ == "__main__":
    print(run_agent("北京今天适合穿什么?"))

这段代码虽然短,但包含了 ReAct 的全部核心机制。第一,TOOLS 是写给模型看的“API 文档”,描述越清晰,模型调用越准。第二,messages 就是短期记忆,用户消息、模型消息、工具消息都在这里累积。第三,msg.tool_calls 可能是一个列表,必须循环处理,不能假设只有一个。第四,工具结果回填时必须带 tool_call_id,否则模型不知道结果对应哪次调用。第五,max_steps 是防死循环的刹车,生产环境还可以加超时、重复调用检测、成本上限。

运行结果类似:

北京今天 31 度、晴天,湿度 40%。建议穿短袖、薄裤,注意防晒补水。

如果你想让它更接近生产,可以在这个骨架上加:工具参数校验(用 Pydantic)、日志追踪(记录每步 token 和耗时)、缓存(相同城市天气 5 分钟内复用)、并发工具调用(asyncio.gather)、错误重试(指数退避)。但不要一开始就上框架。手写一遍,你会真正理解 tool_calls、role=tool、消息历史这些概念,而不是被 LangChain 的抽象层绕晕。

常见错误包括:忘记把模型返回的 msg append 到 messages,导致下一轮模型看不到自己刚才的工具调用;

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

更多硬核网安与AI工具包,请扫码获取完整源码!
tool_call_id 拼错;工具返回非字符串;

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/yiyiyi0322/article/details/167688560

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--