feat(anthropic, gemini, openai): Replace static model lists with dynamic fetching from respective APIs

This commit is contained in:
Willie Zutz 2025-08-16 13:05:13 -06:00
parent 15cefdfbcd
commit 15b75328d2
3 changed files with 178 additions and 148 deletions

View file

@ -7,41 +7,29 @@ export const PROVIDER_INFO = {
displayName: 'Anthropic',
};
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
const ANTHROPIC_MODELS_ENDPOINT = 'https://api.anthropic.com/v1/models';
const anthropicChatModels: Record<string, string>[] = [
{
displayName: 'Claude 4 Opus',
key: 'claude-opus-4-20250514',
async function fetchAnthropicModels(apiKey: string): Promise<any[]> {
const resp = await fetch(ANTHROPIC_MODELS_ENDPOINT, {
method: 'GET',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json',
},
{
displayName: 'Claude 4 Sonnet',
key: 'claude-sonnet-4-20250514',
},
{
displayName: 'Claude 3.7 Sonnet',
key: 'claude-3-7-sonnet-20250219',
},
{
displayName: 'Claude 3.5 Haiku',
key: 'claude-3-5-haiku-20241022',
},
{
displayName: 'Claude 3.5 Sonnet v2',
key: 'claude-3-5-sonnet-20241022',
},
{
displayName: 'Claude 3.5 Sonnet',
key: 'claude-3-5-sonnet-20240620',
},
{
displayName: 'Claude 3 Opus',
key: 'claude-3-opus-20240229',
},
{
displayName: 'Claude 3 Haiku',
key: 'claude-3-haiku-20240307',
},
];
});
if (!resp.ok) {
throw new Error(`Anthropic models endpoint returned ${resp.status}`);
}
const data = await resp.json();
if (!data || !Array.isArray(data.data)) {
throw new Error('Unexpected Anthropic models response format');
}
return data.data;
}
export const loadAnthropicChatModels = async () => {
const anthropicApiKey = getAnthropicApiKey();
@ -49,15 +37,26 @@ export const loadAnthropicChatModels = async () => {
if (!anthropicApiKey) return {};
try {
const models = await fetchAnthropicModels(anthropicApiKey);
const anthropicChatModels = models
.map((model: any) => {
const id = model && model.id ? String(model.id) : '';
const display =
model && model.display_name ? String(model.display_name) : id;
return { id, display };
})
.filter((model: any) => model.id)
.sort((a: any, b: any) => a.display.localeCompare(b.display));
const chatModels: Record<string, ChatModel> = {};
anthropicChatModels.forEach((model) => {
chatModels[model.key] = {
displayName: model.displayName,
anthropicChatModels.forEach((model: any) => {
chatModels[model.id] = {
displayName: model.display,
model: new ChatAnthropic({
apiKey: anthropicApiKey,
modelName: model.key,
// temperature: 0.7,
modelName: model.id,
//temperature: 0.7,
}) as unknown as BaseChatModel,
};
});

View file

@ -12,55 +12,34 @@ export const PROVIDER_INFO = {
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
import { Embeddings } from '@langchain/core/embeddings';
const geminiChatModels: Record<string, string>[] = [
{
displayName: 'Gemini 2.5 Flash Preview 05-20',
key: 'gemini-2.5-flash-preview-05-20',
},
{
displayName: 'Gemini 2.5 Pro Preview',
key: 'gemini-2.5-pro-preview-05-06',
},
{
displayName: 'Gemini 2.5 Pro Experimental',
key: 'gemini-2.5-pro-preview-05-06',
},
{
displayName: 'Gemini 2.0 Flash',
key: 'gemini-2.0-flash',
},
{
displayName: 'Gemini 2.0 Flash-Lite',
key: 'gemini-2.0-flash-lite',
},
{
displayName: 'Gemini 2.0 Flash Thinking Experimental',
key: 'gemini-2.0-flash-thinking-exp-01-21',
},
{
displayName: 'Gemini 1.5 Flash',
key: 'gemini-1.5-flash',
},
{
displayName: 'Gemini 1.5 Flash-8B',
key: 'gemini-1.5-flash-8b',
},
{
displayName: 'Gemini 1.5 Pro',
key: 'gemini-1.5-pro',
},
];
// Replace static model lists with dynamic fetch from Gemini API
const GEMINI_MODELS_ENDPOINT =
'https://generativelanguage.googleapis.com/v1beta/models';
const geminiEmbeddingModels: Record<string, string>[] = [
{
displayName: 'Text Embedding 004',
key: 'models/text-embedding-004',
async function fetchGeminiModels(apiKey: string): Promise<any[]> {
const url = `${GEMINI_MODELS_ENDPOINT}?key=${encodeURIComponent(
apiKey,
)}&pageSize=1000`;
const resp = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
{
displayName: 'Embedding 001',
key: 'models/embedding-001',
},
];
});
if (!resp.ok) {
throw new Error(`Gemini models endpoint returned ${resp.status}`);
}
const data = await resp.json();
if (!data || !Array.isArray(data.models)) {
throw new Error('Unexpected Gemini models response format');
}
return data.models;
}
export const loadGeminiChatModels = async () => {
const geminiApiKey = getGeminiApiKey();
@ -68,9 +47,32 @@ export const loadGeminiChatModels = async () => {
if (!geminiApiKey) return {};
try {
const models = await fetchGeminiModels(geminiApiKey);
const geminiChatModels = models
.map((model: any) => {
const rawName = model && model.name ? String(model.name) : '';
const stripped = rawName.replace(/^models\//i, '');
return {
rawName,
key: stripped,
displayName:
model && model.displayName ? String(model.displayName) : stripped,
};
})
.filter((model: any) => {
const key = model.key.toLowerCase();
const display = (model.displayName || '').toLowerCase();
const excluded = ['audio', 'embedding', 'image', 'tts'];
return (
key.startsWith('gemini') &&
!excluded.some((s) => key.includes(s) || display.includes(s))
);
})
.sort((a: any, b: any) => a.key.localeCompare(b.key));
const chatModels: Record<string, ChatModel> = {};
geminiChatModels.forEach((model) => {
geminiChatModels.forEach((model: any) => {
chatModels[model.key] = {
displayName: model.displayName,
model: new ChatGoogleGenerativeAI({
@ -83,7 +85,7 @@ export const loadGeminiChatModels = async () => {
return chatModels;
} catch (err) {
console.error(`Error loading Gemini models: ${err}`);
console.error(`Error loading Gemini chat models: ${err}`);
return {};
}
};
@ -94,9 +96,28 @@ export const loadGeminiEmbeddingModels = async () => {
if (!geminiApiKey) return {};
try {
const models = await fetchGeminiModels(geminiApiKey);
const geminiEmbeddingModels = models
.map((model: any) => {
const rawName = model && model.name ? String(model.name) : '';
const display =
model && model.displayName ? String(model.displayName) : rawName;
return {
rawName,
key: rawName,
displayName: display,
};
})
.filter(
(model: any) =>
model.key.toLowerCase().includes('embedding') ||
model.displayName.toLowerCase().includes('embedding'),
)
.sort((a: any, b: any) => a.key.localeCompare(b.key));
const embeddingModels: Record<string, EmbeddingModel> = {};
geminiEmbeddingModels.forEach((model) => {
geminiEmbeddingModels.forEach((model: any) => {
embeddingModels[model.key] = {
displayName: model.displayName,
model: new GoogleGenerativeAIEmbeddings({
@ -108,7 +129,7 @@ export const loadGeminiEmbeddingModels = async () => {
return embeddingModels;
} catch (err) {
console.error(`Error loading OpenAI embeddings models: ${err}`);
console.error(`Error loading Gemini embedding models: ${err}`);
return {};
}
};

View file

@ -9,51 +9,32 @@ export const PROVIDER_INFO = {
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
import { Embeddings } from '@langchain/core/embeddings';
const openaiChatModels: Record<string, string>[] = [
{
displayName: 'GPT-3.5 Turbo',
key: 'gpt-3.5-turbo',
},
{
displayName: 'GPT-4',
key: 'gpt-4',
},
{
displayName: 'GPT-4 turbo',
key: 'gpt-4-turbo',
},
{
displayName: 'GPT-4 omni',
key: 'gpt-4o',
},
{
displayName: 'GPT-4 omni mini',
key: 'gpt-4o-mini',
},
{
displayName: 'GPT 4.1 nano',
key: 'gpt-4.1-nano',
},
{
displayName: 'GPT 4.1 mini',
key: 'gpt-4.1-mini',
},
{
displayName: 'GPT 4.1',
key: 'gpt-4.1',
},
];
// Dynamically discover models from OpenAI instead of hardcoding
const OPENAI_MODELS_ENDPOINT = 'https://api.openai.com/v1/models';
const openaiEmbeddingModels: Record<string, string>[] = [
{
displayName: 'Text Embedding 3 Small',
key: 'text-embedding-3-small',
async function fetchOpenAIModels(apiKey: string): Promise<string[]> {
const resp = await fetch(OPENAI_MODELS_ENDPOINT, {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
{
displayName: 'Text Embedding 3 Large',
key: 'text-embedding-3-large',
},
];
});
if (!resp.ok) {
throw new Error(`OpenAI models endpoint returned ${resp.status}`);
}
const data = await resp.json();
if (!data || !Array.isArray(data.data)) {
throw new Error('Unexpected OpenAI models response format');
}
return data.data
.map((model: any) => (model && model.id ? String(model.id) : undefined))
.filter(Boolean) as string[];
}
export const loadOpenAIChatModels = async () => {
const openaiApiKey = getOpenaiApiKey();
@ -61,22 +42,41 @@ export const loadOpenAIChatModels = async () => {
if (!openaiApiKey) return {};
try {
const modelIds = (await fetchOpenAIModels(openaiApiKey)).sort((a, b) =>
a.localeCompare(b),
);
const chatModels: Record<string, ChatModel> = {};
openaiChatModels.forEach((model) => {
chatModels[model.key] = {
displayName: model.displayName,
modelIds.forEach((model) => {
const lid = model.toLowerCase();
const excludedSubstrings = [
'audio',
'embedding',
'image',
'omni-moderation',
'transcribe',
'tts',
];
const isChat =
(lid.startsWith('gpt') || lid.startsWith('o')) &&
!excludedSubstrings.some((s) => lid.includes(s));
if (!isChat) return;
chatModels[model] = {
displayName: model,
model: new ChatOpenAI({
openAIApiKey: openaiApiKey,
modelName: model.key,
// temperature: 0.7,
apiKey: openaiApiKey,
modelName: model,
//temperature: model.includes('gpt-5') ? 1 : 0.7,
}) as unknown as BaseChatModel,
};
});
return chatModels;
} catch (err) {
console.error(`Error loading OpenAI models: ${err}`);
console.error(`Error loading OpenAI chat models: ${err}`);
return {};
}
};
@ -87,21 +87,31 @@ export const loadOpenAIEmbeddingModels = async () => {
if (!openaiApiKey) return {};
try {
const modelIds = (await fetchOpenAIModels(openaiApiKey)).sort((a, b) =>
a.localeCompare(b),
);
const embeddingModels: Record<string, EmbeddingModel> = {};
openaiEmbeddingModels.forEach((model) => {
embeddingModels[model.key] = {
displayName: model.displayName,
modelIds.forEach((model) => {
const lid = model.toLowerCase();
const isEmbedding = lid.includes('embedding');
if (!isEmbedding) return;
embeddingModels[model] = {
displayName: model,
model: new OpenAIEmbeddings({
openAIApiKey: openaiApiKey,
modelName: model.key,
apiKey: openaiApiKey,
modelName: model,
}) as unknown as Embeddings,
};
});
return embeddingModels;
} catch (err) {
console.error(`Error loading OpenAI embeddings models: ${err}`);
console.error(`Error loading OpenAI embedding models: ${err}`);
return {};
}
};