第02章 Agent的三要素:感知、思考与执行

第02章 Agent的三要素:感知、思考与执行

“一个真正的Agent就像一个新员工:你给它一个目标,它知道怎么利用手头的资源把事情搞定,而不是每隔5分钟来问你下一步该怎么做。” —— 某工程团队负责人


上一章我们理解了Agent是什么,以及它和Chatbot的根本区别。

这一章,我们拆解Agent的内部结构:它是怎么工作的?每个部分在做什么?


2.1 感知层:Agent如何理解世界

# 感知层:Agent可以接收的输入类型

class AgentPerception:
    """
    感知层负责:把各种输入转化为Agent可以理解的"状态"
    """
    
    INPUT_TYPES = {
        "文本指令": {
            "示例": "帮我添加一个子域名 blog.example.com",
            "处理方式": "直接传入LLM,提取意图和参数",
        },
        "工具执行结果": {
            "示例": '{"success": true, "record_id": "abc123", "message": "DNS记录已创建"}',
            "处理方式": "格式化后追加到对话历史,影响下一步决策",
        },
        "环境状态": {
            "示例": "当前系统时间、用户的账户计划、工具的速率限制",
            "处理方式": "注入System Prompt或作为工具调用的前置上下文",
        },
        "错误和异常": {
            "示例": '{"error": "403 Forbidden", "message": "Token权限不足"}',
            "处理方式": "触发错误处理路径,Agent需要决定重试/降级/报告",
        },
        "用户反馈": {
            "示例": "不对,我需要的是CNAME记录,不是A记录",
            "处理方式": "更新任务理解,重新规划",
        },
    }
    
    def build_context(
        self,
        user_goal: str,
        available_tools: list[dict],
        current_skills: list[str],
        conversation_history: list[dict],
    ) -> list[dict]:
        """
        构建Agent的完整上下文
        这是感知层的核心产出
        """
        system_message = {
            "role": "system",
            "content": f"""你是一个自主AI助手,可以使用工具完成任务。

可用工具:
{self._format_tools(available_tools)}

已掌握技能(过去成功完成的任务):
{', '.join(current_skills) if current_skills else '暂无'}

执行原则:
- 不确定时先查询,再操作
- 每次工具调用后验证结果
- 出错时说明原因并尝试恢复
- 完成后总结执行结果
"""
        }
        
        return [system_message] + conversation_history + [
            {"role": "user", "content": user_goal}
        ]
    
    def _format_tools(self, tools: list[dict]) -> str:
        return "\n".join(
            f"- {t['name']}: {t['description']}"
            for t in tools
        )

# 感知的质量决定Agent的质量
PERCEPTION_QUALITY_FACTORS = {
    "上下文完整性": "Agent需要知道目标、工具、当前状态",
    "噪音过滤": "不相关的信息会导致Agent分心或出错",
    "格式一致性": "工具输出需要标准化,LLM才能稳定解析",
    "错误可读性": "错误信息要对LLM友好,而不只是技术错误码",
}

2.2 思考层:Agent如何规划和决策

# 思考层:Agent的推理引擎

