Initial import NDC_1C
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OpenAIResponsesClient = void 0;
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const config_1 = require("../config");
|
||||
const http_1 = require("../utils/http");
|
||||
function extractUsage(raw) {
|
||||
const usage = (raw.usage ?? {});
|
||||
const input = Number(usage.input_tokens ?? usage.prompt_tokens ?? 0);
|
||||
const output = Number(usage.output_tokens ?? usage.completion_tokens ?? 0);
|
||||
const total = Number(usage.total_tokens ?? input + output);
|
||||
return {
|
||||
input_tokens: Number.isFinite(input) ? input : 0,
|
||||
output_tokens: Number.isFinite(output) ? output : 0,
|
||||
total_tokens: Number.isFinite(total) ? total : 0
|
||||
};
|
||||
}
|
||||
function extractOutputText(raw) {
|
||||
if (typeof raw.output_text === "string" && raw.output_text.trim().length > 0) {
|
||||
return raw.output_text;
|
||||
}
|
||||
const output = raw.output;
|
||||
if (Array.isArray(output)) {
|
||||
for (const item of output) {
|
||||
if (!item || typeof item !== "object") {
|
||||
continue;
|
||||
}
|
||||
const content = item.content;
|
||||
if (!Array.isArray(content)) {
|
||||
continue;
|
||||
}
|
||||
for (const c of content) {
|
||||
if (!c || typeof c !== "object") {
|
||||
continue;
|
||||
}
|
||||
const block = c;
|
||||
if (typeof block.text === "string" && block.text.trim()) {
|
||||
return block.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const response = raw.response;
|
||||
if (response && typeof response === "object") {
|
||||
const nested = response;
|
||||
if (typeof nested.output_text === "string" && nested.output_text.trim().length > 0) {
|
||||
return nested.output_text;
|
||||
}
|
||||
}
|
||||
throw new http_1.ApiError("OPENAI_OUTPUT_PARSE_FAILED", "Не удалось извлечь output_text из Responses API ответа.", 502, raw);
|
||||
}
|
||||
function loadSchemaForTransport(schemaVersion) {
|
||||
const schemaFile = schemaVersion === "v1"
|
||||
? "normalized_query_v1.json"
|
||||
: schemaVersion === "v2_0_1"
|
||||
? "normalized_query_v2_0_1.json"
|
||||
: schemaVersion === "v2_0_2"
|
||||
? "normalized_query_v2_0_2.json"
|
||||
: "normalized_query_v2.json";
|
||||
const schemaPath = path_1.default.resolve(config_1.SCHEMAS_DIR, schemaFile);
|
||||
return JSON.parse(fs_1.default.readFileSync(schemaPath, "utf-8"));
|
||||
}
|
||||
class OpenAIResponsesClient {
|
||||
async testConnection(config) {
|
||||
const payload = {
|
||||
model: config.model,
|
||||
input: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text: "ping" }]
|
||||
}
|
||||
],
|
||||
max_output_tokens: 16
|
||||
};
|
||||
await this.post(config, payload);
|
||||
return { ok: true, model: config.model };
|
||||
}
|
||||
async normalize(config, prompt) {
|
||||
const schema = loadSchemaForTransport(prompt.schemaVersion);
|
||||
const schemaName = prompt.schemaVersion === "v1"
|
||||
? "normalized_query_v1"
|
||||
: prompt.schemaVersion === "v2_0_1"
|
||||
? "normalized_query_v2_0_1"
|
||||
: prompt.schemaVersion === "v2_0_2"
|
||||
? "normalized_query_v2_0_2"
|
||||
: "normalized_query_v2";
|
||||
const developerPrompt = prompt.controlledRetryInstruction
|
||||
? `${prompt.developerPrompt}\n\n${prompt.controlledRetryInstruction}`
|
||||
: prompt.developerPrompt;
|
||||
const payload = {
|
||||
model: config.model,
|
||||
temperature: config.temperature ?? 0,
|
||||
max_output_tokens: config.maxOutputTokens ?? 700,
|
||||
input: [
|
||||
{
|
||||
role: "system",
|
||||
content: [{ type: "input_text", text: prompt.systemPrompt }]
|
||||
},
|
||||
{
|
||||
role: "developer",
|
||||
content: [{ type: "input_text", text: developerPrompt }]
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "input_text",
|
||||
text: `${prompt.domainPrompt}\n\nПользовательский вопрос:\n${prompt.userQuestion}`
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
text: {
|
||||
format: {
|
||||
type: "json_schema",
|
||||
name: schemaName,
|
||||
strict: true,
|
||||
schema
|
||||
}
|
||||
}
|
||||
};
|
||||
const raw = await this.post(config, payload);
|
||||
const outputText = extractOutputText(raw);
|
||||
return {
|
||||
raw,
|
||||
outputText,
|
||||
usage: extractUsage(raw)
|
||||
};
|
||||
}
|
||||
async post(config, payload) {
|
||||
if (!config.apiKey || config.apiKey.trim().length < 10) {
|
||||
throw new http_1.ApiError("OPENAI_API_KEY_MISSING", "API ключ OpenAI не задан или слишком короткий.", 400);
|
||||
}
|
||||
const url = `${(config.baseUrl ?? config_1.DEFAULT_OPENAI_BASE_URL).replace(/\/$/, "")}/responses`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const text = await response.text();
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
}
|
||||
catch {
|
||||
throw new http_1.ApiError("OPENAI_NON_JSON_RESPONSE", "OpenAI вернул не-JSON ответ.", 502, { status: response.status, body: text.slice(0, 500) });
|
||||
}
|
||||
if (!response.ok) {
|
||||
const errorObj = (data.error ?? {});
|
||||
throw new http_1.ApiError("OPENAI_REQUEST_FAILED", String(errorObj.message ?? `OpenAI request failed with status ${response.status}`), response.status, {
|
||||
status: response.status,
|
||||
type: errorObj.type ?? null,
|
||||
code: errorObj.code ?? null
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
exports.OpenAIResponsesClient = OpenAIResponsesClient;
|
||||
Reference in New Issue
Block a user