mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 01:01:42 +00:00
refactor(ai): Implement unified model system with type-safe createLLM
- Add Model interface to types.ts with normalized structure - Create type-safe generic createLLM function with provider-specific model constraints - Generate models from OpenRouter API and models.dev data - Strip provider prefixes for direct providers (google, openai, anthropic, xai) - Keep full model IDs for OpenRouter-proxied models - Clean separation: types.ts (Model interface), models.ts (factory logic), models.generated.ts (data) - Remove old model scripts and unused dependencies - Rename GeminiLLM to GoogleLLM for consistency - Add tests for new providers (xAI, Groq, Cerebras, OpenRouter) - Support 181 tool-capable models across 7 providers with full type safety
This commit is contained in:
parent
3f36051bc6
commit
c7618db3f7
8 changed files with 409 additions and 418 deletions
|
|
@ -13,12 +13,12 @@ npm install @mariozechner/ai
|
||||||
```typescript
|
```typescript
|
||||||
import { AnthropicLLM } from '@mariozechner/ai/providers/anthropic';
|
import { AnthropicLLM } from '@mariozechner/ai/providers/anthropic';
|
||||||
import { OpenAICompletionsLLM } from '@mariozechner/ai/providers/openai-completions';
|
import { OpenAICompletionsLLM } from '@mariozechner/ai/providers/openai-completions';
|
||||||
import { GeminiLLM } from '@mariozechner/ai/providers/gemini';
|
import { GoogleLLM } from '@mariozechner/ai/providers/gemini';
|
||||||
|
|
||||||
// Pick your provider - same API for all
|
// Pick your provider - same API for all
|
||||||
const llm = new AnthropicLLM('claude-sonnet-4-0');
|
const llm = new AnthropicLLM('claude-sonnet-4-0');
|
||||||
// const llm = new OpenAICompletionsLLM('gpt-5-mini');
|
// const llm = new OpenAICompletionsLLM('gpt-5-mini');
|
||||||
// const llm = new GeminiLLM('gemini-2.5-flash');
|
// const llm = new GoogleLLM('gemini-2.5-flash');
|
||||||
|
|
||||||
// Basic completion
|
// Basic completion
|
||||||
const response = await llm.complete({
|
const response = await llm.complete({
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,11 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"models": "curl -s https://models.dev/api.json -o src/models.json",
|
|
||||||
"generate-models": "npx tsx scripts/generate-models.ts",
|
"generate-models": "npx tsx scripts/generate-models.ts",
|
||||||
"build": "npm run generate-models && tsc -p tsconfig.build.json && cp src/models.json dist/models.json",
|
"build": "npm run generate-models && tsc -p tsconfig.build.json",
|
||||||
"check": "biome check --write .",
|
"check": "biome check --write .",
|
||||||
"test": "vitest",
|
"test": "vitest --run",
|
||||||
"test:ui": "vitest --ui",
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
"test:old": "npx tsx --test test/providers.test.ts",
|
|
||||||
"extract-models": "npx tsx scripts/extract-openai-models.ts",
|
|
||||||
"prepublishOnly": "npm run clean && npm run models && npm run build"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.60.0",
|
"@anthropic-ai/sdk": "^0.60.0",
|
||||||
|
|
@ -48,7 +44,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^24.3.0",
|
||||||
"@vitest/ui": "^3.2.4",
|
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,263 +3,260 @@
|
||||||
import { readFileSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
// Load the models.json file
|
interface ModelsDevModel {
|
||||||
const data = JSON.parse(readFileSync(join(process.cwd(), "src/models.json"), "utf-8"));
|
|
||||||
|
|
||||||
// Categorize providers by their API type
|
|
||||||
const openaiModels: Record<string, any> = {};
|
|
||||||
const openaiCompatibleProviders: Record<string, any> = {};
|
|
||||||
const anthropicModels: Record<string, any> = {};
|
|
||||||
const geminiModels: Record<string, any> = {};
|
|
||||||
|
|
||||||
for (const [providerId, provider] of Object.entries(data)) {
|
|
||||||
const p = provider as any;
|
|
||||||
|
|
||||||
if (providerId === "openai") {
|
|
||||||
// All OpenAI models use the Responses API
|
|
||||||
openaiModels[providerId] = p;
|
|
||||||
} else if (providerId === "anthropic" || providerId === "google-vertex-anthropic") {
|
|
||||||
// Anthropic direct and via Vertex
|
|
||||||
anthropicModels[providerId] = p;
|
|
||||||
} else if (providerId === "google" || providerId === "google-vertex") {
|
|
||||||
// Google Gemini models
|
|
||||||
geminiModels[providerId] = p;
|
|
||||||
} else if (p.npm === "@ai-sdk/openai-compatible" ||
|
|
||||||
p.npm === "@ai-sdk/groq" ||
|
|
||||||
p.npm === "@ai-sdk/cerebras" ||
|
|
||||||
p.npm === "@ai-sdk/fireworks" ||
|
|
||||||
p.npm === "@ai-sdk/openrouter" ||
|
|
||||||
p.npm === "@ai-sdk/openai" && providerId !== "openai" ||
|
|
||||||
p.api?.includes("/v1") ||
|
|
||||||
["together", "ollama", "llama", "github-models", "groq", "cerebras", "openrouter", "fireworks"].includes(providerId)) {
|
|
||||||
// OpenAI-compatible providers - they all speak the OpenAI completions API
|
|
||||||
// Set default base URLs for known providers
|
|
||||||
if (!p.api) {
|
|
||||||
switch (providerId) {
|
|
||||||
case "groq": p.api = "https://api.groq.com/openai/v1"; break;
|
|
||||||
case "cerebras": p.api = "https://api.cerebras.com/v1"; break;
|
|
||||||
case "together": p.api = "https://api.together.xyz/v1"; break;
|
|
||||||
case "fireworks": p.api = "https://api.fireworks.ai/v1"; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openaiCompatibleProviders[providerId] = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the TypeScript file
|
|
||||||
let output = `// This file is auto-generated by scripts/generate-models.ts
|
|
||||||
// Do not edit manually - run 'npm run generate-models' to update
|
|
||||||
|
|
||||||
import type { ModalityInput, ModalityOutput } from "./models.js";
|
|
||||||
|
|
||||||
export interface ModelData {
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
reasoning: boolean;
|
tool_call?: boolean;
|
||||||
tool_call: boolean;
|
reasoning?: boolean;
|
||||||
attachment: boolean;
|
limit?: {
|
||||||
temperature: boolean;
|
context?: number;
|
||||||
knowledge?: string;
|
output?: number;
|
||||||
release_date: string;
|
|
||||||
last_updated: string;
|
|
||||||
modalities: {
|
|
||||||
input: ModalityInput[];
|
|
||||||
output: ModalityOutput[];
|
|
||||||
};
|
|
||||||
open_weights: boolean;
|
|
||||||
limit: {
|
|
||||||
context: number;
|
|
||||||
output: number;
|
|
||||||
};
|
};
|
||||||
cost?: {
|
cost?: {
|
||||||
input: number;
|
input?: number;
|
||||||
output: number;
|
output?: number;
|
||||||
cache_read?: number;
|
cache_read?: number;
|
||||||
cache_write?: number;
|
cache_write?: number;
|
||||||
};
|
};
|
||||||
|
modalities?: {
|
||||||
|
input?: string[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderData {
|
interface NormalizedModel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
baseUrl?: string;
|
provider: string;
|
||||||
env?: string[];
|
reasoning: boolean;
|
||||||
models: Record<string, ModelData>;
|
input: ("text" | "image")[];
|
||||||
|
cost: {
|
||||||
|
input: number;
|
||||||
|
output: number;
|
||||||
|
cacheRead: number;
|
||||||
|
cacheWrite: number;
|
||||||
|
};
|
||||||
|
contextWindow: number;
|
||||||
|
maxTokens: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchOpenRouterModels(): Promise<NormalizedModel[]> {
|
||||||
|
try {
|
||||||
|
console.log("🌐 Fetching models from OpenRouter API...");
|
||||||
|
const response = await fetch("https://openrouter.ai/api/v1/models");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const models: NormalizedModel[] = [];
|
||||||
|
|
||||||
|
for (const model of data.data) {
|
||||||
|
// Only include models that support tools
|
||||||
|
if (!model.supported_parameters?.includes("tools")) continue;
|
||||||
|
|
||||||
|
// Parse provider from model ID
|
||||||
|
const [providerPrefix] = model.id.split("/");
|
||||||
|
let provider = "";
|
||||||
|
let modelKey = model.id;
|
||||||
|
|
||||||
|
// Map provider prefixes to our provider names
|
||||||
|
if (model.id.startsWith("google/")) {
|
||||||
|
provider = "google";
|
||||||
|
modelKey = model.id.replace("google/", "");
|
||||||
|
} else if (model.id.startsWith("openai/")) {
|
||||||
|
provider = "openai";
|
||||||
|
modelKey = model.id.replace("openai/", "");
|
||||||
|
} else if (model.id.startsWith("anthropic/")) {
|
||||||
|
provider = "anthropic";
|
||||||
|
modelKey = model.id.replace("anthropic/", "");
|
||||||
|
} else if (model.id.startsWith("x-ai/")) {
|
||||||
|
provider = "xai";
|
||||||
|
modelKey = model.id.replace("x-ai/", "");
|
||||||
|
} else {
|
||||||
|
// All other models go through OpenRouter
|
||||||
|
provider = "openrouter";
|
||||||
|
modelKey = model.id; // Keep full ID for OpenRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if not one of our supported providers
|
||||||
|
if (!["google", "openai", "anthropic", "xai", "openrouter"].includes(provider)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse input modalities
|
||||||
|
const input: ("text" | "image")[] = ["text"];
|
||||||
|
if (model.architecture?.modality?.includes("image")) {
|
||||||
|
input.push("image");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert pricing from $/token to $/million tokens
|
||||||
|
const inputCost = parseFloat(model.pricing?.prompt || "0") * 1_000_000;
|
||||||
|
const outputCost = parseFloat(model.pricing?.completion || "0") * 1_000_000;
|
||||||
|
const cacheReadCost = parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000;
|
||||||
|
const cacheWriteCost = parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000;
|
||||||
|
|
||||||
|
models.push({
|
||||||
|
id: modelKey,
|
||||||
|
name: model.name,
|
||||||
|
provider,
|
||||||
|
reasoning: model.supported_parameters?.includes("reasoning") || false,
|
||||||
|
input,
|
||||||
|
cost: {
|
||||||
|
input: inputCost,
|
||||||
|
output: outputCost,
|
||||||
|
cacheRead: cacheReadCost,
|
||||||
|
cacheWrite: cacheWriteCost,
|
||||||
|
},
|
||||||
|
contextWindow: model.context_length || 4096,
|
||||||
|
maxTokens: model.top_provider?.max_completion_tokens || 4096,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Fetched ${models.length} tool-capable models from OpenRouter`);
|
||||||
|
return models;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to fetch OpenRouter models:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModelsDevData(): NormalizedModel[] {
|
||||||
|
try {
|
||||||
|
console.log("📁 Loading models from models.json...");
|
||||||
|
const data = JSON.parse(readFileSync(join(process.cwd(), "src/models.json"), "utf-8"));
|
||||||
|
|
||||||
|
const models: NormalizedModel[] = [];
|
||||||
|
|
||||||
|
// Process Groq models
|
||||||
|
if (data.groq?.models) {
|
||||||
|
for (const [modelId, model] of Object.entries(data.groq.models)) {
|
||||||
|
const m = model as ModelsDevModel;
|
||||||
|
if (m.tool_call !== true) continue;
|
||||||
|
|
||||||
|
models.push({
|
||||||
|
id: modelId,
|
||||||
|
name: m.name || modelId,
|
||||||
|
provider: "groq",
|
||||||
|
reasoning: m.reasoning === true,
|
||||||
|
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||||
|
cost: {
|
||||||
|
input: m.cost?.input || 0,
|
||||||
|
output: m.cost?.output || 0,
|
||||||
|
cacheRead: m.cost?.cache_read || 0,
|
||||||
|
cacheWrite: m.cost?.cache_write || 0,
|
||||||
|
},
|
||||||
|
contextWindow: m.limit?.context || 4096,
|
||||||
|
maxTokens: m.limit?.output || 4096,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Cerebras models
|
||||||
|
if (data.cerebras?.models) {
|
||||||
|
for (const [modelId, model] of Object.entries(data.cerebras.models)) {
|
||||||
|
const m = model as ModelsDevModel;
|
||||||
|
if (m.tool_call !== true) continue;
|
||||||
|
|
||||||
|
models.push({
|
||||||
|
id: modelId,
|
||||||
|
name: m.name || modelId,
|
||||||
|
provider: "cerebras",
|
||||||
|
reasoning: m.reasoning === true,
|
||||||
|
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||||
|
cost: {
|
||||||
|
input: m.cost?.input || 0,
|
||||||
|
output: m.cost?.output || 0,
|
||||||
|
cacheRead: m.cost?.cache_read || 0,
|
||||||
|
cacheWrite: m.cost?.cache_write || 0,
|
||||||
|
},
|
||||||
|
contextWindow: m.limit?.context || 4096,
|
||||||
|
maxTokens: m.limit?.output || 4096,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Loaded ${models.length} tool-capable models from models.dev`);
|
||||||
|
return models;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to load models.dev data:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateModels() {
|
||||||
|
// Fetch all models
|
||||||
|
const openRouterModels = await fetchOpenRouterModels();
|
||||||
|
const modelsDevModels = loadModelsDevData();
|
||||||
|
|
||||||
|
// Combine models (models.dev takes priority for Groq/Cerebras)
|
||||||
|
const allModels = [...modelsDevModels, ...openRouterModels];
|
||||||
|
|
||||||
|
// Group by provider
|
||||||
|
const providers: Record<string, NormalizedModel[]> = {};
|
||||||
|
for (const model of allModels) {
|
||||||
|
if (!providers[model.provider]) {
|
||||||
|
providers[model.provider] = [];
|
||||||
|
}
|
||||||
|
providers[model.provider].push(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate TypeScript file
|
||||||
|
let output = `// This file is auto-generated by scripts/generate-models.ts
|
||||||
|
// Do not edit manually - run 'npm run generate-models' to update
|
||||||
|
|
||||||
|
import type { Model } from "./types.js";
|
||||||
|
|
||||||
|
export const PROVIDERS = {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Generate OpenAI models
|
// Generate provider sections
|
||||||
output += `// OpenAI models - all use OpenAIResponsesLLM\n`;
|
for (const [providerId, models] of Object.entries(providers)) {
|
||||||
output += `export const OPENAI_MODELS = {\n`;
|
output += `\t${providerId}: {\n`;
|
||||||
for (const [providerId, provider] of Object.entries(openaiModels)) {
|
output += `\t\tmodels: {\n`;
|
||||||
const p = provider as any;
|
|
||||||
for (const [modelId, model] of Object.entries(p.models || {})) {
|
|
||||||
const m = model as any;
|
|
||||||
output += ` "${modelId}": ${JSON.stringify(m, null, 8).split('\n').join('\n ')},\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output += `} as const;\n\n`;
|
|
||||||
|
|
||||||
// Generate OpenAI-compatible providers
|
for (const model of models) {
|
||||||
output += `// OpenAI-compatible providers - use OpenAICompletionsLLM\n`;
|
output += `\t\t\t"${model.id}": {\n`;
|
||||||
output += `export const OPENAI_COMPATIBLE_PROVIDERS = {\n`;
|
output += `\t\t\t\tid: "${model.id}",\n`;
|
||||||
for (const [providerId, provider] of Object.entries(openaiCompatibleProviders)) {
|
output += `\t\t\t\tname: "${model.name}",\n`;
|
||||||
const p = provider as any;
|
output += `\t\t\t\tprovider: "${model.provider}",\n`;
|
||||||
output += ` "${providerId}": {\n`;
|
output += `\t\t\t\treasoning: ${model.reasoning},\n`;
|
||||||
output += ` id: "${providerId}",\n`;
|
output += `\t\t\t\tinput: ${JSON.stringify(model.input)},\n`;
|
||||||
output += ` name: "${p.name}",\n`;
|
output += `\t\t\t\tcost: {\n`;
|
||||||
if (p.api) {
|
output += `\t\t\t\t\tinput: ${model.cost.input},\n`;
|
||||||
output += ` baseUrl: "${p.api}",\n`;
|
output += `\t\t\t\t\toutput: ${model.cost.output},\n`;
|
||||||
}
|
output += `\t\t\t\t\tcacheRead: ${model.cost.cacheRead},\n`;
|
||||||
if (p.env) {
|
output += `\t\t\t\t\tcacheWrite: ${model.cost.cacheWrite},\n`;
|
||||||
output += ` env: ${JSON.stringify(p.env)},\n`;
|
output += `\t\t\t\t},\n`;
|
||||||
}
|
output += `\t\t\t\tcontextWindow: ${model.contextWindow},\n`;
|
||||||
output += ` models: {\n`;
|
output += `\t\t\t\tmaxTokens: ${model.maxTokens},\n`;
|
||||||
for (const [modelId, model] of Object.entries(p.models || {})) {
|
output += `\t\t\t} satisfies Model,\n`;
|
||||||
const m = model as any;
|
|
||||||
output += ` "${modelId}": ${JSON.stringify(m, null, 12).split('\n').join('\n ')},\n`;
|
|
||||||
}
|
|
||||||
output += ` }\n`;
|
|
||||||
output += ` },\n`;
|
|
||||||
}
|
|
||||||
output += `} as const;\n\n`;
|
|
||||||
|
|
||||||
// Generate Anthropic models (avoiding duplicates)
|
|
||||||
output += `// Anthropic models - use AnthropicLLM\n`;
|
|
||||||
output += `export const ANTHROPIC_MODELS = {\n`;
|
|
||||||
const seenAnthropicModels = new Set<string>();
|
|
||||||
for (const [providerId, provider] of Object.entries(anthropicModels)) {
|
|
||||||
const p = provider as any;
|
|
||||||
for (const [modelId, model] of Object.entries(p.models || {})) {
|
|
||||||
if (!seenAnthropicModels.has(modelId)) {
|
|
||||||
seenAnthropicModels.add(modelId);
|
|
||||||
const m = model as any;
|
|
||||||
output += ` "${modelId}": ${JSON.stringify(m, null, 8).split('\n').join('\n ')},\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output += `} as const;\n\n`;
|
|
||||||
|
|
||||||
// Generate Gemini models (avoiding duplicates)
|
|
||||||
output += `// Gemini models - use GeminiLLM\n`;
|
|
||||||
output += `export const GEMINI_MODELS = {\n`;
|
|
||||||
const seenGeminiModels = new Set<string>();
|
|
||||||
for (const [providerId, provider] of Object.entries(geminiModels)) {
|
|
||||||
const p = provider as any;
|
|
||||||
for (const [modelId, model] of Object.entries(p.models || {})) {
|
|
||||||
if (!seenGeminiModels.has(modelId)) {
|
|
||||||
seenGeminiModels.add(modelId);
|
|
||||||
const m = model as any;
|
|
||||||
output += ` "${modelId}": ${JSON.stringify(m, null, 8).split('\n').join('\n ')},\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output += `} as const;\n\n`;
|
|
||||||
|
|
||||||
// Generate type helpers
|
|
||||||
output += `// Type helpers\n`;
|
|
||||||
output += `export type OpenAIModel = keyof typeof OPENAI_MODELS;\n`;
|
|
||||||
output += `export type OpenAICompatibleProvider = keyof typeof OPENAI_COMPATIBLE_PROVIDERS;\n`;
|
|
||||||
output += `export type AnthropicModel = keyof typeof ANTHROPIC_MODELS;\n`;
|
|
||||||
output += `export type GeminiModel = keyof typeof GEMINI_MODELS;\n\n`;
|
|
||||||
|
|
||||||
// Generate the factory function
|
|
||||||
output += `// Factory function implementation\n`;
|
|
||||||
output += `import { OpenAIResponsesLLM } from "./providers/openai-responses.js";\n`;
|
|
||||||
output += `import { OpenAICompletionsLLM } from "./providers/openai-completions.js";\n`;
|
|
||||||
output += `import { AnthropicLLM } from "./providers/anthropic.js";\n`;
|
|
||||||
output += `import { GeminiLLM } from "./providers/gemini.js";\n`;
|
|
||||||
output += `import type { LLM, LLMOptions } from "./types.js";\n\n`;
|
|
||||||
|
|
||||||
output += `export interface CreateLLMOptions {
|
|
||||||
apiKey?: string;
|
|
||||||
baseUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overloads for type safety
|
|
||||||
export function createLLM(
|
|
||||||
provider: "openai",
|
|
||||||
model: OpenAIModel,
|
|
||||||
options?: CreateLLMOptions
|
|
||||||
): OpenAIResponsesLLM;
|
|
||||||
|
|
||||||
export function createLLM(
|
|
||||||
provider: OpenAICompatibleProvider,
|
|
||||||
model: string, // We'll validate at runtime
|
|
||||||
options?: CreateLLMOptions
|
|
||||||
): OpenAICompletionsLLM;
|
|
||||||
|
|
||||||
export function createLLM(
|
|
||||||
provider: "anthropic",
|
|
||||||
model: AnthropicModel,
|
|
||||||
options?: CreateLLMOptions
|
|
||||||
): AnthropicLLM;
|
|
||||||
|
|
||||||
export function createLLM(
|
|
||||||
provider: "gemini",
|
|
||||||
model: GeminiModel,
|
|
||||||
options?: CreateLLMOptions
|
|
||||||
): GeminiLLM;
|
|
||||||
|
|
||||||
// Implementation
|
|
||||||
export function createLLM(
|
|
||||||
provider: string,
|
|
||||||
model: string,
|
|
||||||
options?: CreateLLMOptions
|
|
||||||
): LLM<LLMOptions> {
|
|
||||||
const apiKey = options?.apiKey || process.env[getEnvVar(provider)];
|
|
||||||
|
|
||||||
if (provider === "openai") {
|
|
||||||
return new OpenAIResponsesLLM(model, apiKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === "anthropic") {
|
output += `\t\t}\n`;
|
||||||
return new AnthropicLLM(model, apiKey);
|
output += `\t},\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === "gemini") {
|
output += `} as const;
|
||||||
return new GeminiLLM(model, apiKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenAI-compatible providers
|
// Helper type to extract models for each provider
|
||||||
if (provider in OPENAI_COMPATIBLE_PROVIDERS) {
|
export type ProviderModels = {
|
||||||
const providerData = OPENAI_COMPATIBLE_PROVIDERS[provider as OpenAICompatibleProvider];
|
[K in keyof typeof PROVIDERS]: typeof PROVIDERS[K]["models"]
|
||||||
const baseUrl = options?.baseUrl || providerData.baseUrl;
|
};
|
||||||
return new OpenAICompletionsLLM(model, apiKey, baseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(\`Unknown provider: \${provider}\`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to get the default environment variable for a provider
|
|
||||||
function getEnvVar(provider: string): string {
|
|
||||||
switch (provider) {
|
|
||||||
case "openai": return "OPENAI_API_KEY";
|
|
||||||
case "anthropic": return "ANTHROPIC_API_KEY";
|
|
||||||
case "gemini": return "GEMINI_API_KEY";
|
|
||||||
case "groq": return "GROQ_API_KEY";
|
|
||||||
case "cerebras": return "CEREBRAS_API_KEY";
|
|
||||||
case "together": return "TOGETHER_API_KEY";
|
|
||||||
case "openrouter": return "OPENROUTER_API_KEY";
|
|
||||||
default: return \`\${provider.toUpperCase()}_API_KEY\`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Write the generated file
|
// Write file
|
||||||
writeFileSync(join(process.cwd(), "src/models.generated.ts"), output);
|
writeFileSync(join(process.cwd(), "src/models.generated.ts"), output);
|
||||||
console.log("✅ Generated src/models.generated.ts");
|
console.log("✅ Generated src/models.generated.ts");
|
||||||
|
|
||||||
// Count statistics
|
// Print statistics
|
||||||
const openaiCount = Object.values(openaiModels).reduce((acc, p: any) => acc + Object.keys(p.models || {}).length, 0);
|
const totalModels = allModels.length;
|
||||||
const compatCount = Object.values(openaiCompatibleProviders).reduce((acc, p: any) => acc + Object.keys(p.models || {}).length, 0);
|
const reasoningModels = allModels.filter(m => m.reasoning).length;
|
||||||
const anthropicCount = Object.values(anthropicModels).reduce((acc, p: any) => acc + Object.keys(p.models || {}).length, 0);
|
|
||||||
const geminiCount = Object.values(geminiModels).reduce((acc, p: any) => acc + Object.keys(p.models || {}).length, 0);
|
|
||||||
|
|
||||||
console.log(`\nModel counts:`);
|
console.log(`\n📊 Model Statistics:`);
|
||||||
console.log(` OpenAI (Responses API): ${openaiCount} models`);
|
console.log(` Total tool-capable models: ${totalModels}`);
|
||||||
console.log(` OpenAI-compatible: ${compatCount} models across ${Object.keys(openaiCompatibleProviders).length} providers`);
|
console.log(` Reasoning-capable models: ${reasoningModels}`);
|
||||||
console.log(` Anthropic: ${anthropicCount} models`);
|
|
||||||
console.log(` Gemini: ${geminiCount} models`);
|
for (const [provider, models] of Object.entries(providers)) {
|
||||||
console.log(` Total: ${openaiCount + compatCount + anthropicCount + geminiCount} models`);
|
console.log(` ${provider}: ${models.length} models`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the generator
|
||||||
|
generateModels().catch(console.error);
|
||||||
|
|
@ -3,41 +3,30 @@
|
||||||
|
|
||||||
export const version = "0.5.8";
|
export const version = "0.5.8";
|
||||||
|
|
||||||
// Export generated models and factory
|
// Export generated models data
|
||||||
|
export { PROVIDERS } from "./models.generated.js";
|
||||||
|
|
||||||
|
// Export models utilities and types
|
||||||
export {
|
export {
|
||||||
ANTHROPIC_MODELS,
|
|
||||||
type AnthropicModel,
|
type AnthropicModel,
|
||||||
type CreateLLMOptions,
|
type CerebrasModel,
|
||||||
createLLM,
|
createLLM,
|
||||||
GEMINI_MODELS,
|
type GoogleModel,
|
||||||
type GeminiModel,
|
type GroqModel,
|
||||||
type ModelData,
|
type Model,
|
||||||
OPENAI_COMPATIBLE_PROVIDERS,
|
|
||||||
OPENAI_MODELS,
|
|
||||||
type OpenAICompatibleProvider,
|
|
||||||
type OpenAIModel,
|
type OpenAIModel,
|
||||||
type ProviderData,
|
type OpenRouterModel,
|
||||||
} from "./models.generated.js";
|
PROVIDER_CONFIG,
|
||||||
// Export models utilities
|
type ProviderModels,
|
||||||
export {
|
type ProviderToLLM,
|
||||||
getAllProviders,
|
type XAIModel,
|
||||||
getModelInfo,
|
|
||||||
getProviderInfo,
|
|
||||||
getProviderModels,
|
|
||||||
loadModels,
|
|
||||||
type ModalityInput,
|
|
||||||
type ModalityOutput,
|
|
||||||
type ModelInfo,
|
|
||||||
type ModelsData,
|
|
||||||
type ProviderInfo,
|
|
||||||
supportsThinking,
|
|
||||||
supportsTools,
|
|
||||||
} from "./models.js";
|
} from "./models.js";
|
||||||
|
|
||||||
// Export providers
|
// Export providers
|
||||||
export { AnthropicLLM } from "./providers/anthropic.js";
|
export { AnthropicLLM } from "./providers/anthropic.js";
|
||||||
export { GeminiLLM } from "./providers/gemini.js";
|
export { GoogleLLM } from "./providers/gemini.js";
|
||||||
export { OpenAICompletionsLLM } from "./providers/openai-completions.js";
|
export { OpenAICompletionsLLM } from "./providers/openai-completions.js";
|
||||||
export { OpenAIResponsesLLM } from "./providers/openai-responses.js";
|
export { OpenAIResponsesLLM } from "./providers/openai-responses.js";
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type * from "./types.js";
|
export type * from "./types.js";
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,97 @@
|
||||||
import { readFileSync } from "fs";
|
import { PROVIDERS } from "./models.generated.js";
|
||||||
import { dirname, join } from "path";
|
import { AnthropicLLM } from "./providers/anthropic.js";
|
||||||
import { fileURLToPath } from "url";
|
import { GoogleLLM } from "./providers/gemini.js";
|
||||||
|
import { OpenAICompletionsLLM } from "./providers/openai-completions.js";
|
||||||
|
import { OpenAIResponsesLLM } from "./providers/openai-responses.js";
|
||||||
|
import type { Model } from "./types.js";
|
||||||
|
|
||||||
export type ModalityInput = "text" | "image" | "audio" | "video" | "pdf";
|
// Provider configuration with factory functions
|
||||||
export type ModalityOutput = "text" | "image" | "audio";
|
export const PROVIDER_CONFIG = {
|
||||||
|
google: {
|
||||||
|
envKey: "GEMINI_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) => new GoogleLLM(model, apiKey),
|
||||||
|
},
|
||||||
|
openai: {
|
||||||
|
envKey: "OPENAI_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) => new OpenAIResponsesLLM(model, apiKey),
|
||||||
|
},
|
||||||
|
anthropic: {
|
||||||
|
envKey: "ANTHROPIC_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) => new AnthropicLLM(model, apiKey),
|
||||||
|
},
|
||||||
|
xai: {
|
||||||
|
envKey: "XAI_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) => new OpenAICompletionsLLM(model, apiKey, "https://api.x.ai/v1"),
|
||||||
|
},
|
||||||
|
groq: {
|
||||||
|
envKey: "GROQ_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) =>
|
||||||
|
new OpenAICompletionsLLM(model, apiKey, "https://api.groq.com/openai/v1"),
|
||||||
|
},
|
||||||
|
cerebras: {
|
||||||
|
envKey: "CEREBRAS_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) => new OpenAICompletionsLLM(model, apiKey, "https://api.cerebras.ai/v1"),
|
||||||
|
},
|
||||||
|
openrouter: {
|
||||||
|
envKey: "OPENROUTER_API_KEY",
|
||||||
|
create: (model: string, apiKey: string) =>
|
||||||
|
new OpenAICompletionsLLM(model, apiKey, "https://openrouter.ai/api/v1"),
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
export interface ModelInfo {
|
// Type mapping from provider to LLM implementation
|
||||||
id: string;
|
export type ProviderToLLM = {
|
||||||
name: string;
|
google: GoogleLLM;
|
||||||
attachment: boolean;
|
openai: OpenAIResponsesLLM;
|
||||||
reasoning: boolean;
|
anthropic: AnthropicLLM;
|
||||||
temperature: boolean;
|
xai: OpenAICompletionsLLM;
|
||||||
tool_call: boolean;
|
groq: OpenAICompletionsLLM;
|
||||||
release_date: string;
|
cerebras: OpenAICompletionsLLM;
|
||||||
last_updated: string;
|
openrouter: OpenAICompletionsLLM;
|
||||||
modalities: {
|
};
|
||||||
input: ModalityInput[];
|
|
||||||
output: ModalityOutput[];
|
// Extract model types for each provider
|
||||||
};
|
export type GoogleModel = keyof typeof PROVIDERS.google.models;
|
||||||
open_weights: boolean;
|
export type OpenAIModel = keyof typeof PROVIDERS.openai.models;
|
||||||
limit: {
|
export type AnthropicModel = keyof typeof PROVIDERS.anthropic.models;
|
||||||
context: number;
|
export type XAIModel = keyof typeof PROVIDERS.xai.models;
|
||||||
output: number;
|
export type GroqModel = keyof typeof PROVIDERS.groq.models;
|
||||||
};
|
export type CerebrasModel = keyof typeof PROVIDERS.cerebras.models;
|
||||||
knowledge?: string; // Optional - knowledge cutoff date
|
export type OpenRouterModel = keyof typeof PROVIDERS.openrouter.models;
|
||||||
cost?: {
|
|
||||||
input: number;
|
// Map providers to their model types
|
||||||
output: number;
|
export type ProviderModels = {
|
||||||
cache_read?: number;
|
google: GoogleModel;
|
||||||
cache_write?: number;
|
openai: OpenAIModel;
|
||||||
};
|
anthropic: AnthropicModel;
|
||||||
|
xai: XAIModel;
|
||||||
|
groq: GroqModel;
|
||||||
|
cerebras: CerebrasModel;
|
||||||
|
openrouter: OpenRouterModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single generic factory function
|
||||||
|
export function createLLM<P extends keyof typeof PROVIDERS, M extends keyof (typeof PROVIDERS)[P]["models"]>(
|
||||||
|
provider: P,
|
||||||
|
model: M,
|
||||||
|
apiKey?: string,
|
||||||
|
): ProviderToLLM[P] {
|
||||||
|
const config = PROVIDER_CONFIG[provider as keyof typeof PROVIDER_CONFIG];
|
||||||
|
if (!config) throw new Error(`Unknown provider: ${provider}`);
|
||||||
|
|
||||||
|
const providerData = PROVIDERS[provider];
|
||||||
|
if (!providerData) throw new Error(`Unknown provider: ${provider}`);
|
||||||
|
|
||||||
|
// Type-safe model lookup
|
||||||
|
const models = providerData.models as Record<string, Model>;
|
||||||
|
const modelData = models[model as string];
|
||||||
|
if (!modelData) throw new Error(`Unknown model: ${String(model)} for provider ${provider}`);
|
||||||
|
|
||||||
|
const key = apiKey || process.env[config.envKey];
|
||||||
|
if (!key) throw new Error(`No API key provided for ${provider}. Set ${config.envKey} or pass apiKey.`);
|
||||||
|
|
||||||
|
return config.create(model as string, key) as ProviderToLLM[P];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderInfo {
|
// Re-export Model type for convenience
|
||||||
id: string;
|
export type { Model };
|
||||||
env?: string[];
|
|
||||||
npm?: string;
|
|
||||||
api?: string;
|
|
||||||
name: string;
|
|
||||||
doc?: string;
|
|
||||||
models: Record<string, ModelInfo>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ModelsData = Record<string, ProviderInfo>;
|
|
||||||
|
|
||||||
let cachedModels: ModelsData | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load models data from models.json
|
|
||||||
* The file is loaded relative to this module's location
|
|
||||||
*/
|
|
||||||
export function loadModels(): ModelsData {
|
|
||||||
if (cachedModels) {
|
|
||||||
return cachedModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get the directory of this module
|
|
||||||
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
||||||
const modelsPath = join(currentDir, "models.json");
|
|
||||||
|
|
||||||
const data = readFileSync(modelsPath, "utf-8");
|
|
||||||
cachedModels = JSON.parse(data);
|
|
||||||
return cachedModels!;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load models.json:", error);
|
|
||||||
// Return empty providers object as fallback
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get information about a specific model
|
|
||||||
*/
|
|
||||||
export function getModelInfo(modelId: string): ModelInfo | undefined {
|
|
||||||
const data = loadModels();
|
|
||||||
|
|
||||||
// Search through all providers
|
|
||||||
for (const provider of Object.values(data)) {
|
|
||||||
if (provider.models && provider.models[modelId]) {
|
|
||||||
return provider.models[modelId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all models for a specific provider
|
|
||||||
*/
|
|
||||||
export function getProviderModels(providerId: string): ModelInfo[] {
|
|
||||||
const data = loadModels();
|
|
||||||
const provider = data[providerId];
|
|
||||||
|
|
||||||
if (!provider || !provider.models) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.values(provider.models);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get provider information
|
|
||||||
*/
|
|
||||||
export function getProviderInfo(providerId: string): ProviderInfo | undefined {
|
|
||||||
const data = loadModels();
|
|
||||||
return data[providerId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a model supports thinking/reasoning
|
|
||||||
*/
|
|
||||||
export function supportsThinking(modelId: string): boolean {
|
|
||||||
const model = getModelInfo(modelId);
|
|
||||||
return model?.reasoning === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a model supports tool calling
|
|
||||||
*/
|
|
||||||
export function supportsTools(modelId: string): boolean {
|
|
||||||
const model = getModelInfo(modelId);
|
|
||||||
return model?.tool_call === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all available providers
|
|
||||||
*/
|
|
||||||
export function getAllProviders(): ProviderInfo[] {
|
|
||||||
const data = loadModels();
|
|
||||||
return Object.values(data);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import type {
|
||||||
ToolCall,
|
ToolCall,
|
||||||
} from "../types.js";
|
} from "../types.js";
|
||||||
|
|
||||||
export interface GeminiLLMOptions extends LLMOptions {
|
export interface GoogleLLMOptions extends LLMOptions {
|
||||||
toolChoice?: "auto" | "none" | "any";
|
toolChoice?: "auto" | "none" | "any";
|
||||||
thinking?: {
|
thinking?: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|
@ -25,7 +25,7 @@ export interface GeminiLLMOptions extends LLMOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GeminiLLM implements LLM<GeminiLLMOptions> {
|
export class GoogleLLM implements LLM<GoogleLLMOptions> {
|
||||||
private client: GoogleGenAI;
|
private client: GoogleGenAI;
|
||||||
private model: string;
|
private model: string;
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ export class GeminiLLM implements LLM<GeminiLLMOptions> {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
async complete(context: Context, options?: GeminiLLMOptions): Promise<AssistantMessage> {
|
async complete(context: Context, options?: GoogleLLMOptions): Promise<AssistantMessage> {
|
||||||
try {
|
try {
|
||||||
const contents = this.convertMessages(context.messages);
|
const contents = this.convertMessages(context.messages);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,20 @@ export interface TokenUsage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StopReason = "stop" | "length" | "toolUse" | "safety" | "error";
|
export type StopReason = "stop" | "length" | "toolUse" | "safety" | "error";
|
||||||
|
|
||||||
|
// Model interface for the unified model system
|
||||||
|
export interface Model {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
reasoning: boolean;
|
||||||
|
input: ("text" | "image")[];
|
||||||
|
cost: {
|
||||||
|
input: number; // $/million tokens
|
||||||
|
output: number; // $/million tokens
|
||||||
|
cacheRead: number; // $/million tokens
|
||||||
|
cacheWrite: number; // $/million tokens
|
||||||
|
};
|
||||||
|
contextWindow: number;
|
||||||
|
maxTokens: number;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
import { describe, it, beforeAll, afterAll, expect } from "vitest";
|
||||||
import { GeminiLLM } from "../src/providers/gemini.js";
|
import { GoogleLLM } from "../src/providers/gemini.js";
|
||||||
import { OpenAICompletionsLLM } from "../src/providers/openai-completions.js";
|
import { OpenAICompletionsLLM } from "../src/providers/openai-completions.js";
|
||||||
import { OpenAIResponsesLLM } from "../src/providers/openai-responses.js";
|
import { OpenAIResponsesLLM } from "../src/providers/openai-responses.js";
|
||||||
import { AnthropicLLM } from "../src/providers/anthropic.js";
|
import { AnthropicLLM } from "../src/providers/anthropic.js";
|
||||||
import type { LLM, LLMOptions, Context, Tool, AssistantMessage } from "../src/types.js";
|
import type { LLM, LLMOptions, Context, Tool, AssistantMessage } from "../src/types.js";
|
||||||
import { spawn, ChildProcess, execSync } from "child_process";
|
import { spawn, ChildProcess, execSync } from "child_process";
|
||||||
|
import { createLLM } from "../src/models.js";
|
||||||
|
|
||||||
// Calculator tool definition (same as examples)
|
// Calculator tool definition (same as examples)
|
||||||
const calculatorTool: Tool = {
|
const calculatorTool: Tool = {
|
||||||
|
|
@ -213,10 +214,10 @@ async function multiTurn<T extends LLMOptions>(llm: LLM<T>, thinkingOptions: T)
|
||||||
|
|
||||||
describe("AI Providers E2E Tests", () => {
|
describe("AI Providers E2E Tests", () => {
|
||||||
describe.skipIf(!process.env.GEMINI_API_KEY)("Gemini Provider", () => {
|
describe.skipIf(!process.env.GEMINI_API_KEY)("Gemini Provider", () => {
|
||||||
let llm: GeminiLLM;
|
let llm: GoogleLLM;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
llm = new GeminiLLM("gemini-2.5-flash", process.env.GEMINI_API_KEY!);
|
llm = new GoogleLLM("gemini-2.5-flash", process.env.GEMINI_API_KEY!);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should complete basic text generation", async () => {
|
it("should complete basic text generation", async () => {
|
||||||
|
|
@ -316,11 +317,11 @@ describe("AI Providers E2E Tests", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skipIf(!process.env.GROK_API_KEY)("Grok Provider (via OpenAI Completions)", () => {
|
describe.skipIf(!process.env.XAI_API_KEY)("xAI Provider (via OpenAI Completions)", () => {
|
||||||
let llm: OpenAICompletionsLLM;
|
let llm: OpenAICompletionsLLM;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
llm = new OpenAICompletionsLLM("grok-code-fast-1", process.env.GROK_API_KEY!, "https://api.x.ai/v1");
|
llm = new OpenAICompletionsLLM("grok-code-fast-1", process.env.XAI_API_KEY!, "https://api.x.ai/v1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should complete basic text generation", async () => {
|
it("should complete basic text generation", async () => {
|
||||||
|
|
@ -509,4 +510,32 @@ describe("AI Providers E2E Tests", () => {
|
||||||
await multiTurn(llm, {reasoningEffort: "medium"});
|
await multiTurn(llm, {reasoningEffort: "medium"});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.skipIf(!process.env.OPENROUTER_API_KEY)("OpenRouter Provider (Kimi K2)", () => {
|
||||||
|
let llm: OpenAICompletionsLLM;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
llm = createLLM("openrouter", "moonshotai/kimi-k2", process.env.OPENROUTER_API_KEY!);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should complete basic text generation", async () => {
|
||||||
|
await basicTextGeneration(llm);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle tool calling", async () => {
|
||||||
|
await handleToolCall(llm);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle streaming", async () => {
|
||||||
|
await handleStreaming(llm);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle thinking mode", async () => {
|
||||||
|
await handleThinking(llm, {reasoningEffort: "medium"}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multi-turn with thinking and tools", async () => {
|
||||||
|
await multiTurn(llm, {reasoningEffort: "medium"});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue