分类
OpenAI工具调用
AI相关
2026-06-23
7

前提

需要大模型支持工具调用

工具提供

# 天气工具 https://open-meteo.com/ 贵阳经纬度 26.647 106.63
def get_weather(latitude, longitude):
    url = f"https://api.open-meteo.com/v1/forecast"
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'current': 'temperature_2m,relative_humidity_2m,wind_speed_10m',
        'hourly': 'temperature_2m,relative_humidity_2m,precipitation,wind_speed_10m,cloud_cover',
        'timezone': 'Asia/Shanghai',
        'forecast_days': 1
    }

    response = httpx.get(url, params=params)
    data = response.json()

    return f"当前温度: {data['current']['temperature_2m']}°C"


# 工具描述
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "根据指定坐标提供当地摄氏温度",
        "parameters": {
            "type": "object",
            "properties": {
                "latitude": {"type": "number"},
                "longitude": {"type": "number"},
            },
            "required": ["latitude", "longitude"],
            "additionalProperties": False # 禁止额外参数
        },
        "strict": True, # 严格模式:AI 必须严格遵守参数定义
    }
}]

关键点

completion = client.chat.completions.create(
    # 模型
    model=OPENAI_MODEL_NAME,
    # 消息
    messages=message_history,
    # 温度
    temperature=0.5,
    # 流式
    stream=True,
    # 工具
    tools=tools
)

首次AI模型会返回是否调用函数工具,当使用"role": "tool"时,必须提供tool_call_id来关联对应的工具调用

代码示例

from openai import OpenAI
import httpx
import json

OPENAI_BASE_URL = "https://api.deepseek.com"
OPENAI_API_KEY = "sk-xxxx"
OPENAI_MODEL_NAME = "deepseek-v4-pro"

# OPENAI_BASE_URL = "http://localhost:11434/v1"
# OPENAI_API_KEY = "ollama"
# OPENAI_MODEL_NAME = "qwen2.5-coder:7b"

client = OpenAI(
    api_key=OPENAI_API_KEY,
    base_url=OPENAI_BASE_URL
)


# 天气工具
def get_weather(latitude, longitude):
    url = f"https://api.open-meteo.com/v1/forecast"
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'current': 'temperature_2m,relative_humidity_2m,wind_speed_10m',
        'hourly': 'temperature_2m,relative_humidity_2m,precipitation,wind_speed_10m,cloud_cover',
        'timezone': 'Asia/Shanghai',
        'forecast_days': 1
    }

    response = httpx.get(url, params=params)
    data = response.json()

    return f"当前温度: {data['current']['temperature_2m']}°C"


# 工具描述
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "根据指定坐标提供当地摄氏温度",
        "parameters": {
            "type": "object",
            "properties": {
                "latitude": {"type": "number"},
                "longitude": {"type": "number"},
            },
            "required": ["latitude", "longitude"],
            "additionalProperties": False
        },
        "strict": True,
    }
}]

message_history = []


def parse_tool_call(message):
    """兼容解析不同模型的工具调用格式"""
    # 标准 OpenAI 格式
    if message.tool_calls:
        return message.tool_calls

    # Qwen2.5-Coder 格式:工具调用在 content 中以 JSON 形式返回
    if message.content and '"name": "get_weather"' in message.content:
        try:
            # 尝试解析 JSON
            content = message.content.strip()
            # 处理可能的 markdown 代码块
            if content.startswith('```json'):
                content = content[7:]
            if content.startswith('```'):
                content = content[3:]
            if content.endswith('```'):
                content = content[:-3]

            data = json.loads(content)
            if data.get('name')  'get_weather':
                # 构造标准格式的 tool_call
                class ToolCall:
                    def __init__(self, id, name, arguments):
                        self.id = id
                        self.function = type('obj', (object,), {
                            'name': name,
                            'arguments': json.dumps(arguments)
                        })()
                        self.type = 'function'

                return [ToolCall(
                    id="call_" + str(hash(str(data))),
                    name=data['name'],
                    arguments=data.get('arguments', {})
                )]
        except:
            pass

    return None


def chat(msg=None):
    # 如果有新消息,添加到历史
    if msg:
        message_history.append({
            "role": "user",
            "content": msg
        })

    # 调用模型
    completion = client.chat.completions.create(
        model=OPENAI_MODEL_NAME,
        messages=message_history,
        temperature=0.5,
        tools=tools
    )

    chatResp = completion
    message = chatResp.choices[0].message

    # 打印详细信息(兼容处理)
    print(chatResp.model_dump_json(indent=2))
    print(f"对话ID:{chatResp.id}")
    print(f"对话人:{message.role}")

    if hasattr(message, 'reasoning_content') and message.reasoning_content:
        print(f"思考:{message.reasoning_content}")

    print(f"回复内容:{message.content}")

    # 兼容获取工具调用
    tool_calls = parse_tool_call(message)
    print(f"工具:{tool_calls}")
    print(f"使用模型:{chatResp.model}")
    print(f"输入token:{chatResp.usage.prompt_tokens}")
    print(f"回复token:{chatResp.usage.completion_tokens}")
    print(f"交互总token:{chatResp.usage.total_tokens}")

    # 兼容处理 usage 中可能不存在的字段
    if hasattr(chatResp.usage, 'prompt_cache_hit_tokens') and chatResp.usage.prompt_cache_hit_tokens:
        print(f"命中缓存token:{chatResp.usage.prompt_cache_hit_tokens}")
    if hasattr(chatResp.usage, 'prompt_cache_miss_tokens') and chatResp.usage.prompt_cache_miss_tokens:
        print(f"未被命中token:{chatResp.usage.prompt_cache_miss_tokens}")

    print("=" * 60)

    # 检查是否有工具调用
    if tool_calls:
        # 添加助手的消息到历史
        message_history.append({
            "role": "assistant",
            "content": message.content
        })

        # 执行工具并添加结果
        for tool_call in tool_calls:
            if tool_call.function.name  "get_weather":
                func_arg = json.loads(tool_call.function.arguments)
                print(f"📊 调用天气API,参数: {func_arg}")
                func_result = get_weather(**func_arg)
                print(f"📊 工具结果: {func_result}")

                # 添加工具结果
                message_history.append({
                    "role": "user",  # Qwen 模型使用 user 角色继续对话
                    "content": f"天气查询结果:{func_result}"
                })

        # 继续对话,让模型根据工具结果生成最终回复
        print("\n🔄 生成最终回复...\n")
        chat()  # 不传参数,继续使用现有的历史

    else:
        # 没有工具调用,这是最终回复
        print(f"\n✅ 最终回复: {message.content}")

        # 保存助手回复到历史
        if message.content:
            # 避免重复添加(因为已经在上面添加过了)
            if not message_history or message_history[-1].get('content') != message.content:
                message_history.append({
                    "role": "assistant",
                    "content": message.content
                })

        return message.content


if __name__  '__main__':
    result = chat("你好,今天天气如何,纬度26.647, 经度106.63")
    print(f"\n最终结果: {result}")
目录
统计
21
分类
207
文档
3
坚持