1. 项目概述:从“调用”到“思考”的范式跃迁

最近和几个做AI应用开发的朋友聊天,发现一个挺有意思的现象:大家聊起大模型,已经从半年前的“哪个模型API便宜又好用”,逐渐变成了“你的Agent工作流是怎么设计的”。这背后其实是一个根本性的转变——我们不再仅仅把大模型当作一个“更聪明的文本生成器”来调用,而是开始尝试构建一个能够自主感知、规划、决策和执行的“智能体”。这就像是从雇佣一个“超级打字员”,变成了培养一个“初级程序员助理”。

“手写一个编程Agent”这个想法,就是在这个背景下冒出来的。市面上已经有了LangChain、AutoGen这些成熟的框架,它们封装得很好,开箱即用。但就像学开车不能只靠自动挡,想真正理解Agent是怎么“跑”起来的,最好的办法就是自己从零开始,用最基础的积木搭一个出来。这个项目的目的不是要造一个比现有框架更强大的轮子,而是要亲手摸清轮子里的每一根辐条、每一个轴承。我们将聚焦于Agent最核心的“底层三件套”: 思考循环(ReAct)、工具调用(Tools)和任务编排(Orchestration) 。通过拆解和实现它们,你会透彻理解一个Agent是如何将一句模糊的指令(比如“帮我分析一下这个项目的代码仓库”),分解成一系列具体的行动(克隆仓库、读取文件、调用分析工具、生成报告),并最终完成目标的。

这个过程,对于任何想深入AI应用层,尤其是智能体开发的开发者来说,都是一次必不可少的“扒开引擎盖”的实践。无论你是想基于现有框架做深度定制,还是未来设计自己的Agent架构,这里的每一个细节都会成为你工具箱里最趁手的扳手。

2. 核心架构拆解:Agent的“大脑”、“手”和“调度中心”

在开始写代码之前,我们必须先在大脑里把Agent的运转模型搭建起来。一个能够处理复杂任务的Agent,其核心架构可以类比为一个经验丰富的工程师。他不能只会埋头写代码,他需要一套完整的“心智能力”。

2.1 思考循环(ReAct):Agent的“大脑”与推理链

ReAct(Reasoning + Acting)范式是让Agent展现出“思考”能力的关键。它不是一个复杂的算法,而是一个极其重要的设计模式。其核心思想是让模型在 推理(Think) 行动(Act) 之间循环迭代。

为什么是循环,而不是一步到位? 因为现实世界的问题往往是模糊、多步骤且充满不确定性的。用户说“帮我优化这个函数”,模型不可能直接输出完美的代码。它需要先“推理”:这个函数是做什么的?可能存在哪些性能瓶颈(比如循环嵌套过深、重复计算)?然后“行动”:调用代码分析工具,获取函数的复杂度指标。接着根据工具返回的结果,进行新一轮“推理”:哦,这里确实有个O(n²)的循环。再“行动”:调用代码改写工具,尝试将其优化为O(n log n)。如此循环,直至问题解决或达到终止条件。

一个典型的ReAct循环步骤:

  1. 观察(Observation) :接收当前环境状态(如上一步工具执行的结果、用户的原始问题)。
  2. 推理(Reasoning) :模型基于观察,分析现状,思考下一步该做什么。这一步的输出是纯文本的“内心独白”。
  3. 行动(Acting) :模型根据推理结论,决定调用哪个工具(或直接给出最终答案)。输出格式是结构化的,例如 Action: 工具名称 Action Input: 工具参数
  4. 执行与再观察 :系统执行指定的工具,将执行结果(Observation)作为下一轮循环的输入。

这个循环的终止条件通常是模型推理后认为任务已完成,从而输出 Final Answer: ...

实操心得 :在实现时,给模型的提示词(Prompt)中必须清晰定义这个循环的格式规范,并给出1-2个完整的示例(Few-shot Learning)。这是引导模型遵循ReAct模式最有效的方法。模型有时会“偷懒”,试图跳过推理直接行动,清晰的格式约束能极大减少这种情况。

2.2 工具(Tools):Agent的“手”与能力边界

工具是Agent与外部世界交互的唯一途径。一个没有工具的Agent,就像是一个被困在密室里的天才,空有想法却无法付诸实践。工具定义了Agent的能力边界。

