feat: migrate AI to llama and use local db
All checks were successful
Deploy / lint-build-deploy (push) Successful in 2m13s

This commit is contained in:
2026-01-19 21:25:38 +01:00
parent 6e75be19ed
commit a8d3bf1b3b
10 changed files with 12059 additions and 980 deletions

View File

@@ -1,52 +1,103 @@
import { generateText, tool, jsonSchema } from 'ai';
import OpenAI from 'openai';
import { BaseTool } from './tool';
import type { JSONSchema7 } from 'json-schema';
export async function getMessage<T>(text: string, baseTool: BaseTool) {
console.info('Vercel AI Gateway request with text: ', text);
let ovhAI: OpenAI | null = null;
try {
const { steps } = await generateText({
model: 'anthropic/claude-sonnet-4.5',
temperature: 1,
tools: {
[baseTool.name]: tool({
description: baseTool.input_schema.description || '',
inputSchema: jsonSchema(baseTool.input_schema as JSONSchema7),
execute: async args => args
})
},
toolChoice: {
type: 'tool',
toolName: baseTool.name
},
prompt: text
function getClient(): OpenAI {
if (!ovhAI) {
ovhAI = new OpenAI({
apiKey: process.env.OVHCLOUD_API_KEY,
baseURL: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1'
});
console.info('Vercel AI Gateway response steps: ', steps);
const toolCalls = steps.flatMap(step => step.toolCalls);
if (!toolCalls || toolCalls.length === 0) {
throw new Error('No tool calls found in response');
}
const typedCall = toolCalls[0] as unknown as {
toolName: string;
input: Record<string, unknown>;
};
console.info('Tool call input: ', typedCall.input);
if (typedCall.toolName !== baseTool.name) {
throw new Error(
`Expected tool ${baseTool.name} but got ${typedCall.toolName}`
);
}
return typedCall.input as T;
} catch (error) {
console.error('Vercel AI Gateway error: ', error);
throw new Error('Failed to get message from AI Gateway');
}
return ovhAI;
}
const MAX_RETRIES = 3;
export async function getMessage<T>(
text: string,
baseTool: BaseTool
): Promise<T> {
const requiredFields = baseTool.input_schema.required
? [...baseTool.input_schema.required]
: Object.keys(baseTool.input_schema.properties);
let lastError: Error | null = null;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
console.info(`OVH AI request attempt ${attempt}/${MAX_RETRIES}`);
const completion = await getClient().chat.completions.create({
model: 'Meta-Llama-3_3-70B-Instruct',
temperature: 0.7,
max_tokens: 16000,
tools: [
{
type: 'function',
function: {
name: baseTool.name,
description: baseTool.input_schema.description || '',
parameters: {
type: 'object',
properties: baseTool.input_schema.properties,
required: requiredFields
}
}
}
],
tool_choice: {
type: 'function',
function: { name: baseTool.name }
},
messages: [
{
role: 'system',
content: `You are a professional tech journalist creating newsletter content.
CRITICAL REQUIREMENTS:
1. You MUST call the function with ALL required fields populated
2. Required fields: ${requiredFields.join(', ')}
3. Every field must be fully populated with high-quality content
4. Do NOT leave any field as null, undefined, or empty`
},
{
role: 'user',
content: text
}
]
});
const message = completion.choices[0]?.message;
if (!message?.tool_calls || message.tool_calls.length === 0) {
throw new Error('No function call found in response');
}
const toolCall = message.tool_calls[0];
if (toolCall.function.name !== baseTool.name) {
throw new Error(
`Expected tool ${baseTool.name} but got ${toolCall.function.name}`
);
}
const result = JSON.parse(toolCall.function.arguments);
console.info('OVH AI response:', result);
return result as T;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
lastError = error as Error;
if (attempt < MAX_RETRIES) {
const delay = 1000 * attempt;
console.info(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
console.error('All retry attempts failed');
throw lastError || new Error('OVH AI Endpoints client error.');
}