UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

300 lines 12 kB
"use strict"; /** * 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