工具的设计哲学:原子化与确定性 一个好的工具应该是“原子化”的,即只做好一件具体、明确的事情。例如:

  • get_current_weather(location: string) :获取指定地点的天气。
  • search_web(query: string) :执行网络搜索。
  • execute_python_code(code: string) :在安全沙箱中运行Python代码。
  • read_file(file_path: string) :读取本地文件内容。

为什么强调原子化?

  1. 降低模型调用难度 :模型更容易理解和使用功能单一、接口明确的工具。
  2. 便于组合与复用 :复杂的任务可以通过串联多个原子工具来完成。
  3. 提升可靠性 :每个工具可以独立进行错误处理和日志记录。

工具描述至关重要 你需要为每个工具编写清晰、准确的描述,包括功能、输入参数(名称、类型、说明)和返回内容。这个描述会作为系统提示词的一部分传给大模型,是模型决定是否及如何调用该工具的依据。描述模糊会导致模型误用或不敢用。

2.3 任务编排(Orchestration):Agent的“调度中心”与流程控制

当单个ReAct循环无法解决复杂任务时,我们就需要任务编排。这是Agent系统中复杂度最高的一部分,它负责管理多个子任务或子Agent之间的执行顺序、数据传递和异常处理。

常见的编排模式:

  1. 顺序执行 :最简单的方式,A任务完成后再执行B。适用于有严格依赖关系的任务链。
  2. 并行执行 :对于相互独立的任务,可以并行执行以提高效率。这就是为什么在热词中会看到 Promise.all ——在前端JavaScript或异步编程中,这是管理并行操作的经典方式。在Agent上下文中,意味着可以同时发起多个工具调用。
  3. 条件分支 :根据某个任务的结果,决定下一步执行哪个分支。例如,“如果代码分析显示复杂度高,则执行重构;否则,直接返回分析报告”。
  4. 循环 :对列表中的每一项重复执行某个子任务。例如,“分析这个目录下的每一个Python文件”。

实现编排的挑战

  • 状态管理 :每个子任务的状态(进行中、成功、失败、结果数据)需要被有效跟踪。
  • 错误处理与重试 :一个子任务失败时,是整个工作流终止,还是尝试重试或执行备用方案?
  • 上下文传递 :任务A的输出如何成为任务B的输入?

目前,LangGraph等框架的核心就是解决这些编排问题。在我们手写Agent时,初期可能只需实现顺序执行,但必须在设计上为更复杂的编排留出扩展空间。

3. 手把手实现:构建一个简易编程助手Agent

理论说得再多,不如动手写一行代码。接下来,我们将用Python(考虑到其在AI领域的绝对主流地位)实现一个简易的“编程助手Agent”。这个Agent能理解关于代码操作的简单指令,并调用我们提供的工具来完成任务。

3.1 环境准备与工具定义

我们选择OpenAI的GPT-3.5/4作为我们Agent的“大脑”,因为它对遵循复杂指令和工具调用有很好的支持。当然,你也可以替换为任何支持类似功能的开源或闭源模型。

# 安装核心依赖
pip install openai python-dotenv

首先,定义几个原子工具。我们将它们实现为Python函数,并附上详细的描述字典。

# tools.py
import subprocess
import json

def execute_shell_command(command: str) -> str:
    """
    执行一个shell命令并返回输出。

    Args:
        command (str): 要执行的shell命令字符串。

    Returns:
        str: 命令的标准输出和错误输出。
    """
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
        return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\nReturn Code: {result.returncode}"
    except subprocess.TimeoutExpired:
        return "Command execution timed out after 30 seconds."
    except Exception as e:
        return f"Failed to execute command: {str(e)}"

def read_file_content(filepath: str) -> str:
    """
    读取指定文件的内容。

    Args:
        filepath (str): 要读取的文件的路径。

    Returns:
        str: 文件的内容。如果文件不存在或读取失败,返回错误信息。
    """
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        return f"Error: File '{filepath}' not found."
    except Exception as e:
        return f"Error reading file: {str(e)}"

def write_file_content(filepath: str, content: str) -> str:
    """
    将内容写入指定文件。

    Args:
        filepath (str): 要写入的文件的路径。
        content (str): 要写入的内容。

    Returns:
        str: 操作结果信息。
    """
    try:
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(content)
        return f"Successfully wrote to '{filepath}'."
    except Exception as e:
        return f"Error writing to file: {str(e)}"

