bookworm-boot/diagnose-tooluse.js

180 lines
5.6 KiB
JavaScript
Raw Permalink Normal View History

#!/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 透传模式');
})();