feat: implement article optimization workflow

This commit is contained in:
Codex
2026-06-21 23:42:37 +08:00
parent cb56ff9ee7
commit 75bcab33c6
10 changed files with 834 additions and 1 deletions
+112
View File
@@ -0,0 +1,112 @@
import OpenAI from "openai";
export interface GenerateInput {
system?: string;
prompt: string;
model?: string;
temperature?: number;
}
export interface LlmProviderStatus {
provider: "deepseek" | "openai";
configured: boolean;
model: string;
baseURL?: string;
reason?: string;
}
function getProvider() {
return (process.env.LLM_PROVIDER || "deepseek").toLowerCase();
}
export function getLlmProviderStatus(): LlmProviderStatus {
const provider = getProvider();
if (provider === "openai") {
return {
provider: "openai",
configured: Boolean(process.env.OPENAI_API_KEY),
model: process.env.OPENAI_MODEL || "gpt-4.1-mini",
reason: process.env.OPENAI_API_KEY ? undefined : "OPENAI_API_KEY is missing",
};
}
return {
provider: "deepseek",
configured: Boolean(process.env.DEEPSEEK_API_KEY),
model: process.env.DEEPSEEK_MODEL || "deepseek-v4-pro",
baseURL: process.env.DEEPSEEK_BASE_URL || "https://api.deepseek.com",
reason: process.env.DEEPSEEK_API_KEY
? undefined
: "DEEPSEEK_API_KEY is missing",
};
}
export function isLlmConfigured() {
return getLlmProviderStatus().configured;
}
function createClient() {
const status = getLlmProviderStatus();
if (!status.configured) {
throw new Error(status.reason ?? "LLM provider is not configured");
}
if (status.provider === "openai") {
return {
client: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
model: status.model,
};
}
return {
client: new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: status.baseURL,
}),
model: status.model,
};
}
export async function generateText(input: GenerateInput) {
try {
const { client, model } = createClient();
const response = await client.chat.completions.create({
model: input.model ?? model,
temperature: input.temperature ?? 0.2,
messages: [
...(input.system ? [{ role: "system" as const, content: input.system }] : []),
{ role: "user" as const, content: input.prompt },
],
});
return response.choices[0]?.message.content ?? "";
} catch (error) {
throw normalizeLlmError(error);
}
}
export async function generateJson<T>(input: GenerateInput): Promise<T> {
try {
const { client, model } = createClient();
const response = await client.chat.completions.create({
model: input.model ?? model,
temperature: input.temperature ?? 0.1,
response_format: { type: "json_object" },
messages: [
...(input.system ? [{ role: "system" as const, content: input.system }] : []),
{ role: "user" as const, content: input.prompt },
],
});
const content = response.choices[0]?.message.content ?? "{}";
return JSON.parse(content) as T;
} catch (error) {
throw normalizeLlmError(error);
}
}
function normalizeLlmError(error: unknown) {
const message = error instanceof Error ? error.message : "Unknown LLM error";
return new Error(`LLM provider error: ${message}`);
}
+14
View File
@@ -0,0 +1,14 @@
export const JSON_ONLY_PROMPT =
"Return valid JSON only. Do not include markdown fences or commentary.";
export const ARTICLE_OPTIMIZER_SYSTEM_PROMPT = [
"You optimize GEO-related articles under a confirmed fact card.",
"Never invent numbers, cases, qualifications, company names, products, or years.",
JSON_ONLY_PROMPT,
].join(" ");
export const QUALITY_INSPECTOR_SYSTEM_PROMPT = [
"Evaluate article quality against the confirmed fact card and target platform.",
"Return one structured check per required quality gate.",
JSON_ONLY_PROMPT,
].join(" ");