跳转至

第三章:多工具协作

多工具场景

在实际应用中,往往需要多个工具协作完成复杂任务:

用户:帮我查一下北京明天的天气,如果会下雨就发邮件提醒我

需要工具:
1. get_weather - 查询天气
2. send_email - 发送邮件

定义多个工具

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"},
                    "date": {"type": "string", "description": "日期,如:今天、明天"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "发送电子邮件",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string", "description": "收件人邮箱"},
                    "subject": {"type": "string", "description": "邮件主题"},
                    "body": {"type": "string", "description": "邮件正文"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "搜索互联网信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {"type": "string", "description": "数学表达式"}
                },
                "required": ["expression"]
            }
        }
    }
]

工具执行器

创建一个统一的工具执行框架:

import json
from typing import Dict, Callable, Any
from openai import OpenAI

client = OpenAI()


class ToolExecutor:
    """工具执行器"""

    def __init__(self):
        self.tools: Dict[str, Callable] = {}
        self.tool_definitions: list = []

    def register(self, definition: dict, func: Callable):
        """注册工具"""
        name = definition["function"]["name"]
        self.tools[name] = func
        self.tool_definitions.append(definition)
        return self  # 支持链式调用

    def execute(self, tool_call) -> Any:
        """执行单个工具调用"""
        name = tool_call.function.name
        args = json.loads(tool_call.function.arguments)

        if name not in self.tools:
            return {"error": f"未知工具: {name}"}

        try:
            return self.tools[name](**args)
        except Exception as e:
            return {"error": str(e)}

    def execute_all(self, tool_calls: list) -> list:
        """执行多个工具调用"""
        return [self.execute(tc) for tc in tool_calls]


# 使用示例
executor = ToolExecutor()

# 注册工具
executor.register(
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    },
    get_weather  # 实际函数
)

executor.register(
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "发送邮件",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    },
    send_email
)

多轮对话管理

class ConversationManager:
    """对话管理器"""

    def __init__(self, tool_executor: ToolExecutor, model: str = "gpt-4o"):
        self.client = OpenAI()
        self.executor = tool_executor
        self.model = model
        self.messages: list = []

    def chat(self, user_message: str, max_turns: int = 10) -> str:
        """处理用户消息"""

        self.messages.append({"role": "user", "content": user_message})

        for _ in range(max_turns):
            # 调用模型
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                tools=self.executor.tool_definitions,
                tool_choice="auto"
            )

            message = response.choices[0].message
            self.messages.append(message)

            # 检查是否需要调用工具
            if not message.tool_calls:
                return message.content

            # 执行所有工具调用
            for tool_call in message.tool_calls:
                result = self.executor.execute(tool_call)

                self.messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result, ensure_ascii=False)
                })

        return "抱歉,处理您的请求时超出了最大轮次限制。"

    def reset(self):
        """重置对话"""
        self.messages = []


# 使用示例
manager = ConversationManager(executor)

# 复杂查询
answer = manager.chat(
    "查一下北京明天的天气,如果会下雨就发邮件到 user@example.com 提醒我带伞"
)
print(answer)

工具依赖处理

有些场景下,工具调用之间存在依赖关系:

# 用户:查一下苹果公司的股价,然后计算如果我买100股需要多少钱

# 工具调用顺序:
# 1. get_stock_price("AAPL") → 180.5
# 2. calculate("180.5 * 100") → 18050

模型会自动处理这种依赖:

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "查一下苹果公司的股价,然后计算如果我买100股需要多少钱"}
    ],
    tools=tools
)

# 第一轮:模型返回 get_stock_price 调用
# 执行后,第二轮:模型返回 calculate 调用(使用第一轮的结果)

条件执行

def smart_tool_conversation(user_message: str) -> str:
    """智能工具调用对话"""

    messages = [{"role": "user", "content": user_message}]

    while True:
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )

        message = response.choices[0].message
        messages.append(message)

        if not message.tool_calls:
            return message.content

        # 处理工具调用
        for tool_call in message.tool_calls:
            result = executor.execute(tool_call)

            # 添加条件判断逻辑
            if tool_call.function.name == "get_weather":
                weather_data = result
                # 可以在这里添加额外逻辑

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

工具调用日志

import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class LoggedToolExecutor(ToolExecutor):
    """带日志的工具执行器"""

    def execute(self, tool_call) -> Any:
        start_time = datetime.now()
        name = tool_call.function.name
        args = tool_call.function.arguments

        logger.info(f"[工具调用] {name}({args})")

        try:
            result = super().execute(tool_call)
            duration = (datetime.now() - start_time).total_seconds()
            logger.info(f"[工具结果] {name} 完成,耗时 {duration:.2f}s")
            return result
        except Exception as e:
            logger.error(f"[工具错误] {name} 失败: {e}")
            raise


# 使用示例
executor = LoggedToolExecutor()
# ... 注册工具 ...

工具权限控制

class PermissionControlledExecutor(ToolExecutor):
    """带权限控制的工具执行器"""

    def __init__(self):
        super().__init__()
        self.permissions: Dict[str, set] = {}  # 用户 -> 允许的工具

    def set_permissions(self, user_id: str, allowed_tools: set):
        """设置用户权限"""
        self.permissions[user_id] = allowed_tools

    def execute(self, tool_call, user_id: str = None) -> Any:
        """执行时检查权限"""
        name = tool_call.function.name

        if user_id and user_id in self.permissions:
            if name not in self.permissions[user_id]:
                return {"error": f"您没有权限使用工具: {name}"}

        return super().execute(tool_call)


# 使用示例
executor = PermissionControlledExecutor()

# 用户 A 只能查询天气
executor.set_permissions("user_a", {"get_weather", "search_web"})

# 用户 B 可以使用所有工具
executor.set_permissions("user_b", {"get_weather", "send_email", "search_web", "calculate"})

小结

本章学习了:

  • ✅ 多工具定义和管理
  • ✅ 工具执行器设计
  • ✅ 多轮对话管理
  • ✅ 工具依赖和条件执行
  • ✅ 日志和权限控制

下一章

第四章:自定义工具开发 - 学习如何开发自己的工具库。