实验元数据 (Meta Data)

实验编号/标题:例如:LLM-ReAct 搜索 Agent 实验

日期:2026-02-23

所属领域/标签:例如:#LLM #ReAct

🎯 实验前:假设与目标 (Plan)

当前问题 (Problem):理解 Function Calling / Tool Use、ReAct 推理循环、Agent 的核心工作机制

实验目标 (Objective):一个能使用搜索引擎和计算器回答复杂问题的 Agent。比如:“今年诺贝尔物理学奖得主的年龄加起来是多少?

🧪 实验中:执行步骤与变量 (Do)

记录“我到底做了什么”。如果是代码,粘贴关键片段;如果是实物操作,记录参数。

准备工作/工具:

项目结构

react-agent/
├── .env                     # API 密钥
├── src/
│   ├── __init__.py
│   ├── tools/               # 工具集合
│   │   ├── __init__.py
│   │   ├── base.py          # 工具基类与注册机制
│   │   ├── search.py        # 搜索工具
│   │   ├── calculator.py    # 计算器工具
│   │   └── weather.py       # 天气工具
│   ├── agent.py             # 核心 Agent 循环
│   ├── schema.py            # Tool Schema 定义
│   └── cli.py               # 命令行入口
├── tests/

控制变量 (Variable):

不变的量:(例:目标网址、抓取频率)

改变的量 (测试点):(例:User-Agent 字符串,IP代理池)

执行步骤 (Log):

工具基类与注册机制

为什么需要工具注册机制? 在真实 Agent 中,工具可能有几十个。手动维护工具列表容易出错。注册机制让"添加新工具"变成"写一个类并注册”。

# src/tools/base.py

from abc import ABC, abstractmethod

class Tool(ABC):
    """工具基类 - 所有工具必须继承此类
    为什么用抽象类?
    1. 强制每个工具实现 name, description, parameters, execute
    2. 统一接口,Agent 不需要关心工具的具体实现
    3. 便于自动生成 Tool Schema 发送给 LLM
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """工具名称(英文,作为 function name)"""
        ...

    @property
    @abstractmethod
    def description(self) -> str:
        """工具描述 - 这段代码会直接发给 LLM
        描述质量直接影响模型是否能正确选择和使用工具
        好的描述:明确说明工具能做什么,什么时候该用、输入输出是什么
        坏的描述:过于笼统或缺少使用场景说明
        """
        ...

    @property
    @abstractmethod
    def parameters(self) -> dict:
        """参数的 JSON schema - 告诉 LLM 这个工具接受什么参数

        遵循 JSON Schema 规范:
        {
            "type": "object",
            "properties": {
                "param_name": {
                    "type": "string",
                    "description": "参数说明"
                }
            },
            "required": ["param_name"]
        }
        """
        ...

    @abstractmethod
    def execute(self, **kwargs) -> str:
        """执行工具并返回字符串结果

        为什么返回字符串?
        因为结果要作为 tool_result 发回给 LLM,LLM 只理解文本
        即使是结构化数据,也应该序列化为字符串
        """
        ...

    def to_anthropic_schema(self) -> dict:
        """转换为 Anthropic API 要求的 tool schema 格式

        这个方法让每个工具能自动生成自己的 schema,
        不需要手动维护一个大的 tools 列表
        """
        return {
            "name": self.name,
            "description": self.description,
            "input_schema": self.parameters,
        }

class ToolRegistry:
    """工具注册中心

    功能:
    1. 注册/管理所有可用工具
    2. 根据工具名查找并执行工具
    3. 自动生成发给 LLM 的 tools 列表
    """

    def __init__(self):
        self._tools: dict[str, Tool] = {}

    def register(self, tool: Tool) -> None:
        """注册一个工具"""
        if tool.name in self._tools:
            raise ValueError(f"工具 '{tool.name}' 已注册,名称不能重复")
        self._tools[tool.name] = tool
        print(f"已注册工具:{tool.name}")

    def get(self, name: str) -> Tool:
        """根据名称获取工具"""
        if name not in self._tools:
            raise KeyError(
                f"工具 '{name}' 未注册。可用工具: {list(self._tools.keys())}"
            )
        return self._tools[name]

    def execute(self, name: str, **kwargs) -> str:
        """查找并执行工具

        这是 Agent Loop 中调用工具的统一入口。
        Agent 不需要知道具体工具的实现,只需要
        register.execute("search", query="xxx")
        """
        tool = self.get(name)
        try:
            result = tool.execute(**kwargs)
            return result
        except Exception as e:
            # 工具执行失败时,把错误信息返回给 LLM
            # LLM 可能会换一种方式重试
            return f"工具执行出错: {type(e).__name__}: {str(e)}"

    def get_all_schemas(self) -> list[dict]:
        """生成所有工具的 schema 列表 - 直接传给 Anthropic API 的 tools 参数"""
        return [tool.to_anthropic_schema() for tool in self._tools.values()]

    def list_tools(self) -> list[str]:
        """列出所有已注册的工具名"""
        return list(self._tools.keys())

实现具体工具

搜索工具:

# src/agent.py
from pyexpat.errors import messages

from anthropic import Anthropic

from src.tools.base import ToolRegistry


class ReActAgent:
    """ReAct Agent - 本项目的核心

    这个类实现了 Agent 的核心运行循环
    """

    def __init__(self,
                 tool_registry: ToolRegistry,
                 model: str = "claude-sonnet-4-20250514",
                 max_iterations: int = 100,
                 verbose: bool = True
                 ):
        self.client = Anthropic()
        self.client.base_url= "http://1.95.142.151:3000"
        self.model = model
        self.tools = tool_registry
        print(self.tools.get_all_schemas())
        # 防止无限循环
        self.max_iterations = max_iterations
        self.verbose = verbose

        # ---- System Prompt ----
        # 这个 Prompt 定义了 Agent 的行为模式
        self.system_prompt = """你是一个智能研究助手,能够使用工具来回答用户的问题。

