docs: rewrite README
Full rewrite focused on what consumers actually need: install, a working snippet per provider, and the public API surface. Changes vs the previous version: - Document ClaudeCodeProvider, including the subscription-via-CLI auth path that the new provider enables. - Remove the ProviderRegistry section (the class was removed in R1). - Drop the stale Provider Comparison and Detailed Capabilities tables; vendor capabilities and model lists move too fast for a README to track. - Remove the inaccurate Zero Dependencies and Comprehensive Testing claims (post-refactor 44/91 tests need updating). - Refresh default models (Gemini 1.5 -> 2.5) and the package list to match the current build. - Add a brief Architecture note covering the base hooks introduced in R2 and the OpenWebUI strategy split from R3. README is 315 lines (was 701).
This commit is contained in:
732
README.md
732
README.md
@@ -1,35 +1,10 @@
|
|||||||
# Simple AI Provider
|
# Simple AI Provider
|
||||||
|
|
||||||
A professional, type-safe TypeScript package that provides a unified interface for multiple AI providers. Currently supports **Claude (Anthropic)**, **OpenAI**, **Google Gemini**, and **OpenWebUI** with a consistent API across all providers.
|
A type-safe TypeScript library with a single API surface for **Claude (Anthropic)**, **OpenAI**, **Google Gemini**, **OpenWebUI**, and **Claude Code** (subscription-friendly via local CLI).
|
||||||
|
|
||||||
## ✨ Features
|
The same `complete()` / `stream()` interface works across every provider, with consistent error types, streaming, and optional structured (typed JSON) output.
|
||||||
|
|
||||||
- 🔗 **Unified Interface**: Same API for Claude, OpenAI, Gemini, and OpenWebUI
|
## Install
|
||||||
- 🎯 **Type Safety**: Full TypeScript support with comprehensive type definitions
|
|
||||||
- 🚀 **Streaming Support**: Real-time response streaming for all providers
|
|
||||||
- 🛡️ **Error Handling**: Standardized error types with provider-specific details
|
|
||||||
- 🏭 **Factory Pattern**: Easy provider creation and management
|
|
||||||
- 🔧 **Configurable**: Extensive configuration options for each provider
|
|
||||||
- 📦 **Zero Dependencies**: Lightweight with minimal external dependencies
|
|
||||||
- 🌐 **Local Support**: OpenWebUI integration for local/private AI models
|
|
||||||
- 🎨 **Structured Output**: Define custom response types for type-safe AI outputs
|
|
||||||
- 🏗️ **Provider Registry**: Dynamic provider registration and creation system
|
|
||||||
- ✅ **Comprehensive Testing**: Full test coverage with Bun test framework
|
|
||||||
- 🔍 **Advanced Validation**: Input validation with detailed error messages
|
|
||||||
|
|
||||||
## 🏗️ Architecture
|
|
||||||
|
|
||||||
The library is built on solid design principles:
|
|
||||||
|
|
||||||
- **Template Method Pattern**: Base provider defines the workflow, subclasses implement specifics
|
|
||||||
- **Factory Pattern**: Clean provider creation and management
|
|
||||||
- **Strategy Pattern**: Unified interface across different AI providers
|
|
||||||
- **Type Safety**: Comprehensive TypeScript support throughout
|
|
||||||
- **Error Normalization**: Consistent error handling across all providers
|
|
||||||
- **Validation First**: Input validation before processing
|
|
||||||
- **Extensibility**: Easy to add new providers via registry system
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install simple-ai-provider
|
npm install simple-ai-provider
|
||||||
@@ -37,37 +12,14 @@ npm install simple-ai-provider
|
|||||||
bun add simple-ai-provider
|
bun add simple-ai-provider
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic Usage
|
Requires Node ≥ 18 (or Bun). TypeScript ≥ 5 is recommended as a peer dependency.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { ClaudeProvider, OpenAIProvider, GeminiProvider, OpenWebUIProvider } from 'simple-ai-provider';
|
import { ClaudeProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
// Claude
|
const claude = new ClaudeProvider({ apiKey: process.env.ANTHROPIC_API_KEY! });
|
||||||
const claude = new ClaudeProvider({
|
|
||||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
||||||
defaultModel: 'claude-3-5-sonnet-20241022'
|
|
||||||
});
|
|
||||||
|
|
||||||
// OpenAI
|
|
||||||
const openai = new OpenAIProvider({
|
|
||||||
apiKey: process.env.OPENAI_API_KEY!,
|
|
||||||
defaultModel: 'gpt-4o'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Google Gemini
|
|
||||||
const gemini = new GeminiProvider({
|
|
||||||
apiKey: process.env.GOOGLE_AI_API_KEY!,
|
|
||||||
defaultModel: 'gemini-1.5-flash'
|
|
||||||
});
|
|
||||||
|
|
||||||
// OpenWebUI (local)
|
|
||||||
const openwebui = new OpenWebUIProvider({
|
|
||||||
apiKey: 'ollama', // Often not required
|
|
||||||
baseUrl: 'http://localhost:3000',
|
|
||||||
defaultModel: 'llama2'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize and use any provider
|
|
||||||
await claude.initialize();
|
await claude.initialize();
|
||||||
|
|
||||||
const response = await claude.complete({
|
const response = await claude.complete({
|
||||||
@@ -75,183 +27,88 @@ const response = await claude.complete({
|
|||||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||||
{ role: 'user', content: 'Explain TypeScript in one sentence.' }
|
{ role: 'user', content: 'Explain TypeScript in one sentence.' }
|
||||||
],
|
],
|
||||||
maxTokens: 100,
|
maxTokens: 200
|
||||||
temperature: 0.7
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(response.content);
|
console.log(response.content);
|
||||||
|
console.log(response.usage); // { promptTokens, completionTokens, totalTokens }
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🏭 Factory Functions
|
Every provider follows the same pattern: construct, `initialize()`, then `complete()` or `stream()`.
|
||||||
|
|
||||||
Create providers using factory functions for cleaner code:
|
## Providers
|
||||||
|
|
||||||
|
### Claude (Anthropic API)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createProvider, createClaudeProvider, createOpenAIProvider, createGeminiProvider, createOpenWebUIProvider } from 'simple-ai-provider';
|
import { ClaudeProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
// Method 1: Specific factory functions
|
const claude = new ClaudeProvider({
|
||||||
const claude = createClaudeProvider('your-key', { defaultModel: 'claude-3-5-sonnet-20241022' });
|
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||||
const openai = createOpenAIProvider('your-key', { defaultModel: 'gpt-4o' });
|
defaultModel: 'claude-3-5-sonnet-20241022', // optional
|
||||||
const gemini = createGeminiProvider('your-key', { defaultModel: 'gemini-1.5-flash' });
|
version: '2023-06-01', // optional
|
||||||
const openwebui = createOpenWebUIProvider({ apiKey: 'your-key', baseUrl: 'http://localhost:3000' });
|
timeout: 30_000, // optional, ms
|
||||||
|
maxRetries: 3 // optional
|
||||||
// Method 2: Generic factory
|
});
|
||||||
const provider = createProvider('claude', { apiKey: 'your-key' });
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Provider Registry
|
### Claude Code (subscription via local CLI)
|
||||||
|
|
||||||
For dynamic provider creation and registration:
|
`ClaudeCodeProvider` wraps `@anthropic-ai/claude-agent-sdk`, which authenticates through the local `claude` CLI. This is the supported path for **Claude Pro / Max subscribers** who don't have a console API key.
|
||||||
|
|
||||||
|
**Setup:** install the CLI and run `claude login` once. No API key required.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { ProviderRegistry, ClaudeProvider } from 'simple-ai-provider';
|
import { ClaudeCodeProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
// Register a custom provider
|
const claude = new ClaudeCodeProvider({}); // uses local credentials
|
||||||
ProviderRegistry.register('my-claude', ClaudeProvider);
|
|
||||||
|
|
||||||
// Create provider dynamically
|
|
||||||
const provider = ProviderRegistry.create('my-claude', { apiKey: 'your-key' });
|
|
||||||
|
|
||||||
// Check available providers
|
|
||||||
const availableProviders = ProviderRegistry.getRegisteredProviders();
|
|
||||||
console.log(availableProviders); // ['claude', 'openai', 'gemini', 'openwebui', 'my-claude']
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎨 Structured Response Types
|
|
||||||
|
|
||||||
Define custom response types for type-safe, structured AI outputs. The library automatically parses the AI's response into your desired type.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createResponseType, createClaudeProvider } from 'simple-ai-provider';
|
|
||||||
|
|
||||||
// 1. Define your response type
|
|
||||||
interface ProductAnalysis {
|
|
||||||
productName: string;
|
|
||||||
priceRange: 'budget' | 'mid-range' | 'premium';
|
|
||||||
pros: string[];
|
|
||||||
cons: string[];
|
|
||||||
overallRating: number; // 1-10 scale
|
|
||||||
recommendation: 'buy' | 'consider' | 'avoid';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Create a ResponseType object
|
|
||||||
const productAnalysisType = createResponseType<ProductAnalysis>(
|
|
||||||
'A comprehensive product analysis with pros, cons, rating, and recommendation'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Use with any provider
|
|
||||||
const claude = createClaudeProvider({ apiKey: 'your-key' });
|
|
||||||
await claude.initialize();
|
await claude.initialize();
|
||||||
|
|
||||||
const response = await claude.complete<ProductAnalysis>({
|
const response = await claude.complete({
|
||||||
messages: [
|
messages: [{ role: 'user', content: 'Hello!' }]
|
||||||
{ role: 'user', content: 'Analyze the iPhone 15 Pro from a consumer perspective.' }
|
|
||||||
],
|
|
||||||
responseType: productAnalysisType,
|
|
||||||
maxTokens: 800
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Get the fully typed and parsed response
|
|
||||||
const analysis = response.content;
|
|
||||||
console.log(`Product: ${analysis.productName}`);
|
|
||||||
console.log(`Recommendation: ${analysis.recommendation}`);
|
|
||||||
console.log(`Rating: ${analysis.overallRating}/10`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Benefits
|
|
||||||
|
|
||||||
- **Automatic Parsing**: The AI's JSON response is automatically parsed into your specified type.
|
|
||||||
- **Type Safety**: Get fully typed responses from AI providers with IntelliSense.
|
|
||||||
- **Automatic Prompting**: System prompts are automatically generated to guide the AI.
|
|
||||||
- **Validation**: Built-in response validation and parsing logic.
|
|
||||||
- **Consistency**: Ensures AI outputs match your expected format.
|
|
||||||
- **Developer Experience**: Catch errors at compile-time instead of runtime.
|
|
||||||
|
|
||||||
### Streaming with Response Types
|
|
||||||
|
|
||||||
You can also use response types with streaming. The raw stream provides real-time text, and you can parse the final string once the stream is complete.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { parseAndValidateResponseType } from 'simple-ai-provider';
|
|
||||||
|
|
||||||
const stream = claude.stream({
|
|
||||||
messages: [{ role: 'user', content: 'Analyze the Tesla Model 3.' }],
|
|
||||||
responseType: productAnalysisType,
|
|
||||||
maxTokens: 600
|
|
||||||
});
|
|
||||||
|
|
||||||
let fullResponse = '';
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (!chunk.isComplete) {
|
|
||||||
process.stdout.write(chunk.content);
|
|
||||||
fullResponse += chunk.content;
|
|
||||||
} else {
|
|
||||||
console.log('\n\nStream complete!');
|
|
||||||
// Validate the complete streamed response
|
|
||||||
try {
|
|
||||||
const analysis = parseAndValidateResponseType(fullResponse, productAnalysisType);
|
|
||||||
console.log('Validation successful!');
|
|
||||||
console.log(`Product: ${analysis.productName}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Validation failed:', (e as Error).message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Environment Variables
|
|
||||||
|
|
||||||
Set up your API keys:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Required for respective providers
|
|
||||||
export ANTHROPIC_API_KEY="your-claude-api-key"
|
|
||||||
export OPENAI_API_KEY="your-openai-api-key"
|
|
||||||
export GOOGLE_AI_API_KEY="your-gemini-api-key"
|
|
||||||
|
|
||||||
# OpenWebUI Bearer Token (get from Settings > Account in OpenWebUI)
|
|
||||||
export OPENWEBUI_API_KEY="your-bearer-token"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Provider-Specific Configuration
|
|
||||||
|
|
||||||
### Claude Configuration
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const claude = new ClaudeProvider({
|
|
||||||
apiKey: 'your-api-key',
|
|
||||||
defaultModel: 'claude-3-5-sonnet-20241022',
|
|
||||||
version: '2023-06-01',
|
|
||||||
maxRetries: 3,
|
|
||||||
timeout: 30000
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenAI Configuration
|
You can still pass `apiKey` to override (it's set as `ANTHROPIC_API_KEY` for the SDK). Optional config:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
new ClaudeCodeProvider({
|
||||||
|
defaultModel: 'sonnet', // 'sonnet' | 'opus' | 'haiku' | 'inherit' | full model ID
|
||||||
|
maxTurns: 1, // 1 for plain completion; raise for agent/tool loops
|
||||||
|
allowedTools: [], // tool names to enable (default: none)
|
||||||
|
cwd: process.cwd() // working directory for the agent
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Trade-offs to know:**
|
||||||
|
- Requires `claude` CLI installed on the host. Not ideal for typical server deployments.
|
||||||
|
- Higher latency than the direct API (spawns a CLI process per request).
|
||||||
|
- Streaming yields text as the SDK emits successive assistant messages, not token-by-token deltas.
|
||||||
|
|
||||||
|
### OpenAI
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { OpenAIProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
const openai = new OpenAIProvider({
|
const openai = new OpenAIProvider({
|
||||||
apiKey: 'your-api-key',
|
apiKey: process.env.OPENAI_API_KEY!,
|
||||||
defaultModel: 'gpt-4o',
|
defaultModel: 'gpt-4o',
|
||||||
organization: 'your-org-id',
|
organization: 'org-...', // optional
|
||||||
project: 'your-project-id',
|
project: 'proj-...' // optional
|
||||||
maxRetries: 3,
|
|
||||||
timeout: 30000
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gemini Configuration
|
`baseUrl` is supported for OpenAI-compatible endpoints.
|
||||||
|
|
||||||
|
### Gemini (Google)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { GeminiProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
const gemini = new GeminiProvider({
|
const gemini = new GeminiProvider({
|
||||||
apiKey: 'your-api-key',
|
apiKey: process.env.GOOGLE_AI_API_KEY!,
|
||||||
defaultModel: 'gemini-1.5-flash',
|
defaultModel: 'gemini-2.5-flash',
|
||||||
safetySettings: [
|
safetySettings: [/* SafetySetting[] from @google/genai */],
|
||||||
{
|
|
||||||
category: 'HARM_CATEGORY_HARASSMENT',
|
|
||||||
threshold: 'BLOCK_MEDIUM_AND_ABOVE'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
generationConfig: {
|
generationConfig: {
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
topP: 0.8,
|
topP: 0.8,
|
||||||
@@ -261,441 +118,198 @@ const gemini = new GeminiProvider({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenWebUI Configuration
|
Backed by `@google/genai` (the successor to the deprecated `@google/generative-ai`).
|
||||||
|
|
||||||
|
### OpenWebUI (local / self-hosted)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { OpenWebUIProvider } from 'simple-ai-provider';
|
||||||
|
|
||||||
const openwebui = new OpenWebUIProvider({
|
const openwebui = new OpenWebUIProvider({
|
||||||
apiKey: 'your-bearer-token', // Get from OpenWebUI Settings > Account
|
apiKey: 'your-bearer-token', // from Settings > Account in OpenWebUI
|
||||||
baseUrl: 'http://localhost:3000', // Your OpenWebUI instance
|
baseUrl: 'http://localhost:3000',
|
||||||
defaultModel: 'llama3.1',
|
defaultModel: 'llama3.1:latest',
|
||||||
useOllamaProxy: false, // Use OpenWebUI's chat API (recommended)
|
useOllamaProxy: false, // false: OpenWebUI chat API (default)
|
||||||
// useOllamaProxy: true, // Use Ollama API proxy for direct model access
|
// true: direct Ollama proxy
|
||||||
dangerouslyAllowInsecureConnections: true, // For local HTTPS
|
dangerouslyAllowInsecureConnections: true
|
||||||
timeout: 60000, // Longer timeout for local inference
|
|
||||||
maxRetries: 2
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🌊 Streaming Support
|
`useOllamaProxy` flips between two internal strategies — the OpenAI-compatible chat completions endpoint and the direct Ollama generate endpoint.
|
||||||
|
|
||||||
All providers support real-time streaming:
|
## Streaming
|
||||||
|
|
||||||
|
Identical shape across providers:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const stream = provider.stream({
|
for await (const chunk of provider.stream({ messages, maxTokens: 200 })) {
|
||||||
messages: [{ role: 'user', content: 'Count from 1 to 10' }],
|
|
||||||
maxTokens: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (!chunk.isComplete) {
|
if (!chunk.isComplete) {
|
||||||
process.stdout.write(chunk.content);
|
process.stdout.write(chunk.content);
|
||||||
} else {
|
} else {
|
||||||
console.log('\nDone! Usage:', chunk.usage);
|
console.log('\n', chunk.usage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔀 Multi-Provider Usage
|
## Structured output
|
||||||
|
|
||||||
Use multiple providers seamlessly:
|
Ask any provider for a typed JSON response. The library injects a system prompt describing the expected shape and parses the result.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const providers = {
|
import { createResponseType } from 'simple-ai-provider';
|
||||||
claude: new ClaudeProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
|
|
||||||
openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
||||||
gemini: new GeminiProvider({ apiKey: process.env.GOOGLE_AI_API_KEY! }),
|
|
||||||
openwebui: new OpenWebUIProvider({
|
|
||||||
apiKey: 'ollama',
|
|
||||||
baseUrl: 'http://localhost:3000'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize all providers
|
interface UserProfile {
|
||||||
await Promise.all(Object.values(providers).map(p => p.initialize()));
|
name: string;
|
||||||
|
age: number;
|
||||||
// Use the same interface for all
|
hobbies: string[];
|
||||||
const prompt = {
|
|
||||||
messages: [{ role: 'user', content: 'Hello!' }],
|
|
||||||
maxTokens: 50
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [name, provider] of Object.entries(providers)) {
|
|
||||||
try {
|
|
||||||
const response = await provider.complete(prompt);
|
|
||||||
console.log(`${name}: ${response.content}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`${name} failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profileType = createResponseType<UserProfile>(
|
||||||
|
'A user profile with name, age, and hobbies',
|
||||||
|
{ name: 'Alice', age: 30, hobbies: ['climbing', 'photography'] }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await claude.complete({
|
||||||
|
messages: [{ role: 'user', content: 'Generate a fictional user profile.' }],
|
||||||
|
responseType: profileType
|
||||||
|
});
|
||||||
|
|
||||||
|
// response.content is typed as UserProfile
|
||||||
|
// response.rawContent is the original string
|
||||||
|
console.log(response.content.name);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📊 Provider Comparison
|
This also works with `stream()` — chunks deliver text, then the final chunk parses.
|
||||||
|
|
||||||
| Provider | Context Length | Streaming | Vision | Function Calling | Local Execution | Best For |
|
## Factory functions
|
||||||
|----------|---------------|-----------|--------|------------------|-----------------|----------|
|
|
||||||
| **Claude** | 200K tokens | ✅ | ✅ | ✅ | ❌ | Reasoning, Analysis, Code Review |
|
|
||||||
| **OpenAI** | 128K tokens | ✅ | ✅ | ✅ | ❌ | General Purpose, Function Calling |
|
|
||||||
| **Gemini** | 1M tokens | ✅ | ✅ | ✅ | ❌ | Large Documents, Multimodal |
|
|
||||||
| **OpenWebUI** | 32K tokens | ✅ | ❌ | ❌ | ✅ | Privacy, Custom Models, Local |
|
|
||||||
|
|
||||||
### Detailed Capabilities
|
If you prefer a single entry point:
|
||||||
|
|
||||||
Each provider offers unique capabilities:
|
|
||||||
|
|
||||||
#### Claude (Anthropic)
|
|
||||||
|
|
||||||
- Advanced reasoning and analysis
|
|
||||||
- Excellent code review capabilities
|
|
||||||
- Strong safety features
|
|
||||||
- System message support
|
|
||||||
|
|
||||||
#### OpenAI
|
|
||||||
|
|
||||||
- Broad model selection
|
|
||||||
- Function calling support
|
|
||||||
- JSON mode for structured outputs
|
|
||||||
- Vision capabilities
|
|
||||||
|
|
||||||
#### Gemini (Google)
|
|
||||||
|
|
||||||
- Largest context window (1M tokens)
|
|
||||||
- Multimodal capabilities
|
|
||||||
- Cost-effective pricing
|
|
||||||
- Strong multilingual support
|
|
||||||
|
|
||||||
#### OpenWebUI
|
|
||||||
|
|
||||||
- Complete privacy (local execution)
|
|
||||||
- Custom model support
|
|
||||||
- No API costs
|
|
||||||
- RAG (Retrieval Augmented Generation) support
|
|
||||||
|
|
||||||
## 🎯 Model Selection
|
|
||||||
|
|
||||||
### Getting Available Models
|
|
||||||
|
|
||||||
Instead of maintaining a static list, you can programmatically get available models:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Get provider information including available models
|
import { createProvider } from 'simple-ai-provider';
|
||||||
const info = provider.getInfo();
|
|
||||||
console.log('Available models:', info.models);
|
|
||||||
|
|
||||||
// Example output:
|
const claude = createProvider('claude', { apiKey: '…' });
|
||||||
// Claude: ['claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', ...]
|
const openai = createProvider('openai', { apiKey: '…' });
|
||||||
// OpenAI: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', ...]
|
const gemini = createProvider('gemini', { apiKey: '…' });
|
||||||
// Gemini: ['gemini-1.5-flash', 'gemini-1.5-pro', ...]
|
const openwebui = createProvider('openwebui', { apiKey: '…', baseUrl: '…' });
|
||||||
// OpenWebUI: ['llama3.1:latest', 'mistral:latest', ...]
|
const claudeCode = createProvider('claude-code', {});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Model Selection Guidelines
|
Per-provider shortcut factories also exist: `createClaudeProvider`, `createOpenAIProvider`, `createGeminiProvider`, `createOpenWebUIProvider`, `createClaudeCodeProvider`.
|
||||||
|
|
||||||
**For Claude (Anthropic):**
|
## Error handling
|
||||||
- Check [Anthropic's model documentation](https://docs.anthropic.com/claude/docs/models-overview) for latest models
|
|
||||||
|
|
||||||
**For OpenAI:**
|
Every error thrown by the library is an `AIProviderError` with a typed `.type` and optional `.statusCode`:
|
||||||
- Check [OpenAI's model documentation](https://platform.openai.com/docs/models) for latest models
|
|
||||||
|
|
||||||
**For Gemini (Google):**
|
|
||||||
- Check [Google AI's model documentation](https://ai.google.dev/docs/models) for latest models
|
|
||||||
|
|
||||||
**For OpenWebUI:**
|
|
||||||
- Models depend on your local installation
|
|
||||||
- Check your OpenWebUI instance for available models
|
|
||||||
|
|
||||||
## 🚨 Error Handling
|
|
||||||
|
|
||||||
The package provides comprehensive, standardized error handling with detailed error types:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { AIProviderError, AIErrorType } from 'simple-ai-provider';
|
import { AIProviderError, AIErrorType } from 'simple-ai-provider';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await provider.complete({
|
await provider.complete({ messages: [...] });
|
||||||
messages: [{ role: 'user', content: 'Hello' }]
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AIProviderError) {
|
if (error instanceof AIProviderError) {
|
||||||
switch (error.type) {
|
switch (error.type) {
|
||||||
case AIErrorType.AUTHENTICATION:
|
case AIErrorType.AUTHENTICATION: /* bad API key, expired token */ break;
|
||||||
console.log('Invalid API key or authentication failed');
|
case AIErrorType.RATE_LIMIT: /* slow down */ break;
|
||||||
break;
|
case AIErrorType.MODEL_NOT_FOUND: /* wrong model name */ break;
|
||||||
case AIErrorType.RATE_LIMIT:
|
case AIErrorType.INVALID_REQUEST: /* bad input */ break;
|
||||||
console.log('Rate limited, try again later');
|
case AIErrorType.NETWORK: /* transient connectivity */ break;
|
||||||
break;
|
case AIErrorType.TIMEOUT: /* slow request */ break;
|
||||||
case AIErrorType.MODEL_NOT_FOUND:
|
case AIErrorType.UNKNOWN: /* fall back */ break;
|
||||||
console.log('Model not available or not found');
|
|
||||||
break;
|
|
||||||
case AIErrorType.INVALID_REQUEST:
|
|
||||||
console.log('Invalid request parameters');
|
|
||||||
break;
|
|
||||||
case AIErrorType.NETWORK:
|
|
||||||
console.log('Network/connection issue');
|
|
||||||
break;
|
|
||||||
case AIErrorType.TIMEOUT:
|
|
||||||
console.log('Request timed out');
|
|
||||||
break;
|
|
||||||
case AIErrorType.UNKNOWN:
|
|
||||||
console.log('Unknown error:', error.message);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('Error:', error.message);
|
|
||||||
}
|
}
|
||||||
|
console.error(error.statusCode, error.originalError);
|
||||||
// Access additional error details
|
|
||||||
console.log('Status Code:', error.statusCode);
|
|
||||||
console.log('Original Error:', error.originalError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Types
|
## Configuration reference
|
||||||
|
|
||||||
- **AUTHENTICATION**: Invalid API keys or authentication failures
|
All providers share these base options:
|
||||||
- **RATE_LIMIT**: API rate limits exceeded
|
|
||||||
- **INVALID_REQUEST**: Malformed requests or invalid parameters
|
|
||||||
- **MODEL_NOT_FOUND**: Requested model is not available
|
|
||||||
- **NETWORK**: Connection issues or server errors
|
|
||||||
- **TIMEOUT**: Request timeout exceeded
|
|
||||||
- **UNKNOWN**: Unclassified errors
|
|
||||||
|
|
||||||
## 🔧 Advanced Usage
|
| Option | Type | Default | Notes |
|
||||||
|
|--------------|----------|-----------|----------------------------------------|
|
||||||
|
| `apiKey` | `string` | required¹ | ¹ Optional for `ClaudeCodeProvider` |
|
||||||
|
| `baseUrl` | `string` | provider default | Custom or self-hosted endpoint |
|
||||||
|
| `timeout` | `number` | `30000` | Request timeout in ms |
|
||||||
|
| `maxRetries` | `number` | `3` | SDK-level retry attempts |
|
||||||
|
|
||||||
### Custom Base URLs
|
Each provider adds its own config (see the sections above).
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
Full type definitions ship with the package. The main types you'll use:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// OpenAI-compatible endpoint
|
import type {
|
||||||
const customOpenAI = new OpenAIProvider({
|
AIMessage,
|
||||||
apiKey: 'your-key',
|
CompletionParams,
|
||||||
baseUrl: 'https://api.custom-provider.com/v1'
|
CompletionResponse,
|
||||||
});
|
CompletionChunk,
|
||||||
|
TokenUsage,
|
||||||
|
ProviderInfo,
|
||||||
|
ResponseType,
|
||||||
|
|
||||||
// Custom OpenWebUI instance
|
// Per-provider config interfaces
|
||||||
const remoteOpenWebUI = new OpenWebUIProvider({
|
ClaudeConfig,
|
||||||
apiKey: 'your-key',
|
ClaudeCodeConfig,
|
||||||
baseUrl: 'https://my-openwebui.example.com',
|
OpenAIConfig,
|
||||||
apiPath: '/api/v1'
|
GeminiConfig,
|
||||||
});
|
OpenWebUIConfig
|
||||||
|
} from 'simple-ai-provider';
|
||||||
```
|
```
|
||||||
|
|
||||||
### Provider Information
|
`CompletionResponse<T>` is generic — when you pass `responseType`, `content` is `T` (and the original string is preserved on `rawContent`).
|
||||||
|
|
||||||
|
## Provider metadata
|
||||||
|
|
||||||
|
Every provider exposes `getInfo()`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const info = provider.getInfo();
|
const info = provider.getInfo();
|
||||||
console.log(`Provider: ${info.name} v${info.version}`);
|
// { name, version, models, maxContextLength, supportsStreaming, capabilities }
|
||||||
console.log(`Models: ${info.models.join(', ')}`);
|
|
||||||
console.log(`Max Context: ${info.maxContextLength} tokens`);
|
|
||||||
console.log(`Supports Streaming: ${info.supportsStreaming}`);
|
|
||||||
console.log('Capabilities:', info.capabilities);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenWebUI-Specific Features
|
Model lists in `info.models` are example values — refer to each vendor's docs for the current authoritative list. The library accepts any model string the underlying SDK supports.
|
||||||
|
|
||||||
OpenWebUI offers unique advantages for local AI deployment:
|
## Architecture (brief)
|
||||||
|
|
||||||
```typescript
|
The library uses a template-method base class (`BaseAIProvider`) that owns the public lifecycle (`initialize`, `complete`, `stream`), input validation, response-type parsing, and error normalization. Each provider supplies:
|
||||||
const openwebui = new OpenWebUIProvider({
|
|
||||||
apiKey: 'your-bearer-token', // Get from OpenWebUI Settings > Account
|
|
||||||
baseUrl: 'http://localhost:3000',
|
|
||||||
defaultModel: 'llama3.1',
|
|
||||||
useOllamaProxy: false, // Use chat completions API (recommended)
|
|
||||||
// Longer timeout for local inference
|
|
||||||
timeout: 120000,
|
|
||||||
// Allow self-signed certificates for local development
|
|
||||||
dangerouslyAllowInsecureConnections: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test connection and list available models
|
- `doInitialize()` / `doComplete()` / `doStream()` — the actual SDK calls
|
||||||
try {
|
- `getModelNamePatterns()` — regexes for naming-convention warnings
|
||||||
await openwebui.initialize();
|
- `sendValidationProbe()` — minimal request used during `initialize()`
|
||||||
console.log('Connected to local OpenWebUI instance');
|
- `mapProviderError()` / `providerErrorMessages()` — provider-specific error translation
|
||||||
|
|
||||||
// Use either chat completions or Ollama proxy
|
|
||||||
const response = await openwebui.complete({
|
|
||||||
messages: [{ role: 'user', content: 'Hello!' }],
|
|
||||||
maxTokens: 100
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log('OpenWebUI not available:', error.message);
|
|
||||||
// Gracefully fallback to cloud providers
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**OpenWebUI API Modes:**
|
`OpenWebUIProvider` additionally uses an internal strategy split (`OpenWebUIChatStrategy` vs `OpenWebUIOllamaStrategy`) selected by `useOllamaProxy`.
|
||||||
|
|
||||||
- **Chat Completions** (`useOllamaProxy: false`): OpenWebUI's native API with full features
|
## Development
|
||||||
- **Ollama Proxy** (`useOllamaProxy: true`): Direct access to Ollama API for raw model interaction
|
|
||||||
|
|
||||||
## 📦 TypeScript Support
|
|
||||||
|
|
||||||
Full TypeScript support with comprehensive type definitions:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import type {
|
|
||||||
CompletionParams,
|
|
||||||
CompletionResponse,
|
|
||||||
CompletionChunk,
|
|
||||||
ProviderInfo,
|
|
||||||
ClaudeConfig,
|
|
||||||
OpenAIConfig,
|
|
||||||
GeminiConfig,
|
|
||||||
OpenWebUIConfig,
|
|
||||||
AIMessage,
|
|
||||||
ResponseType,
|
|
||||||
TokenUsage
|
|
||||||
} from 'simple-ai-provider';
|
|
||||||
|
|
||||||
// Type-safe configuration
|
|
||||||
const config: ClaudeConfig = {
|
|
||||||
apiKey: 'your-key',
|
|
||||||
defaultModel: 'claude-3-5-sonnet-20241022',
|
|
||||||
// TypeScript will validate all options
|
|
||||||
};
|
|
||||||
|
|
||||||
// Type-safe responses
|
|
||||||
const response: CompletionResponse = await provider.complete(params);
|
|
||||||
|
|
||||||
// Type-safe messages with metadata
|
|
||||||
const messages: AIMessage[] = [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: 'Hello',
|
|
||||||
metadata: { timestamp: Date.now() }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Type-safe response types
|
|
||||||
interface UserProfile {
|
|
||||||
name: string;
|
|
||||||
age: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseType: ResponseType<UserProfile> = createResponseType(
|
|
||||||
'A user profile with name and age',
|
|
||||||
{ name: 'John', age: 30 }
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Type Features
|
|
||||||
|
|
||||||
- **Generic Response Types**: Type-safe structured outputs
|
|
||||||
- **Message Metadata**: Support for custom message properties
|
|
||||||
- **Provider-Specific Configs**: Type-safe configuration for each provider
|
|
||||||
- **Error Types**: Comprehensive error type definitions
|
|
||||||
- **Factory Functions**: Type-safe provider creation
|
|
||||||
|
|
||||||
## 🧪 Testing
|
|
||||||
|
|
||||||
The package includes comprehensive tests using Bun test framework:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests
|
|
||||||
bun test
|
|
||||||
|
|
||||||
# Run tests for specific provider
|
|
||||||
bun test tests/claude.test.ts
|
|
||||||
bun test tests/openai.test.ts
|
|
||||||
bun test tests/gemini.test.ts
|
|
||||||
bun test tests/openwebui.test.ts
|
|
||||||
|
|
||||||
# Run tests with coverage
|
|
||||||
bun test --coverage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Coverage
|
|
||||||
|
|
||||||
- ✅ Provider initialization and configuration
|
|
||||||
- ✅ Message validation and conversion
|
|
||||||
- ✅ Error handling and normalization
|
|
||||||
- ✅ Response formatting
|
|
||||||
- ✅ Streaming functionality
|
|
||||||
- ✅ Structured response types
|
|
||||||
- ✅ Factory functions
|
|
||||||
- ✅ Provider registry
|
|
||||||
|
|
||||||
## 🛠️ Development
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18.0.0 or higher
|
|
||||||
- Bun (recommended) or npm/yarn
|
|
||||||
- TypeScript 5.0 or higher
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repository
|
|
||||||
git clone https://gitea.jleibl.net/jleibl/simple-ai-provider.git
|
git clone https://gitea.jleibl.net/jleibl/simple-ai-provider.git
|
||||||
cd simple-ai-provider
|
cd simple-ai-provider
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Build the project
|
|
||||||
bun run build
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
# Run tests
|
Examples live in `examples/`:
|
||||||
bun test
|
|
||||||
|
|
||||||
# Run examples
|
```bash
|
||||||
bun run examples/basic-usage.ts
|
bun run examples/basic-usage.ts
|
||||||
bun run examples/structured-response-types.ts
|
|
||||||
bun run examples/multi-provider.ts
|
bun run examples/multi-provider.ts
|
||||||
|
bun run examples/structured-response-types.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
Tests in `tests/` use Bun's test runner (`bun test`). Note: post-refactor some tests need updating before they pass cleanly.
|
||||||
|
|
||||||
```text
|
## License
|
||||||
src/
|
|
||||||
├── index.ts # Main entry point
|
|
||||||
├── types/
|
|
||||||
│ └── index.ts # Type definitions and utilities
|
|
||||||
├── providers/
|
|
||||||
│ ├── base.ts # Abstract base provider
|
|
||||||
│ ├── claude.ts # Claude provider implementation
|
|
||||||
│ ├── openai.ts # OpenAI provider implementation
|
|
||||||
│ ├── gemini.ts # Gemini provider implementation
|
|
||||||
│ ├── openwebui.ts # OpenWebUI provider implementation
|
|
||||||
│ └── index.ts # Provider exports
|
|
||||||
└── utils/
|
|
||||||
└── factory.ts # Factory functions and registry
|
|
||||||
|
|
||||||
examples/
|
MIT — see [LICENSE](LICENSE).
|
||||||
├── basic-usage.ts # Basic usage examples
|
|
||||||
├── structured-response-types.ts # Structured output examples
|
|
||||||
└── multi-provider.ts # Multi-provider examples
|
|
||||||
|
|
||||||
tests/
|
## Links
|
||||||
├── claude.test.ts # Claude provider tests
|
|
||||||
├── openai.test.ts # OpenAI provider tests
|
|
||||||
├── gemini.test.ts # Gemini provider tests
|
|
||||||
└── openwebui.test.ts # OpenWebUI provider tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
||||||
|
|
||||||
### Development Guidelines
|
|
||||||
|
|
||||||
1. **Code Style**: Follow the existing TypeScript patterns
|
|
||||||
2. **Testing**: Add tests for new features
|
|
||||||
3. **Documentation**: Update README for new features
|
|
||||||
4. **Type Safety**: Maintain comprehensive type definitions
|
|
||||||
5. **Error Handling**: Use standardized error types
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
## 🔗 Links
|
|
||||||
|
|
||||||
- [Anthropic Claude API](https://docs.anthropic.com/claude/reference/)
|
- [Anthropic Claude API](https://docs.anthropic.com/claude/reference/)
|
||||||
|
- [Claude Code SDK](https://docs.anthropic.com/claude/docs/claude-code-sdk)
|
||||||
- [OpenAI API](https://platform.openai.com/docs/)
|
- [OpenAI API](https://platform.openai.com/docs/)
|
||||||
- [Google Gemini API](https://ai.google.dev/)
|
- [Google Gen AI SDK](https://ai.google.dev/)
|
||||||
- [OpenWebUI](https://openwebui.com/)
|
- [OpenWebUI](https://openwebui.com/)
|
||||||
- [Gitea Repository](https://gitea.jleibl.net/jleibl/simple-ai-provider)
|
- [Repository](https://gitea.jleibl.net/jleibl/simple-ai-provider)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
⭐ **Star this repo if you find it helpful!**
|
|
||||||
|
|||||||
Reference in New Issue
Block a user