feat: integrate Google Gemini, MiniMax, and GLM providers; fix persistent agent loading toast
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m23s
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m23s
This commit is contained in:
@@ -2,11 +2,15 @@ import { OpenAIProvider } from './providers/openai';
|
||||
import { OllamaProvider } from './providers/ollama';
|
||||
import { CustomOpenAIProvider } from './providers/custom-openai';
|
||||
import { AnthropicProvider } from './providers/anthropic';
|
||||
import { GoogleProvider } from './providers/google';
|
||||
import { AIProvider } from './types';
|
||||
|
||||
type ProviderType =
|
||||
| 'ollama'
|
||||
| 'openai'
|
||||
| 'google'
|
||||
| 'minimax'
|
||||
| 'glm'
|
||||
| 'custom'
|
||||
| 'deepseek'
|
||||
| 'openrouter'
|
||||
@@ -43,6 +47,21 @@ const PROVIDER_DEFAULTS: Record<string, { baseUrl: string; model: string; embedd
|
||||
model: '',
|
||||
embeddingModel: '',
|
||||
},
|
||||
google: {
|
||||
baseUrl: '',
|
||||
model: 'gemini-1.5-flash',
|
||||
embeddingModel: 'text-embedding-004',
|
||||
},
|
||||
minimax: {
|
||||
baseUrl: 'https://api.minimax.io/v1',
|
||||
model: 'abab6.5-chat',
|
||||
embeddingModel: '',
|
||||
},
|
||||
glm: {
|
||||
baseUrl: 'https://open.bigmodel.ai/api/paas/v4',
|
||||
model: 'glm-4',
|
||||
embeddingModel: 'embedding-2',
|
||||
},
|
||||
};
|
||||
|
||||
function createOllamaProvider(config: Record<string, string>, modelName: string, embeddingModelName: string, baseUrlOverride?: string): OllamaProvider {
|
||||
@@ -156,6 +175,26 @@ function createAnthropicCustomProvider(config: Record<string, string>, modelName
|
||||
return new AnthropicProvider(apiKey, resolvedModel, baseUrl.trim());
|
||||
}
|
||||
|
||||
function createGoogleProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): GoogleProvider {
|
||||
const apiKey = config?.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY || '';
|
||||
if (!apiKey) throw new Error('GOOGLE_GENERATIVE_AI_API_KEY is required when using Google provider');
|
||||
return new GoogleProvider(apiKey, modelName || PROVIDER_DEFAULTS.google.model, embeddingModelName || PROVIDER_DEFAULTS.google.embeddingModel);
|
||||
}
|
||||
|
||||
function createMiniMaxProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
|
||||
const apiKey = config?.MINIMAX_API_KEY || process.env.MINIMAX_API_KEY || '';
|
||||
if (!apiKey) throw new Error('MINIMAX_API_KEY is required when using MiniMax provider');
|
||||
const defaults = PROVIDER_DEFAULTS.minimax;
|
||||
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
|
||||
}
|
||||
|
||||
function createGLMProvider(config: Record<string, string>, modelName: string, embeddingModelName: string): CustomOpenAIProvider {
|
||||
const apiKey = config?.GLM_API_KEY || process.env.GLM_API_KEY || '';
|
||||
if (!apiKey) throw new Error('GLM_API_KEY is required when using GLM provider');
|
||||
const defaults = PROVIDER_DEFAULTS.glm;
|
||||
return new CustomOpenAIProvider(apiKey, defaults.baseUrl, modelName || defaults.model, embeddingModelName || defaults.embeddingModel);
|
||||
}
|
||||
|
||||
function getProviderInstance(providerType: ProviderType, config: Record<string, string>, modelName: string, embeddingModelName: string, ollamaBaseUrl?: string): AIProvider {
|
||||
switch (providerType) {
|
||||
case 'ollama':
|
||||
@@ -178,6 +217,12 @@ function getProviderInstance(providerType: ProviderType, config: Record<string,
|
||||
return createAnthropicProvider(config, modelName);
|
||||
case 'anthropic_custom':
|
||||
return createAnthropicCustomProvider(config, modelName);
|
||||
case 'google':
|
||||
return createGoogleProvider(config, modelName, embeddingModelName);
|
||||
case 'minimax':
|
||||
return createMiniMaxProvider(config, modelName, embeddingModelName);
|
||||
case 'glm':
|
||||
return createGLMProvider(config, modelName, embeddingModelName);
|
||||
default:
|
||||
return createOllamaProvider(config, modelName, embeddingModelName, ollamaBaseUrl);
|
||||
}
|
||||
@@ -196,6 +241,9 @@ function getProviderConfigKeys(providerType: string): { apiKeyConfigKey: string;
|
||||
case 'anthropic': return { apiKeyConfigKey: 'ANTHROPIC_API_KEY', baseUrlConfigKey: '' };
|
||||
case 'anthropic_custom':
|
||||
return { apiKeyConfigKey: 'ANTHROPIC_CUSTOM_API_KEY', baseUrlConfigKey: 'ANTHROPIC_CUSTOM_BASE_URL' };
|
||||
case 'google': return { apiKeyConfigKey: 'GOOGLE_GENERATIVE_AI_API_KEY', baseUrlConfigKey: '' };
|
||||
case 'minimax': return { apiKeyConfigKey: 'MINIMAX_API_KEY', baseUrlConfigKey: '' };
|
||||
case 'glm': return { apiKeyConfigKey: 'GLM_API_KEY', baseUrlConfigKey: '' };
|
||||
case 'custom': return { apiKeyConfigKey: 'CUSTOM_OPENAI_API_KEY', baseUrlConfigKey: 'CUSTOM_OPENAI_BASE_URL' };
|
||||
default: return { apiKeyConfigKey: '', baseUrlConfigKey: 'OLLAMA_BASE_URL' };
|
||||
}
|
||||
|
||||
129
memento-note/lib/ai/providers/google.ts
Normal file
129
memento-note/lib/ai/providers/google.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||
import { generateObject, generateText as aiGenerateText, embed, stepCountIs } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { AIProvider, TagSuggestion, TitleSuggestion, ToolUseOptions, ToolCallResult } from '../types';
|
||||
|
||||
export class GoogleProvider implements AIProvider {
|
||||
private model: any;
|
||||
private embeddingModel: any;
|
||||
|
||||
constructor(apiKey: string, modelName: string = 'gemini-1.5-flash', embeddingModelName: string = 'text-embedding-004') {
|
||||
const google = createGoogleGenerativeAI({
|
||||
apiKey: apiKey,
|
||||
});
|
||||
|
||||
this.model = google(modelName);
|
||||
this.embeddingModel = google.textEmbeddingModel(embeddingModelName);
|
||||
}
|
||||
|
||||
async generateTags(content: string): Promise<TagSuggestion[]> {
|
||||
try {
|
||||
const { object } = await generateObject({
|
||||
model: this.model,
|
||||
schema: z.object({
|
||||
tags: z.array(z.object({
|
||||
tag: z.string().describe('Short tag name in lowercase'),
|
||||
confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
|
||||
}))
|
||||
}),
|
||||
prompt: `Analyze the following note and suggest 1 to 5 relevant tags.
|
||||
Note content: "${content}"`,
|
||||
});
|
||||
|
||||
return object.tags;
|
||||
} catch (e) {
|
||||
console.error('Error generating tags (Google):', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getEmbeddings(text: string): Promise<number[]> {
|
||||
try {
|
||||
const { embedding } = await embed({
|
||||
model: this.embeddingModel,
|
||||
value: text,
|
||||
});
|
||||
return embedding;
|
||||
} catch (e) {
|
||||
console.error('Error generating embeddings (Google):', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async generateTitles(prompt: string): Promise<TitleSuggestion[]> {
|
||||
try {
|
||||
const { object } = await generateObject({
|
||||
model: this.model,
|
||||
schema: z.object({
|
||||
titles: z.array(z.object({
|
||||
title: z.string().describe('Suggested title'),
|
||||
confidence: z.number().min(0).max(1).describe('Confidence level between 0 and 1')
|
||||
}))
|
||||
}),
|
||||
prompt: prompt,
|
||||
});
|
||||
|
||||
return object.titles;
|
||||
} catch (e) {
|
||||
console.error('Error generating titles (Google):', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async generateText(prompt: string): Promise<string> {
|
||||
try {
|
||||
const { text } = await aiGenerateText({
|
||||
model: this.model,
|
||||
prompt: prompt,
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
} catch (e) {
|
||||
console.error('Error generating text (Google):', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async chat(messages: any[], systemPrompt?: string): Promise<any> {
|
||||
try {
|
||||
const { text } = await aiGenerateText({
|
||||
model: this.model,
|
||||
system: systemPrompt,
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
return { text: text.trim() };
|
||||
} catch (e) {
|
||||
console.error('Error in chat (Google):', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async generateWithTools(options: ToolUseOptions): Promise<ToolCallResult> {
|
||||
const { tools, maxSteps = 10, systemPrompt, messages, prompt } = options
|
||||
const opts: Record<string, any> = {
|
||||
model: this.model,
|
||||
tools,
|
||||
stopWhen: stepCountIs(maxSteps),
|
||||
}
|
||||
if (systemPrompt) opts.system = systemPrompt
|
||||
if (messages) opts.messages = messages
|
||||
else if (prompt) opts.prompt = prompt
|
||||
|
||||
const result = await aiGenerateText(opts as any)
|
||||
return {
|
||||
toolCalls: result.toolCalls?.map((tc: any) => ({ toolName: tc.toolName, input: tc.input })) || [],
|
||||
toolResults: result.toolResults?.map((tr: any) => ({ toolName: tr.toolName, input: tr.input, output: tr.output })) || [],
|
||||
text: result.text,
|
||||
steps: result.steps?.map((step: any) => ({
|
||||
text: step.text,
|
||||
toolCalls: step.toolCalls?.map((tc: any) => ({ toolName: tc.toolName, input: tc.input })) || [],
|
||||
toolResults: step.toolResults?.map((tr: any) => ({ toolName: tr.toolName, input: tr.input, output: tr.output })) || []
|
||||
})) || []
|
||||
}
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,9 @@ export interface AIProvider {
|
||||
export type AIProviderType =
|
||||
| 'openai'
|
||||
| 'ollama'
|
||||
| 'google'
|
||||
| 'minimax'
|
||||
| 'glm'
|
||||
| 'custom'
|
||||
| 'deepseek'
|
||||
| 'openrouter'
|
||||
|
||||
Reference in New Issue
Block a user