## 工作方式
1. 仔细分析用户的问题,思考需要那些信息
2. 使用可用的工具获取所需的信息
3. 如果一次工具调用不够,继续调用直到获得足够信息
4. 基于收集到的所有信息,给出准确、完整的最终答案

## 重要规则
- 每次思考时,先说明你的推理过程(为什么要调用这个工具)
- 如果搜索结果不够好,尝试更换不同的关键词来重新搜索
- 计算任务务必使用 calculator 工具,不要心算
- 最终回答要引用具体的信息来源
- 如果无法找到所需信息,诚实地告诉用户"""

    # ===============================
    # 核心方法:Agent Loop
    # ===============================
    def run(self, user_query: str) -> str:
        """允许 Agent - 这是整个项目最核心的方法

        流程:
        1. 将用户问题发送给 LLM
        2. LLM 返回内容,检查时候有 tool_use
        3. 如果有 -> 执行工具 -> 把结果发回给 LLM -> 回到第 2 步
        4. 如果没有 -> LLM 已得出最终答案 -> 返回

        Args:
            user_query: 用户的问题

        Returns:
            Agent 的最终答案
        """

        # --- 初始化消息列表 ----
        # message 是 Agent 的工作记忆,记录整个推理过程
        messages = [
            {"role": "user", "content": user_query},
        ]

        if self.verbose:
            print(f"\n{'=' * 60}")
            print(f"🧑 用户: {user_query}")
            print(f"{'=' * 60}")

        # ---- Agent 循环 -----
        for iteration in range(self.max_iterations):
            if self.verbose:
                print(f"\n--- 第 {iteration + 1} 轮推理 ---")

            print(self.tools.get_all_schemas())
            # step 1: 调用 LLM
            response = self.client.messages.create(
                model=self.model,
                max_tokens=4096,
                system=self.system_prompt,
                tools=self.tools.get_all_schemas(),
                messages=messages,
            )

            if self.verbose:
                self._log_response(response)

            # Step 2: 检查停止原因
            #
            # stop_reason 的含义
            # - "end_turn" -> 模型认为可以回答,不需要工具
            # - "tool_use" -> 模型决定调用工具
            # - "max_tokens" -> 输出被截断
            if response.stop_reason == "end_turn":
                # 模型给出了最终回答
                final_answer = self._extract_text(response)
                if self.verbose:
                    print(f"\n{'=' * 60}")
                    print(f"🤖 最终回答: {final_answer}")
                    print(f"{'=' * 60}")
                    print(f"(共 {iteration + 1} 轮推理)")
                return final_answer

            if response.stop_reason == "tool_use":
                # Step 3: 将模型的回复(包含 tool_use 块)加入消息历史
                #
                # 关键:必须把完整的 response.content 加入 messages
                # 它包含模型的思考文本 AND 工具调用请求
                # 如果只加文本不加 tool_use,API 会报错
                #
                messages.append(
                    {
                        "role": "assistant",
                        "content": response.content, # 包含 TextBlock + ToolUseBlock
                    }
                )

                # Step 4: 执行每个工具调用,收集结果
                tool_results = self._execute_tool_calls(response)

                # Step 5: 将工具结果发回给 LLM
                #
                # 关键:tool_result 必须作为 "user" 角色的消息发送
                # 每个 tool_result 必须包含对应的 tool_use_id
                #
                messages.append({
                    "role": "user",
                    "content": tool_results,
                })
                # 回到循环顶部,LLM 将基于工具结果继续推理
                continue

            # 异常情况: max_tokens 截断
            if response.stop_reason == "max_tokens":
                return "回答被截断,请尝试更简短的问题。"

        # 超过最大迭代次数
        return "抱歉,我尝试了多次但无法得出满意的答案。请尝试换一种方式提问。"

    # ===========================================
    # 工具执行
    # ===========================================
    def _execute_tool_calls(self, response) -> list[dict]:
        """执行响应中的所有工具调用

        一个 response 可能包含多个 tool_use 块(并行工具调用)
        例如模型可能同时请求两个不同的关键词
        """
        tool_results = []

        for block in response.content:
            if block.type == "tool_use":
                tool_name = block.name
                tool_input = block.input
                tool_use_id = block.id # 必须用此 ID 关联结果

                if self.verbose:
                    print(f"  🔧 执行: {tool_name}({tool_input})")

                # 通过注册中心执行工具
                result = self.tools.execute(tool_name, **tool_input)

                if self.verbose:
                    # 截断过长的结果以便于阅读日志
                    display_result = result[:300] + "..." if len(result) > 300 else result
                    print(f"  📋 结果: {display_result}")

                # 构造 tool_result 结果
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,  # ⚠ 必须与 tool_use 的 id 匹配
                    "content": result,
                })
        return tool_results

    # ===========================================
    # 辅助方法
    # ===========================================
    def _extract_text(self, response) -> str:
        """从 response 中提取纯文本内容"""
        texts = []
        for block in response.content:
            if hasattr(block, "text"):
                texts.append(block.text)
        return "\n".join(texts)


    def _log_response(self, response) -> None:
        """打印 response 的详细内容(调试用)"""
        for block in response.content:
            if hasattr(block, "text") and block.text:
                print(f"  💭 思考: {block.text[:200]}")
            if block.type == "tool_use":
                print(f"  🎯 决定调用: {block.name}({block.input})")
        print(f"  ⏹️  停止原因: {response.stop_reason}")
        print(f"  📊 Token: {response.usage.input_tokens} in / {response.usage.output_tokens} out")

计算器工具

# src/tools/calculator.py
import ast
import operator

from src.tools.base import Tool


class Calculator(Tool):
    """安全计算器工具
    
    为什么不直接用 eval()?
    eval() 可以执行任意 Python 代码,存在严重安全风险。
    例如:eval("__import__('os').system('rm -rf /')")
    
    使用 ast.parse 只允许数学运算,这是安全实践
    """
    
    # 允许的运算符白名单
    ALLOWED_OPERATORS = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.FloorDiv: operator.floordiv,
        ast.Mod: operator.mod,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
    }
    
    @property
    def name(self) -> str:
        return "calculator"
    
    @property
    def description(self) -> str:
        return (
            "执行数学计算。支持加减乘除、幂运算、取模等基本运算。"
            "输入一个数学表达式字符串"
            "示例:'(91 + 77) * 2'、'3.14 * 5 ** 2'、'1000/12'"
        )
    
    @property
    def parameters(self) -> dict:
        return {
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "数学表达式,如 '91 + 77'、'3.14 * 5 ** 2'"
                }
            },
            "required": ["expression"],
        }
    
    def execute(self, expression: str) -> str:
        try:
            # 解析表达式为 AST
            tree = ast.parse(expression, model = "eval")
            result = self._safe_eval(tree.body)
            return f"{expression} = {result}"
        except (SyntaxError, TypeError):
            return f"无法计算表达式:'{expression}'。请使用有效的数学表达式。"
        except ZeroDivisionError:
            return "错误:除数不能为零"
        except Exception as e:
            return f"计算出错:{str(e)}"

天气工具:

# src/tools/weather.py
import requests

from src.tools.base import Tool


class WeatherTool(Tool):
    """天气查询工具 - 使用 Open-Meteo 免费 API"""

    # 常用城市坐标(生产环境应该使用地理编码 API)
    CITY_COORDS = {
        "beijing": (39.9042, 116.4074),
        "北京": (39.9042, 116.4074),
        "shanghai": (31.2304, 121.4737),
        "上海": (31.2304, 121.4737),
        "tokyo": (35.6762, 139.6503),
        "东京": (35.6762, 139.6503),
        "new york": (40.7128, -74.0060),
        "纽约": (40.7128, -74.0060),
        "london": (51.5074, -0.1278),
        "伦敦": (51.5074, -0.1278),
        "san francisco": (37.7749, -122.4194),
        "旧金山": (37.7749, -122.4194),
    }

    @property
    def name(self) -> str:
        return "weather"

    @property
    def description(self) -> str:
        return (
            "查询指定城市的当前天气信息,包括温度、湿度、风速等"
            "支持的城市:北京、上海、东京、纽约、伦敦、旧金山。"
            "输入城市名称(中文或英文均可)"
        )

    @property
    def parameters(self) -> dict:
        return {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如 '北京'、'Tokyo'"
                }
            },
            "required": ["city"]
        }

    def execute(self, city: str) -> str:
        city_lower = city.lower().strip()
        coords = self.CITY_COORDS.get(city_lower)

        if not coords:
            available = ", ".join(self.CITY_COORDS.keys())
            return f"不支持查询 '{city}' 的天气。支持的城市: {available}"

        lat, lon = coords
        try:
            url = "https://api.open-meteo.com/v1/forecast"
            params = {
                "latitude": lat,
                "longitude": lon,
                "current_weather": True,
                "timezone": "auto",
            }
            response = requests.get(url, params=params, timeout=10)
            data = response.json()
            weather = data["current_weather"]

            return (
                f"📍 {city} 当前天气:\n"
                f"  🌡️ 温度: {weather['temperature']}°C\n"
                f"  💨 风速: {weather['windspeed']} km/h\n"
                f"  🧭 风向: {weather['winddirection']}°\n"
                f"  ⏰ 更新时间: {weather['time']}"
            )

        except Exception as e:
            return f"获取天气信息失败: {str(e)}"

核心 Agent ReAct 循环

# src/agent.py
from pyexpat.errors import messages

from anthropic import Anthropic

from src.tools.base import ToolRegistry


class ReActAgent:
    """ReAct Agent - 本项目的核心

    这个类实现了 Agent 的核心运行循环
    """

    def __init__(self,
                 tool_registry: ToolRegistry,
                 model: str = "claude-sonnet-4-20250514",
                 max_iterations: int = 100,
                 verbose: bool = True
                 ):
        self.client = Anthropic()
        self.client.base_url= "http://1.95.142.151:3000"
        self.model = model
        self.tools = tool_registry
        # 防止无限循环
        self.max_iterations = max_iterations
        self.verbose = verbose

        # ---- System Prompt ----
        # 这个 Prompt 定义了 Agent 的行为模式
        self.system_prompt = """你是一个智能研究助手,能够使用工具来回答用户的问题。

