新增对 OpenAI Responses API 端点的部分支持,包括请求转换、流式响应处理和供应商适配。主要变更: - 新增 OpenAIResponsesApiService 核心服务实现 - 实现 Claude/Gemini 到 Responses API 的双向协议转换(能聊天,不能调用工具) - 添加流式响应状态管理和事件生成机制 - 扩展路由支持 /v1/responses 端点 - 更新文档说明配置方法和使用示例
123 lines
No EOL
4.9 KiB
JavaScript
123 lines
No EOL
4.9 KiB
JavaScript
import { ProviderStrategy } from '../provider-strategy.js';
|
|
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../common.js';
|
|
|
|
/**
|
|
* OpenAI Responses API strategy implementation.
|
|
* Migrated from Chat Completions API to Responses API.
|
|
*/
|
|
class ResponsesAPIStrategy extends ProviderStrategy {
|
|
extractModelAndStreamInfo(req, requestBody) {
|
|
const model = requestBody.model;
|
|
const isStream = requestBody.stream === true;
|
|
return { model, isStream };
|
|
}
|
|
|
|
extractResponseText(response) {
|
|
if (!response.output) {
|
|
return '';
|
|
}
|
|
|
|
// In Responses API, output is an array of items
|
|
for (const item of response.output) {
|
|
if (item.type === 'message' && item.content && item.content.length > 0) {
|
|
for (const content of item.content) {
|
|
if (content.type === 'output_text' && content.text) {
|
|
return content.text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
extractPromptText(requestBody) {
|
|
// In Responses API, input can be a string or array of items
|
|
if (typeof requestBody.input === 'string') {
|
|
return requestBody.input;
|
|
} else if (Array.isArray(requestBody.input)) {
|
|
// If input is an array of items/messages, get the last user content
|
|
const userInputItems = requestBody.input.filter(item =>
|
|
(item.role && item.role === 'user') ||
|
|
(item.type && item.type === 'message' && item.role === 'user') ||
|
|
(item.type && item.type === 'user')
|
|
);
|
|
|
|
if (userInputItems.length > 0) {
|
|
const lastInput = userInputItems[userInputItems.length - 1];
|
|
if (typeof lastInput.content === 'string') {
|
|
return lastInput.content;
|
|
} else if (Array.isArray(lastInput.content)) {
|
|
return lastInput.content.map(item => item.text || item.content || '').join('\n');
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
async applySystemPromptFromFile(config, requestBody) {
|
|
if (!config.SYSTEM_PROMPT_FILE_PATH) {
|
|
return requestBody;
|
|
}
|
|
|
|
const filePromptContent = config.SYSTEM_PROMPT_CONTENT;
|
|
if (filePromptContent === null) {
|
|
return requestBody;
|
|
}
|
|
|
|
// In Responses API, system instructions are typically passed in 'instructions' field
|
|
// or in the input array with role: 'system'
|
|
requestBody.instructions = requestBody.instructions || filePromptContent;
|
|
|
|
// If using instructions field is not desired, append to input array instead
|
|
if (!requestBody.instructions || config.SYSTEM_PROMPT_MODE === 'append') {
|
|
if (typeof requestBody.input === 'string') {
|
|
// Convert to array format to add system message
|
|
requestBody.input = [
|
|
{ role: 'system', content: filePromptContent },
|
|
{ role: 'user', content: requestBody.input }
|
|
];
|
|
} else if (Array.isArray(requestBody.input)) {
|
|
// Check if system message already exists
|
|
const systemMessageIndex = requestBody.input.findIndex(m =>
|
|
m.role === 'system' || (m.type && m.type === 'system')
|
|
);
|
|
|
|
if (systemMessageIndex !== -1) {
|
|
requestBody.input[systemMessageIndex].content = filePromptContent;
|
|
} else {
|
|
requestBody.input.unshift({ role: 'system', content: filePromptContent });
|
|
}
|
|
} else {
|
|
// If input is not defined, initialize with system message
|
|
requestBody.input = [{ role: 'system', content: filePromptContent }];
|
|
}
|
|
} else if (requestBody.instructions) {
|
|
// If system prompt mode is not append, then replace the instructions
|
|
requestBody.instructions = filePromptContent;
|
|
}
|
|
|
|
console.log(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'responses'.`);
|
|
|
|
return requestBody;
|
|
}
|
|
|
|
async manageSystemPrompt(requestBody) {
|
|
// For Responses API, we may extract instructions or system messages from input
|
|
let incomingSystemText = '';
|
|
|
|
if (requestBody.instructions) {
|
|
incomingSystemText = requestBody.instructions;
|
|
} else if (Array.isArray(requestBody.input)) {
|
|
const systemMessage = requestBody.input.find(item =>
|
|
item.role === 'system' || (item.type && item.type === 'system')
|
|
);
|
|
if (systemMessage && systemMessage.content) {
|
|
incomingSystemText = systemMessage.content;
|
|
}
|
|
}
|
|
|
|
await this._updateSystemPromptFile(incomingSystemText, MODEL_PROTOCOL_PREFIX.OPENAI);
|
|
}
|
|
}
|
|
|
|
export { ResponsesAPIStrategy }; |