# 工具元数据列表,用于构建提示词
TOOLS = [
    {
        "name": "execute_shell_command",
        "description": "Executes a shell command on the local system. Use this for listing files, running scripts, or any system operations.",
        "parameters": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "The shell command to execute."}
            },
            "required": ["command"]
        }
    },
    {
        "name": "read_file_content",
        "description": "Reads the entire content of a specified file.",
        "parameters": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "The path to the file to read."}
            },
            "required": ["filepath"]
        }
    },
    {
        "name": "write_file_content",
        "description": "Writes content to a specified file. Will overwrite if the file exists.",
        "parameters": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "The path to the file to write."},
                "content": {"type": "string", "description": "The content to write into the file."}
            },
            "required": ["filepath", "content"]
        }
    }
]

# 工具名称到函数对象的映射
TOOL_MAPPING = {
    "execute_shell_command": execute_shell_command,
    "read_file_content": read_file_content,
    "write_file_content": write_file_content,
}

注意事项 execute_shell_command 工具极其强大,但也非常危险。在真实生产环境中,你必须实现严格的沙箱机制、命令白名单或用户权限检查,绝对禁止直接将未经处理的用户输入传递给此工具。这里的实现仅用于演示和本地学习。

3.2 构建ReAct引擎核心

这是Agent的“大脑”核心。我们将构建一个 ReActAgent 类,它负责维护与LLM的对话、解析模型输出、调用工具并管理循环。

# react_agent.py
import openai
import re
import json
from typing import Dict, Any, List, Optional

class ReActAgent:
    def __init__(self, api_key: str, model: str = "gpt-3.5-turbo"):
        openai.api_key = api_key
        self.model = model
        self.conversation_history: List[Dict[str, str]] = [] # 维护对话历史
        self.max_iterations = 10 # 防止无限循环

    def _build_system_prompt(self, tools: List[Dict]) -> str:
        """构建系统提示词,包含工具定义和ReAct格式说明。"""
        tools_json = json.dumps(tools, indent=2)
        prompt = f"""
You are a helpful programming assistant. You can use tools to interact with the system.
Your thought process must follow the ReAct format:

Thought: [Your reasoning about the current situation and what to do next]
Action: [The name of the tool to use, must be one of the available tools]
Action Input: [The input to the tool, must be a valid JSON string]
Observation: [The result of the action, provided by the system]

After the Observation, you will output another Thought, Action, Action Input... This loop continues until you have enough information to answer the user's question.
When you are ready to give the final answer, output:
Thought: I now have the final answer.
Final Answer: [Your final answer to the user]

Available Tools:
{tools_json}

Important: The `Action Input` must be a valid JSON object matching the tool's parameters. Do not add any other text.
"""
        return prompt

    def _parse_model_response(self, response: str) -> Dict[str, str]:
        """解析模型返回的文本,提取 Thought, Action, Action Input。"""
        thought_match = re.search(r'Thought:\s*(.*?)(?=\nAction:|\nFinal Answer:|$)', response, re.DOTALL)
        action_match = re.search(r'Action:\s*(\w+)', response)
        action_input_match = re.search(r'Action Input:\s*(.*?)(?=\nObservation:|\nThought:|$)', response, re.DOTALL)
        final_answer_match = re.search(r'Final Answer:\s*(.*)', response, re.DOTALL)

        parsed = {}
        if thought_match:
            parsed['thought'] = thought_match.group(1).strip()
        if action_match:
            parsed['action'] = action_match.group(1).strip()
        if action_input_match:
            # 尝试将Action Input解析为JSON
            input_str = action_input_match.group(1).strip()
            try:
                parsed['action_input'] = json.loads(input_str)
            except json.JSONDecodeError:
                # 如果解析失败,可能模型没有输出严格JSON,这里可以尝试修复或记录错误
                parsed['action_input'] = input_str
        if final_answer_match:
            parsed['final_answer'] = final_answer_match.group(1).strip()

        return parsed

    def run(self, user_query: str, tools: List[Dict], tool_mapping: Dict[str, Any]) -> str:
        """执行ReAct循环,处理用户查询。"""
        system_prompt = self._build_system_prompt(tools)
        self.conversation_history = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_query}
        ]

        for i in range(self.max_iterations):
            # 1. 调用LLM,获取下一步的思考/行动
            try:
                response = openai.ChatCompletion.create(
                    model=self.model,
                    messages=self.conversation_history,
                    temperature=0.1, # 低温度保证输出稳定性
                    max_tokens=500
                )
                model_message = response.choices[0].message.content
                print(f"\n--- Iteration {i+1} ---")
                print(f"Model Raw Output:\n{model_message}")
            except Exception as e:
                return f"Error calling LLM: {str(e)}"

            # 2. 解析输出
            parsed = self._parse_model_response(model_message)
            self.conversation_history.append({"role": "assistant", "content": model_message})

            # 3. 检查是否为最终答案
            if 'final_answer' in parsed:
                print(f"\nFinal Answer: {parsed['final_answer']}")
                return parsed['final_answer']

            # 4. 检查是否有有效行动
            if 'action' not in parsed or 'action_input' not in parsed:
                # 模型没有输出有效格式,可能出错了
                error_msg = "I did not receive a valid Action format. Please check your output."
                self.conversation_history.append({"role": "user", "content": error_msg})
                continue

            action = parsed['action']
            action_input = parsed['action_input']

            # 5. 执行工具调用
            if action not in tool_mapping:
                observation = f"Error: Unknown tool '{action}'. Available tools: {list(tool_mapping.keys())}"
            else:
                try:
                    # 根据工具函数签名动态调用
                    tool_func = tool_mapping[action]
                    if isinstance(action_input, dict):
                        observation = tool_func(**action_input)
                    else:
                        # 如果action_input不是dict(解析失败),尝试作为单一参数传递
                        observation = tool_func(action_input)
                except Exception as e:
                    observation = f"Error executing tool '{action}': {str(e)}"

            print(f"Observation: {observation[:200]}...") # 打印部分观察结果

            # 6. 将观察结果加入历史,开启下一轮循环
            self.conversation_history.append({"role": "user", "content": f"Observation: {observation}"})

        return f"Reached maximum iterations ({self.max_iterations}) without final answer."

