fix: 修复非流式响应JSON解析错误和max_tokens参数校验

- 修复gzip压缩响应处理:添加zlib模块支持,正确解压缩Claude API响应
- 优化max_tokens验证机制:从硬编码改为动态读取model_pricing.json文件
- 改进错误处理:增强响应数据处理的容错性
- 修复SSE错误事件格式:统一字符串引号风格

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-16 18:12:19 +08:00
parent 66a53230e1
commit a64ced0e36

View File

@@ -1,4 +1,7 @@
const https = require('https'); const https = require('https');
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
const { SocksProxyAgent } = require('socks-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent');
const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpsProxyAgent } = require('https-proxy-agent');
const claudeAccountService = require('./claudeAccountService'); const claudeAccountService = require('./claudeAccountService');
@@ -91,6 +94,9 @@ class ClaudeRelayService {
// 深拷贝请求体 // 深拷贝请求体
const processedBody = JSON.parse(JSON.stringify(body)); const processedBody = JSON.parse(JSON.stringify(body));
// 验证并限制max_tokens参数
this._validateAndLimitMaxTokens(processedBody);
// 移除cache_control中的ttl字段 // 移除cache_control中的ttl字段
this._stripTtlFromCacheControl(processedBody); this._stripTtlFromCacheControl(processedBody);
@@ -133,6 +139,49 @@ class ClaudeRelayService {
return processedBody; 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字段 // 🧹 移除TTL字段
_stripTtlFromCacheControl(body) { _stripTtlFromCacheControl(body) {
if (!body || typeof body !== 'object') return; if (!body || typeof body !== 'object') return;
@@ -251,18 +300,40 @@ class ClaudeRelayService {
} }
const req = https.request(options, (res) => { const req = https.request(options, (res) => {
let responseData = ''; let responseData = Buffer.alloc(0);
res.on('data', (chunk) => { res.on('data', (chunk) => {
responseData += chunk; responseData = Buffer.concat([responseData, chunk]);
}); });
res.on('end', () => { res.on('end', () => {
try { 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 = { const response = {
statusCode: res.statusCode, statusCode: res.statusCode,
headers: res.headers, headers: res.headers,
body: responseData body: bodyString
}; };
logger.debug(`🔗 Claude API response: ${res.statusCode}`); logger.debug(`🔗 Claude API response: ${res.statusCode}`);
@@ -505,7 +576,7 @@ class ClaudeRelayService {
if (!responseStream.destroyed) { if (!responseStream.destroyed) {
// 发送 SSE 错误事件 // 发送 SSE 错误事件
responseStream.write(`event: error\n`); responseStream.write('event: error\n');
responseStream.write(`data: ${JSON.stringify({ responseStream.write(`data: ${JSON.stringify({
error: errorMessage, error: errorMessage,
code: error.code, code: error.code,
@@ -528,7 +599,7 @@ class ClaudeRelayService {
} }
if (!responseStream.destroyed) { if (!responseStream.destroyed) {
// 发送 SSE 错误事件 // 发送 SSE 错误事件
responseStream.write(`event: error\n`); responseStream.write('event: error\n');
responseStream.write(`data: ${JSON.stringify({ responseStream.write(`data: ${JSON.stringify({
error: 'Request timeout', error: 'Request timeout',
code: 'TIMEOUT', code: 'TIMEOUT',
@@ -635,7 +706,7 @@ class ClaudeRelayService {
if (!responseStream.destroyed) { if (!responseStream.destroyed) {
// 发送 SSE 错误事件 // 发送 SSE 错误事件
responseStream.write(`event: error\n`); responseStream.write('event: error\n');
responseStream.write(`data: ${JSON.stringify({ responseStream.write(`data: ${JSON.stringify({
error: errorMessage, error: errorMessage,
code: error.code, code: error.code,
@@ -658,7 +729,7 @@ class ClaudeRelayService {
} }
if (!responseStream.destroyed) { if (!responseStream.destroyed) {
// 发送 SSE 错误事件 // 发送 SSE 错误事件
responseStream.write(`event: error\n`); responseStream.write('event: error\n');
responseStream.write(`data: ${JSON.stringify({ responseStream.write(`data: ${JSON.stringify({
error: 'Request timeout', error: 'Request timeout',
code: 'TIMEOUT', code: 'TIMEOUT',