mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-07-14 16:32:14 +02:00
feat: add openai_base_url config for LiteLLM/OpenAI-compatible proxy support
This commit is contained in:
parent
945375eca8
commit
89c71a3dde
9 changed files with 383 additions and 296 deletions
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "priceghost-backend",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "priceghost-backend",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.24.0",
|
||||
"@google/generative-ai": "^0.24.1",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export interface AISettings {
|
|||
anthropic_model: string | null;
|
||||
openai_api_key: string | null;
|
||||
openai_model: string | null;
|
||||
openai_base_url: string | null;
|
||||
ollama_base_url: string | null;
|
||||
ollama_model: string | null;
|
||||
gemini_api_key: string | null;
|
||||
|
|
@ -244,11 +245,11 @@ export const userQueries = {
|
|||
return (result.rowCount ?? 0) > 0;
|
||||
},
|
||||
|
||||
getAISettings: async (id: number): Promise<AISettings | null> => {
|
||||
getAISettings: async (id: number): Promise<AISettings | null> => {
|
||||
const result = await pool.query(
|
||||
`SELECT ai_enabled, COALESCE(ai_verification_enabled, false) as ai_verification_enabled,
|
||||
ai_provider, anthropic_api_key, anthropic_model, openai_api_key, openai_model,
|
||||
ollama_base_url, ollama_model, gemini_api_key, gemini_model
|
||||
openai_base_url, ollama_base_url, ollama_model, gemini_api_key, gemini_model
|
||||
FROM users WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
|
|
@ -291,6 +292,10 @@ export const userQueries = {
|
|||
fields.push(`openai_model = $${paramIndex++}`);
|
||||
values.push(settings.openai_model);
|
||||
}
|
||||
if (settings.openai_base_url !== undefined) {
|
||||
fields.push(`openai_base_url = $${paramIndex++}`);
|
||||
values.push(settings.openai_base_url);
|
||||
}
|
||||
if (settings.ollama_base_url !== undefined) {
|
||||
fields.push(`ollama_base_url = $${paramIndex++}`);
|
||||
values.push(settings.ollama_base_url);
|
||||
|
|
@ -315,7 +320,7 @@ export const userQueries = {
|
|||
`UPDATE users SET ${fields.join(', ')} WHERE id = $${paramIndex}
|
||||
RETURNING ai_enabled, COALESCE(ai_verification_enabled, false) as ai_verification_enabled,
|
||||
ai_provider, anthropic_api_key, anthropic_model, openai_api_key, openai_model,
|
||||
ollama_base_url, ollama_model, gemini_api_key, gemini_model`,
|
||||
openai_base_url, ollama_base_url, ollama_model, gemini_api_key, gemini_model`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
|
|
|
|||
|
|
@ -335,6 +335,7 @@ router.get('/ai', async (req: AuthRequest, res: Response) => {
|
|||
anthropic_model: settings.anthropic_model || null,
|
||||
openai_api_key: settings.openai_api_key || null,
|
||||
openai_model: settings.openai_model || null,
|
||||
openai_base_url: settings.openai_base_url || null,
|
||||
ollama_base_url: settings.ollama_base_url || null,
|
||||
ollama_model: settings.ollama_model || null,
|
||||
gemini_api_key: settings.gemini_api_key || null,
|
||||
|
|
@ -358,6 +359,7 @@ router.put('/ai', async (req: AuthRequest, res: Response) => {
|
|||
anthropic_model,
|
||||
openai_api_key,
|
||||
openai_model,
|
||||
openai_base_url,
|
||||
ollama_base_url,
|
||||
ollama_model,
|
||||
gemini_api_key,
|
||||
|
|
@ -372,6 +374,7 @@ router.put('/ai', async (req: AuthRequest, res: Response) => {
|
|||
anthropic_model,
|
||||
openai_api_key,
|
||||
openai_model,
|
||||
openai_base_url,
|
||||
ollama_base_url,
|
||||
ollama_model,
|
||||
gemini_api_key,
|
||||
|
|
@ -391,6 +394,7 @@ router.put('/ai', async (req: AuthRequest, res: Response) => {
|
|||
anthropic_model: settings.anthropic_model || null,
|
||||
openai_api_key: settings.openai_api_key || null,
|
||||
openai_model: settings.openai_model || null,
|
||||
openai_base_url: settings.openai_base_url || null,
|
||||
ollama_base_url: settings.ollama_base_url || null,
|
||||
ollama_model: settings.ollama_model || null,
|
||||
gemini_api_key: settings.gemini_api_key || null,
|
||||
|
|
|
|||
|
|
@ -253,9 +253,12 @@ async function extractWithAnthropic(
|
|||
async function extractWithOpenAI(
|
||||
html: string,
|
||||
apiKey: string,
|
||||
model?: string | null
|
||||
model?: string | null,
|
||||
baseURL?: string | null
|
||||
): Promise<AIExtractionResult> {
|
||||
const openai = new OpenAI({ apiKey });
|
||||
const openaiConfig: Record<string, string> = { apiKey };
|
||||
if (baseURL) openaiConfig.baseURL = baseURL;
|
||||
const openai = new OpenAI(openaiConfig);
|
||||
|
||||
const preparedHtml = prepareHtmlForAI(html);
|
||||
const modelToUse = model || DEFAULT_OPENAI_MODEL;
|
||||
|
|
@ -383,9 +386,12 @@ async function verifyWithOpenAI(
|
|||
scrapedPrice: number,
|
||||
currency: string,
|
||||
apiKey: string,
|
||||
model?: string | null
|
||||
model?: string | null,
|
||||
baseURL?: string | null
|
||||
): Promise<AIVerificationResult> {
|
||||
const openai = new OpenAI({ apiKey });
|
||||
const openaiConfig: Record<string, string> = { apiKey };
|
||||
if (baseURL) openaiConfig.baseURL = baseURL;
|
||||
const openai = new OpenAI(openaiConfig);
|
||||
|
||||
const preparedHtml = prepareHtmlForAI(html);
|
||||
const prompt = VERIFICATION_PROMPT
|
||||
|
|
@ -509,9 +515,12 @@ async function verifyStockStatusWithOpenAI(
|
|||
variantPrice: number,
|
||||
currency: string,
|
||||
apiKey: string,
|
||||
model?: string | null
|
||||
model?: string | null,
|
||||
baseURL?: string | null
|
||||
): Promise<AIStockStatusResult> {
|
||||
const openai = new OpenAI({ apiKey });
|
||||
const openaiConfig: Record<string, string> = { apiKey };
|
||||
if (baseURL) openaiConfig.baseURL = baseURL;
|
||||
const openai = new OpenAI(openaiConfig);
|
||||
|
||||
const preparedHtml = prepareHtmlForAI(html);
|
||||
const prompt = STOCK_STATUS_PROMPT
|
||||
|
|
@ -808,7 +817,7 @@ export async function extractWithAI(
|
|||
if (settings.ai_provider === 'anthropic' && settings.anthropic_api_key) {
|
||||
return extractWithAnthropic(html, settings.anthropic_api_key, settings.anthropic_model);
|
||||
} else if (settings.ai_provider === 'openai' && settings.openai_api_key) {
|
||||
return extractWithOpenAI(html, settings.openai_api_key, settings.openai_model);
|
||||
return extractWithOpenAI(html, settings.openai_api_key, settings.openai_model, settings.openai_base_url);
|
||||
} else if (settings.ai_provider === 'ollama' && settings.ollama_base_url && settings.ollama_model) {
|
||||
return extractWithOllama(html, settings.ollama_base_url, settings.ollama_model);
|
||||
} else if (settings.ai_provider === 'gemini' && settings.gemini_api_key) {
|
||||
|
|
@ -841,7 +850,7 @@ export async function tryAIExtraction(
|
|||
} else if (settings.ai_provider === 'openai' && settings.openai_api_key) {
|
||||
const modelToUse = settings.openai_model || DEFAULT_OPENAI_MODEL;
|
||||
console.log(`[AI] Using OpenAI (${modelToUse}) for ${url}`);
|
||||
return await extractWithOpenAI(html, settings.openai_api_key, settings.openai_model);
|
||||
return await extractWithOpenAI(html, settings.openai_api_key, settings.openai_model, settings.openai_base_url);
|
||||
} else if (settings.ai_provider === 'ollama' && settings.ollama_base_url && settings.ollama_model) {
|
||||
console.log(`[AI] Using Ollama (${settings.ollama_model}) for ${url}`);
|
||||
return await extractWithOllama(html, settings.ollama_base_url, settings.ollama_model);
|
||||
|
|
@ -883,7 +892,7 @@ export async function tryAIVerification(
|
|||
} else if (settings.ai_provider === 'openai' && settings.openai_api_key) {
|
||||
const modelToUse = settings.openai_model || DEFAULT_OPENAI_MODEL;
|
||||
console.log(`[AI Verify] Using OpenAI (${modelToUse}) to verify $${scrapedPrice} for ${url}`);
|
||||
return await verifyWithOpenAI(html, scrapedPrice, currency, settings.openai_api_key, settings.openai_model);
|
||||
return await verifyWithOpenAI(html, scrapedPrice, currency, settings.openai_api_key, settings.openai_model, settings.openai_base_url);
|
||||
} else if (settings.ai_provider === 'ollama' && settings.ollama_base_url && settings.ollama_model) {
|
||||
console.log(`[AI Verify] Using Ollama (${settings.ollama_model}) to verify $${scrapedPrice} for ${url}`);
|
||||
return await verifyWithOllama(html, scrapedPrice, currency, settings.ollama_base_url, settings.ollama_model);
|
||||
|
|
@ -926,7 +935,7 @@ export async function tryAIStockStatusVerification(
|
|||
} else if (settings.ai_provider === 'openai' && settings.openai_api_key) {
|
||||
const modelToUse = settings.openai_model || DEFAULT_OPENAI_MODEL;
|
||||
console.log(`[AI Stock] Using OpenAI (${modelToUse}) to verify stock status for $${variantPrice} variant at ${url}`);
|
||||
return await verifyStockStatusWithOpenAI(html, variantPrice, currency, settings.openai_api_key, settings.openai_model);
|
||||
return await verifyStockStatusWithOpenAI(html, variantPrice, currency, settings.openai_api_key, settings.openai_model, settings.openai_base_url);
|
||||
} else if (settings.ai_provider === 'ollama' && settings.ollama_base_url && settings.ollama_model) {
|
||||
console.log(`[AI Stock] Using Ollama (${settings.ollama_model}) to verify stock status for $${variantPrice} variant at ${url}`);
|
||||
return await verifyStockStatusWithOllama(html, variantPrice, currency, settings.ollama_base_url, settings.ollama_model);
|
||||
|
|
@ -1009,9 +1018,12 @@ async function arbitrateWithOpenAI(
|
|||
html: string,
|
||||
candidates: PriceCandidate[],
|
||||
apiKey: string,
|
||||
model?: string | null
|
||||
model?: string | null,
|
||||
baseURL?: string | null
|
||||
): Promise<AIArbitrationResult> {
|
||||
const openai = new OpenAI({ apiKey });
|
||||
const openaiConfig: Record<string, string> = { apiKey };
|
||||
if (baseURL) openaiConfig.baseURL = baseURL;
|
||||
const openai = new OpenAI(openaiConfig);
|
||||
|
||||
const candidatesList = candidates.map((c, i) =>
|
||||
`${i}. ${c.price} ${c.currency} (method: ${c.method}, context: ${c.context || 'none'})`
|
||||
|
|
@ -1180,7 +1192,7 @@ export async function tryAIArbitration(
|
|||
} else if (settings.ai_provider === 'openai' && settings.openai_api_key) {
|
||||
const modelToUse = settings.openai_model || DEFAULT_OPENAI_MODEL;
|
||||
console.log(`[AI Arbitrate] Using OpenAI (${modelToUse}) to arbitrate ${candidates.length} prices for ${url}`);
|
||||
return await arbitrateWithOpenAI(html, candidates, settings.openai_api_key, settings.openai_model);
|
||||
return await arbitrateWithOpenAI(html, candidates, settings.openai_api_key, settings.openai_model, settings.openai_base_url);
|
||||
} else if (settings.ai_provider === 'ollama' && settings.ollama_base_url && settings.ollama_model) {
|
||||
console.log(`[AI Arbitrate] Using Ollama (${settings.ollama_model}) to arbitrate ${candidates.length} prices for ${url}`);
|
||||
return await arbitrateWithOllama(html, candidates, settings.ollama_base_url, settings.ollama_model);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue