工具使用:Agent 的双手

没有工具的 LLM 是一个"只会说话的大脑"——它知道很多,但什么都做不了。工具使用让 Agent 从"问答机器"变成"行动者"。

演进路径

2023: Hard-coded Functions(硬编码函数)
2024: Function Calling(OpenAI 标准化)
2025: Tool Use API(Anthropic 扩展)
2026: MCP + 自主工具发现(工具市场)

第一阶段:Function Calling

基本用法

from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如北京、上海"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京今天多少度?"}],
    tools=tools,
)

# LLM 返回工具调用
tool_call = response.choices[0].message.tool_calls[0]
# {"name": "get_weather", "arguments": {"city": "北京", "unit": "celsius"}}

# 执行工具
result = get_weather(**json.loads(tool_call.function.arguments))

# 把结果返回给 LLM
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "北京今天多少度?"},
        response.choices[0].message,
        {"role": "tool", "tool_call_id": tool_call.id, "content": result}
    ],
    tools=tools,
)

多工具并行调用

# 现代 LLM 支持一轮调用多个工具
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京和上海今天天气怎么样?"}],
    tools=tools,
)

# 返回两个 tool_calls: get_weather(北京) + get_weather(上海)
# 并行执行
results = await asyncio.gather(*[
    execute_tool(tc) for tc in response.choices[0].message.tool_calls
])

第二阶段:工具设计原则

原则一:描述要精确

# ❌ 差:描述模糊
{
    "name": "search",
    "description": "搜索内容"
}

# ✅ 好:描述精确
{
    "name": "search_web",
    "description": "在互联网上搜索给定查询,返回前10个结果的标题、URL和摘要。适用于查找最新信息、新闻、技术文档。",
    "parameters": {
        "properties": {
            "query": {
                "type": "string",
                "description": "搜索关键词,30字以内,去除无意义词"
            },
            "time_range": {
                "type": "string",
                "enum": ["day", "week", "month", "year", "all"],
                "description": "时间范围过滤"
            }
        }
    }
}

原则二:参数要简洁

# ❌ 差:参数太多
{
    "name": "send_email",
    "parameters": {
        "properties": {
            "to": {}, "cc": {}, "bcc": {},
            "subject": {}, "body": {},
            "attachments": {}, "priority": {},
            "format": {}, "encoding": {},
            "reply_to": {}, "sender_name": {}
        }
    }
}

# ✅ 好:只保留必要参数
{
    "name": "send_email",
    "parameters": {
        "properties": {
            "to": {"type": "string", "description": "收件人邮箱"},
            "subject": {"type": "string", "description": "邮件主题"},
            "body": {"type": "string", "description": "邮件正文"}
        },
        "required": ["to", "subject", "body"]
    }
}

原则三:错误信息要友好

async def execute_tool(tool_call):
    try:
        result = await tool_registry[tool_call.name](**tool_call.args)
        return {"status": "success", "data": result}
    except ValidationError as e:
        return {
            "status": "error",
            "error": f"参数错误:{e}",
            "hint": "请检查参数格式和必填项"
        }
    except Exception as e:
        return {
            "status": "error",
            "error": str(e),
            "hint": "如果重试仍失败,请尝试其他方法"
        }

第三阶段:MCP(Model Context Protocol)

MCP 是 Anthropic 提出的工具标准协议,让工具可以跨模型使用:

# MCP 服务器定义工具
from mcp import Server

server = Server("my-tools")

@server.tool()
async def search_database(query: str, limit: int = 10):
    """搜索数据库"""
    results = await db.search(query, limit)
    return results

@server.tool()
async def create_chart(data: list, chart_type: str = "bar"):
    """创建图表"""
    return ChartGenerator.create(data, chart_type)

# MCP 客户端自动发现工具
client = MCPClient()
client.connect("my-tools-server")

# Agent 自动获取可用工具列表
tools = await client.list_tools()
# 无需手动定义,Agent 知道有哪些工具可用

MCP vs Function Calling

特性Function CallingMCP
工具定义每次调用手动传服务器注册
工具发现手动维护列表自动发现
跨模型需要适配标准协议
工具共享代码复制服务器复用
生态各厂商自有开放标准

第四阶段:自主工具发现

class AutonomousToolUser:
    """Agent 自主发现和选择工具"""
    
    def __init__(self):
        self.tool_registry = ToolRegistry()
        self.tool_history = []
    
    async def solve(self, task):
        # 1. 分析任务需要什么能力
        needed = await self.analyze_needs(task)
        # {"search": True, "calculate": True, "visualize": False}
        
        # 2. 搜索可用工具
        candidates = await self.tool_registry.search(needed)
        
        # 3. 选择最佳工具
        selected = await self.select_tools(candidates, task)
        
        # 4. 执行
        for tool in selected:
            result = await self.use_tool(tool, task)
            if self.is_sufficient(result):
                return result
        
        # 5. 如果现有工具不够,尝试组合
        return await self.compose_tools(selected, task)

工具链组合

class ToolChain:
    """工具链式调用"""
    
    async def run(self, task):
        # Step 1: 搜索
        search_results = await self.use("web_search", query=task)
        
        # Step 2: 提取
        content = await self.use("extract_content", url=search_results[0].url)
        
        # Step 3: 分析
        analysis = await self.use("analyze", text=content, focus=task)
        
        # Step 4: 可视化
        chart = await self.use("create_chart", data=analysis.data)
        
        # Step 5: 输出
        return {"analysis": analysis, "chart": chart}

工具权限管理

class ToolPermission:
    """工具调用权限控制"""
    
    PERMISSIONS = {
        "read_only": ["search", "get", "list", "analyze"],
        "write_allowed": ["create", "update", "send", "delete"],
        "dangerous": ["execute_command", "delete_file", "format_disk"],
    }
    
    def check(self, tool_name, user_role):
        if tool_name in self.PERMISSIONS["dangerous"]:
            return user_role == "admin"
        
        if tool_name in self.PERMISSIONS["write_allowed"]:
            return user_role in ["admin", "editor"]
        
        return True  # read_only 工具所有人可用

常见问题

问题1:LLM 不调用工具

# 原因:工具描述不清晰或温度太低
# 解决:
# 1. 优化工具描述
# 2. 增加温度(temperature: 0.3-0.7)
# 3. 在 system prompt 中强调使用工具
system_prompt = """
你可以使用以下工具来帮助用户。
当用户的问题需要实时信息或计算时,必须使用工具,不要凭记忆回答。
"""

问题2:参数格式错误

# 原因:JSON 解析失败
# 解决:添加参数验证和修复
def parse_tool_args(args_str):
    try:
        return json.loads(args_str)
    except json.JSONDecodeError:
        # 尝试修复常见 JSON 错误
        fixed = args_str.replace("'", '"').replace("None", "null")
        return json.loads(fixed)

问题3:无限工具调用循环

# 解决:设置最大调用次数
class ToolLoopGuard:
    def __init__(self, max_calls=10):
        self.max_calls = max_calls
        self.call_count = 0
    
    def should_continue(self):
        self.call_count += 1
        return self.call_count <= self.max_calls

结论

工具使用是 Agent 的核心能力。2026 年的最佳实践:

  1. 工具描述是第一优先级——LLM 靠描述选择工具
  2. MCP 标准化——用开放协议,不绑死单一模型
  3. 权限分级——读/写/危险操作三级控制
  4. 错误友好——工具报错要让 LLM 能理解并修正
  5. 限制循环——最大工具调用次数保护

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。