Trae-Agent中的Function Calling逻辑分析

6 阅读8分钟

Trae Agent Tools 核心逻辑详解

概述

Trae Agent 的 Tools 模块采用插件化架构,通过抽象基类 Tool 定义统一接口,支持内置工具和 MCP(Model Context Protocol)外部工具。Tools 是 Agent 与外部环境交互的核心机制。


核心架构

┌─────────────────────────────────────────────────────────────────┐
                      Tools 核心架构                              
├─────────────────────────────────────────────────────────────────┤
                                                                 
  ┌─────────────────────────────────────────────────────────┐   
    Tool 抽象基类 (base.py)                                     
    ├── name: 工具名称                                          
    ├── description: 工具描述                                   
    ├── parameters: 参数定义                                    
    └── execute(): 执行逻辑                                     
  └────────────────────┬────────────────────────────────────┘   
                                                                
           ┌───────────┼───────────┐                            
                                                             
                                                             
  ┌────────────┐ ┌──────────┐ ┌──────────┐                     
   内置工具      MCP工具     注册表                         
                                                         
    BashTool   MCPTool    tools_                        
    EditTool   (动态)     registry                      
    TaskDone                                            
  └────────────┘ └──────────┘ └──────────┘                     
                                                                 
└─────────────────────────────────────────────────────────────────┘

核心类详解

1. Tool 抽象基类

文件: trae_agent/tools/base.py

class Tool(ABC):
    """所有工具的抽象基类"""
    
    def __init__(self, model_provider: str | None = None):
        self._model_provider = model_provider
    
    @abstractmethod
    def get_name(self) -> str:
        """获取工具名称"""
        pass
    
    @abstractmethod
    def get_description(self) -> str:
        """获取工具描述"""
        pass
    
    @abstractmethod
    def get_parameters(self) -> list[ToolParameter]:
        """获取工具参数定义"""
        pass
    
    @abstractmethod
    async def execute(self, arguments: ToolCallArguments) -> ToolExecResult:
        """执行工具"""
        pass
    
    def json_definition(self) -> dict[str, object]:
        """获取工具的 JSON 定义(用于 LLM API)"""
        return {
            "name": self.name,
            "description": self.description,
            "parameters": self.get_input_schema(),
        }

核心属性:

属性类型说明
namestr工具唯一标识
descriptionstr工具功能描述
parameterslist[ToolParameter]参数定义列表
model_providerstr | None适配的模型提供商

2. ToolParameter 参数定义

@dataclass
class ToolParameter:
    """工具参数定义"""
    
    name: str                    # 参数名
    type: str | list[str]       # 参数类型
    description: str            # 参数描述
    enum: list[str] | None = None      # 枚举值
    items: dict[str, object] | None = None  # 数组项定义
    required: bool = True       # 是否必填

3. ToolCall 工具调用请求

@dataclass
class ToolCall:
    """LLM 返回的工具调用请求"""
    
    name: str                   # 工具名称
    call_id: str               # 调用唯一标识
    arguments: ToolCallArguments = field(default_factory=dict)  # 参数值
    id: str | None = None      # OpenAI 特有字段

4. ToolResult 工具执行结果

@dataclass
class ToolResult:
    """工具执行结果"""
    
    call_id: str               # 对应调用的 ID
    name: str                  # 工具名称
    success: bool              # 是否成功
    result: str | None = None  # 成功时的输出
    error: str | None = None   # 失败时的错误信息
    id: str | None = None      # OpenAI 特有字段

5. ToolExecutor 工具执行器

class ToolExecutor:
    """工具执行器,管理工具的执行"""
    
    def __init__(self, tools: list[Tool]):
        self._tools = tools
        self._tool_map: dict[str, Tool] | None = None
    
    @property
    def tools(self) -> dict[str, Tool]:
        """获取工具映射表(懒加载)"""
        if self._tool_map is None:
            self._tool_map = {
                self._normalize_name(tool.name): tool 
                for tool in self._tools
            }
        return self._tool_map
    
    async def execute_tool_call(self, tool_call: ToolCall) -> ToolResult:
        """执行单个工具调用"""
        normalized_name = self._normalize_name(tool_call.name)
        
        # 查找工具
        if normalized_name not in self.tools:
            return ToolResult(
                name=tool_call.name,
                success=False,
                error=f"Tool '{tool_call.name}' not found",
                call_id=tool_call.call_id,
            )
        
        tool = self.tools[normalized_name]
        
        # 执行工具
        try:
            exec_result = await tool.execute(tool_call.arguments)
            return ToolResult(
                name=tool_call.name,
                success=exec_result.error_code == 0,
                result=exec_result.output,
                error=exec_result.error,
                call_id=tool_call.call_id,
            )
        except Exception as e:
            return ToolResult(
                name=tool_call.name,
                success=False,
                error=f"Error: {str(e)}",
                call_id=tool_call.call_id,
            )
    
    async def parallel_tool_call(self, tool_calls: list[ToolCall]) -> list[ToolResult]:
        """并行执行多个工具调用"""
        return await asyncio.gather(*[
            self.execute_tool_call(call) for call in tool_calls
        ])
    
    async def sequential_tool_call(self, tool_calls: list[ToolCall]) -> list[ToolResult]:
        """串行执行多个工具调用"""
        return [await self.execute_tool_call(call) for call in tool_calls]

