๐ญ Factory Pattern Architecture
Understanding NeuroLink's unified architecture with BaseProvider inheritance and automatic tool support.
๐ Overviewโ
NeuroLink uses a Factory Pattern architecture with BaseProvider inheritance to provide consistent functionality across all AI providers. This design eliminates code duplication and ensures every provider has the same core capabilities, including built-in tool support.
Key Benefitsโ
- โ Zero Code Duplication: Shared logic in BaseProvider
- โ Automatic Tool Support: All providers inherit 6 built-in tools
- โ Consistent Interface: Same methods across all providers
- โ Easy Provider Addition: Minimal code for new providers
- โ Centralized Updates: Fix once, apply everywhere
๐๏ธ Architecture Componentsโ
1. BaseProvider (Core Foundation)โ
The BaseProvider class is the foundation of all AI providers:
// src/lib/core/baseProvider.ts
export abstract class BaseProvider implements LanguageModelV1 {
// Core properties
readonly specVersion = "v1";
readonly defaultObjectGenerationMode = "tool";
// Abstract methods that providers must implement
abstract readonly provider: string;
abstract doGenerate(request: LanguageModelV1CallRequest): PromiseOrValue<...>;
abstract doStream(request: LanguageModelV1CallRequest): PromiseOrValue<...>;
// Shared tool management
protected tools: Map<string, SimpleTool> = new Map();
// Built-in tools available to all providers
constructor() {
this.registerBuiltInTools();
}
// Tool registration shared by all providers
registerTool(name: string, tool: SimpleTool): void {
this.tools.set(name, tool);
}
// Generate with tool support
async generate(options: GenerateOptions): Promise<GenerateResult> {
// Common logic for all providers
// Including tool execution, analytics, evaluation
}
}
2. Provider-Specific Implementationโ
Each provider extends BaseProvider with minimal code:
// src/lib/providers/openai.ts
export class OpenAIProvider extends BaseProvider {
readonly provider = "openai";
private model: OpenAILanguageModel;
constructor(apiKey: string, modelName: string = "gpt-4o") {
super(); // Inherits all BaseProvider functionality
this.model = openai(modelName, { apiKey });
}
// Only implement provider-specific logic
protected async doGenerate(request: LanguageModelV1CallRequest) {
return this.model.doGenerate(request);
}
protected async doStream(request: LanguageModelV1CallRequest) {
return this.model.doStream(request);
}
}
3. Factory Pattern Implementationโ
The factory creates providers with consistent configuration:
// src/lib/factories/providerRegistry.ts
export class ProviderRegistry {
private static instance: ProviderRegistry;
private providers = new Map<string, ProviderFactory>();
// Register provider factories
register(name: string, factory: ProviderFactory) {
this.providers.set(name, factory);
}
// Create provider instances
create(name: string, config?: ProviderConfig): BaseProvider {
const factory = this.providers.get(name);
if (!factory) {
throw new Error(`Unknown provider: ${name}`);
}
return factory.create(config);
}
}
// Usage
const registry = ProviderRegistry.getInstance();
registry.register("openai", new OpenAIProviderFactory());
registry.register("google-ai", new GoogleAIProviderFactory());
// ... register all providers
๐ง Built-in Tool Systemโ
Tool Registration in BaseProviderโ
All providers automatically get these tools:
private registerBuiltInTools() {
// Time tool
this.registerTool('getCurrentTime', {
description: 'Get the current date and time',
parameters: z.object({
timezone: z.string().optional()
}),
execute: async ({ timezone }) => {
return { time: new Date().toLocaleString('en-US', { timeZone: timezone }) };
}
});
// File operations
this.registerTool('readFile', {
description: 'Read contents of a file',
parameters: z.object({
path: z.string()
}),
execute: async ({ path }) => {
const content = await fs.readFile(path, 'utf-8');
return { content };
}
});
// Math calculations
this.registerTool('calculateMath', {
description: 'Perform mathematical calculations',
parameters: z.object({
expression: z.string()
}),
execute: async ({ expression }) => {
const result = evaluate(expression); // Safe math evaluation
return { result };
}
});
// ... other built-in tools
}
Tool Conversion for AI Modelsโ
BaseProvider converts tools to provider-specific format:
protected convertToolsForModel(): LanguageModelV1FunctionTool[] {
const tools: LanguageModelV1FunctionTool[] = [];
for (const [name, tool] of this.tools) {
tools.push({
type: 'function',
name,
description: tool.description,
parameters: tool.parameters ?
zodToJsonSchema(tool.parameters) :
{ type: 'object', properties: {} }
});
}
return tools;
}
๐ Factory Pattern Benefitsโ
1. Consistent Provider Creationโ
// All providers created the same way
const provider1 = createBestAIProvider("openai");
const provider2 = createBestAIProvider("google-ai");
const provider3 = createBestAIProvider("anthropic");
// All have the same interface and tools
await provider1.generate({ input: { text: "What time is it?" } });
await provider2.generate({ input: { text: "Calculate 42 * 10" } });
await provider3.generate({ input: { text: "Read config.json" } });
2. Easy Provider Additionโ
Adding a new provider requires minimal code:
// 1. Create provider class
export class NewAIProvider extends BaseProvider {
readonly provider = "newai";
private model: NewAIModel;
constructor(apiKey: string, modelName: string) {
super(); // Get all BaseProvider features
this.model = createNewAIModel(apiKey, modelName);
}
protected async doGenerate(request) {
return this.model.generate(request);
}
protected async doStream(request) {
return this.model.stream(request);
}
}
// 2. Create factory
export class NewAIProviderFactory implements ProviderFactory {
create(config?: ProviderConfig): BaseProvider {
const apiKey = process.env.NEWAI_API_KEY;
const model = config?.model || "default-model";
return new NewAIProvider(apiKey, model);
}
}
// 3. Register with system
registry.register("newai", new NewAIProviderFactory());
3. Centralized Feature Additionโ
Add features once in BaseProvider, all providers get them:
// Add new feature to BaseProvider
export abstract class BaseProvider {
// New feature: token counting
async countTokens(text: string): Promise<number> {
// Implementation here
return tokenCount;
}
// New feature: cost estimation
async estimateCost(options: GenerateOptions): Promise<CostEstimate> {
const tokens = await this.countTokens(options.input.text);
return this.calculateCost(tokens);
}
}
// Now ALL providers have token counting and cost estimation!
๐ Architecture Diagramโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NeuroLink SDK โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Factory Layer โ
โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โ โ Provider โ โ Provider โ โ Unified โ โ
โ โ Registry โ โ Factory โ โ Registry โ โ
โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโค
โ BaseProvider (Core) โ
โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โ โ Built-in โ โ Tool โ โ Interface โ โ
โ โ Tools (6) โ โ Management โ โ Methods โ โ
โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Provider Implementations โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ OpenAI โ โ Google โ โ Anthropicโ โ Bedrock โ ... โ
โ โ Provider โ โ Provider โ โ Provider โ โ Provider โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฏ Design Principlesโ
1. Single Responsibilityโ
Each component has one clear purpose:
- BaseProvider: Core functionality and tool management
- Provider Classes: Provider-specific API integration
- Factory: Provider instantiation
- Registry: Provider registration and lookup
2. Open/Closed Principleโ
- Open for extension: Easy to add new providers
- Closed for modification: Core logic doesn't change
3. Dependency Inversionโ
- Providers depend on BaseProvider abstraction
- High-level modules don't depend on low-level details
4. Interface Segregationโ
- Clean, minimal interface for each provider
- Only implement what's needed
๐ Request Flowโ
Here's how a request flows through the architecture:
// 1. User makes request
const result = await provider.generate({
input: { text: "What time is it in Tokyo?" }
});
// 2. BaseProvider.generate() handles common logic
async generate(options: GenerateOptions): Promise<GenerateResult> {
// Convert tools for model
const tools = this.convertToolsForModel();
// Create request
const request: LanguageModelV1CallRequest = {
inputFormat: "messages",
messages: this.formatMessages(options),
tools: options.disableTools ? undefined : tools,
// ... other common setup
};
// 3. Call provider-specific implementation
const response = await this.doGenerate(request);
// 4. Handle tool calls if any
if (response.toolCalls) {
const toolResults = await this.executeTools(response.toolCalls);
// Make follow-up request with tool results
}
// 5. Format and return result
return this.formatResponse(response);
}
๐ก Real-World Benefitsโ
Before Factory Pattern (Old Architecture)โ
// Lots of duplicated code
class OpenAIProvider {
async generate(options) {
// Tool setup code (duplicated)
// Request formatting (duplicated)
// OpenAI-specific API call
// Response handling (duplicated)
// Tool execution (duplicated)
}
}
class GoogleAIProvider {
async generate(options) {
// Tool setup code (duplicated)
// Request formatting (duplicated)
// Google-specific API call
// Response handling (duplicated)
// Tool execution (duplicated)
}
}
// ... repeated for each provider
After Factory Pattern (Current Architecture)โ
// No duplication, clean separation
class OpenAIProvider extends BaseProvider {
provider = "openai";
doGenerate(request) {
// Only OpenAI-specific code
return this.model.doGenerate(request);
}
}
class GoogleAIProvider extends BaseProvider {
provider = "google-ai";
doGenerate(request) {
// Only Google-specific code
return this.model.doGenerate(request);
}
}
// BaseProvider handles all common logic
๐ Future Extensibilityโ
The factory pattern makes it easy to add new features:
1. New Tool Categoriesโ
// Add to BaseProvider
protected registerAdvancedTools() {
this.registerTool('imageGeneration', { ... });
this.registerTool('audioTranscription', { ... });
this.registerTool('codeExecution', { ... });
}
2. Provider Capabilitiesโ
// Add capability checking
abstract class BaseProvider {
abstract capabilities: ProviderCapabilities;
supportsStreaming(): boolean {
return this.capabilities.streaming;
}
supportsTools(): boolean {
return this.capabilities.tools;
}
supportsVision(): boolean {
return this.capabilities.vision;
}
}
3. Middleware Systemโ
// Add middleware support
abstract class BaseProvider {
private middleware: Middleware[] = [];
use(middleware: Middleware) {
this.middleware.push(middleware);
}
async generate(options: GenerateOptions) {
// Run through middleware chain
let processedOptions = options;
for (const mw of this.middleware) {
processedOptions = await mw.before(processedOptions);
}
// ... rest of generation
}
}
๐ Code Examplesโ
Creating Providersโ
import { createBestAIProvider, AIProviderFactory } from "@juspay/neurolink";
// Auto-select best provider
const provider = createBestAIProvider();
// Create specific provider
const openai = AIProviderFactory.createProvider("openai", "gpt-4o");
const googleAI = AIProviderFactory.createProvider(
"google-ai",
"gemini-2.0-flash",
);
// All providers have the same interface
const result1 = await openai.generate({ input: { text: "Hello" } });
const result2 = await googleAI.generate({ input: { text: "Hello" } });
Using Built-in Toolsโ
// All providers can use tools
const timeResult = await provider.generate({
input: { text: "What time is it in Paris?" },
});
// Automatically uses getCurrentTime tool
const mathResult = await provider.generate({
input: { text: "Calculate the square root of 144" },
});
// Automatically uses calculateMath tool
const fileResult = await provider.generate({
input: { text: "What's in the package.json file?" },
});
// Automatically uses readFile tool
Extending with Custom Toolsโ
// Custom tools work with all providers
const provider = createBestAIProvider();
// Register custom tool
provider.registerTool("weather", {
description: "Get weather for a city",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => {
// Implementation
return { city, temp: 72, condition: "sunny" };
},
});
// Works with any provider that supports tools
const result = await provider.generate({
input: { text: "What's the weather in London?" },
});
๐ Summaryโ
The Factory Pattern architecture provides:
- Unified Experience: All providers work the same way
- Automatic Tools: 6 built-in tools for every provider
- Easy Extension: Add providers with minimal code
- Clean Code: No duplication, clear separation
- Future-Proof: Easy to add new features
This architecture ensures NeuroLink remains maintainable, extensible, and consistent as new AI providers and features are added.
๐ฅ Video Generation Handler Architectureโ
Video generation via Veo 3.1 follows the same factory pattern architecture with specialized handling for long-running operations.
Video Handler Implementationโ
// src/lib/handlers/videoHandler.ts
export class VideoGenerationHandler {
private readonly provider: VertexAIProvider;
private readonly pollInterval: number = 5000; // 5 seconds
private readonly maxPolls: number = 36; // 3 minutes max
constructor(provider: VertexAIProvider) {
this.provider = provider;
}
async generate(options: VideoGenerationOptions): Promise<VideoResult> {
// 1. Validate inputs
this.validateInputs(options);
// 2. Submit video generation request
const operationId = await this.submitRequest(options);
// 3. Poll for completion
const result = await this.pollForCompletion(operationId);
// 4. Download and return video
return this.processResult(result);
}
private async pollForCompletion(operationId: string): Promise<any> {
let attempts = 0;
while (attempts < this.maxPolls) {
const status = await this.checkStatus(operationId);
if (status.done) {
return status.response;
}
if (status.error) {
throw new VideoGenerationError(status.error);
}
await this.sleep(this.pollInterval);
attempts++;
}
throw new VideoGenerationError("VIDEO_POLL_TIMEOUT");
}
private validateInputs(options: VideoGenerationOptions): void {
if (!options.image) {
throw new VideoGenerationError("VIDEO_INVALID_INPUT: Image required");
}
const validResolutions = ["720p", "1080p"];
if (options.resolution && !validResolutions.includes(options.resolution)) {
throw new VideoGenerationError("VIDEO_INVALID_INPUT: Invalid resolution");
}
const validLengths = [4, 6, 8];
if (options.length && !validLengths.includes(options.length)) {
throw new VideoGenerationError("VIDEO_INVALID_INPUT: Invalid length");
}
}
}
Integration with BaseProviderโ
// src/lib/providers/vertex.ts
export class VertexAIProvider extends BaseProvider {
private videoHandler?: VideoGenerationHandler;
constructor(config: VertexConfig) {
super();
if (config.enableVideoGeneration) {
this.videoHandler = new VideoGenerationHandler(this);
}
}
async generate(options: GenerateOptions): Promise<GenerateResult> {
// Check if this is a video generation request
if (options.output?.mode === "video") {
if (!this.videoHandler) {
throw new Error("Video generation not enabled for this provider");
}
const videoResult = await this.videoHandler.generate({
prompt: options.input.text,
image: options.input.images?.[0],
resolution: options.output.video?.resolution,
length: options.output.video?.length,
aspectRatio: options.output.video?.aspectRatio,
audio: options.output.video?.audio,
});
return {
content: "",
video: videoResult,
provider: "vertex",
model: "veo-3.1",
};
}
// Regular text generation
return super.generate(options);
}
}
Factory Pattern Benefits for Video Generationโ
1. Provider-Specific Features: Video generation is only available on Vertex AI, but the architecture allows graceful handling:
const provider = createBestAIProvider();
try {
const result = await provider.generate({
input: { text: "Video prompt", images: [image] },
output: { mode: "video" },
});
} catch (error) {
if (error.code === "FEATURE_NOT_SUPPORTED") {
// Automatically fall back or switch provider
console.log("Video generation not supported by this provider");
}
}
2. Automatic Provider Routing: Factory can route video requests to Vertex AI:
class AIProviderFactory {
static createProvider(name: string, config?: any): BaseProvider {
// Auto-route video generation to Vertex AI
if (config?.output?.mode === "video") {
if (name !== "vertex") {
console.warn(`Video generation requires Vertex AI, switching provider`);
name = "vertex";
}
}
switch (name) {
case "vertex":
return new VertexAIProvider(config);
// ... other providers
}
}
}
3. Consistent Error Handling: Video-specific errors follow the same pattern:
// All errors follow the same structure
try {
const result = await provider.generate(options);
} catch (error) {
switch (error.code) {
case "VIDEO_GENERATION_FAILED":
case "VIDEO_POLL_TIMEOUT":
case "VIDEO_INVALID_INPUT":
// Handle video-specific errors
break;
case "PROVIDER_NOT_CONFIGURED":
case "RATE_LIMIT_EXCEEDED":
// Handle general provider errors
break;
}
}
Architecture Diagramโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NeuroLink Instance โ
โ (High-level SDK interface) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AIProviderFactory โ
โ (Selects appropriate provider) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ BaseProvider โ โ OpenAI โ โ Anthropic โ
โ (Core logic) โ โ Provider โ โ Provider โ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VertexAIProvider โ
โ (Extends BaseProvider) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Text Generation โ โ VideoGenerationHandler โ
โ (Standard flow) โ โ (Specialized for video) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Polling & Download โ
โ (Long-running ops) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Design Decisionsโ
- Separation of Concerns: Video handler is separate from core provider logic
- Extensibility: Easy to add image generation, audio generation, etc.
- Consistent Interface: Same
generate()method for text and video - Provider-Specific Features: Only Vertex AI supports video, handled gracefully
- Error Handling: Unified error codes across all features
Understanding the architecture helps you build better AI applications! ๐