Spaces:
Paused
Paused
| # 测试 tool/function calling 功能 | |
| # 用法: ./scripts/test_tool_call.sh [TOKEN] [BASE_URL] | |
| # | |
| # TOKEN 可以是你的 z.ai token 或 "free"(匿名) | |
| # BASE_URL 默认 http://localhost:8000 | |
| TOKEN="${1:-free}" | |
| BASE_URL="${2:-http://localhost:8000}" | |
| PASS=0 | |
| FAIL=0 | |
| check() { | |
| local desc="$1" output="$2" pattern="$3" | |
| if echo "$output" | grep -qE "$pattern"; then | |
| echo " ✓ $desc" | |
| ((PASS++)) | |
| else | |
| echo " ✗ $desc (未匹配: $pattern)" | |
| ((FAIL++)) | |
| fi | |
| } | |
| check_not() { | |
| local desc="$1" output="$2" pattern="$3" | |
| if echo "$output" | grep -qE "$pattern"; then | |
| echo " ✗ $desc (不应包含: $pattern)" | |
| ((FAIL++)) | |
| else | |
| echo " ✓ $desc" | |
| ((PASS++)) | |
| fi | |
| } | |
| echo "=== 测试 Tool/Function Calling ===" | |
| echo "BASE_URL: $BASE_URL" | |
| echo "TOKEN: ${TOKEN:0:10}..." | |
| echo "" | |
| # ===== 测试 1: 带 tools 的流式请求 ===== | |
| echo "--- 测试 1: 流式 tool calling ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": true, | |
| "messages": [ | |
| {"role": "user", "content": "北京今天天气怎么样?请调用 get_weather 函数查询。"} | |
| ], | |
| "tools": [{ | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取指定城市的当前天气信息", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "location": { | |
| "type": "string", | |
| "description": "城市名称,如:北京" | |
| } | |
| }, | |
| "required": ["location"] | |
| } | |
| } | |
| }], | |
| "tool_choice": "auto" | |
| }' 2>&1) | |
| echo "$OUT" | head -20 | |
| check "包含 tool_calls" "$OUT" '"tool_calls"' | |
| check "包含函数名 get_weather" "$OUT" '"get_weather"' | |
| check "finish_reason 为 tool_calls" "$OUT" '"finish_reason"\s*:\s*"tool_calls"' | |
| check "包含 [DONE]" "$OUT" 'data: \[DONE\]' | |
| echo "" | |
| # ===== 测试 2: 带 tools 的非流式请求 ===== | |
| echo "--- 测试 2: 非流式 tool calling ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "帮我查一下上海的天气,用 get_weather 工具。"} | |
| ], | |
| "tools": [{ | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取指定城市的当前天气信息", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "location": { | |
| "type": "string", | |
| "description": "城市名称" | |
| } | |
| }, | |
| "required": ["location"] | |
| } | |
| } | |
| }], | |
| "tool_choice": "auto" | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "包含 tool_calls" "$OUT" '"tool_calls"' | |
| check "包含函数名 get_weather" "$OUT" '"get_weather"' | |
| check "finish_reason 为 tool_calls" "$OUT" '"finish_reason"\s*:\s*"tool_calls"' | |
| check_not "不包含 delta 字段" "$OUT" '"delta"' | |
| echo "" | |
| # ===== 测试 3: 多工具 ===== | |
| echo "--- 测试 3: 多工具非流式 ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "北京天气怎么样?现在几点了?请分别调用对应的工具。"} | |
| ], | |
| "tools": [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取天气", | |
| "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]} | |
| } | |
| }, | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "get_current_time", | |
| "description": "获取当前时间", | |
| "parameters": {"type": "object", "properties": {"timezone": {"type": "string"}}, "required": ["timezone"]} | |
| } | |
| } | |
| ], | |
| "tool_choice": "auto" | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "包含 tool_calls" "$OUT" '"tool_calls"' | |
| check "包含 get_weather" "$OUT" '"get_weather"' | |
| check "包含 get_current_time" "$OUT" '"get_current_time"' | |
| echo "" | |
| # ===== 测试 4: 完整多轮对话(tool result 回传)===== | |
| echo "--- 测试 4: 多轮对话 (tool result 回传) ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "北京天气怎么样?"}, | |
| { | |
| "role": "assistant", | |
| "content": "", | |
| "tool_calls": [{ | |
| "id": "call_abc123", | |
| "type": "function", | |
| "function": {"name": "get_weather", "arguments": "{\"location\":\"北京\"}"} | |
| }] | |
| }, | |
| { | |
| "role": "tool", | |
| "tool_call_id": "call_abc123", | |
| "content": "{\"temperature\": 25, \"condition\": \"晴\", \"humidity\": 40}" | |
| } | |
| ], | |
| "tools": [{ | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取天气", | |
| "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]} | |
| } | |
| }] | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "finish_reason 为 stop" "$OUT" '"finish_reason"\s*:\s*"stop"' | |
| check "包含 message 字段" "$OUT" '"message"' | |
| check "包含回复内容 (content 非空)" "$OUT" '"content"' | |
| echo "" | |
| # ===== 测试 5: 不带 tools 的普通请求(回归测试)===== | |
| echo "--- 测试 5: 不带 tools 的普通请求(回归)---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "你好,1+1等于几?"} | |
| ] | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "finish_reason 为 stop" "$OUT" '"finish_reason"\s*:\s*"stop"' | |
| check_not "不包含 tool_calls" "$OUT" '"tool_calls"' | |
| check_not "不包含 delta" "$OUT" '"delta"' | |
| echo "" | |
| # ===== 测试 6: -tools 模型后缀 ===== | |
| echo "--- 测试 6: -tools 模型后缀 (GLM-4.7-tools) ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7-tools", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "现在几点了?"} | |
| ] | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "包含 tool_calls 或正常回复" "$OUT" '"choices"' | |
| echo "(注意: -tools 模型自动注入内置工具,模型可能调用也可能不调用)" | |
| echo "" | |
| # ===== 测试 7: tool_choice required ===== | |
| echo "--- 测试 7: tool_choice=required ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "查询北京天气"} | |
| ], | |
| "tools": [{ | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取指定城市的当前天气信息", | |
| "parameters": { | |
| "type": "object", | |
| "properties": {"location": {"type": "string", "description": "城市名称"}}, | |
| "required": ["location"] | |
| } | |
| } | |
| }], | |
| "tool_choice": "required" | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "包含 tool_calls" "$OUT" '"tool_calls"' | |
| check "finish_reason 为 tool_calls" "$OUT" '"finish_reason"\s*:\s*"tool_calls"' | |
| echo "" | |
| # ===== 测试 8: tool_choice 指定具体函数 ===== | |
| echo "--- 测试 8: tool_choice 指定具体函数 ---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": false, | |
| "messages": [ | |
| {"role": "user", "content": "你好"} | |
| ], | |
| "tools": [{ | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "获取指定城市的当前天气信息", | |
| "parameters": { | |
| "type": "object", | |
| "properties": {"location": {"type": "string"}}, | |
| "required": ["location"] | |
| } | |
| } | |
| }], | |
| "tool_choice": {"type": "function", "function": {"name": "get_weather"}} | |
| }' 2>&1) | |
| echo "$OUT" | python3 -m json.tool 2>/dev/null || echo "$OUT" | |
| check "包含 get_weather" "$OUT" '"get_weather"' | |
| echo "" | |
| # ===== 测试 9: 流式普通请求回归 ===== | |
| echo "--- 测试 9: 流式不带 tools(回归)---" | |
| OUT=$(curl -sS "${BASE_URL}/v1/chat/completions" \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "GLM-4.7", | |
| "stream": true, | |
| "messages": [ | |
| {"role": "user", "content": "你好"} | |
| ] | |
| }' 2>&1) | |
| echo "$OUT" | head -10 | |
| check "finish_reason 为 stop" "$OUT" '"finish_reason"\s*:\s*"stop"' | |
| check "包含 [DONE]" "$OUT" 'data: \[DONE\]' | |
| check_not "不包含 tool_calls" "$OUT" '"tool_calls"' | |
| echo "" | |
| # ===== 汇总 ===== | |
| echo "================================" | |
| echo "=== 测试汇总 ===" | |
| echo " 通过: $PASS" | |
| echo " 失败: $FAIL" | |
| echo " 总计: $((PASS + FAIL))" | |
| echo "================================" | |
| echo "" | |
| echo "检查要点:" | |
| echo " 1. 测试 1/2: 响应中应有 tool_calls 字段和 finish_reason=tool_calls" | |
| echo " 2. 测试 3: 应返回多个 tool_calls(get_weather 和 get_current_time)" | |
| echo " 3. 测试 4: 模型应基于 tool result 生成自然语言回复,finish_reason=stop" | |
| echo " 4. 测试 5/9: 不带 tools 时正常返回文本,无 tool_calls 字段" | |
| echo " 5. 测试 6: -tools 后缀会自动注入内置工具(模型可能触发也可能不触发)" | |
| echo " 6. 测试 7: tool_choice=required 应强制模型调用工具" | |
| echo " 7. 测试 8: tool_choice 指定函数名应调用该函数" | |
| echo " 8. 查看服务端日志中的 [ToolCall] 行,确认上游返回的原始格式" | |
| if [ "$FAIL" -gt 0 ]; then | |
| exit 1 | |
| fi | |