内置工具列表

1. BashTool - Bash 命令执行

文件: trae_agent/tools/bash_tool.py

class BashTool(Tool):
    """执行 Bash 命令的工具"""
    
    def get_name(self) -> str:
        return "bash"
    
    def get_description(self) -> str:
        return "Execute bash commands"
    
    def get_parameters(self) -> list[ToolParameter]:
        return [
            ToolParameter(
                name="command",
                type="string",
                description="The bash command to execute",
                required=True,
            ),
        ]
    
    async def execute(self, arguments: ToolCallArguments) -> ToolExecResult:
        command = arguments.get("command", "")
        # 使用 _BashSession 执行命令
        result = await self._session.run(command)
        return result

特点:

  • 维护持久的 Bash 会话
  • 支持超时控制(默认 120 秒)
  • 跨平台支持(Unix/Windows)

2. TextEditorTool - 文本编辑器

文件: trae_agent/tools/edit_tool.py

class TextEditorTool(Tool):
    """文件编辑工具"""
    
    def get_name(self) -> str:
        return "str_replace_based_edit_tool"
    
    def get_description(self) -> str:
        return """Custom editing tool for viewing, creating and editing files
* State is persistent across command calls
* Commands: view, create, str_replace, insert
* ...
"""
    
    def get_parameters(self) -> list[ToolParameter]:
        return [
            ToolParameter(
                name="command",
                type="string",
                description="The commands to run",
                required=True,
                enum=["view", "create", "str_replace", "insert"],
            ),
            ToolParameter(name="path", type="string", description="File path", required=True),
            ToolParameter(name="old_str", type="string", description="String to replace"),
            ToolParameter(name="new_str", type="string", description="Replacement string"),
            # ... 更多参数
        ]

支持的命令:

命令功能
view查看文件内容
create创建新文件
str_replace字符串替换
insert在指定行后插入

3. TaskDoneTool - 任务完成标记

文件: trae_agent/tools/task_done_tool.py

class TaskDoneTool(Tool):
    """标记任务完成的工具"""
    
    def get_name(self) -> str:
        return "task_done"
    
    def get_description(self) -> str:
        return "Report the completion of the task"
    
    def get_parameters(self) -> list[ToolParameter]:
        return []  # 无参数
    
    async def execute(self, arguments: ToolCallArguments) -> ToolExecResult:
        return ToolExecResult(output="Task done.")

用途: 通知 Agent 任务已完成。


4. JSONEditTool - JSON 编辑器

文件: trae_agent/tools/json_edit_tool.py

class JSONEditTool(Tool):
    """JSON 文件编辑工具"""
    
    def get_name(self) -> str:
        return "json_edit_tool"
    
    def get_parameters(self) -> list[ToolParameter]:
        return [
            ToolParameter(name="path", type="string", required=True),
            ToolParameter(name="json_path", type="string", required=True),
            ToolParameter(name="value", type="object", required=True),
        ]

5. SequentialThinkingTool - 顺序思考

文件: trae_agent/tools/sequential_thinking_tool.py

class SequentialThinkingTool(Tool):
    """顺序思考工具,用于复杂推理"""
    
    def get_name(self) -> str:
        return "sequentialthinking"

6. CKGTool - 代码知识图谱

文件: trae_agent/tools/ckg_tool.py

class CKGTool(Tool):
    """代码知识图谱工具"""
    
    def get_name(self) -> str:
        return "ckg"

工具注册表

文件: trae_agent/tools/__init__.py

# 工具注册表:名称 -> 工具类
tools_registry: dict[str, type[Tool]] = {
    "bash": BashTool,
    "str_replace_based_edit_tool": TextEditorTool,
    "json_edit_tool": JSONEditTool,
    "sequentialthinking": SequentialThinkingTool,
    "task_done": TaskDoneTool,
    "ckg": CKGTool,
}

使用方式:

from trae_agent.tools import tools_registry

# 通过名称动态创建工具实例
tool = tools_registry["bash"](model_provider="anthropic")

MCP 工具集成

MCPTool 类

文件: trae_agent/tools/mcp_tool.py

