找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2764

积分

0

好友

394

主题
发表于 8 小时前 | 查看: 1| 回复: 0

海滩上的人群剪影,背景是落日与海面,氛围轻松活跃

0. 现象:RAG 的天花板与 Agent 的必然性

当你一步步实现完 RAG 系统后,可能会发现一个尴尬的现象:你的大模型成了一个“博学的图书管理员”。

你可以问它:“我们公司的年假制度是怎样的?” 它能从知识库中找到文档,并头头是道地回答你(这是RAG的功劳)。

但当你问它:“帮我查一下 A 仓库里 SKU 为 A001 的商品还有多少库存?” 它可能就傻了,回答你:“对不起,我的知识库里没有实时库存数据。”

如果你更进一步:“那帮我根据库存结果,给采购经理发一封催货的邮件吧。” 它会更加手足无措,只能坦白:“抱歉,我只是一个语言模型,无法执行这类操作。”

这就是 RAG 的天花板——它让模型学会了“读”(检索与理解),但无法“做”(执行与操作)。要让模型真正“长出手脚”去干活,我们需要引入 Agent 的概念。

Agent(智能体) 的第一性原理可以概括为:大语言模型 (LLM) + 记忆 (Memory) + 规划 (Planning) + 工具 (Tools) = 自主智能体

听起来很复杂?从工程落地的角度看,可以简单理解为:Agent 本质上就是一个循环调用 (Loop) 加上一套工具路由 (Router) 机制。它让模型能够自主决定在什么时候、调用哪个工具来完成用户的任务。

1. 核心解密:ReAct 模式 (Reasoning + Acting)

Agent 为什么能自己决定下一步该干什么?这背后主要依赖一个高效的 Prompt 工程模式,叫做 ReAct (Reasoning + Acting)

别被这个学术名词吓到,它的逻辑其实非常符合人类的思考方式。让我们用一个查库存的场景来拆解这个过程:

  1. Thought(思考):用户想查询商品库存。我应该先调用“查询库存”这个工具。
  2. Action(行动):执行 search_inventory(sku=“A001”)
  3. Observation(观察):工具(即后端接口)返回结果 { “A001”: 50 }
  4. Thought(再思考):库存查到了,有50件。用户还问了够不够发货?我需要查看发货规则来判断。
  5. Action(行动):调用 check_shipping_rule(min_inventory=30)
  6. Observation(观察):规则检查通过,库存大于最低要求。
  7. Final Answer(最终回答):库存充足(50件),满足发货条件。

在具体的工程实现中,这并不是什么魔法。其核心是 让 LLM 运行在一个 while 循环里。在每一次循环中,模型根据当前的对话历史和工具执行结果,输出下一步的“思考(Thought)”和要执行的“行动(Action)”,直到它自己判断任务已经完成,输出最终答案并跳出循环。

这整个思考-行动-观察的闭环,就是 Agent 自主性的来源。

2. 工程基石:Function Calling (工具定义)

要让 Agent 真正跑起来,第一步就是把你的后端 API 包装成模型能看懂的“工具说明书”。这个过程在 OpenAI 等主流平台被称为 Function Calling

2.1 定义工具 (Schema)

这和你写 Swagger 或 OpenAPI 文档的思路一模一样。你必须清晰地告诉模型三件事:

  1. 工具叫什么名字? (例如:send_email)
  2. 这个工具是干什么用的? (例如:向指定的收件人发送一封邮件)
  3. 调用这个工具需要哪些参数? (例如:to: string, subject: string, body: string)

以下是一个符合 OpenAI 格式的“查询库存”工具定义示例:

{
  "type": "function",
  "function": {
    "name": "query_inventory",
    "description": "查询指定商品的实时库存数量",
    "parameters": {
      "type": "object",
      "properties": {
        "sku_code": {
          "type": "string",
          "description": "商品SKU编码,如 A-101"
        }
      },
      "required": ["sku_code"]
    }
  }
}

2.2 工具调用的工程坑点

  1. 描述 (Description) 比代码更重要:模型完全是依赖你对工具的 description 字段来决定是否、以及何时调用这个工具的。如果你的描述写得含糊不清,模型就可能不调用,或者错误地调用其他工具。因此,把描述当成给模型看的 API 文档来写,务必准确、清晰。
  2. 参数校验 (Validation):模型返回的参数可能是错的。例如,它可能把日期参数写成 “tomorrow”,而你的接口期望的是 “2023-10-27”。因此,后端在拿到参数后,必须先进行严格的校验。如果校验失败,需要把清晰的错误信息(如 “Error: date must be in format YYYY-MM-DD”)作为 Observation 塞回给模型,让它有机会重试或修正。

3. 多租户下的 Agent 架构:权限是个大雷

RAG 的权限控制相对简单,主要在“检索”阶段进行过滤即可。但 Agent 的权限设计则复杂且危险得多,因为它涉及“写操作”(如发邮件、修改订单状态、调整库存),一旦权限失控,后果严重。

3.1 权限透传 (User Context Injection)

绝对不能给 Agent 后端配置一个拥有所有权限的“超级管理员”账号。

正确的做法是:当 Agent 决定调用某个后端 API 时,必须复用当前发起请求的用户的身份凭证 (Token/Session)

