feat: migrate AI to llama and use local db
All checks were successful
Deploy / lint-build-deploy (push) Successful in 2m13s
All checks were successful
Deploy / lint-build-deploy (push) Successful in 2m13s
This commit is contained in:
@@ -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.');
|
||||
}
|
||||
|
||||
@@ -25,20 +25,25 @@ export const newsletterTool: BaseTool = {
|
||||
name: 'NewsletterTool' as const,
|
||||
input_schema: {
|
||||
type: 'object' as const,
|
||||
description: 'Newsletter',
|
||||
description:
|
||||
'Generate a tech newsletter with title, content, and focus sections',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string' as const,
|
||||
description: 'The title of the newsletter'
|
||||
description:
|
||||
'The title of the newsletter (40-50 characters, capturing key themes)'
|
||||
},
|
||||
content: {
|
||||
type: 'string' as const,
|
||||
description: 'The main content of the newsletter'
|
||||
description:
|
||||
'The main content of the newsletter (300-400 words, HTML formatted with paragraph tags and links)'
|
||||
},
|
||||
focus: {
|
||||
type: 'string' as const,
|
||||
description: 'The text of the focus segment'
|
||||
description:
|
||||
'Forward-looking assessment of key developments and trends'
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['title', 'content', 'focus'] as const
|
||||
}
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user