class MCPTool(Tool):
    """MCP 外部工具包装器"""
    
    def __init__(self, client, tool: mcp.types.Tool, model_provider: str | None = None):
        super().__init__(model_provider)
        self.client = client          # MCP 客户端
        self.tool = tool              # MCP 工具定义
    
    def get_name(self) -> str:
        return self.tool.name
    
    def get_description(self) -> str:
        return self.tool.description
    
    def get_parameters(self) -> list[ToolParameter]:
        """将 MCP 参数定义转换为 ToolParameter"""
        parameters = []
        input_schema = self.tool.inputSchema
        required = input_schema.get("required", [])
        properties = input_schema.get("properties", {})
        
        for name, prop in properties.items():
            parameters.append(ToolParameter(
                name=name,
                type=prop["type"],
                description=prop["description"],
                required=name in required,
            ))
        return parameters
    
    async def execute(self, arguments: ToolCallArguments) -> ToolExecResult:
        """通过 MCP 客户端调用工具"""
        try:
            output = await self.client.call_tool(self.get_name(), arguments)
            if output.isError:
                return ToolExecResult(error=output.content[0].text)
            else:
                return ToolExecResult(output=output.content[0].text)
        except Exception as e:
            return ToolExecResult(error=f"Error: {e}", error_code=-1)

与其他模块的交互

1. 与 Agent 模块的交互

文件: trae_agent/agent/base_agent.py

工具初始化
class BaseAgent:
    def __init__(self, agent_config: AgentConfig, ...):
        # 1. 从配置创建工具实例
        self._tools: list[Tool] = [
            tools_registry[tool_name](
                model_provider=self._model_config.model_provider.provider
            )
            for tool_name in agent_config.tools
        ]
        
        # 2. 创建工具执行器
        self._tool_caller = ToolExecutor(self._tools)
工具调用处理
async def _tool_call_handler(
    self, 
    tool_calls: list[ToolCall] | None, 
    step: AgentStep
) -> list[LLMMessage]:
    """处理 LLM 返回的工具调用"""
    
    messages: list[LLMMessage] = []
    
    # 1. 更新状态为 CALLING_TOOL
    step.state = AgentStepState.CALLING_TOOL
    step.tool_calls = tool_calls
    self._update_cli_console(step)
    
    # 2. 执行工具调用(并行或串行)
    if self._model_config.parallel_tool_calls:
        tool_results = await self._tool_caller.parallel_tool_call(tool_calls)
    else:
        tool_results = await self._tool_caller.sequential_tool_call(tool_calls)
    
    step.tool_results = tool_results
    self._update_cli_console(step)
    
    # 3. 将结果转换为消息
    for tool_result in tool_results:
        message = LLMMessage(role="user", tool_result=tool_result)
        messages.append(message)
    
    # 4. 反思结果
    reflection = self.reflect_on_result(tool_results)
    if reflection:
        step.state = AgentStepState.REFLECTING
        step.reflection = reflection
        messages.append(LLMMessage(role="assistant", content=reflection))
    
    return messages

交互流程:

┌─────────────┐     tool_calls      ┌─────────────────┐
│   Agent     │ ──────────────────▶ │  _tool_call_    │
│             │                     │    handler      │
│             │ ◀────────────────── │                 │
└─────────────┘    messages         └────────┬────────┘
                                             │
                              ┌──────────────┼──────────────┐
                              │              │              │
                              ▼              ▼              ▼
                        ┌─────────┐   ┌──────────┐   ┌──────────┐
                        │ ToolExe-│   │  状态更新 │   │  反思     │
                        │ cutor   │   │          │   │          │
                        └─────────┘   └──────────┘   └──────────┘

2. 与 LLM Client 模块的交互

文件: trae_agent/utils/llm_clients/anthropic_client.py

工具定义传递
class AnthropicClient(BaseLLMClient):
    def chat(self, messages, model_config, tools):
        # 1. 将 Tool 对象转换为 Anthropic 格式
        tool_schemas = []
        for tool in tools:
            if tool.name == "str_replace_based_edit_tool":
                # Anthropic 原生工具
                tool_schemas.append(
                    anthropic.types.ToolTextEditor20250429Param(...)
                )
            elif tool.name == "bash":
                tool_schemas.append(
                    anthropic.types.ToolBash20250124Param(...)
                )
            else:
                # 通用工具
                tool_schemas.append(
                    anthropic.types.ToolParam(
                        name=tool.name,
                        description=tool.description,
                        input_schema=tool.get_input_schema(),  # ← 关键调用
                    )
                )
        
        # 2. 调用 API
        response = self._create_anthropic_response(model_config, tool_schemas)
        
        # 3. 解析工具调用
        tool_calls = []
        for block in response.content:
            if block.type == "tool_use":
                tool_calls.append(ToolCall(
                    call_id=block.id,
                    name=block.name,
                    arguments=block.input,
                ))
        
        return LLMResponse(content=text, tool_calls=tool_calls)

交互流程:

