主要功能更新: - 新增auto推理级别,完全遵循客户端原始请求参数 - 支持五档推理级别:auto/off/low/medium/high - auto模式零干预:不修改推理字段和anthropic-beta头 - 除gpt-5-codex外,所有模型默认设为auto模式 文档完善: - 更新核心功能说明,突出智能推理级别控制 - 新增auto推理模式详细说明和使用场景 - 添加推理级别对比表格和配置示例 - 增强FAQ部分,分场景解答推理相关问题 - 提供OpenAI和Anthropic模型字段对应关系 技术实现: - 更新getModelReasoning函数支持auto选项 - 完善所有transformer的auto模式处理逻辑 - 优化routes.js中直接转发端点的auto支持 - 确保auto模式下头信息和请求体完全透传
178 lines
5.9 KiB
JavaScript
178 lines
5.9 KiB
JavaScript
import { logDebug } from '../logger.js';
|
|
import { getSystemPrompt, getModelReasoning, getUserAgent } from '../config.js';
|
|
|
|
export function transformToOpenAI(openaiRequest) {
|
|
logDebug('Transforming OpenAI request to target OpenAI format');
|
|
|
|
const targetRequest = {
|
|
model: openaiRequest.model,
|
|
input: [],
|
|
store: false
|
|
};
|
|
|
|
// Only add stream parameter if explicitly provided by client
|
|
if (openaiRequest.stream !== undefined) {
|
|
targetRequest.stream = openaiRequest.stream;
|
|
}
|
|
|
|
// Transform max_tokens to max_output_tokens
|
|
if (openaiRequest.max_tokens) {
|
|
targetRequest.max_output_tokens = openaiRequest.max_tokens;
|
|
} else if (openaiRequest.max_completion_tokens) {
|
|
targetRequest.max_output_tokens = openaiRequest.max_completion_tokens;
|
|
}
|
|
|
|
// Transform messages to input
|
|
if (openaiRequest.messages && Array.isArray(openaiRequest.messages)) {
|
|
for (const msg of openaiRequest.messages) {
|
|
const inputMsg = {
|
|
role: msg.role,
|
|
content: []
|
|
};
|
|
|
|
// Determine content type based on role
|
|
// user role uses 'input_text', assistant role uses 'output_text'
|
|
const textType = msg.role === 'assistant' ? 'output_text' : 'input_text';
|
|
const imageType = msg.role === 'assistant' ? 'output_image' : 'input_image';
|
|
|
|
if (typeof msg.content === 'string') {
|
|
inputMsg.content.push({
|
|
type: textType,
|
|
text: msg.content
|
|
});
|
|
} else if (Array.isArray(msg.content)) {
|
|
for (const part of msg.content) {
|
|
if (part.type === 'text') {
|
|
inputMsg.content.push({
|
|
type: textType,
|
|
text: part.text
|
|
});
|
|
} else if (part.type === 'image_url') {
|
|
inputMsg.content.push({
|
|
type: imageType,
|
|
image_url: part.image_url
|
|
});
|
|
} else {
|
|
// Pass through other types as-is
|
|
inputMsg.content.push(part);
|
|
}
|
|
}
|
|
}
|
|
|
|
targetRequest.input.push(inputMsg);
|
|
}
|
|
}
|
|
|
|
// Transform tools if present
|
|
if (openaiRequest.tools && Array.isArray(openaiRequest.tools)) {
|
|
targetRequest.tools = openaiRequest.tools.map(tool => ({
|
|
...tool,
|
|
strict: false
|
|
}));
|
|
}
|
|
|
|
// Extract system message as instructions and prepend system prompt
|
|
const systemPrompt = getSystemPrompt();
|
|
const systemMessage = openaiRequest.messages?.find(m => m.role === 'system');
|
|
|
|
if (systemMessage) {
|
|
let userInstructions = '';
|
|
if (typeof systemMessage.content === 'string') {
|
|
userInstructions = systemMessage.content;
|
|
} else if (Array.isArray(systemMessage.content)) {
|
|
userInstructions = systemMessage.content
|
|
.filter(p => p.type === 'text')
|
|
.map(p => p.text)
|
|
.join('\n');
|
|
}
|
|
targetRequest.instructions = systemPrompt + userInstructions;
|
|
targetRequest.input = targetRequest.input.filter(m => m.role !== 'system');
|
|
} else if (systemPrompt) {
|
|
// If no user-provided system message, just add the system prompt
|
|
targetRequest.instructions = systemPrompt;
|
|
}
|
|
|
|
// Handle reasoning field based on model configuration
|
|
const reasoningLevel = getModelReasoning(openaiRequest.model);
|
|
if (reasoningLevel === 'auto') {
|
|
// Auto mode: preserve original request's reasoning field exactly as-is
|
|
if (openaiRequest.reasoning !== undefined) {
|
|
targetRequest.reasoning = openaiRequest.reasoning;
|
|
}
|
|
// If original request has no reasoning field, don't add one
|
|
} else if (reasoningLevel && ['low', 'medium', 'high'].includes(reasoningLevel)) {
|
|
// Specific level: override with model configuration
|
|
targetRequest.reasoning = {
|
|
effort: reasoningLevel,
|
|
summary: 'auto'
|
|
};
|
|
} else {
|
|
// Off or invalid: explicitly remove reasoning field
|
|
// This ensures any reasoning field from the original request is deleted
|
|
delete targetRequest.reasoning;
|
|
}
|
|
|
|
// Pass through other parameters
|
|
if (openaiRequest.temperature !== undefined) {
|
|
targetRequest.temperature = openaiRequest.temperature;
|
|
}
|
|
if (openaiRequest.top_p !== undefined) {
|
|
targetRequest.top_p = openaiRequest.top_p;
|
|
}
|
|
if (openaiRequest.presence_penalty !== undefined) {
|
|
targetRequest.presence_penalty = openaiRequest.presence_penalty;
|
|
}
|
|
if (openaiRequest.frequency_penalty !== undefined) {
|
|
targetRequest.frequency_penalty = openaiRequest.frequency_penalty;
|
|
}
|
|
if (openaiRequest.parallel_tool_calls !== undefined) {
|
|
targetRequest.parallel_tool_calls = openaiRequest.parallel_tool_calls;
|
|
}
|
|
|
|
logDebug('Transformed target OpenAI request', targetRequest);
|
|
return targetRequest;
|
|
}
|
|
|
|
export function getOpenAIHeaders(authHeader, clientHeaders = {}) {
|
|
// Generate unique IDs if not provided
|
|
const sessionId = clientHeaders['x-session-id'] || generateUUID();
|
|
const messageId = clientHeaders['x-assistant-message-id'] || generateUUID();
|
|
|
|
const headers = {
|
|
'content-type': 'application/json',
|
|
'authorization': authHeader || '',
|
|
'x-api-provider': 'azure_openai',
|
|
'x-factory-client': 'cli',
|
|
'x-session-id': sessionId,
|
|
'x-assistant-message-id': messageId,
|
|
'user-agent': getUserAgent(),
|
|
'connection': 'keep-alive'
|
|
};
|
|
|
|
// Pass through Stainless SDK headers with defaults
|
|
const stainlessDefaults = {
|
|
'x-stainless-arch': 'x64',
|
|
'x-stainless-lang': 'js',
|
|
'x-stainless-os': 'MacOS',
|
|
'x-stainless-runtime': 'node',
|
|
'x-stainless-retry-count': '0',
|
|
'x-stainless-package-version': '5.23.2',
|
|
'x-stainless-runtime-version': 'v24.3.0'
|
|
};
|
|
|
|
// Copy Stainless headers from client or use defaults
|
|
Object.keys(stainlessDefaults).forEach(header => {
|
|
headers[header] = clientHeaders[header] || stainlessDefaults[header];
|
|
});
|
|
|
|
return headers;
|
|
}
|
|
|
|
function generateUUID() {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
const r = Math.random() * 16 | 0;
|
|
const v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|