## 工作方式
1. 仔细分析用户的问题,思考需要那些信息
2. 使用可用的工具获取所需的信息
3. 如果一次工具调用不够,继续调用直到获得足够信息
4. 基于收集到的所有信息,给出准确、完整的最终答案

## 重要规则
- 每次思考时,先说明你的推理过程(为什么要调用这个工具)
- 如果搜索结果不够好,尝试更换不同的关键词来重新搜索
- 计算任务务必使用 calculator 工具,不要心算
- 最终回答要引用具体的信息来源
- 如果无法找到所需信息,诚实地告诉用户"""

    # ===============================
    # 核心方法:Agent Loop
    # ===============================
    def run(self, user_query: str) -> str:
        """允许 Agent - 这是整个项目最核心的方法

        流程:
        1. 将用户问题发送给 LLM
        2. LLM 返回内容,检查时候有 tool_use
        3. 如果有 -> 执行工具 -> 把结果发回给 LLM -> 回到第 2 步
        4. 如果没有 -> LLM 已得出最终答案 -> 返回

        Args:
            user_query: 用户的问题

        Returns:
            Agent 的最终答案
        """

        # --- 初始化消息列表 ----
        # message 是 Agent 的工作记忆,记录整个推理过程
        messages = [
            {"role": "user", "content": user_query},
        ]

        if self.verbose:
            print(f"\n{'=' * 60}")
            print(f"🧑 用户: {user_query}")
            print(f"{'=' * 60}")

        # ---- Agent 循环 -----
        for iteration in range(self.max_iterations):
            if self.verbose:
                print(f"\n--- 第 {iteration + 1} 轮推理 ---")

            # step 1: 调用 LLM
            response = self.client.message.create(
                model=self.model,
                max_tokens=4096,
                system=self.system_prompt,
                tools=self.tools.get_all_schemas(),
                messages=messages,
            )

            if self.verbose:
                self._log_response(response)

            # Step 2: 检查停止原因
            #
            # stop_reason 的含义
            # - "end_turn" -> 模型认为可以回答,不需要工具
            # - "tool_use" -> 模型决定调用工具
            # - "max_tokens" -> 输出被截断
            if response.stop_reason == "end_turn":
                # 模型给出了最终回答
                final_answer = response._extract_text(response)
                if self.verbose:
                    print(f"\n{'=' * 60}")
                    print(f"🤖 最终回答: {final_answer}")
                    print(f"{'=' * 60}")
                    print(f"(共 {iteration + 1} 轮推理)")
                return final_answer

            if response.stop_reason == "tool_use":
                # Step 3: 将模型的回复(包含 tool_use 块)加入消息历史
                #
                # 关键:必须把完整的 response.content 加入 messages
                # 它包含模型的思考文本 AND 工具调用请求
                # 如果只加文本不加 tool_use,API 会报错
                #
                messages.append(
                    {
                        "role": "assistant",
                        "content": response.content, # 包含 TextBlock + ToolUseBlock
                    }
                )

                # Step 4: 执行每个工具调用,收集结果
                tool_results = self._execute_tool_calls(response)

                # Step 5: 将工具结果发回给 LLM
                #
                # 关键:tool_result 必须作为 "user" 角色的消息发送
                # 每个 tool_result 必须包含对应的 tool_use_id
                #
                messages.append({
                    "role": "user",
                    "content": tool_results,
                })
                # 回到循环顶部,LLM 将基于工具结果继续推理
                continue

            # 异常情况: max_tokens 截断
            if response.stop_reason == "max_tokens":
                return "回答被截断,请尝试更简短的问题。"

        # 超过最大迭代次数
        return "抱歉,我尝试了多次但无法得出满意的答案。请尝试换一种方式提问。"

    # ===========================================
    # 工具执行
    # ===========================================
    def _execute_tool_calls(self, response) -> list[dict]:
        """执行响应中的所有工具调用

        一个 response 可能包含多个 tool_use 块(并行工具调用)
        例如模型可能同时请求两个不同的关键词
        """
        tool_results = []

        for block in response.content:
            if block.type == "tool_use":
                tool_name = block.name
                tool_input = block.input
                tool_use_id = block.id # 必须用此 ID 关联结果

                if self.verbose:
                    print(f"  🔧 执行: {tool_name}({tool_input})")

                # 通过注册中心执行工具
                result = self.tools.execute(tool_name, **tool_input)

                if self.verbose:
                    # 截断过长的结果以便于阅读日志
                    display_result = result[:300] + "..." if len(result) > 300 else result
                    print(f"  📋 结果: {display_result}")

                # 构造 tool_result 结果
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,  # ⚠ 必须与 tool_use 的 id 匹配
                    "content": result,
                })
        return tool_results

    # ===========================================
    # 辅助方法
    # ===========================================
    def _extract_text(self, response) -> str:
        """从 response 中提取纯文本内容"""
        texts = []
        for block in response.content:
            if hasattr(block, "text"):
                texts.append(block.text)
        return "\n".join(texts)


    def _log_response(self, response) -> None:
        """打印 response 的详细内容(调试用)"""
        for block in response.content:
            if hasattr(block, "text") and block.text:
                print(f"  💭 思考: {block.text[:200]}")
            if block.type == "tool_use":
                print(f"  🎯 决定调用: {block.name}({block.input})")
        print(f"  ⏹️  停止原因: {response.stop_reason}")
        print(f"  📊 Token: {response.usage.input_tokens} in / {response.usage.output_tokens} out")

组装与命令行入门

import typer
from dotenv import load_dotenv

from src.agent import ReActAgent
from src.tools.base import ToolRegistry
from src.tools.caculator import CalculatorTool
from src.tools.search import WebSearchTool,TavilySearchTool
from src.tools.weather import WeatherTool

load_dotenv()


app = typer.Typer(help="ReAct 搜索 Agent")

def create_agent(model: str="claude-sonnet-4-20250514", verbose: bool=True) -> ReActAgent:
    """创建并配置 Agent"""
    # 注册工具
    registry = ToolRegistry()
    print("正在注册工具...")
    registry.register(CalculatorTool())
    registry.register(WeatherTool())
    registry.register(TavilySearchTool())
    print(f"共注册 {len(registry.list_tools())} 个工具\n")

    # 创建 Agent
    return ReActAgent(
        tool_registry=registry,
        model=model,
        verbose=verbose,
    )

@app.command()
def ask(
        question: str = typer.Argument(help="你的问题"),
        model: str = typer.Option("claude-sonnet-4-20250514", "--model", "-m"),
        quiet: bool = typer.Option(False, "--quiet", "-q", help="只显示最终答案"),
):
    """ 单词提问 """
    agent = create_agent(model=model, verbose=not quiet)
    answer = agent.run(question)
    if quiet:
        print(answer)

@app.command()
def chat(
        model: str = typer.Option("claude-sonnet-4-20250514", "--model", "-m")
):
    """交互式对话模式"""
    agent = create_agent(model=model)
    print("\n💬 进入交互模式(输入 'quit' 退出)\n")

    while True:
        try:
            question = input("🧑 你: ").strip()
            if question.lower() in ("quit", "exit", "q"):
                print("👋 再见!")
                break
            if not question:
                continue
            agent.run(question)
        except KeyboardInterrupt:
            print("\n👋 再见!")
            break

if __name__ == "__main__":
    app()

👁️ 实验后:现象与数据 (Check)

网络搜索工具

天气查询工具

🧠 深度复盘:分析与结论 (Act)

Q: 解释一下 Tool Use / Function Calling 的工作原理? A: LLM 本身不执行工具。我们在 API 请求中传入工具的 schema(名称、描述、参数定义),模型根据用户问题判断是否需要调用工具。如果需要,模型返回一个包含工具名和参数的 JSON 块(tool_use),我们的代码负责执行对应函数,然后把结果(tool_result)发回给模型。模型看到结果后决定是否需要继续调用工具或给出最终回答。整个过程是一个循环。

Q: ReAct 和 Chain-of-Thought 有什么区别? A: Chain-of-Thought 只让模型"想"(纯推理),ReAct 让模型"边想边做"——在推理过程中穿插工具调用获取真实信息。CoT 适合纯逻辑推理任务,ReAct 适合需要外部信息或执行操作的任务。实际中 Agent 几乎都用 ReAct 模式。

Q: Agent 循环中最容易出问题的地方是什么? A: 四个常见问题:(1) 无限循环——模型反复调用同一工具但得不到满意结果,需要 max_iterations 兜底;(2) 消息格式错误——tool_result 的 tool_use_id 必须与 tool_use 的 id 匹配,否则 API 报错;(3) 上下文膨胀——多轮工具调用后消息过长,超过上下文窗口或成本过高;(4) 工具描述不清——模型选错工具或传错参数。

Q: 如何设计好的 Tool Schema? A: 三个要点:(1) description 要明确说明工具的使用场景和限制,而不是只说功能(“当需要查询实时信息时使用” vs “搜索工具”);(2) 参数描述要包含示例(“搜索关键词,如 ‘2025 Nobel Prize’");(3) 工具数量多时要避免功能重叠,否则模型容易混淆选择。

Q: ReAct 模式有什么局限?适合什么场景? A: 局限:(1) 每一步都是贪心决策,缺乏全局规划能力;(2) 复杂任务可能需要很多轮迭代,成本高且可能迷失方向。适合场景:问题可以分解为"搜索-处理-回答"的信息检索任务、需要实时数据的问答。不适合的场景:需要先制定详细计划再执行的复杂任务(这时用 Plan-and-Execute 模式)。

下一步行动 (Next Actions):

✅ 验证通过,纳入标准流程。

🔄 验证失败,修改假设,开启下一次实验(EXP-002)。

❓ 产生新问题:[记录新问题]