class AgentReasoning:
    """
    思考层负责:
    1. 理解用户真实意图(不只是字面意思)
    2. 制定行动计划(选择工具、确定顺序)
    3. 处理不确定性(何时询问用户、何时自己决定)
    4. 从失败中恢复(识别错误类型、选择恢复策略)
    """
    
    # === 意图理解 ===
    
    INTENT_LAYERS = {
        "表层意图": "用户说了什么",
        "深层意图": "用户真正想要什么",
        "隐含约束": "用户没说但理所当然的要求",
    }
    
    # 示例:
    # 用户说:"把我的博客部署上去"
    # 表层意图:执行部署操作
    # 深层意图:让博客对外可访问,并且正常运行
    # 隐含约束:不要把现有的数据清空;尽量不要停机;出错先告诉我
    
    # === 规划方法 ===
    
    PLANNING_PATTERNS = {
        "直接执行(简单任务)": {
            "适用": "1-2个步骤,工具调用很确定",
            "示例": "查询当前DNS记录",
            "代码": """
response = await llm.complete(messages, tools=tools)
# 直接执行tool_call
""",
        },
        
        "Sequential(顺序规划)": {
            "适用": "步骤之间有依赖,必须顺序执行",
            "示例": "获取zone_id → 添加记录 → 验证生效",
            "代码": """
# Agent在每步的tool_call结果后重新规划下一步
""",
        },
        
        "Plan-then-Execute(先规划后执行)": {
            "适用": "复杂任务,需要全局视角",
            "示例": "迁移整个DNS配置",
            "代码": """
# 第一次调用:让LLM生成完整计划(不执行工具)
# 审查计划后再执行
""",
        },
        
        "ReAct(交替推理和行动)": {
            "适用": "探索性任务,结果不可预测",
            "示例": "排查为什么某个子域名不生效",
            "代码": """
# 每步: Thought → Action → Observation → Thought → ...
""",
        },
    }
    
    async def reason(
        self,
        context: list[dict],
        tools: list,
        previous_results: list = None,
    ) -> dict:
        """
        核心推理:决定下一步做什么
        """
        # 注入已有结果
        if previous_results:
            context.append({
                "role": "assistant",
                "content": f"已完成步骤: {previous_results}"
            })
        
        response = await self.llm.complete(
            messages=context,
            tools=tools,
            tool_choice="auto",  # 让LLM自己决定用不用工具
        )
        
        return {
            "tool_calls": response.tool_calls,  # 要执行的工具
            "thinking": self._extract_thinking(response.content),  # 推理过程
            "final_answer": response.content if not response.tool_calls else None,
        }
    
    def _extract_thinking(self, content: str) -> str:
        """从<思考>标签中提取推理过程"""
        import re
        match = re.search(r"<思考>(.*?)</思考>", content, re.DOTALL)
        return match.group(1).strip() if match else ""

# 思考质量的关键因素
THINKING_QUALITY = {
    "明确性": "Agent需要清楚地知道当前目标是什么",
    "步骤分解": "复杂目标需要拆成可执行的小步骤",
    "不确定性处理": "不确定时询问,而不是猜测后执行",
    "错误识别": "能识别出哪些结果是错误的,哪些是正常的",
}

2.3 执行层:Agent如何与世界交互

# 执行层:工具执行引擎

import asyncio
from typing import Any

class AgentExecutor:
    """
    执行层负责:
    1. 解析LLM的tool_call请求
    2. 调用对应工具
    3. 处理执行结果(包括错误)
    4. 把结果格式化返回给LLM
    """
    
    def __init__(self, tools: dict):
        self.tools = tools  # {tool_name: tool_function}
    
    async def execute_tool_call(self, tool_call: dict) -> dict:
        """
        执行单个工具调用
        """
        tool_name = tool_call["name"]
        arguments = tool_call["arguments"]
        
        if tool_name not in self.tools:
            return {
                "success": False,
                "error": f"工具 '{tool_name}' 不存在",
                "available_tools": list(self.tools.keys()),
            }
        
        try:
            tool_fn = self.tools[tool_name]
            result = await tool_fn(**arguments)
            return {
                "success": True,
                "tool": tool_name,
                "arguments": arguments,
                "result": result,
            }
        except TypeError as e:
            # 参数不对
            return {
                "success": False,
                "error": f"参数错误: {str(e)}",
                "hint": "检查参数名称和类型是否正确",
            }
        except Exception as e:
            # 执行出错
            return {
                "success": False,
                "error": str(e),
                "error_type": type(e).__name__,
            }
    
    async def execute_all(self, tool_calls: list[dict]) -> list[dict]:
        """
        执行多个工具调用
        注意:某些工具必须顺序执行(有依赖关系),某些可以并行
        """
        results = []
        for call in tool_calls:
            result = await self.execute_tool_call(call)
            results.append(result)
            
            # 如果关键步骤失败,停止后续执行
            if not result["success"] and call.get("critical", True):
                results.append({
                    "stopped": True,
                    "reason": "关键步骤失败,已中止后续操作",
                })
                break
        
        return results
    
    def format_results_for_llm(self, results: list[dict]) -> str:
        """
        把执行结果格式化,让LLM容易理解
        """
        formatted = []
        for r in results:
            if r.get("success"):
                formatted.append(f"✅ {r['tool']}({r['arguments']})\n   结果: {r['result']}")
            elif r.get("stopped"):
                formatted.append(f"⏹️ 执行已中止: {r['reason']}")
            else:
                formatted.append(f"❌ 执行失败: {r['error']}")
        
        return "\n".join(formatted)

