logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔5
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置基于Node的WIKI管理
前端知识16
HTML常用知识CSS常用知识CSS美化checkbox复选框JavaScript常用知识JavaScript格式化时间戳JavaScript窗口宽高处理JavaScript黑夜主题切换实现方案JavaScript数字转大写简易图片查看器TypeScript基础知识Threejs基础三要素Threejs网格辅助和轨道控制器Threejs物体绘制Electron基础使用Nodejs基础知识animate.css页面动画
Vue框架19
Vite的使用及扩展Vue3父子组件Vue3使用Marked解析MarkdownMermaid图表生成库初始化页面加载动画Axios表单提交二维码解决方案NProgress加载进度条Vue3动态菜单实现Vue3使用ECharts图表Vue3处理Excel导入导出keep-alive页面缓存及setup问题Element:文件上传Element:结合Pinia实现动态菜单Element:图片上传组件Element:自定义统一弹窗组件Element:表格自定义指令控制按钮显示(鉴权)可视化大屏使用缩放适配分辨率
UniApp15
UniApp的基础使用封装网络请求工具及文件上传uni-app的开发记录微信小程序分享原生文件上传Pinia取消滚动条(兼容小程序)tabbar消息数量显示scroll-view上滑到底部加载数据状态栏高度动态设配数据共享与传递uview-plus导航栏实现背景融合Wot UIWot UI实现顶部背景图融合uni-app x
Java基础知识10
基础知识面向对象Lambda表达式常用API常用知识积累try-with-resource注解反射多线程经纬度距离计算
SpringBoot31
application配置Maven创建聚合项目全局异常处理锁机制项目启动初始化数据方式邮件功能集成原生定时任务异步集成阿里云OSS阿里OSS预签名上传基于hutool读excelJSR303WebSocketWebSocket版AI接口流式调用Smart-Doc接口文档生成器application配置信息加密雪花算法工具AOP实现请求参数脱敏思路JWT生成Token及工具类SpringBoot默认JSON与对象转换若依框架:安装使用若依框架:优化和调整文件上传若依框架:管理后台页面优化若依框架:后端接口代码优化SpringAISpringBoot实现AI接口流式调用服务启动时创建MySQL连接自建项目工程树形结构处理工具微信支付代码微信手机号登录
SpringMVC14
跨域处理拦截器RESTful风格伪前后端分离Jackson转换器调整Thymeleaf基于拦截器做权限校验AOP打印接口请求响应日志AOP打印接口请求响应耗时文件上传和回显POST请求加解密实现(AES)POST请求加解密实现(RSA+AES)参数动态校验实现方案真实IP和归属地
MyBatis8
MyBatis基本使用与配置Mapper使用相关MaBatis多数据源配置MyBatisPlus数据统计类处理方案MyBatisPlus条件查询正向工程的实现(H2)mybatis-plus-join
SpringCloud15
Netflix:微服务与搭建Netflix:服务的消费与提供Netflix:EurekaNetflix:ActuatorNetflix:RibbonNetflix:FeignNetflix:HystrixNetflix:ZuulAlibaba:简介与搭建Alibaba:Nacos注册中心Alibaba:RibbonAlibaba:OpenFeignAlibaba:Nacos配置中心Alibaba:GetewayAlibaba:Sentinel
MySQL6
MySQL基础知识MySQL多表查询与事务MySQL常用函数及解决方案MySQL视图MySQL索引安装MySQL
Redis7
Redis介绍和安装Redis配置文件Redis持久化Redis集群Redis语法基础Redis相关问题及解决方案SpringBoot集成Redis使用记录
MongoDB10
Linux安装MongoDBMongoDB基础语法MongoTemplate及SpringBoot配置MongoTemplate中Update操作MongoTemplate中聚合查询MongoTemplate日期归档示例项目使用相关知识归纳地理位置存储与距离查询MongoDB副本集与事务获取类名和属性名工具类
其他数据库1
H2数据库
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
工具集合13
IDEAMavenGradleGitNginx安装Nginx配置VSCodeJMeter压测DockerOllamaRustFSPicGoObs录制
Linux知识11
Linux常用命令Jar启动脚本VirtualBox安装CentOSVirtualBox安装Ubuntu树莓派安装及使用frp内网穿透ArchLinux:基础系统安装ArchLInux:图形化界面安装ArchLinux:常用软件ArchLinux:深度优化ArchLinux:Niri
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

前提

需要大模型支持工具调用

工具提供

# 天气工具 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}")
前提
工具提供
关键点
代码示例