Provider Implementation Guide
Provider Implementation Guide
Section titled “Provider Implementation Guide”This guide explains how to implement a UPP-compliant provider for AI services.
Provider Module Structure
Section titled “Provider Module Structure”Each provider module exports a single factory that combines all modality handlers:
// @providerprotocol/ai/openai/index.tsimport { createProvider } from '@providerprotocol/ai';import { createLLMHandler } from './llm';import { createEmbeddingHandler } from './embed';import { createImageHandler } from './image';
export const openai = createProvider({ name: 'openai', version: '1.0.0', modalities: { llm: createLLMHandler(), embedding: createEmbeddingHandler(), image: createImageHandler(), },});
// Re-export param types for consumersexport type { OpenAILLMParams } from './llm';export type { OpenAIEmbedParams } from './embed';export type { OpenAIImageParams } from './image';createProvider Helper
Section titled “createProvider Helper”UPP provides a helper to create providers:
import { createProvider, Provider, ModelReference } from '@providerprotocol/ai';
interface CreateProviderOptions { name: string; version: string; modalities: { llm?: LLMHandler; embedding?: EmbeddingHandler; image?: ImageHandler; };}
function createProvider(options: CreateProviderOptions): Provider { const provider: Provider = Object.assign( function (modelId: string): ModelReference { return { modelId, provider }; }, { name: options.name, version: options.version, modalities: options.modalities, } ); return provider;}HTTP-First Approach
Section titled “HTTP-First Approach”Per UPP design principles, providers SHOULD use direct HTTP calls rather than vendor SDKs:
import { resolveApiKey, doFetch } from '@providerprotocol/ai/http';
async function callVendorAPI(request: LLMRequest) { const apiKey = await resolveApiKey(request.config, 'OPENAI_API_KEY');
const response = await doFetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(transformRequest(request)), }, request.config);
return await response.json();}Shared Utilities
Section titled “Shared Utilities”UPP provides utilities for provider implementations from @providerprotocol/ai/http:
/** * Resolve API key from ProviderConfig. * Falls back to environment variable if config.apiKey is not set. */function resolveApiKey(config: ProviderConfig, envVar?: string): Promise<string>;
/** * Execute fetch with retry, timeout, error normalization, and custom headers. * Custom headers from config.headers are merged with init.headers. */function doFetch(url: string, init: RequestInit, config: ProviderConfig): Promise<Response>;
/** * Parse Server-Sent Events stream into JSON objects. */function parseSSEStream(body: ReadableStream<Uint8Array>): AsyncGenerator<unknown>;
/** * Normalize HTTP error responses to UPPError. */function normalizeHttpError(response: Response, provider: string, modality: Modality): Promise<UPPError>;LLM Handler Pattern
Section titled “LLM Handler Pattern”// @providerprotocol/ai/anthropic/llm.tsimport { LLMHandler, BoundLLMModel, LLMResponse } from '@providerprotocol/ai';import { resolveApiKey, doFetch } from '@providerprotocol/ai/http';
export interface AnthropicLLMParams { max_tokens?: number; temperature?: number; // ... consult vendor documentation}
export function createLLMHandler(): LLMHandler<AnthropicLLMParams> { return { bind(modelId: string): BoundLLMModel<AnthropicLLMParams> { return { modelId, capabilities: { streaming: true, tools: true, structuredOutput: true, imageInput: true, // Claude supports vision videoInput: false, audioInput: false, },
async complete(request) { const apiKey = await resolveApiKey(request.config, 'ANTHROPIC_API_KEY');
// Transform UPP request to vendor format const body = transformRequest(request, modelId);
const response = await doFetch(VENDOR_URL, { method: 'POST', headers: { 'x-api-key': apiKey, 'anthropic-version': '2024-01-01', 'content-type': 'application/json', }, body: JSON.stringify(body), }, request.config);
const data = await response.json();
// Transform vendor response to UPP format return transformResponse(data); },
stream(request) { // Similar pattern with SSE parsing // Return LLMStreamResult with async iterator }, }; }, };}Embedding Handler Pattern
Section titled “Embedding Handler Pattern”export function createEmbeddingHandler(): EmbeddingHandler<OpenAIEmbedParams> { return { supportedInputs: ['text'],
bind(modelId: string): BoundEmbeddingModel<OpenAIEmbedParams> { return { modelId, maxBatchSize: 2048, maxInputLength: 8191, dimensions: 1536,
async embed(request) { const apiKey = await resolveApiKey(request.config, 'OPENAI_API_KEY');
// Transform inputs, call vendor API const body = { model: modelId, input: request.inputs.map(transformInput), ...request.params, };
const response = await doFetch(VENDOR_URL, { ... }, request.config); const data = await response.json();
// Return EmbeddingResponse return { embeddings: data.data.map(transformEmbedding), usage: { totalTokens: data.usage.total_tokens }, }; }, }; }, };}Image Handler Pattern
Section titled “Image Handler Pattern”export function createImageHandler(): ImageHandler<OpenAIImageParams> { return { bind(modelId: string): BoundImageModel<OpenAIImageParams> { return { modelId, capabilities: { generate: true, streaming: false, edit: modelId.includes('dall-e-2'), vary: modelId.includes('dall-e-2'), upscale: false, outpaint: false, imageToImage: false, maxImages: modelId === 'dall-e-3' ? 1 : 10, supportedSizes: ['1024x1024', '1792x1024', '1024x1792'], supportedFormats: ['png'], },
async generate(request) { // Transform prompt, call vendor API, return ImageResponse },
// Implement edit, vary if capabilities indicate support }; }, };}Key Implementation Requirements
Section titled “Key Implementation Requirements”System Prompt Handling
Section titled “System Prompt Handling”Different providers handle system prompts differently:
| Provider | System Prompt Handling |
|---|---|
| Anthropic | Top-level system parameter |
| OpenAI | Message with role: 'system' |
systemInstruction field |
Your provider MUST transform system prompts according to vendor requirements.
Metadata Handling
Section titled “Metadata Handling”Providers MUST handle their own metadata namespace:
// Extracting metadata from responsesconst message = new AssistantMessage(content, toolCalls, { metadata: { google: { thought_signature: vendorResponse.thought_signature, }, },});
// Including metadata in requestsfunction transformToVendor(message: Message) { const googleMeta = message.metadata?.google; return { role: message.type, content: transformContent(message.content), ...(googleMeta?.thought_signature && { thought_signature: googleMeta.thought_signature, }), };}