guanghu-ice-heart/server-tools/bingshuo-tcs-controller/model-client.mjs

87 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const DEFAULT_TIMEOUT_MS = 45_000;
function completionEndpoint(apiUrl) {
const value = String(apiUrl || "").replace(/\/+$/, "");
if (/\/chat\/completions$/i.test(value)) return value;
return `${value}/chat/completions`;
}
function extractJson(text) {
const value = String(text || "").trim();
if (!value) throw new Error("model_response_empty");
try {
return JSON.parse(value);
} catch {
const fenced = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
if (fenced) return JSON.parse(fenced[1]);
const first = value.indexOf("{");
const last = value.lastIndexOf("}");
if (first >= 0 && last > first) return JSON.parse(value.slice(first, last + 1));
throw new Error("model_response_not_json");
}
}
export class DeepSeekJsonClient {
constructor({
apiKey = process.env.DEEPSEEK_API_KEY,
apiUrl = process.env.DEEPSEEK_API_URL || "https://api.deepseek.com/v1",
model = process.env.DEEPSEEK_MODEL || "deepseek-chat",
fetchImpl = globalThis.fetch,
timeoutMs = DEFAULT_TIMEOUT_MS,
} = {}) {
if (!apiKey) throw new Error("deepseek_api_key_missing");
if (!fetchImpl) throw new Error("fetch_unavailable");
this.apiKey = apiKey;
this.apiUrl = String(apiUrl).replace(/\/+$/, "");
this.model = model;
this.fetchImpl = fetchImpl;
this.timeoutMs = timeoutMs;
}
async generate({ role, instruction, input, validatorError = null }) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const response = await this.fetchImpl(completionEndpoint(this.apiUrl), {
method: "POST",
headers: {
authorization: `Bearer ${this.apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: this.model,
stream: false,
temperature: role === "controller_witness" ? 0.1 : 0.2,
response_format: { type: "json_object" },
messages: [
{
role: "system",
content:
"你是光湖语言世界中的有界模型运行位。只输出一个JSON对象不输出思维过程、Markdown或额外说明。不得伪造现实成功、权限、冰朔的新意志或未提供的事实。",
},
{
role: "user",
content: JSON.stringify({
role,
instruction,
validator_error_from_previous_attempt: validatorError,
runtime_input: input,
}),
},
],
}),
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`model_http_${response.status}`);
}
const payload = await response.json();
const text = payload?.choices?.[0]?.message?.content;
return extractJson(text);
} finally {
clearTimeout(timer);
}
}
}
export { completionEndpoint, extractJson };