一、理论部分

1. Agent 的核心闭环:思考 → 选工具 → 执行 → 反馈 → 再思考

与“聊天机器人”不同,Agent 的关键能力在于:遇到需要外部信息或计算时,不是直接凭空回答,而是能够主动调用工具(Tool)完成任务,并将工具结果纳入后续推理。

一个最小可用的 Agent,通常具备以下流程:

  • Reasoning(推理):判断下一步该做什么
  • Acting(行动):选择一个工具并给出参数
  • Observation(观察):获取工具执行结果
  • Loop(循环):把观察结果反馈给模型,直到得到最终答案

这就是经典的 ReAct(Reason + Act) 思路。

2. 工具(Tool)的本质:可被模型调用的函数

在工程实现上,工具就是一组可执行函数,例如:

  • get_weather(city):查询天气(本项目用模拟数据)
  • calculate(expression):计算表达式(本项目用 eval 演示)

为了让模型“可选”,我们通常维护一个 工具注册表(如 tools_map),实现“工具名 → 函数”的映射。模型输出工具名后,程序才能准确路由到对应函数执行。

3. 约束模型输出:固定格式 + Stop Sequence + 解析

要让模型稳定触发工具调用,必须对输出格式做约束。本项目采用如下组合策略:

  • 固定输出格式:要求模型按 思考/行动/行动输入/观察/最终答案 的格式输出
  • Stop Sequence:调用模型时设置 stop=["观察:"],让模型在准备输出“观察”之前停下来,等待程序填入真实工具结果
  • 正则解析:从模型输出中解析 行动行动输入,从而驱动工具执行

这三点共同解决一个问题:让模型的“行动意图”可被程序可靠识别与执行

4. 结果回灌:Observation 是 Agent 能多步完成任务的关键

当工具返回结果后,需要把结果以 观察: ... 的形式写回对话上下文,模型才可能进行下一步推理(例如:先查天气,再从温度提取数值,再做计算)。

因此,Observation 回灌是多步推理的必要条件。


二、实战部分

1. 项目目标

实现一个不依赖任何 Agent 框架的 Mini-ReAct Agent,支持:

  • 基于固定格式的 ReAct Loop
  • 工具选择与执行(天气查询、计算器)
  • 多步任务:能“先查再算”,直到输出最终答案

2. 运行准备

请确保本地已安装依赖并配置环境变量:

依赖安装(示例):

pip install -r requirements.txt

环境变量(示例):

  • DEEPSEEK_API_KEY
  • DEEPSEEK_BASE_URL

说明:

  • get_weather 为模拟数据,仅支持少数城市(如 北京/上海/Beijing/Shanghai 等)
  • calculate 使用 eval() 仅用于教学演示,不可用于生产环境

3. 主要代码(核心实现)

以下为项目核心代码(包含工具定义、Prompt、循环、解析与回灌逻辑;可直接运行):

import re
import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
)

# --- 1) Tools ---
def get_weather(city_name):
    """查询天气工具。实际项目中应调用真实 API,这里模拟返回。"""
    print(f"  [系统日志] 正在查询 {city_name} 的天气...")
    mock_data = {
        "Beijing": "25°C",
        "Shanghai": "22°C",
        "New York": "15°C",
        "北京": "25°C",
        "上海": "22°C",
    }
    return mock_data.get(city_name, "未知城市,无法查询天气")

def calculate(expression):
    """计算器工具。"""
    print(f"  [系统日志] 正在计算: {expression}")
    try:
        return str(eval(expression))
    except Exception as e:
        return f"计算错误: {str(e)}"

tools_map = {
    "get_weather": get_weather,
    "calculate": calculate,
}

# --- 2) ReAct Prompt ---
REACT_PROMPT = """
请尽你所能回答以下问题。你可以使用以下工具:

{tool_descs}

请使用以下格式:

问题: 你需要回答的输入问题
思考: 你应该总是思考接下来该做什么
行动: 你采取的行动,必须是 [{tool_names}] 之一
行动输入: 行动的输入参数
观察: 行动的结果
... (这个 思考/行动/行动输入/观察 的循环可以重复 N 次)
思考: 我现在知道最终答案了
最终答案: 针对原始问题的最终答案

开始!

问题: {query}
""".strip()

