ChatGLM3-6B工具调用功能详解:扩展模型能力的实践指南
1. 为什么工具调用让ChatGLM3-6B真正“活”起来
你有没有试过让大模型帮你查天气、算数学题,或者实时获取股票价格?传统对话模型只能靠自己“猜”答案,而ChatGLM3-6B不一样——它能主动调用外部工具,像人类一样打开计算器、查询数据库、发送网络请求。这不是简单的API封装,而是模型真正理解了“什么时候该用什么工具”、“怎么把问题拆解给工具处理”、“如何把工具结果整合成自然回答”。
我第一次用工具调用功能时,输入“上海今天气温多少度”,模型没有凭空编造,而是自动调用天气查询工具,拿到真实数据后才组织语言回复。这种能力让模型从“知识库”变成了“智能助手”,从被动应答转向主动服务。
工具调用不是锦上添花的功能,而是ChatGLM3-6B区别于前代模型的核心升级之一。它让6B规模的模型在实际业务中有了更扎实的落地基础——不需要堆砌参数,而是通过精准的工具协同,解决真实世界的问题。
2. 工具调用的基础原理与工作流程
2.1 模型如何“决定”要调用工具
ChatGLM3-6B的工具调用不是硬编码的规则匹配,而是一套完整的推理决策链。当你提问时,模型内部会经历三个关键阶段:
第一阶段是意图识别:模型分析你的问题,判断是否需要外部信息。比如“37乘以8加7除2等于多少”明显是计算任务,“北京到上海的航班”则指向查询类工具。
第二阶段是工具选择:模型从已注册的工具列表中,选出最匹配的一个。这个过程依赖工具描述的清晰度——描述越具体,模型选得越准。比如一个叫“Calculator”的工具,如果描述只写“计算工具”,模型可能犹豫;但如果写“支持四则运算和括号优先级的数学计算器”,模型就会果断选择它。
第三阶段是参数生成:模型提取问题中的关键参数,填入工具所需的字段。比如查询天气时,它会准确提取“上海”作为城市参数;计算时则提取“37”“8”“7”“2”和运算符。
整个过程就像一个经验丰富的助理:先听懂你要什么,再想清楚该找谁帮忙,最后把事情交代得明明白白。
2.2 工具调用的完整生命周期
一次典型的工具调用包含四个环节,每个环节都有明确的输入输出:
- 用户输入:原始问题,如“帮我查一下苹果公司(AAPL)当前股价”
- 模型响应(Tool Call):模型不直接回答,而是返回一个结构化指令:
{ "name": "stock_price", "arguments": {"symbol": "AAPL"} } - 工具执行:你的代码接收到这个指令,调用真实接口获取股价数据,返回结果
- 模型整合(Final Answer):模型把工具返回的数据加工成自然语言:“苹果公司(AAPL)当前股价为192.45美元”
这个闭环设计保证了结果的真实性和可追溯性。你永远知道答案来自哪里,而不是模型“脑补”的幻觉内容。
3. 从零开始实现工具调用功能
3.1 环境准备与模型加载
我们先搭建一个轻量级运行环境。不需要高端显卡,一块RTX 3060(12G显存)就能流畅运行,甚至MacBook Pro的M1芯片也能胜任。
# 创建独立环境 python -m venv glm_env source glm_env/bin/activate # Linux/Mac # glm_env\Scripts\activate # Windows # 安装核心依赖(注意版本匹配) pip install transformers==4.30.2 torch>=2.0 sentencepiece accelerate gradio加载模型时,推荐使用量化版本节省显存:
from transformers import AutoTokenizer, AutoModel # 加载分词器 tokenizer = AutoTokenizer.from_pretrained( "THUDM/chatglm3-6b", trust_remote_code=True ) # 加载4位量化模型(约6GB显存) model = AutoModel.from_pretrained( "THUDM/chatglm3-6b", trust_remote_code=True, device='cuda' ).quantize(4).cuda() model = model.eval()如果你只有CPU,把.cuda()换成.float(),内存需求约32GB,响应会慢些但完全可用。
3.2 定义你的第一个工具:简易计算器
工具的本质就是一段Python函数,加上清晰的描述。我们从最简单的计算器开始:
import json import re def simple_calculator(expression: str) -> str: """ 执行基础四则运算表达式 支持 + - * / 和括号,如 "37*8+7/2" 返回计算结果字符串 """ try: # 安全计算:只允许数字、运算符和括号 if not re.match(r'^[0-9+\-*/().\s]+$', expression): return "错误:表达式包含非法字符" # 使用eval需谨慎,生产环境建议用ast.literal_eval或专用库 result = eval(expression) return str(round(result, 2)) # 保留两位小数 except Exception as e: return f"计算错误:{str(e)}" # 工具注册信息(供模型理解用) calculator_tool = { "name": "simple_calculator", "description": "执行基础四则运算的计算器,支持加减乘除和括号,例如 '37*8+7/2'", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "要计算的数学表达式,如 '12+3*4'" } }, "required": ["expression"] } }这个工具的关键在于description字段——它不是写给人看的文档,而是模型做决策的“说明书”。描述越贴近日常语言,模型调用越准确。
3.3 构建工具调用主循环
现在把模型、工具和执行逻辑串起来。核心思路是:让模型先思考是否需要工具,如果需要就执行,然后把结果喂回去让它总结。
def run_tool_calling(user_input: str, tools: list, tool_functions: dict): """ 工具调用主循环 user_input: 用户原始问题 tools: 工具描述列表(供模型参考) tool_functions: 工具函数字典 {name: function} """ history = [] # 第一轮:让模型判断是否需要工具 response, history = model.chat( tokenizer, user_input, history=history, tools=tools # 关键!把工具描述传给模型 ) # 检查模型是否返回了工具调用指令 if hasattr(response, 'tool_calls') and response.tool_calls: # 模型要求调用工具 for tool_call in response.tool_calls: tool_name = tool_call.name tool_args = json.loads(tool_call.arguments) print(f"正在调用工具: {tool_name},参数: {tool_args}") # 执行工具函数 if tool_name in tool_functions: tool_result = tool_functions[tool_name](**tool_args) print(f"工具返回: {tool_result}") # 把工具结果作为新消息喂给模型 history.append({ "role": "tool", "name": tool_name, "content": tool_result }) # 第二轮:让模型基于工具结果生成最终回答 final_response, history = model.chat( tokenizer, "", # 空输入,因为上下文已有工具结果 history=history ) return final_response else: return f"错误:未找到工具 '{tool_name}'" else: # 模型认为无需工具,直接回答 return response # 使用示例 tools = [calculator_tool] tool_functions = {"simple_calculator": simple_calculator} result = run_tool_calling( "37乘以8加7除2等于多少?", tools, tool_functions ) print("最终回答:", result)这段代码展示了工具调用的精髓:模型不直接输出答案,而是输出“行动指令”,由外部系统执行后再反馈给模型。这种分离设计让你可以随时替换工具实现,而不影响模型逻辑。
4. 实战:构建一个实用的天气查询工具
4.1 选择可靠的数据源
免费天气API有很多,但稳定性和调用限制差异很大。推荐使用Open-Meteo,它完全免费、无需密钥、支持全球数据,且有简洁的REST接口。
import requests import time def get_weather(city: str) -> str: """ 查询指定城市的当前天气 使用 Open-Meteo 免费API """ try: # 先通过地理编码获取经纬度(简化版,实际项目建议用缓存) geocode_url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=zh&format=json" geo_resp = requests.get(geocode_url, timeout=5) geo_resp.raise_for_status() geo_data = geo_resp.json() if not geo_data.get("results"): return f"未找到城市 '{city}' 的位置信息" lat = geo_data["results"][0]["latitude"] lon = geo_data["results"][0]["longitude"] name = geo_data["results"][0]["name"] # 获取天气数据 weather_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=temperature_2m,weather_code,wind_speed_10m&timezone=auto" weather_resp = requests.get(weather_url, timeout=5) weather_resp.raise_for_status() weather_data = weather_resp.json() current = weather_data["current"] # 天气代码映射(简化版) weather_codes = { 0: "晴朗", 1: "晴朗", 2: "晴朗", 3: "晴朗", 45: "雾", 48: "雾", 51: "毛毛雨", 53: "毛毛雨", 55: "毛毛雨", 61: "小雨", 63: "小雨", 65: "小雨", 71: "小雪", 73: "小雪", 75: "小雪", 80: "降雨", 81: "降雨", 82: "强降雨", 95: "雷暴" } weather_desc = weather_codes.get(current["weather_code"], "未知天气") temp = current["temperature_2m"] wind = current["wind_speed_10m"] return f"{name}当前天气:{weather_desc},温度{temp}°C,风速{wind}m/s" except requests.exceptions.RequestException as e: return f"网络请求失败:{str(e)}" except KeyError as e: return f"数据解析错误:缺少字段 {e}" except Exception as e: return f"查询异常:{str(e)}" # 注册天气工具 weather_tool = { "name": "get_weather", "description": "查询指定城市的当前天气状况,包括温度、天气类型和风速", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如 '北京'、'上海'" } }, "required": ["city"] } }4.2 让模型学会“追问”模糊问题
现实场景中,用户提问往往不精确。比如“今天天气怎么样”,没说城市。好的工具调用系统应该能主动追问,而不是报错。
我们稍作改进,在主循环中加入追问逻辑:
def smart_tool_calling(user_input: str, tools: list, tool_functions: dict): """支持追问的智能工具调用""" history = [] # 第一轮:模型判断 response, history = model.chat( tokenizer, user_input, history=history, tools=tools ) # 如果模型返回工具调用,直接执行 if hasattr(response, 'tool_calls') and response.tool_calls: return execute_tools(response, history, tools, tool_functions) # 如果模型返回普通文本,检查是否在追问 elif "哪个城市" in response or "请告诉我" in response or "具体是" in response: # 模型在追问,等待用户补充信息 print("模型需要更多信息:", response) follow_up = input("请输入城市名:") # 把追问和用户补充一起作为新对话 history.append({"role": "assistant", "content": response}) history.append({"role": "user", "content": follow_up}) # 第二轮:重新调用 response2, history = model.chat( tokenizer, "", history=history, tools=tools ) if hasattr(response2, 'tool_calls') and response2.tool_calls: return execute_tools(response2, history, tools, tool_functions) else: return response2 else: return response def execute_tools(response, history, tools, tool_functions): """执行工具调用并返回最终结果""" for tool_call in response.tool_calls: tool_name = tool_call.name tool_args = json.loads(tool_call.arguments) print(f"调用工具: {tool_name}({tool_args})") result = tool_functions[tool_name](**tool_args) print(f"结果: {result}") history.append({ "role": "tool", "name": tool_name, "content": result }) # 最终生成回答 final_response, _ = model.chat(tokenizer, "", history=history) return final_response # 测试模糊问题 tools = [calculator_tool, weather_tool] tool_functions = { "simple_calculator": simple_calculator, "get_weather": get_weather } result = smart_tool_calling( "今天天气怎么样?", tools, tool_functions ) print("回答:", result)这个改进让系统更接近真实助手机验:当信息不足时,它会礼貌追问,而不是冷冰冰地报错。
5. 常见问题与调试技巧
5.1 模型“假装”调用工具怎么办?
有时模型明明不需要工具,却返回tool_calls。这通常是因为工具描述太宽泛,或者问题本身有歧义。解决方法很简单:
- 收紧工具描述:把“查询信息的工具”改成“查询实时股票价格的工具”
- 添加否定示例:在few-shot提示中加入“不需要工具”的例子
- 设置调用阈值:在代码中检查模型置信度,低置信度时降级为普通对话
5.2 工具执行失败的优雅处理
网络波动、API限流、参数错误都可能导致工具执行失败。不要让整个流程崩溃,而是设计降级策略:
def robust_tool_call(tool_func, **kwargs): """带重试和降级的工具调用""" for attempt in range(3): try: result = tool_func(**kwargs) if "错误" not in result and "失败" not in result: return result except Exception as e: pass # 指数退避 time.sleep(2 ** attempt) # 三次失败后返回友好提示 return "暂时无法获取数据,请稍后再试" # 在execute_tools中替换调用方式 result = robust_tool_call(tool_functions[tool_name], **tool_args)5.3 调试工具调用的黄金三步法
- 检查工具描述:把
description字段读给自己听,问“如果我是模型,看到这个描述会想到什么场景?” - 验证参数提取:打印
tool_call.arguments,确认模型提取的参数是否合理 - 模拟工具返回:临时把工具函数改成
return "模拟的天气结果",排除网络问题
很多问题其实出在第一步——描述不够“人话”。记住,模型不是程序员,它理解的是生活场景,不是技术规格。
6. 进阶:构建多工具协同工作流
工具调用的真正威力在于组合。比如“帮我规划周末上海一日游”,需要:
- 天气工具 → 判断是否适合出行
- 景点工具 → 获取热门景点列表
- 交通工具 → 查询地铁线路
- 餐饮工具 → 推荐附近餐厅
实现多工具协同的关键是分层设计:
第一层:路由工具(Router Tool)
专门负责把复杂问题拆解成子任务,决定调用哪些工具、按什么顺序第二层:原子工具(Atomic Tools)
每个工具只做一件事,如get_weather、get_attractions,职责单一第三层:聚合器(Aggregator)
把多个工具结果整合成连贯回答,处理冲突(如天气不好但景点开放)
# 简化的路由工具示例 def route_query(query: str) -> list: """根据问题返回需要调用的工具列表""" query_lower = query.lower() tools_needed = [] if "天气" in query_lower or "气温" in query_lower: tools_needed.append("get_weather") if "景点" in query_lower or "旅游" in query_lower or "玩" in query_lower: tools_needed.append("get_attractions") if "吃饭" in query_lower or "餐厅" in query_lower or "美食" in query_lower: tools_needed.append("get_restaurants") return tools_needed or ["get_weather"] # 默认查天气 # 在主循环中使用 required_tools = route_query(user_input) active_tools = [t for t in all_tools if t["name"] in required_tools]这种设计让系统可扩展性强——新增一个工具,只需更新路由逻辑,不影响其他部分。
7. 总结:工具调用不是功能,而是思维方式的转变
用下来最深的感受是:工具调用彻底改变了我和模型的协作方式。以前是“我问,它答”,现在是“我们一起解决问题”。模型不再需要成为全知全能的神,而是优秀的项目经理——它擅长拆解问题、分配任务、整合结果,而把专业工作交给更可靠的工具。
部署过程中,我发现几个关键心得:工具描述的质量比代码实现更重要;参数校验比功能丰富更关键;优雅的错误处理比炫酷的效果更实用。这些都不是技术难题,而是对真实用户场景的理解深度。
如果你刚接触工具调用,建议从一个计算器开始,跑通整个流程;再加一个天气查询,体会多工具切换;最后尝试组合两个工具。每一步都不难,难的是保持对用户真实需求的敏感度——毕竟,所有技术的终点,都是让人用得更顺心。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。