route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
300 lines • 12 kB
JavaScript
;
/**
* OpenAI Tool Call Analyzer
* 分析tool call被错误处理为文本的问题
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.analyzeToolCallIssue = analyzeToolCallIssue;
exports.analyzeAllCapturedFiles = analyzeAllCapturedFiles;
exports.generateAnalysisReport = generateAnalysisReport;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const logger_1 = require("@/utils/logger");
// 工具调用模式识别
const TOOL_CALL_PATTERNS = [
// 标准工具调用模式
/Tool call:\s*(\w+)\((.*?)\)(?:\n|$)/g,
// 带前缀的工具调用模式
/(?:⏺\s*)?Tool call:\s*(\w+)\((.*?)\)(?:\n|$)/g,
// JSON格式的工具调用
/"tool_call":\s*\{[^}]*"name":\s*"([^"]+)"[^}]*"arguments":\s*(\{.*?\})/g,
// 函数调用模式
/function_call\s*=\s*(\w+)\((.*?)\)/g
];
/**
* 分析捕获的数据,识别tool call处理问题
*/
function analyzeToolCallIssue(captureData) {
const analysis = {
requestId: captureData.requestId,
provider: captureData.provider,
model: captureData.model,
hasToolCallsInRequest: false,
hasToolCallsInResponse: false,
toolCallsInRequest: [],
toolCallsInResponse: [],
textContainsToolCallPattern: false,
extractedToolCalls: [],
issueType: 'none',
recommendations: []
};
try {
// 检查请求中的工具调用
if (captureData.request?.tools) {
analysis.hasToolCallsInRequest = true;
analysis.toolCallsInRequest = captureData.request.tools;
}
// 检查响应中的工具调用
if (captureData.response) {
// 检查标准响应格式中的工具调用
if (captureData.response.choices?.[0]?.message?.tool_calls) {
analysis.hasToolCallsInResponse = true;
analysis.toolCallsInResponse = captureData.response.choices[0].message.tool_calls;
}
// 检查流式响应格式
if (captureData.response.events) {
const toolCallEvents = captureData.response.events.filter((e) => e.choices?.[0]?.delta?.tool_calls);
if (toolCallEvents.length > 0) {
analysis.hasToolCallsInResponse = true;
analysis.toolCallsInResponse = toolCallEvents.map((e) => e.choices[0].delta.tool_calls);
}
}
// 检查响应文本中是否包含工具调用模式
const responseText = getResponseText(captureData.response);
if (responseText) {
analysis.textContainsToolCallPattern = checkForToolCallPatterns(responseText);
if (analysis.textContainsToolCallPattern) {
// 尝试提取工具调用
analysis.extractedToolCalls = extractToolCallsFromText(responseText);
// 如果在文本中发现了工具调用模式但没有正确解析为工具调用,则标记问题
if (!analysis.hasToolCallsInResponse && analysis.extractedToolCalls.length > 0) {
analysis.issueType = 'tool_as_text';
analysis.recommendations.push('工具调用被错误地处理为文本内容');
analysis.recommendations.push('需要在响应处理中添加工具调用模式检测');
analysis.recommendations.push('考虑使用缓冲式处理避免分段解析问题');
}
}
}
}
// 识别具体问题类型
if (analysis.hasToolCallsInRequest && !analysis.hasToolCallsInResponse &&
analysis.textContainsToolCallPattern) {
analysis.issueType = 'tool_as_text';
}
else if (analysis.hasToolCallsInRequest && !analysis.hasToolCallsInResponse) {
analysis.issueType = 'missing_tool_parsing';
}
}
catch (error) {
logger_1.logger.error('Failed to analyze tool call issue', {
error: error instanceof Error ? error.message : String(error),
requestId: captureData.requestId
});
}
return analysis;
}
/**
* 获取响应中的文本内容
*/
function getResponseText(response) {
try {
// 非流式响应
if (response.choices?.[0]?.message?.content) {
return response.choices[0].message.content;
}
// 流式响应中的文本事件
if (response.events) {
return response.events
.filter((e) => e.choices?.[0]?.delta?.content)
.map((e) => e.choices[0].delta.content)
.join('');
}
// Anthropic格式响应
if (response.content) {
return response.content
.filter((c) => c.type === 'text')
.map((c) => c.text)
.join('');
}
return '';
}
catch (error) {
return '';
}
}
/**
* 检查文本中是否包含工具调用模式
*/
function checkForToolCallPatterns(text) {
for (const pattern of TOOL_CALL_PATTERNS) {
const testPattern = new RegExp(pattern.source, pattern.flags);
if (testPattern.test(text)) {
return true;
}
}
return false;
}
/**
* 从文本中提取工具调用
*/
function extractToolCallsFromText(text) {
const toolCalls = [];
try {
// 使用第一个模式提取工具调用
const pattern = TOOL_CALL_PATTERNS[0];
const globalPattern = new RegExp(pattern.source, 'g');
let match;
while ((match = globalPattern.exec(text)) !== null) {
const [fullMatch, toolName, argsString] = match;
let toolInput = {};
try {
if (argsString.trim()) {
// 如果参数看起来像JSON,尝试解析
if (argsString.trim().startsWith('{') && argsString.trim().endsWith('}')) {
toolInput = JSON.parse(argsString);
}
else {
// 否则,假设它是一个简单的命令字符串
toolInput = { command: argsString };
}
}
}
catch (e) {
toolInput = { command: argsString };
}
toolCalls.push({
id: `extracted_${Date.now()}_${toolCalls.length}`,
name: toolName,
input: toolInput
});
}
}
catch (error) {
logger_1.logger.error('Failed to extract tool calls from text', {
error: error instanceof Error ? error.message : String(error)
});
}
return toolCalls;
}
/**
* 分析所有捕获的文件中的tool call问题
*/
function analyzeAllCapturedFiles() {
const analyses = [];
try {
const databaseDir = path.join(process.env.HOME || '', '.route-claude-code', 'database');
const capturesDir = path.join(databaseDir, 'captures', 'openai');
if (!fs.existsSync(capturesDir)) {
logger_1.logger.warn('OpenAI captures directory not found', { capturesDir });
return analyses;
}
const files = fs.readdirSync(capturesDir);
const jsonFiles = files.filter(file => file.endsWith('.json')).sort();
logger_1.logger.info('Analyzing captured files for tool call issues', {
fileCount: jsonFiles.length,
capturesDir
});
for (const file of jsonFiles) {
try {
const filepath = path.join(capturesDir, file);
const data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
// 只分析包含工具调用的请求
if (data.request?.tools || (data.response && getResponseText(data.response))) {
const analysis = analyzeToolCallIssue(data);
if (analysis.issueType !== 'none') {
analyses.push(analysis);
}
}
}
catch (error) {
logger_1.logger.error('Failed to analyze captured file', {
file,
error: error instanceof Error ? error.message : String(error)
});
}
}
}
catch (error) {
logger_1.logger.error('Failed to analyze all captured files', {
error: error instanceof Error ? error.message : String(error)
});
}
return analyses;
}
/**
* 生成分析报告
*/
function generateAnalysisReport(analyses) {
let report = '# OpenAI Tool Call Analysis Report\n\n';
const totalIssues = analyses.length;
const toolAsTextIssues = analyses.filter(a => a.issueType === 'tool_as_text').length;
const missingParsingIssues = analyses.filter(a => a.issueType === 'missing_tool_parsing').length;
report += `## Summary\n`;
report += `- Total issues found: ${totalIssues}\n`;
report += `- Tool calls as text: ${toolAsTextIssues}\n`;
report += `- Missing tool parsing: ${missingParsingIssues}\n\n`;
if (totalIssues > 0) {
report += `## Detailed Issues\n\n`;
for (const analysis of analyses) {
report += `### Request ID: ${analysis.requestId}\n`;
report += `- Provider: ${analysis.provider}\n`;
report += `- Model: ${analysis.model}\n`;
report += `- Issue Type: ${analysis.issueType}\n`;
report += `- Has tools in request: ${analysis.hasToolCallsInRequest}\n`;
report += `- Has tools in response: ${analysis.hasToolCallsInResponse}\n`;
report += `- Text contains tool call pattern: ${analysis.textContainsToolCallPattern}\n`;
if (analysis.extractedToolCalls.length > 0) {
report += `- Extracted tool calls: ${analysis.extractedToolCalls.length}\n`;
for (const toolCall of analysis.extractedToolCalls) {
report += ` - ${toolCall.name}: ${JSON.stringify(toolCall.input)}\n`;
}
}
if (analysis.recommendations.length > 0) {
report += `- Recommendations:\n`;
for (const recommendation of analysis.recommendations) {
report += ` - ${recommendation}\n`;
}
}
report += `\n`;
}
}
else {
report += `## No issues found\n\n`;
report += `All tool calls are properly handled.\n`;
}
return report;
}
//# sourceMappingURL=tool-call-analyzer.js.map