┌─────────────┐    get_input_schema()   ┌─────────────┐
│   Tool      │ ───────────────────────▶ │  Anthropic  │
│             │                          │   Client    │
│             │ ◀─────────────────────── │             │
└─────────────┘    JSON Schema           └──────┬──────┘
                                                │
                                                ▼
                                         ┌─────────────┐
                                         │  Anthropic  │
                                         │    API      │
                                         └──────┬──────┘
                                                │
                                                ▼
                                         ┌─────────────┐
                                         │  ToolCall   │
                                         │   对象      │
                                         └─────────────┘

3. 与配置模块的交互

文件: trae_agent/utils/config.py

@dataclass
class AgentConfig:
    """Agent 配置包含工具列表"""
    
    tools: list[str]  # 工具名称列表
    allow_mcp_servers: list[str]  # 允许的 MCP 服务器
    mcp_servers_config: dict[str, MCPServerConfig]  # MCP 配置

配置示例:

agents:
    trae_agent:
        tools:
            - bash
            - str_replace_based_edit_tool
            - sequentialthinking
            - task_done
        allow_mcp_servers:
            - playwright
        mcp_servers:
            playwright:
                command: npx
                args: ["@playwright/mcp@0.0.27"]

4. 与 CLI 模块的交互

文件: trae_agent/cli.py

from trae_agent.tools import tools_registry

# 列出所有可用工具
for tool_name in tools_registry:
    tool = tools_registry[tool_name]()
    print(f"- {tool_name}: {tool.description}")

工具 Schema 生成

OpenAI 兼容格式

def get_input_schema(self) -> dict[str, object]:
    """生成 OpenAI Function Calling 格式的 Schema"""
    
    schema: dict[str, object] = {
        "type": "object",
        "properties": {},
        "required": [],
    }
    
    for param in self.parameters:
        param_schema = {
            "type": param.type,
            "description": param.description,
        }
        
        # OpenAI strict mode: 所有参数必须 required
        if self.model_provider == "openai":
            schema["required"].append(param.name)
            # 可选参数标记为 nullable
            if not param.required:
                param_schema["type"] = [param.type, "null"]
        elif param.required:
            schema["required"].append(param.name)
        
        if param.enum:
            param_schema["enum"] = param.enum
        
        schema["properties"][param.name] = param_schema
    
    # OpenAI strict mode 需要 additionalProperties: false
    if self.model_provider == "openai":
        schema["additionalProperties"] = False
    
    return schema

生成的 Schema 示例:

{
    "type": "object",
    "properties": {
        "command": {
            "type": "string",
            "description": "The bash command to execute"
        },
        "timeout": {
            "type": ["integer", "null"],
            "description": "Timeout in seconds"
        }
    },
    "required": ["command", "timeout"],
    "additionalProperties": false
}

Docker 工具执行器

文件: trae_agent/tools/docker_tool_executor.py

class DockerToolExecutor:
    """在 Docker 容器中执行工具"""
    
    def __init__(self, original_executor, docker_manager, ...):
        self._original_executor = original_executor
        self._docker_manager = docker_manager
        self._docker_tools = ["bash", "str_replace_based_edit_tool", "json_edit_tool"]
    
    async def sequential_tool_call(self, tool_calls: list[ToolCall]) -> list[ToolResult]:
        """在 Docker 中串行执行工具"""
        results = []
        for tool_call in tool_calls:
            if tool_call.name in self._docker_tools:
                # 在 Docker 中执行
                result = await self._execute_in_docker(tool_call)
            else:
                # 在宿主机执行
                result = await self._original_executor.execute_tool_call(tool_call)
            results.append(result)
        return results

核心文件索引

文件职责重要性
tools/base.pyTool 抽象基类和 ToolExecutor⭐⭐⭐
tools/__init__.py工具注册表⭐⭐⭐
tools/bash_tool.pyBash 命令执行⭐⭐⭐
tools/edit_tool.py文件编辑⭐⭐⭐
tools/task_done_tool.py任务完成标记⭐⭐⭐
tools/mcp_tool.pyMCP 工具包装⭐⭐⭐
tools/docker_tool_executor.pyDocker 执行环境⭐⭐
agent/base_agent.py工具调用处理⭐⭐⭐

总结

特性说明
架构插件化、抽象基类 + 具体实现
注册机制全局注册表,支持动态加载
MCP 支持原生支持外部工具服务
执行模式并行/串行可选
Schema 适配自动适配不同 LLM 提供商的格式
环境隔离支持 Docker 容器执行

核心设计原则:

  1. 统一接口: 所有工具继承 Tool 基类
  2. 声明式定义: 通过 ToolParameter 声明参数
  3. 自动转换: Schema 自动生成,适配不同 LLM
  4. 灵活扩展: 注册表模式支持动态添加工具

最后更新: 2026-03-16