流程应该是这样的:

  • 用户 U 向 Agent 发起请求:“帮我查一下我的工资”。
  • Agent 经过思考,决定调用 get_salary() 工具。
  • Agent 在调用 get_salary()后端API 时,需要在 HTTP Header 中携带用户 U 的 Token。
  • 后端API 进行权限检查:这个 Token 是否属于用户 U?用户 U 是否有权限查询工资?
  • 如果无权,后端应返回 403 Forbidden。
  • Agent 收到 403 的 Observation 后,会进行思考,然后回复用户:“对不起,您没有权限查询工资信息。”

这就对了! Agent 只是一个代理,它不应该、也不能绕过业务系统固有的权限体系。

3.2 敏感操作的“人机确认” (Human-in-the-loop)

对于所有写操作或高风险操作(如发送邮件、删除数据、发起转账),永远不要让 Agent 自动执行到底。

推荐的架构模式如下:

  1. Agent 经过规划,决定调用 transfer_money(to=“Bob”, amount=100)
  2. 系统在此处 暂停 (Suspend) Agent 的执行循环。
  3. 前端向用户弹出一个确认窗口:“智能体请求向 Bob 转账 100 元,是否批准?”
  4. 用户点击“批准”。
  5. 系统将“用户已批准”作为一个 Observation 塞回给 Agent 的消息历史。
  6. Agent 接收到这个“批准”的观察结果,然后继续执行转账操作。

这既保证了自动化流程的推进,又将最终决策权牢牢掌握在用户手中,是生产级 后端架构 中不可或缺的安全兜底策略。

4. 一个可落地的 Agent Loop 代码逻辑(伪代码)

以下是 Agent 后端核心执行逻辑的伪代码展示,你可以清晰地看到,它就是一个增强了工具调用和权限控制的 While 循环。

# 初始化对话历史
messages = [{"role": "user", "content": "帮我查下 A001 库存,够的话发个邮件给老板"}]
# 可用的工具列表
tools = [query_inventory_tool, send_email_tool]

while True:
    # 1. 调用大模型,传入对话历史和工具定义
    response = llm.chat(messages, tools=tools)

    # 2. 判断模型是否想要调用工具
    if response.tool_calls:
        tool_call = response.tool_calls[0]
        func_name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)

        # --- 关键:权限校验与工具执行 ---
        # 必须带上当前用户的 context (如 current_user)
        if func_name == "query_inventory":
            result = backend_api.query_inventory(sku=args[‘sku_code’], user=current_user)
        elif func_name == "send_email":
            # 敏感操作,插入人工确认逻辑
            if not user_approved(func_name, args):
                result = “User denied the operation.”
            else:
                result = backend_api.send_email(**args, user=current_user)

        # 3. 把执行结果(Observation)追加到消息历史
        messages.append(response.message)  # 加入模型的 Action 消息
        messages.append({                  # 加入工具执行结果的 Observation 消息
            “role”: “tool”,
            “tool_call_id”: tool_call.id,
            “content”: str(result)
        })
        # 进入下一轮循环,模型会看到这个结果,然后决定下一步
    else:
        # 4. 模型不调用工具了,直接给出最终答案 (Final Answer)
        print(“Agent 最终回复:”, response.content)
        break

5. Agent 的常见死法与救法

5.1 死循环 (Looping)

  • 现象:模型陷入逻辑怪圈。例如,不停地查询同一个库存,查完又查,就是不输出最终结论。
  • 解法
    1. 设置最大步数 (max_steps):例如限制为10步,超过则强制终止循环,并提示“任务超时”。
    2. 优化 System Prompt:在给模型的指令中明确要求,例如:“如果你尝试了多次仍无法获得有效信息或得出结论,请停止尝试,并告知用户当前状态或请求更多信息。”

5.2 乱调参 (Hallucinated Arguments)

  • 现象:模型调用了正确的工具,但传入了错误的参数格式。例如,调用 search_user(id=“张三”),但接口要求 id 必须是整型数字。
  • 解法
    1. 错误信息回馈:将后端的详细错误信息(如 “Error: Parameter \‘id\’ must be an integer.”)作为 Observation 完整地返回给模型。
    2. 模型自我修正:能力较强的模型(如 GPT-4)在看到具体的错误信息后,常能自我修正。例如,它可能会接着发起新的 Action:get_user_id(name=“张三”) 来获取ID,然后再用ID去查询。

6. 本篇小结:从 Demo 到生产

总结一下,将一个 Agent 从演示原型推向生产环境,你需要牢牢抓住以下几个核心点:

  1. 本质理解:Agent = 循环 (Loop) + 工具 (Tools)。其智能核心是 ReAct 推理循环。
  2. 工具定义:像编写严格的代码文档一样定义工具,因为 description 是模型决定行动的“产品手册”。
  3. 权限控制:权限校验必须在 后端业务逻辑 中完成。Agent 仅是代理,绝不能成为绕过系统安全的“后门”。
  4. 安全兜底:对于所有敏感操作,必须设计“人机确认”环节,这是生产系统的安全生命线。
  5. 容错设计:做好错误处理,将 API 的报错信息清晰地喂回给模型,赋予其自我调试和修正的能力。

从 RAG 到 Agent,是从“让模型更懂”到“让模型能干”的关键跨越。希望这篇关于 ReAct 原理与工程落地的探讨,能帮助你在 云栈社区 的探索之路上,更稳健地构建出真正实用的自主智能体。




上一篇:无锁CAS详解:ConcurrentLinkedQueue如何实现高并发读写的性能标杆?
下一篇:Node.js代码注释指南:如何编写简洁明了的注释提升代码可读性
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-27 18:14 , Processed in 0.249490 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表