From a64ced0e36cf0e63f461e018cc6aba49816c387c Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 16 Jul 2025 18:12:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=9D=9E=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E5=93=8D=E5=BA=94JSON=E8=A7=A3=E6=9E=90=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=92=8Cmax=5Ftokens=E5=8F=82=E6=95=B0=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复gzip压缩响应处理:添加zlib模块支持,正确解压缩Claude API响应 - 优化max_tokens验证机制:从硬编码改为动态读取model_pricing.json文件 - 改进错误处理:增强响应数据处理的容错性 - 修复SSE错误事件格式:统一字符串引号风格 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/services/claudeRelayService.js | 85 +++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/src/services/claudeRelayService.js b/src/services/claudeRelayService.js index 4c2f2899..3f6d1f4f 100644 --- a/src/services/claudeRelayService.js +++ b/src/services/claudeRelayService.js @@ -1,4 +1,7 @@ const https = require('https'); +const zlib = require('zlib'); +const fs = require('fs'); +const path = require('path'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { HttpsProxyAgent } = require('https-proxy-agent'); const claudeAccountService = require('./claudeAccountService'); @@ -91,6 +94,9 @@ class ClaudeRelayService { // 深拷贝请求体 const processedBody = JSON.parse(JSON.stringify(body)); + // 验证并限制max_tokens参数 + this._validateAndLimitMaxTokens(processedBody); + // 移除cache_control中的ttl字段 this._stripTtlFromCacheControl(processedBody); @@ -133,6 +139,49 @@ class ClaudeRelayService { return processedBody; } + // 🔢 验证并限制max_tokens参数 + _validateAndLimitMaxTokens(body) { + if (!body || !body.max_tokens) return; + + try { + // 读取模型定价配置文件 + const pricingFilePath = path.join(__dirname, '../../data/model_pricing.json'); + + if (!fs.existsSync(pricingFilePath)) { + logger.warn('⚠️ Model pricing file not found, skipping max_tokens validation'); + return; + } + + const pricingData = JSON.parse(fs.readFileSync(pricingFilePath, 'utf8')); + const model = body.model || 'claude-sonnet-4-20250514'; + + // 查找对应模型的配置 + const modelConfig = pricingData[model]; + + if (!modelConfig) { + logger.debug(`🔍 Model ${model} not found in pricing file, skipping max_tokens validation`); + return; + } + + // 获取模型的最大token限制 + const maxLimit = modelConfig.max_tokens || modelConfig.max_output_tokens; + + if (!maxLimit) { + logger.debug(`🔍 No max_tokens limit found for model ${model}, skipping validation`); + return; + } + + // 检查并调整max_tokens + if (body.max_tokens > maxLimit) { + logger.warn(`⚠️ max_tokens ${body.max_tokens} exceeds limit ${maxLimit} for model ${model}, adjusting to ${maxLimit}`); + body.max_tokens = maxLimit; + } + } catch (error) { + logger.error('❌ Failed to validate max_tokens from pricing file:', error); + // 如果文件读取失败,不进行校验,让请求继续处理 + } + } + // 🧹 移除TTL字段 _stripTtlFromCacheControl(body) { if (!body || typeof body !== 'object') return; @@ -251,18 +300,40 @@ class ClaudeRelayService { } const req = https.request(options, (res) => { - let responseData = ''; + let responseData = Buffer.alloc(0); res.on('data', (chunk) => { - responseData += chunk; + responseData = Buffer.concat([responseData, chunk]); }); res.on('end', () => { try { + let bodyString = ''; + + // 根据Content-Encoding处理响应数据 + const contentEncoding = res.headers['content-encoding']; + if (contentEncoding === 'gzip') { + try { + bodyString = zlib.gunzipSync(responseData).toString('utf8'); + } catch (unzipError) { + logger.error('❌ Failed to decompress gzip response:', unzipError); + bodyString = responseData.toString('utf8'); + } + } else if (contentEncoding === 'deflate') { + try { + bodyString = zlib.inflateSync(responseData).toString('utf8'); + } catch (unzipError) { + logger.error('❌ Failed to decompress deflate response:', unzipError); + bodyString = responseData.toString('utf8'); + } + } else { + bodyString = responseData.toString('utf8'); + } + const response = { statusCode: res.statusCode, headers: res.headers, - body: responseData + body: bodyString }; logger.debug(`🔗 Claude API response: ${res.statusCode}`); @@ -505,7 +576,7 @@ class ClaudeRelayService { if (!responseStream.destroyed) { // 发送 SSE 错误事件 - responseStream.write(`event: error\n`); + responseStream.write('event: error\n'); responseStream.write(`data: ${JSON.stringify({ error: errorMessage, code: error.code, @@ -528,7 +599,7 @@ class ClaudeRelayService { } if (!responseStream.destroyed) { // 发送 SSE 错误事件 - responseStream.write(`event: error\n`); + responseStream.write('event: error\n'); responseStream.write(`data: ${JSON.stringify({ error: 'Request timeout', code: 'TIMEOUT', @@ -635,7 +706,7 @@ class ClaudeRelayService { if (!responseStream.destroyed) { // 发送 SSE 错误事件 - responseStream.write(`event: error\n`); + responseStream.write('event: error\n'); responseStream.write(`data: ${JSON.stringify({ error: errorMessage, code: error.code, @@ -658,7 +729,7 @@ class ClaudeRelayService { } if (!responseStream.destroyed) { // 发送 SSE 错误事件 - responseStream.write(`event: error\n`); + responseStream.write('event: error\n'); responseStream.write(`data: ${JSON.stringify({ error: 'Request timeout', code: 'TIMEOUT',