4. 实现多工具选择的Agent

任务目标

构建一个具备工具选择能力的Agent:解析用户自然语言请求,自动匹配并调用最合适的工具(搜索或计算器)完成任务。本案例聚焦工具选择与调用的核心机制,不涉及工具内部的具体实现细节。

核心实现

import re
from typing import Callable, Dict

# ---------- 工具定义 ----------
def search_tool(query: str) -> str:
    """模拟搜索引擎,返回格式化结果"""
    return f"[搜索] 关于'{query}'的模拟结果:找到3条相关条目"

def calculator_tool(expression: str) -> str:
    """安全计算器:仅支持数字与四则运算"""
    # 移除空白字符,仅保留数字、运算符、括号和小数点
    cleaned = re.sub(r'[^\d+\-*/().]', '', expression)
    if not cleaned or cleaned != expression.strip():
        return "[计算器] 错误:表达式包含非法字符"
    try:
        # 使用eval但已通过白名单过滤,仅限数学表达式
        result = eval(cleaned, {"__builtins__": {}}, {})
        return f"[计算器] {expression} = {result}"
    except Exception:
        return "[计算器] 错误:无法计算该表达式"

# ---------- 工具注册表 ----------
TOOLS: Dict[str, Dict[str, Callable]] = {
    "search": {
        "description": "搜索互联网信息,适用于查询新闻、知识、事实等",
        "keywords": ["搜索", "查找", "查询", "搜一下", "了解", "什么是"],
        "function": search_tool
    },
    "calculator": {
        "description": "执行数学计算,适用于算术表达式求值",
        "keywords": ["计算", "算一下", "多少", "等于", "加减乘除"],
        "function": calculator_tool
    }
}

# ---------- 工具选择逻辑 ----------
def select_tool(user_input: str) -> str:
    """根据用户输入选择最合适的工具名称"""
    # 优先级1:显式工具名匹配
    for tool_name in TOOLS:
        if tool_name in user_input.lower():
            return tool_name

    # 优先级2:关键词匹配(计算器优先,避免"查询计算结果"歧义)
    for tool_name, config in TOOLS.items():
        if tool_name == "calculator":
            continue
        for kw in config["keywords"]:
            if kw in user_input:
                return tool_name

    # 优先级3:检测数学表达式模式(如"3+5*2")
    if re.search(r'\d+\s*[\+\-\*/]\s*\d+', user_input):
        return "calculator"

    # 优先级4:计算器关键词兜底
    for kw in TOOLS["calculator"]["keywords"]:
        if kw in user_input:
            return "calculator"

    return "search"  # 默认搜索

# ---------- Agent主流程 ----------
def agent_execute(user_input: str) -> str:
    """Agent入口:选择工具并执行"""
    tool_name = select_tool(user_input)
    tool_config = TOOLS[tool_name]

    # 提取工具参数(简化:搜索取整句,计算器提取表达式)
    if tool_name == "calculator":
        # 提取第一个数学表达式
        match = re.search(r'[\d\s+\-*/().]+', user_input)
        param = match.group(0).strip() if match else user_input
    else:
        # 搜索:去除常见前缀词后作为查询
        param = re.sub(r'^(请|帮我|搜索|查找|查询|搜一下|了解一下)\s*', '', user_input)

    return tool_config["function"](param)

# ---------- 验证入口 ----------
if __name__ == "__main__":
    test_cases = [
        "请搜索人工智能最新进展",
        "计算 25*4+18/2",
        "帮我查一下Python的创始人",
        "3.5*2+7等于多少",
        "搜索量子计算是什么"
    ]
    for case in test_cases:
        print(f"输入: {case}")
        print(f"输出: {agent_execute(case)}\n")

运行与验证

运行方式:将代码保存为 agent_tool_selector.py,执行 python agent_tool_selector.py。

验证方法:观察输出是否符合以下预期(代码逻辑可唯一推出):

  • 包含"搜索"或"查询"的输入 → 调用搜索工具,返回[搜索]前缀结果
  • 包含"计算"或数学表达式(如25*4+18/2)→ 调用计算器,返回正确数值结果
  • 混合输入(如"搜索量子计算是什么")→ 正确识别为搜索意图

通过条件:5个测试用例均返回对应工具的结果,且计算器输出数值与手动计算一致(如25*4+18/2 = 109.0)。

关键说明

  • 工具选择采用多级匹配策略:显式工具名 → 搜索关键词 → 数学模式识别 → 计算器关键词 → 默认搜索,确保常见请求能正确路由。
  • 计算器通过正则白名单过滤非法字符,仅允许数字、四则运算符、括号和小数点,避免任意代码执行风险。
  • 搜索工具为模拟实现,实际工程中可替换为真实API调用(如SerpAPI、Bing Search),只需保持相同函数签名即可。
  • 参数提取采用简化策略:搜索取整句(去除前缀),计算器提取第一个数学表达式。更复杂的槽位填充可引入LLM或正则增强。