3.3 运行你的第一个Agent

现在,让我们把各部分组装起来,并运行一个简单的测试。

# main.py
import os
from dotenv import load_dotenv
from tools import TOOLS, TOOL_MAPPING
from react_agent import ReActAgent

# 加载环境变量,假设你的OPENAI_API_KEY保存在.env文件中
load_dotenv()

def main():
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        print("请设置 OPENAI_API_KEY 环境变量")
        return

    agent = ReActAgent(api_key=api_key, model="gpt-3.5-turbo")

    # 测试用例1:一个简单的文件操作任务
    user_query = """
    请帮我做以下事情:
    1. 查看当前目录下有哪些文件和文件夹。
    2. 如果存在一个叫 `test.txt` 的文件,请读取它的内容并告诉我。
    3. 如果不存在,请创建一个 `test.txt` 文件,并写入内容 'Hello from Agent!'。
    """

    print("用户查询:", user_query)
    final_answer = agent.run(user_query=user_query, tools=TOOLS, tool_mapping=TOOL_MAPPING)
    print("\n=== 任务完成 ===")
    print("最终回复:", final_answer)

if __name__ == "__main__":
    main()

运行这个脚本,你会看到类似以下的输出:

用户查询: (你的查询内容)

--- Iteration 1 ---
Model Raw Output:
Thought: First, I need to see what files and folders are in the current directory. I can use the execute_shell_command tool for that.
Action: execute_shell_command
Action Input: {"command": "ls -la"}
Observation: STDOUT: total 24 drwxr-xr-x ... . drwxr-xr-x ... .. -rw-r--r-- ... main.py -rw-r--r-- ... react_agent.py ...

--- Iteration 2 ---
Model Raw Output:
Thought: I can see the list of files. There is no `test.txt` file. So I need to create it and write the content.
Action: write_file_content
Action Input: {"filepath": "test.txt", "content": "Hello from Agent!"}
Observation: Successfully wrote to 'test.txt'.

--- Iteration 3 ---
Model Raw Output:
Thought: Now I should verify the file was created and read its content to confirm.
Action: read_file_content
Action Input: {"filepath": "test.txt"}
Observation: Hello from Agent!

