feat: implement article optimization workflow
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
@@ -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(" ");
|
||||
Reference in New Issue
Block a user