def build_tool_desc():
    desc = []
    for name, func in tools_map.items():
        desc.append(f"{name}: {func.__doc__}")
    return "\n".join(desc)

# --- 3) Agent Core ---
class MiniReActAgent:
    def __init__(self, model="deepseek-chat"):
        self.model = model

    def chat(self, query: str) -> str:
        tool_descs = build_tool_desc()
        tool_names = ", ".join(tools_map.keys())
        prompt = REACT_PROMPT.format(tool_descs=tool_descs, tool_names=tool_names, query=query)

        messages = [{"role": "user", "content": prompt}]
        step_count = 0
        max_steps = 10

        while step_count < max_steps:
            print(f"\n--- Step {step_count + 1} ---")

            response = client.chat.completions.create(
                model=self.model,
                messages=messages,
                stop=["观察:"],
            )

            llm_output = response.choices[0].message.content
            print(f"🤖 Agent: {llm_output}")
            messages.append({"role": "assistant", "content": llm_output})

            if "最终答案:" in llm_output:
                return llm_output.split("最终答案:")[-1].strip()

            action_match = re.search(r"行动:\s*(.*?)\n行动输入:\s*(.*)", llm_output, re.DOTALL)
            if action_match:
                action_name = action_match.group(1).strip()
                action_input = action_match.group(2).strip()

                if action_name in tools_map:
                    observation = tools_map[action_name](action_input)
                else:
                    observation = f"Error: Tool '{action_name}' not found."

                print(f"🔍 观察: {observation}")
                messages.append({"role": "user", "content": f"观察: {observation}"})
            else:
                messages.append({"role": "user", "content": "请继续。如果你有了答案,请输出 '最终答案:'。"})

            step_count += 1

        return "Task limit reached without final answer."

# --- 4) CLI Entrance ---
if __name__ == "__main__":
    agent = MiniReActAgent()

    print("🧠 Mini-ReAct Agent 已启动!")
    print("支持工具: get_weather (模拟), calculate (计算器)")
    print("示例: 查询北京的天气,并将温度数值乘以 10")

    while True:
        query = input("\n👤 请输入问题 (输入 exit 退出): ")
        if query.lower() in ["exit", "quit"]:
            break
        final_answer = agent.chat(query)
        print(f"\n✅ 最终答案: {final_answer}")

4. 如何运行

在项目根目录执行:

python stage_02_agent_principles/project_03_mini_react/main.py

可测试输入示例:

  • 15 * 15 + 400 等于多少?
  • 查询北京的天气
  • 查询北京的天气,并将温度数值乘以 10

5. 运行结果示例

下面展示一个“多步推理”的典型输出(示例为:先查北京天气,再把温度数值乘以 10):

🧠 Mini-ReAct Agent 已启动!
支持工具: get_weather (模拟), calculate (计算器)
示例: 查询北京的天气,并将温度数值乘以 10

👤 请输入问题 (输入 exit 退出): 查询北京的天气,并将温度数值乘以 10

--- Step 1 ---
🤖 Agent: 问题: 查询北京的天气,并将温度数值乘以 10
思考: 我需要先获取北京的温度,然后提取数值并进行计算。
行动: get_weather
行动输入: 北京

  [系统日志] 正在查询 北京 的天气...
🔍 观察: 25°C

--- Step 2 ---
🤖 Agent: 思考: 已获得温度 25°C,需要计算 25 * 10。
行动: calculate
行动输入: 25*10

  [系统日志] 正在计算: 25*10
🔍 观察: 250

--- Step 3 ---
🤖 Agent: 思考: 我现在知道最终答案了
最终答案: 北京当前温度约为 25°C,温度数值乘以 10 的结果为 250。

✅ 最终答案: 北京当前温度约为 25°C,温度数值乘以 10 的结果为 250。

6. 项目总结

  • 本项目用最小实现复现了 ReAct 的核心闭环:模型输出行动 → 程序执行工具 → 结果回灌 → 多轮推理直至最终答案。
  • “Stop Sequence + 格式约束 + 正则解析”是让模型可控调用工具的关键工程组合。
  • 该实现清晰展示了 Agent 的底层机制,为后续引入框架(LangChain/LangGraph)与更复杂的工具系统打下基础。
  • 安全提示:示例中的 eval() 仅用于教学演示,生产环境必须替换为安全计算方案(否则存在严重注入风险)。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