--- Iteration 4 ---
Model Raw Output:
Thought: I now have the final answer. I have completed all requested steps.
Final Answer: 我已经完成了您的要求。首先,我列出了当前目录的内容,确认没有 `test.txt` 文件。然后,我创建了该文件并写入了“Hello from Agent!”。最后,我读取了文件内容进行确认,内容正确。

=== 任务完成 ===
最终回复: 我已经完成了您的要求。首先,我列出了当前目录的内容,确认没有 `test.txt` 文件。然后,我创建了该文件并写入了“Hello from Agent!”。最后,我读取了文件内容进行确认,内容正确。

看,你的Agent“活”过来了!它通过ReAct循环,自主规划并使用了三个不同的工具,完成了一个多步骤的任务。

4. 深入优化与高级模式探索

一个能跑起来的Demo只是起点。要让这个Agent真正可靠、强大,我们需要在基础三件套上做大量优化和扩展。

4.1 提升工具调用的稳定性与安全性

1. 结构化输出(Function Calling) 我们之前的实现依赖于模型输出文本,然后用正则表达式解析 Action Action Input 。这种方式非常脆弱,容易因为模型输出的微小格式偏差而失败。更现代、更稳定的方式是使用OpenAI API原生支持的 函数调用(Function Calling) 功能。

函数调用允许你在请求中直接定义工具(函数)的Schema,模型会返回一个结构化的JSON对象,明确指出它想调用哪个函数以及参数是什么,完全无需文本解析。

# 使用OpenAI函数调用功能的简化示例
def run_with_function_calling(user_query):
    messages = [{"role": "user", "content": user_query}]
    # 将我们的TOOLS列表作为`functions`参数传入
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages,
        functions=TOOLS, # 直接使用之前定义的TOOLS元数据
        function_call="auto", # 让模型决定是否调用函数
    )
    response_message = response.choices[0].message

    if response_message.get("function_call"):
        # 模型决定调用函数
        function_name = response_message["function_call"]["name"]
        function_args = json.loads(response_message["function_call"]["arguments"])
        # 执行工具...
        tool_func = TOOL_MAPPING[function_name]
        result = tool_func(**function_args)
        # 将结果作为新的消息追加,让模型继续
        messages.append(response_message)
        messages.append({
            "role": "function",
            "name": function_name,
            "content": str(result),
        })
        # 再次调用模型...

使用函数调用能极大提升Agent的稳定性和开发效率,是生产级应用的必选项。

2. 工具执行的安全性加固

  • 沙箱化 :对于 execute_shell_command execute_python_code 这类高危工具,必须在独立的Docker容器或安全的沙箱环境(如 pysandbox gVisor )中运行,严格限制资源(CPU、内存、网络、文件系统)。
  • 输入验证与白名单 :对所有工具的参数进行严格的类型和范围校验。对于命令执行,可以维护一个允许的命令前缀白名单(如只允许 ls , cat , find . -name “*.py” 等查询类命令),禁止 rm sudo wget 等危险操作。
  • 权限控制 :根据运行Agent的用户身份,限制其可访问的文件路径和系统资源。

4.2 实现并行与异步任务编排

当任务中的多个子步骤相互独立时,并行执行可以显著缩短总耗时。例如,用户要求“统计项目里所有Python文件的代码行数和找出所有的TODO注释”,这两个任务可以同时进行。

我们可以利用Python的 asyncio 库和 aiohttp (如果需要调用网络API)来实现异步工具调用。核心思想是:当模型规划出的多个 Action 之间没有数据依赖时,将它们放入一个任务列表,然后用 asyncio.gather() 并发执行。

import asyncio

async def execute_tool_async(tool_name, tool_input):
    # 模拟一个耗时的工具调用,比如网络请求
    await asyncio.sleep(1)
    return f"Result of {tool_name} with {tool_input}"

async def parallel_agent_phase(independent_actions):
    """并行执行一组独立的工具调用。"""
    tasks = []
    for action in independent_actions:
        task = execute_tool_async(action['name'], action['input'])
        tasks.append(task)

    results = await asyncio.gather(*tasks, return_exceptions=True)
    # 处理结果,合并观察...
    return combined_observation

# 在主循环中,需要判断哪些Action可以并行。
# 这需要更复杂的任务规划(Planner)模块来分析任务间的依赖图(DAG)。