2.4 三层结构的完整循环

# 把三层组合在一起:一个完整的Agent运行一次任务

async def run_agent_task(
    goal: str,
    tools: dict,
    max_iterations: int = 10,
) -> str:
    """
    完整的Agent执行循环
    """
    perception = AgentPerception()
    reasoning = AgentReasoning(llm)
    executor = AgentExecutor(tools)
    
    # 构建初始上下文
    context = perception.build_context(
        user_goal=goal,
        available_tools=list(tools.values()),
        current_skills=[],
        conversation_history=[],
    )
    
    print(f"🎯 目标: {goal}")
    print("=" * 50)
    
    for iteration in range(max_iterations):
        print(f"\n🔄 迭代 {iteration + 1}")
        
        # 思考:决定下一步
        decision = await reasoning.reason(context, list(tools.values()))
        
        if decision["thinking"]:
            print(f"💭 思考: {decision['thinking']}")
        
        # 检查是否完成
        if decision["final_answer"]:
            print(f"\n✅ 任务完成")
            return decision["final_answer"]
        
        # 执行:调用工具
        if decision["tool_calls"]:
            results = await executor.execute_all(decision["tool_calls"])
            formatted_results = executor.format_results_for_llm(results)
            
            print(f"🔧 执行结果:\n{formatted_results}")
            
            # 把结果加入上下文
            context.append({
                "role": "tool",
                "content": formatted_results,
            })
    
    return "已达到最大迭代次数"

# 运行示例
# asyncio.run(run_agent_task(
#     goal="帮我把 blog.example.com 的DNS指向 1.2.3.4,并验证生效",
#     tools={"dns_add": add_dns_record, "dns_verify": verify_dns},
# ))

本章小结

五个核心认知:

  1. 感知层决定Agent"看到什么":System Prompt的质量、工具描述的清晰度、错误信息的可读性——都直接影响Agent的行为;垃圾进,垃圾出

  2. 思考层是Agent的"大脑":同一个LLM,加上好的推理框架(ReAct、CoT)和清晰的目标,表现会天差地别;思考层的设计是Agent工程最关键的部分

  3. 执行层需要防御性设计:工具执行会出错;Agent需要能够识别错误类型(权限问题、参数问题、网络问题)并做出不同的恢复策略

  4. 三层的质量相互依赖:感知不清楚→思考会偏;思考不好→执行会错;执行不稳定→思考无法正确判断;需要整体设计,不能只优化某一层

  5. 迭代循环是Agent的本质:不是一次性的感知-思考-执行,而是循环,直到目标达成;这也是为什么"最大迭代次数"和"完成检测"是必须设计的安全机制

核心行动

# 今天的实验:
# 1. 选一个你日常用LLM辅助的任务
# 2. 画出它的"感知-思考-执行"循环图
# 3. 标出每层的输入和输出
# 4. 找出哪层是当前的瓶颈(为什么需要人工干预)

本章提示词模板

模板一:分析任务的三层结构

我有一个任务:[描述你的任务]

请帮我分析这个任务的Agent三层结构:

感知层分析:
- Agent需要知道哪些信息才能开始执行?
- 哪些信息是已知的?哪些需要先查询获取?
- 执行过程中会有哪些中间状态需要感知?

思考层分析:
- 这个任务需要哪种规划模式?(直接执行/顺序/ReAct)
- 可能的决策分支是什么?(成功路径 vs 失败恢复路径)
- 什么情况下Agent应该暂停并询问用户?

执行层分析:
- 需要哪些工具?(列出工具名和功能)
- 工具之间有什么依赖关系?
- 哪些步骤的失败是可恢复的?哪些是不可逆的?

模板二:设计Agent的System Prompt

我要给一个Agent设计System Prompt,Agent的用途是:[描述用途]

Agent可以使用的工具:[列出工具]
目标用户:[描述用户]
典型任务类型:[举3个例子]
必须避免的操作:[列出危险操作]

请帮我写一个高质量的System Prompt,要求:
1. 明确Agent的角色和能力边界
2. 说明决策原则(何时用工具、何时询问用户)
3. 错误处理规范(失败时如何报告)
4. 安全约束(哪些事情绝对不做)
5. 输出格式要求

然后指出这个System Prompt可能的弱点和改进建议。

→ 第03章:工具是Agent的手:Function Calling初探