stops import: swap OpenAI for MiniMax as default AI provider
Deploy to route.crispygoat.com / deploy (push) Successful in 5m27s
Deploy to route.crispygoat.com / deploy (push) Successful in 5m27s
This commit is contained in:
@@ -9,19 +9,25 @@ type RequestBody = {
|
|||||||
/** Raw file content (CSV text). AI parsing is attempted if useAI=true. */
|
/** Raw file content (CSV text). AI parsing is attempted if useAI=true. */
|
||||||
text: string;
|
text: string;
|
||||||
brandId: string;
|
brandId: string;
|
||||||
/** If true and OPENAI_API_KEY is set, parse unstructured text with AI. */
|
/** If true and an AI API key is set, parse unstructured text with AI. */
|
||||||
useAI?: boolean;
|
useAI?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AI_MODEL = "gpt-4o-mini";
|
async function parseWithAI(text: string, _brandId: string): Promise<{
|
||||||
|
|
||||||
async function parseWithAI(text: string, brandId: string): Promise<{
|
|
||||||
stops: ParsedStop[];
|
stops: ParsedStop[];
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
}> {
|
}> {
|
||||||
const apiKey = process.env.OPENAI_API_KEY;
|
// Prefer MiniMax (env-level) — fall back to OpenAI.
|
||||||
|
const provider: "minimax" | "openai" =
|
||||||
|
process.env.MINIMAX_API_KEY ? "minimax" : process.env.OPENAI_API_KEY ? "openai" : "openai";
|
||||||
|
const apiKey = provider === "minimax" ? process.env.MINIMAX_API_KEY! : process.env.OPENAI_API_KEY!;
|
||||||
|
const baseURL = provider === "minimax"
|
||||||
|
? (process.env.MINIMAX_BASE_URL || "https://api.minimax.io/v1")
|
||||||
|
: "https://api.openai.com/v1";
|
||||||
|
const model = provider === "minimax" ? "MiniMax-M3" : "gpt-4o-mini";
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error("OPENAI_API_KEY is not configured. Use CSV format for reliable parsing.");
|
throw new Error("MINIMAX_API_KEY or OPENAI_API_KEY is not configured. Use CSV format for reliable parsing.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemPrompt = `You are a schedule extraction assistant. Given raw schedule text, extract all stop entries.
|
const systemPrompt = `You are a schedule extraction assistant. Given raw schedule text, extract all stop entries.
|
||||||
@@ -37,26 +43,27 @@ Return a JSON array where each entry has:
|
|||||||
|
|
||||||
If a row lacks required fields (city, state, location), omit it and add a warning.`;
|
If a row lacks required fields (city, state, location), omit it and add a warning.`;
|
||||||
|
|
||||||
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
const res = await fetch(`${baseURL}/chat/completions`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: AI_MODEL,
|
model,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: systemPrompt },
|
{ role: "system", content: systemPrompt },
|
||||||
{ role: "user", content: `Extract stops from this schedule:\n\n${text.slice(0, 8000)}` },
|
{ role: "user", content: `Extract stops from this schedule:\n\n${text.slice(0, 8000)}` },
|
||||||
],
|
],
|
||||||
response_format: { type: "json_object" },
|
// response_format is OpenAI-specific. MiniMax /v1/chat/completions may not honor it.
|
||||||
|
...(provider === "openai" ? { response_format: { type: "json_object" } } : {}),
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.text();
|
const err = await res.text();
|
||||||
throw new Error(`OpenAI API error: ${res.status} — ${err}`);
|
throw new Error(`${provider} API error: ${res.status} — ${err}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
Reference in New Issue
Block a user