#!/usr/bin/env node /** * Bookworm tool_use 诊断脚本 * 测试中转站是否正确透传 Anthropic tool_use 协议 * 用法: node diagnose-tooluse.js */ const https = require('https'); const http = require('http'); const API_KEY = process.env.ANTHROPIC_API_KEY; const BASE_URL = process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com'; if (!API_KEY) { console.error('[FAIL] ANTHROPIC_API_KEY 未设置'); process.exit(1); } console.log(`[INFO] 中转站: ${BASE_URL}`); console.log(`[INFO] API Key: ${API_KEY.substring(0, 8)}...`); console.log(''); // 测试 1: 非流式 tool_use async function testToolUse() { const url = new URL('/v1/messages', BASE_URL); const isHttps = url.protocol === 'https:'; const lib = isHttps ? https : http; const body = JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 200, tools: [{ name: 'get_disk_info', description: 'Get disk space information', input_schema: { type: 'object', properties: { drive: { type: 'string', description: 'Drive letter' } }, required: ['drive'] } }], tool_choice: { type: 'tool', name: 'get_disk_info' }, messages: [{ role: 'user', content: 'Check disk C:' }] }); return new Promise((resolve, reject) => { const req = lib.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, 'anthropic-version': '2023-06-01' } }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const json = JSON.parse(data); resolve({ status: res.statusCode, body: json }); } catch (e) { resolve({ status: res.statusCode, raw: data.substring(0, 500) }); } }); }); req.on('error', reject); req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); }); req.write(body); req.end(); }); } // 测试 2: 流式 tool_use async function testStreamToolUse() { const url = new URL('/v1/messages', BASE_URL); const isHttps = url.protocol === 'https:'; const lib = isHttps ? https : http; const body = JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 200, stream: true, tools: [{ name: 'get_disk_info', description: 'Get disk space information', input_schema: { type: 'object', properties: { drive: { type: 'string', description: 'Drive letter' } }, required: ['drive'] } }], tool_choice: { type: 'tool', name: 'get_disk_info' }, messages: [{ role: 'user', content: 'Check disk C:' }] }); return new Promise((resolve, reject) => { const req = lib.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, 'anthropic-version': '2023-06-01' } }, (res) => { let data = ''; let hasToolUseStart = false; let hasInputJsonDelta = false; res.on('data', chunk => { const text = chunk.toString(); data += text; if (text.includes('"type":"tool_use"') || text.includes('"type": "tool_use"')) hasToolUseStart = true; if (text.includes('input_json_delta')) hasInputJsonDelta = true; }); res.on('end', () => { resolve({ status: res.statusCode, hasToolUseStart, hasInputJsonDelta, sample: data.substring(0, 800) }); }); }); req.on('error', reject); req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); }); req.write(body); req.end(); }); } (async () => { // 测试 1: 非流式 console.log('=== 测试 1: 非流式 tool_use ==='); try { const r = await testToolUse(); if (r.status !== 200) { console.log(`[FAIL] HTTP ${r.status}`); console.log(JSON.stringify(r.body || r.raw, null, 2).substring(0, 300)); } else { const content = r.body?.content || []; const toolUseBlock = content.find(b => b.type === 'tool_use'); if (toolUseBlock) { console.log(`[PASS] 收到 tool_use block: name=${toolUseBlock.name}, id=${toolUseBlock.id}`); console.log(` input: ${JSON.stringify(toolUseBlock.input)}`); } else { console.log('[FAIL] 未收到 tool_use block!'); console.log(' content types:', content.map(b => b.type).join(', ') || '(empty)'); console.log(' stop_reason:', r.body?.stop_reason); if (content[0]?.text) console.log(' text:', content[0].text.substring(0, 200)); } } } catch (e) { console.log(`[FAIL] 请求失败: ${e.message}`); } console.log(''); // 测试 2: 流式 console.log('=== 测试 2: 流式 tool_use (Claude Code 实际使用模式) ==='); try { const r = await testStreamToolUse(); if (r.status !== 200) { console.log(`[FAIL] HTTP ${r.status}`); console.log(r.sample?.substring(0, 300)); } else { console.log(` tool_use block in stream: ${r.hasToolUseStart ? '[PASS]' : '[FAIL]'}`); console.log(` input_json_delta events: ${r.hasInputJsonDelta ? '[PASS]' : '[FAIL]'}`); if (!r.hasToolUseStart) { console.log(' stream sample:', r.sample?.substring(0, 400)); } } } catch (e) { console.log(`[FAIL] 请求失败: ${e.message}`); } console.log(''); console.log('=== 诊断结论 ==='); console.log('如果两项测试均 PASS: 中转站 tool_use 正常'); console.log('如果非流式 PASS 流式 FAIL: 中转站流式 SSE 处理有 bug, 联系管理员升级 NewAPI'); console.log('如果均 FAIL: 中转站不支持 Anthropic tool_use, 需开启原生 Anthropic 透传模式'); })();