实现一个通用的并行编排器是复杂的,它涉及到 任务规划(Planning) ,即自动分析用户指令,拆解出子任务并识别其依赖关系,生成一个有向无环图(DAG)。这通常是LangGraph这类框架的核心价值所在。在我们的手写项目中,可以先从简单的、预定义的并行模式开始。

4.3 记忆与上下文管理优化

我们的简单Agent只在单次会话中维护了对话历史。但一个实用的Agent可能需要:

  • 长上下文管理 :处理很长的对话或文档时,需要智能地压缩或总结历史信息,以避免超出模型的上下文窗口。
  • 持久化记忆 :将重要的交互结果(如用户偏好、项目状态)存储到数据库或向量库中,在后续会话中读取,实现“记忆”功能。
  • 短期记忆(Working Memory) :在单次任务中,临时存储中间变量和状态,供后续步骤使用。

一个常见的模式是引入“记忆”模块,它可以是简单的键值存储,也可以是向量数据库,用于存储和检索历史交互的嵌入(Embeddings)。

5. 避坑指南与实战经验

在开发和调试Agent的过程中,我踩过不少坑,这里分享几个最关键的经验。

1. 提示词工程是成败的关键 Agent的行为几乎完全由提示词塑造。除了定义工具和格式,以下几点至关重要:

  • 明确角色和约束 :在系统提示词开头就强调“你是一个编程助手,只能使用提供的工具,不能直接回答问题”。
  • 提供高质量示例(Few-shot) :在提示词中包含2-3个完整的、正确的ReAct循环示例,这比任何文字描述都有效。
  • 引导分解思维 :鼓励模型“一步一步思考”,对于复杂问题,可以要求它先输出一个“计划(Plan)”,再逐步执行。
  • 处理模型“幻觉” :模型有时会“假装”调用了工具或“编造”观察结果。在提示词中必须严厉禁止,并强调“Observation”只能来自系统的真实反馈。

2. 工具设计的“粒度”陷阱 工具不是越强大越好。一个“万能”的 run_python_script 工具,远不如 calculate_code_complexity run_unit_tests format_code 几个细分工具来得可靠。细粒度工具让模型的决策更简单,也更容易进行错误处理和权限控制。 工具的设计过程,本质上是为模型定义一套它能理解的“原子操作”API。

3. 错误处理与鲁棒性

  • 工具调用失败 :网络超时、权限错误、资源不足等。Agent需要有重试机制(如最多3次),并且当工具失败时,模型应该能理解错误信息并尝试替代方案(降级策略)。
  • 模型输出解析失败 :即使使用函数调用,也可能遇到模型输出不符合预期的情况。代码中必须有健壮的异常捕获和fallback逻辑,比如尝试重新提问或返回一个友好的错误信息给用户,而不是让整个Agent崩溃。
  • 循环失控 :必须设置 max_iterations 硬性限制,防止因逻辑错误或模型“钻牛角尖”导致无限循环。

4. 评估与调试非常困难 传统的软件测试(单元测试、集成测试)对Agent不完全适用,因为其输出具有非确定性。你需要建立一套评估体系:

  • 端到端测试 :给定一组标准查询,检查最终答案是否符合预期。
  • 过程评估 :不仅看结果,还要检查Agent的“思考过程”(Thought)是否合理,工具调用序列是否正确。
  • 模糊测试 :用大量随机或边缘案例的查询来测试Agent的稳定性和安全性。

调试时,将每一步的 Thought Action Observation 都详细打印或记录到日志中,是定位问题最直接的方法。我们上面代码中的 print 语句就是最简单的调试工具。

手写一个Agent的过程,是一个不断与模型“对齐”其思考过程、不断打磨工具接口、不断强化系统鲁棒性的过程。它没有魔法,所有的“智能”都来自于清晰的定义、严谨的工程和大量的调试。当你亲手实现完这个简易的编程助手,再回头去看LangChain的 AgentExecutor 或LangGraph的 StateGraph ,你会恍然大悟,明白它们每一个设计选择背后的深意。这时,你就不再是框架的使用者,而是其设计思想的洞察者,具备了根据实际业务需求去定制、去创造新架构的能力。这,正是从“调用”走向“思考”的真正起点。

更多推荐