{"id":31644510,"url":"https://github.com/liteobject/demo-llm-integration","last_synced_at":"2026-05-16T11:32:05.639Z","repository":{"id":317886415,"uuid":"1068612349","full_name":"LiteObject/demo-llm-integration","owner":"LiteObject","description":"TypeScript LLM integration layer using Strategy, Abstract Factory, and Adapter patterns. Runtime platform switching, unified API, and migration guide for adding providers.","archived":false,"fork":false,"pushed_at":"2025-10-03T16:46:20.000Z","size":91,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-03T18:27:33.266Z","etag":null,"topics":["adapter-pattern","architecture","aws-bedrock","azure-openai","chatbot","design-patterns","factory-pattern","llm","ollama","strategy-pattern","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LiteObject.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-02T16:37:17.000Z","updated_at":"2025-10-03T16:46:23.000Z","dependencies_parsed_at":"2025-10-03T18:28:37.382Z","dependency_job_id":"65f28ce4-1256-448f-b353-420594e122a5","html_url":"https://github.com/LiteObject/demo-llm-integration","commit_stats":null,"previous_names":["liteobject/demo-llm-integration"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/LiteObject/demo-llm-integration","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiteObject%2Fdemo-llm-integration","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiteObject%2Fdemo-llm-integration/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiteObject%2Fdemo-llm-integration/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiteObject%2Fdemo-llm-integration/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LiteObject","download_url":"https://codeload.github.com/LiteObject/demo-llm-integration/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LiteObject%2Fdemo-llm-integration/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278722768,"owners_count":26034461,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adapter-pattern","architecture","aws-bedrock","azure-openai","chatbot","design-patterns","factory-pattern","llm","ollama","strategy-pattern","typescript"],"created_at":"2025-10-07T04:53:34.520Z","updated_at":"2026-05-16T11:32:05.627Z","avatar_url":"https://github.com/LiteObject.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Demo LLM Integration\n\nThis project demonstrates how to combine the Strategy, Abstract Factory, and Adapter design patterns to build a flexible large language model (LLM) integration layer in TypeScript. The code shows how to switch between platforms (Bedrock, Azure AI, Google Vertex, Ollama), providers, and models at runtime without sprinkling platform-specific conditionals across the application.\n\n## Design Patterns Used\n\n- **Strategy Pattern**: Encapsulates different LLM provider implementations behind a common interface (`LLMStrategy`), allowing runtime switching between providers without changing client code.\n- **Abstract Factory Pattern**: Creates families of related objects (platform-specific strategies) without specifying their concrete classes. Each platform has its own factory that knows how to instantiate the correct strategy.\n- **Adapter Pattern**: Converts provider-specific APIs into a unified interface that the application expects. Each platform adapter translates between the vendor SDK and our domain types.\n\nTogether, these patterns provide flexibility (Strategy), organized creation (Factory), and compatibility (Adapter).\n\n## Architecture Diagrams\n\n### How the Patterns Work Together\n\n```mermaid\ngraph TB\n    subgraph \"Client Layer\"\n        App[Application Code]\n        Demo[demo.ts]\n    end\n    \n    subgraph \"Service Layer (Facade)\"\n        ChatService[ChatService]\n        Builder[LLMClientBuilder]\n    end\n    \n    subgraph \"Registry (Service Locator)\"\n        Registry[LLMRegistry]\n    end\n    \n    subgraph \"Factory Layer (Abstract Factory)\"\n        BF[BedrockFactory]\n        AF[AzureFactory]\n        GF[GoogleFactory]\n        OF[OllamaFactory]\n    end\n    \n    subgraph \"Strategy Layer (Strategy + Adapter)\"\n        BS[BedrockStrategy\u003cbr/\u003e«Adapter»]\n        AS[AzureStrategy\u003cbr/\u003e«Adapter»]\n        GS[GoogleStrategy\u003cbr/\u003e«Adapter»]\n        OS[OllamaStrategy\u003cbr/\u003e«Adapter»]\n    end\n    \n    subgraph \"External SDKs\"\n        BSDK[AWS Bedrock SDK]\n        ASDK[Azure OpenAI SDK]\n        GSDK[Google Vertex SDK]\n        OSDK[Ollama API]\n    end\n    \n    App --\u003e ChatService\n    Demo --\u003e Builder\n    Builder --\u003e Registry\n    ChatService --\u003e Registry\n    \n    Registry --\u003e BF\n    Registry --\u003e AF\n    Registry --\u003e GF\n    Registry --\u003e OF\n    \n    BF --\u003e BS\n    AF --\u003e AS\n    GF --\u003e GS\n    OF --\u003e OS\n    \n    BS --\u003e BSDK\n    AS --\u003e ASDK\n    GS --\u003e GSDK\n    OS --\u003e OSDK\n    \n    style ChatService fill:#e1f5e1\n    style Registry fill:#fff3cd\n    style BS fill:#d4e9ff\n    style AS fill:#d4e9ff\n    style GS fill:#d4e9ff\n    style OS fill:#d4e9ff\n```\n\n### Runtime Platform Switching Sequence\n\n```mermaid\nsequenceDiagram\n    participant App\n    participant ChatService\n    participant Registry\n    participant Factory\n    participant Strategy\n    participant SDK\n    \n    App-\u003e\u003eChatService: configure({platform: 'bedrock', model: 'claude'})\n    ChatService-\u003e\u003eRegistry: getFactory('bedrock')\n    Registry--\u003e\u003eChatService: BedrockFactory\n    ChatService-\u003e\u003eFactory: createClient('claude')\n    Factory-\u003e\u003eStrategy: new BedrockStrategy(client, 'claude')\n    Factory--\u003e\u003eChatService: strategy instance\n    \n    App-\u003e\u003eChatService: send('Hello')\n    ChatService-\u003e\u003eStrategy: sendMessage('Hello')\n    Strategy-\u003e\u003eSDK: invokeModel(request)\n    SDK--\u003e\u003eStrategy: response\n    Strategy--\u003e\u003eChatService: ChatResponse (unified)\n    ChatService--\u003e\u003eApp: ChatResponse\n    \n    Note over App,ChatService: User switches platform\n    \n    App-\u003e\u003eChatService: configure({platform: 'azure', model: 'gpt-4'})\n    ChatService-\u003e\u003eRegistry: getFactory('azure')\n    Registry--\u003e\u003eChatService: AzureFactory\n    ChatService-\u003e\u003eFactory: createClient('gpt-4')\n    Factory-\u003e\u003eStrategy: new AzureStrategy(client, 'gpt-4')\n    Factory--\u003e\u003eChatService: new strategy instance\n    \n    App-\u003e\u003eChatService: send('Hello again')\n    ChatService-\u003e\u003eStrategy: sendMessage('Hello again')\n    Strategy-\u003e\u003eSDK: chat.completions.create(request)\n    SDK--\u003e\u003eStrategy: response\n    Strategy--\u003e\u003eChatService: ChatResponse (unified)\n    ChatService--\u003e\u003eApp: ChatResponse\n```\n\n\n\n### Data Flow Through Patterns\n\n```mermaid\ngraph LR\n    subgraph \"Input\"\n        Prompt[User Prompt]\n        Config[Platform Config]\n    end\n    \n    subgraph \"Strategy Pattern\"\n        Strategy{Which Strategy?}\n    end\n    \n    subgraph \"Adapter Pattern\"\n        Adapt1[Bedrock Adapter]\n        Adapt2[Azure Adapter]\n        Adapt3[Google Adapter]\n    end\n    \n    subgraph \"External APIs\"\n        API1[AWS API]\n        API2[Azure API]\n        API3[Google API]\n    end\n    \n    subgraph \"Output\"\n        Response[Unified Response]\n    end\n    \n    Config --\u003e |Selects| Strategy\n    Prompt --\u003e Strategy\n    \n    Strategy --\u003e |bedrock| Adapt1\n    Strategy --\u003e |azure| Adapt2\n    Strategy --\u003e |google| Adapt3\n    \n    Adapt1 --\u003e |Transform| API1\n    Adapt2 --\u003e |Transform| API2\n    Adapt3 --\u003e |Transform| API3\n    \n    API1 --\u003e |Normalize| Response\n    API2 --\u003e |Normalize| Response\n    API3 --\u003e |Normalize| Response\n    \n    style Strategy fill:#fff3cd\n    style Adapt1 fill:#d4e9ff\n    style Adapt2 fill:#d4e9ff\n    style Adapt3 fill:#d4e9ff\n    style Response fill:#e1f5e1\n```\n\n### Component Dependencies\n\n```mermaid\ngraph BT\n    subgraph \"External Dependencies\"\n        SDK1[AWS SDK]\n        SDK2[Azure SDK]\n        SDK3[Google SDK]\n        SDK4[Ollama API]\n    end\n    \n    subgraph \"Platform Adapters\"\n        BA[Bedrock Adapter]\n        AA[Azure Adapter]\n        GA[Google Adapter]\n        OA[Ollama Adapter]\n    end\n    \n    subgraph \"Factories\"\n        BF[Bedrock Factory]\n        AF[Azure Factory]\n        GF[Google Factory]\n        OF[Ollama Factory]\n    end\n    \n    subgraph \"Core Contracts\"\n        Strategy[LLMStrategy Interface]\n        Factory[LLMFactory Interface]\n        Types[Chat Types]\n    end\n    \n    subgraph \"Service Layer\"\n        Registry[LLM Registry]\n        Service[Chat Service]\n        Builder[Client Builder]\n    end\n    \n    subgraph \"Application\"\n        App[Your Application]\n    end\n    \n    SDK1 --\u003e BA\n    SDK2 --\u003e AA\n    SDK3 --\u003e GA\n    SDK4 --\u003e OA\n    \n    BA --\u003e Strategy\n    AA --\u003e Strategy\n    GA --\u003e Strategy\n    OA --\u003e Strategy\n    \n    BA --\u003e BF\n    AA --\u003e AF\n    GA --\u003e GF\n    OA --\u003e OF\n    \n    BF --\u003e Factory\n    AF --\u003e Factory\n    GF --\u003e Factory\n    OF --\u003e Factory\n    \n    Factory --\u003e Registry\n    Strategy --\u003e Service\n    Registry --\u003e Service\n    Registry --\u003e Builder\n    \n    Service --\u003e App\n    Builder --\u003e App\n    Types --\u003e Strategy\n    Types --\u003e Service\n    \n    style Strategy fill:#ffd700\n    style Factory fill:#ffd700\n    style Types fill:#ffd700\n    style Service fill:#90ee90\n    style App fill:#87ceeb\n```\n\n### Pattern Responsibilities\n\n```mermaid\ngraph TD\n    subgraph \"Strategy Pattern Responsibility\"\n        SP[Define common interface\u003cbr/\u003eEnable runtime switching\u003cbr/\u003eEncapsulate algorithms]\n    end\n    \n    subgraph \"Abstract Factory Responsibility\"\n        AF[Create related objects\u003cbr/\u003eValidate model support\u003cbr/\u003eHide instantiation logic]\n    end\n    \n    subgraph \"Adapter Pattern Responsibility\"\n        AP[Convert vendor APIs\u003cbr/\u003eNormalize responses\u003cbr/\u003eHandle SDK specifics]\n    end\n    \n    subgraph \"Service/Facade Responsibility\"\n        SF[Simplify client interface\u003cbr/\u003eManage configuration\u003cbr/\u003eCoordinate patterns]\n    end\n    \n    subgraph \"Registry Responsibility\"\n        RP[Manage factory instances\u003cbr/\u003eEnable platform discovery\u003cbr/\u003eCentralize registration]\n    end\n    \n    SP --\u003e |Provides| Interface[Uniform LLM Interface]\n    AF --\u003e |Creates| Instances[Platform-Specific Instances]\n    AP --\u003e |Translates| APIs[Vendor SDK Calls]\n    SF --\u003e |Exposes| Simple[Simple API to Clients]\n    RP --\u003e |Enables| Discovery[Runtime Platform Discovery]\n    \n    Interface --\u003e Simple\n    Instances --\u003e Simple\n    APIs --\u003e Instances\n    Discovery --\u003e Simple\n    \n    style SP fill:#e6f3ff\n    style AF fill:#ffe6f0\n    style AP fill:#fff0e6\n    style SF fill:#e6ffe6\n    style RP fill:#f0e6ff\n```\n\n## When to Use This Pattern\n\n### Use This Approach When:\n\n- **Multiple Provider Support Required**: Your application needs to work with 3+ different LLM platforms or you anticipate adding more providers over time.\n- **Runtime Switching Needed**: Users need to switch between providers/models dynamically without redeploying the application.\n- **Platform Abstraction Valuable**: You want to protect your business logic from vendor-specific API changes.\n- **Testing Flexibility Important**: You need to easily mock or swap implementations for testing purposes.\n- **Complex Provider Logic**: Each platform has unique authentication, request formatting, or response handling requirements.\n\n### Consider Simpler Approaches When:\n\n- **Single Provider**: If you only use one LLM provider and don't anticipate changes, direct SDK integration is simpler.\n- **No Runtime Switching**: If the provider is configured once at deployment time, environment variables and a single adapter may suffice.\n- **Prototype/MVP**: Early-stage projects benefit from direct implementation first, then refactor to patterns when complexity justifies it.\n- **Minimal Abstraction Needs**: If all providers you use have nearly identical APIs, a thin wrapper function may be enough.\n\n### Rule of Thumb\n\nStart simple. Introduce these patterns when you:\n1. Add a second provider\n2. Need runtime configuration\n3. Find conditional logic spreading across multiple files\n\n## Key Concepts\n\n### Platform vs Provider\n\nThis project distinguishes between **platforms** and **providers**:\n\n- **Platform**: The hosting infrastructure or service that provides access to LLM models\n  - Examples: AWS Bedrock, Azure AI, Google Vertex AI, Ollama (local)\n  - Platforms have their own SDKs, authentication mechanisms, and API structures\n  - Each platform is represented by a Factory in this codebase\n\n- **Provider**: The organization that created the underlying LLM model\n  - Examples: OpenAI, Anthropic, Mistral, Meta, Google\n  - Providers create the actual models (GPT-4, Claude, Llama, Gemini)\n  - The same provider's models may be available on multiple platforms\n\n**Example relationships:**\n```\nPlatform: AWS Bedrock\n├── Provider: Anthropic → Models: claude-3-5-sonnet, claude-3-opus\n├── Provider: Mistral → Models: mistral-large, mistral-medium\n└── Provider: Meta → Models: llama-3-70b\n\nPlatform: Azure AI\n├── Provider: OpenAI → Models: gpt-4o, gpt-35-turbo\n└── Provider: Meta → Models: llama-3-8b\n\nPlatform: Ollama (Local)\n├── Provider: Meta → Models: llama3, codellama\n└── Provider: Mistral → Models: mistral\n```\n\nIn this codebase, you configure both the **platform** (where the model is hosted) and the **model** (which includes the provider implicitly):\n\n```typescript\n// Configure platform + model\nchatService.configure({ \n  platform: 'bedrock',           // Platform: AWS Bedrock\n  model: 'anthropic.claude-v2'   // Provider (Anthropic) + Model\n});\n\nchatService.configure({ \n  platform: 'azure',              // Platform: Azure AI\n  model: 'gpt-4o'                 // Provider (OpenAI) + Model\n});\n```\n\n**Model Naming Conventions:**\nDifferent platforms use different naming conventions:\n- **Bedrock**: Prefixes with provider name → `anthropic.claude-v2`, `mistral.mixtral-8x7b`\n- **Azure/Google**: Direct model names → `gpt-4o`, `gemini-pro`\n- **Ollama**: Simple names → `llama3`, `mistral`\n\n## Project Layout\n\n- `src/core`: Shared domain contracts (`ChatOptions`, `LLMStrategy`, `LLMFactory`).\n- `src/platforms`: Platform-specific adapters and factories.\n- `src/service`: Higher-level façade (`ChatService`) and a convenience builder for wiring.\n- `src/registry`: Lightweight registry used to register factories at startup.\n- `src/demo.ts`: Minimal script illustrating runtime switching.\n\n## Getting Started\n\n### Requirements\n\n- Node.js 18+ (for native fetch support)\n- TypeScript 5.3+ (for proper async iterable support)\n- npm or yarn package manager\n\n### Installation\n\n```pwsh\n# Install dependencies\nnpm install\n\n# Compile TypeScript\nnpm run build\n\n# Run the Vitest suite\nnpm test\n\n# Execute the demo script\nnpm run demo\n```\n\n### Configuration\n\nIn production environments, manage credentials using environment variables:\n\n```typescript\n// Example: Configure with environment variables\nimport { AnthropicClient } from '@anthropic-ai/sdk';\n\nconst client = new AnthropicClient({\n  apiKey: process.env.ANTHROPIC_API_KEY\n});\n\nconst factory = new AnthropicFactory(client);\n```\n\nConfiguration file example:\n```json\n{\n  \"platforms\": {\n    \"anthropic\": {\n      \"apiKey\": \"${ANTHROPIC_API_KEY}\",\n      \"baseUrl\": \"https://api.anthropic.com\",\n      \"maxRetries\": 3\n    },\n    \"azure\": {\n      \"endpoint\": \"${AZURE_OPENAI_ENDPOINT}\",\n      \"apiKey\": \"${AZURE_OPENAI_KEY}\",\n      \"apiVersion\": \"2024-02-15-preview\"\n    }\n  }\n}\n```\n\n## Migration Guide: Adding a New Platform\n\nThis guide walks you through adding support for a new LLM platform (e.g., Anthropic Direct, Cohere, HuggingFace).\n\n### Step 1: Create Platform Directory Structure\n\n```pwsh\n# Create directory for the new platform\nmkdir src/platforms/anthropic\n```\n\n### Step 2: Define the Strategy (Adapter)\n\nCreate `src/platforms/anthropic/anthropic-strategy.ts`:\n\n```typescript\n/**\n * Anthropic adapter implementing the Strategy contract. Translates between\n * the Anthropic SDK format and our unified domain types.\n */\n\nimport type { ChatOptions, ChatResponse, StreamingChunk } from '../../core/chat-types.js';\nimport type { LLMStrategy } from '../../core/llm-strategy.js';\nimport { AnthropicClient } from '../sdk-clients.js'; // or import actual SDK\n\nexport class AnthropicStrategy implements LLMStrategy {\n  constructor(\n    private readonly client: AnthropicClient,\n    private readonly model: string\n  ) {}\n\n  async sendMessage(prompt: string, options: ChatOptions = {}): Promise\u003cChatResponse\u003e {\n    try {\n      // Call the actual Anthropic SDK\n      const response = await this.client.messages.create({\n        model: this.model,\n        messages: [{ role: 'user', content: prompt }],\n        max_tokens: options.maxTokens ?? 1024,\n        temperature: options.temperature ?? 1.0,\n        system: options.systemPrompt\n      });\n\n      // Adapt the response to our unified format\n      return {\n        model: response.model,\n        content: response.content[0].text,\n        usage: {\n          promptTokens: response.usage.input_tokens,\n          completionTokens: response.usage.output_tokens,\n          totalTokens: response.usage.input_tokens + response.usage.output_tokens\n        }\n      };\n    } catch (error) {\n      throw new Error(`Anthropic API call failed: ${error.message}`);\n    }\n  }\n\n  async * streamMessage(prompt: string, options: ChatOptions = {}): AsyncIterable\u003cStreamingChunk\u003e {\n    try {\n      // Use Anthropic's streaming API\n      const stream = await this.client.messages.stream({\n        model: this.model,\n        messages: [{ role: 'user', content: prompt }],\n        max_tokens: options.maxTokens ?? 1024,\n        temperature: options.temperature ?? 1.0\n      });\n\n      for await (const event of stream) {\n        if (event.type === 'content_block_delta') {\n          yield {\n            model: this.model,\n            contentFragment: event.delta.text,\n            isLast: false\n          };\n        } else if (event.type === 'message_stop') {\n          yield {\n            model: this.model,\n            contentFragment: '',\n            isLast: true\n          };\n        }\n      }\n    } catch (error) {\n      throw new Error(`Anthropic streaming failed: ${error.message}`);\n    }\n  }\n}\n```\n\n### Step 3: Create the Factory\n\nCreate `src/platforms/anthropic/anthropic-factory.ts`:\n\n```typescript\n/**\n * Anthropic factory that knows which models are supported and how to create\n * the corresponding strategy instances.\n */\n\nimport type { LLMFactory } from '../../core/llm-factory.js';\nimport type { LLMStrategy } from '../../core/llm-strategy.js';\nimport { AnthropicClient } from '../sdk-clients.js';\nimport { AnthropicStrategy } from './anthropic-strategy.js';\n\nconst SUPPORTED_MODELS = Object.freeze([\n  'claude-3-5-sonnet-20241022',\n  'claude-3-opus-20240229',\n  'claude-3-sonnet-20240229',\n  'claude-3-haiku-20240307'\n]);\n\nexport class AnthropicFactory implements LLMFactory {\n  constructor(\n    private readonly client: AnthropicClient = new AnthropicClient()\n  ) {}\n\n  createClient(model: string): LLMStrategy {\n    if (!SUPPORTED_MODELS.includes(model)) {\n      throw new Error(`Anthropic model \"${model}\" is not supported.`);\n    }\n    return new AnthropicStrategy(this.client, model);\n  }\n\n  listAvailableModels(): readonly string[] {\n    return SUPPORTED_MODELS;\n  }\n}\n```\n\n### Step 4: Add SDK Client (if needed)\n\nIf using a mock client, add to `src/platforms/sdk-clients.ts`:\n\n```typescript\nexport interface AnthropicRequest {\n  readonly model: string;\n  readonly messages: Array\u003c{ role: string; content: string }\u003e;\n  readonly max_tokens?: number;\n  readonly temperature?: number;\n  readonly system?: string;\n}\n\nexport interface AnthropicResponse {\n  readonly model: string;\n  readonly content: Array\u003c{ text: string }\u003e;\n  readonly usage: {\n    readonly input_tokens: number;\n    readonly output_tokens: number;\n  };\n}\n\nexport class AnthropicClient {\n  messages = {\n    create: async (request: AnthropicRequest): Promise\u003cAnthropicResponse\u003e =\u003e {\n      await Promise.resolve();\n      return {\n        model: request.model,\n        content: [{ text: `Anthropic response to: ${request.messages[0].content}` }],\n        usage: { input_tokens: 15, output_tokens: 25 }\n      };\n    },\n    stream: async (request: AnthropicRequest) =\u003e {\n      // Mock streaming implementation\n      return {\n        async *[Symbol.asyncIterator]() {\n          yield { type: 'content_block_delta', delta: { text: 'Mock' } };\n          yield { type: 'message_stop' };\n        }\n      };\n    }\n  };\n}\n```\n\n### Step 5: Register the Factory\n\nUpdate `src/platforms/register.ts`:\n\n```typescript\nimport { llmRegistry } from '../registry/llm-registry.js';\nimport { AzureFactory } from './azure/azure-factory.js';\nimport { BedrockFactory } from './bedrock/bedrock-factory.js';\nimport { GoogleFactory } from './google/google-factory.js';\nimport { OllamaFactory } from './ollama/ollama-factory.js';\nimport { AnthropicFactory } from './anthropic/anthropic-factory.js'; // Add this\n\nexport function registerDefaultFactories(): void {\n  llmRegistry.register('bedrock', new BedrockFactory());\n  llmRegistry.register('azure', new AzureFactory());\n  llmRegistry.register('google', new GoogleFactory());\n  llmRegistry.register('ollama', new OllamaFactory());\n  llmRegistry.register('anthropic', new AnthropicFactory()); // Add this\n}\n```\n\n### Step 6: Add Type Support (Optional)\n\nUpdate `src/platforms/factory-resolver.ts` to include the new platform:\n\n```typescript\nexport type SupportedPlatform = 'bedrock' | 'azure' | 'google' | 'ollama' | 'anthropic';\n\nexport function createFactory(platform: SupportedPlatform): LLMFactory {\n  switch (platform) {\n    case 'bedrock':\n      return new BedrockFactory();\n    case 'azure':\n      return new AzureFactory();\n    case 'google':\n      return new GoogleFactory();\n    case 'ollama':\n      return new OllamaFactory();\n    case 'anthropic':\n      return new AnthropicFactory();\n    default: {\n      const exhaustiveCheck: never = platform;\n      throw new Error(`Unsupported platform: ${String(exhaustiveCheck)}`);\n    }\n  }\n}\n```\n\n### Step 7: Add Tests\n\nCreate `src/platforms/anthropic/anthropic-strategy.test.ts`:\n\n```typescript\nimport { describe, expect, it } from 'vitest';\nimport { AnthropicFactory } from './anthropic-factory.js';\nimport { AnthropicStrategy } from './anthropic-strategy.js';\n\ndescribe('AnthropicFactory', () =\u003e {\n  it('creates strategy for supported models', () =\u003e {\n    const factory = new AnthropicFactory();\n    const strategy = factory.createClient('claude-3-5-sonnet-20241022');\n    expect(strategy).toBeDefined();\n    expect(strategy).toBeInstanceOf(AnthropicStrategy);\n  });\n\n  it('throws error for unsupported models', () =\u003e {\n    const factory = new AnthropicFactory();\n    expect(() =\u003e factory.createClient('invalid-model')).toThrow('not supported');\n  });\n\n  it('lists available models', () =\u003e {\n    const factory = new AnthropicFactory();\n    const models = factory.listAvailableModels();\n    expect(models).toContain('claude-3-5-sonnet-20241022');\n    expect(models.length).toBeGreaterThan(0);\n  });\n\n  it('sends message and returns unified response', async () =\u003e {\n    const factory = new AnthropicFactory();\n    const strategy = factory.createClient('claude-3-5-sonnet-20241022');\n    const response = await strategy.sendMessage('Test prompt');\n    \n    expect(response).toHaveProperty('model');\n    expect(response).toHaveProperty('content');\n    expect(response).toHaveProperty('usage');\n    expect(response.usage.totalTokens).toBeGreaterThan(0);\n  });\n});\n```\n\n### Testing Strategies\n\n**Unit Tests**: Test strategies with mocked SDK clients\n```typescript\n// Mock the SDK client for isolated testing\nvi.mock('../sdk-clients.js', () =\u003e ({\n  AnthropicClient: vi.fn().mockImplementation(() =\u003e ({\n    messages: {\n      create: vi.fn().mockResolvedValue({ /* mock response */ })\n    }\n  }))\n}));\n```\n\n**Integration Tests**: Test with real APIs (optional, separate suite)\n```typescript\ndescribe.skip('Anthropic Integration', () =\u003e {\n  // Only run when ANTHROPIC_API_KEY is set\n  it('calls real API', async () =\u003e {\n    const client = new AnthropicClient({ apiKey: process.env.ANTHROPIC_API_KEY });\n    // ... real API test\n  });\n});\n```\n\n**Contract Tests**: Verify all strategies return consistent response shapes\n```typescript\ndescribe('Strategy Contract', () =\u003e {\n  const platforms = ['bedrock', 'azure', 'google', 'anthropic'];\n  \n  platforms.forEach(platform =\u003e {\n    it(`${platform} returns valid ChatResponse`, async () =\u003e {\n      const strategy = getStrategyForPlatform(platform);\n      const response = await strategy.sendMessage('test');\n      \n      expect(response).toMatchObject({\n        model: expect.any(String),\n        content: expect.any(String),\n        usage: {\n          promptTokens: expect.any(Number),\n          completionTokens: expect.any(Number),\n          totalTokens: expect.any(Number)\n        }\n      });\n    });\n  });\n});\n```\n\n### Step 8: Update Documentation\n\nAdd the new platform to your demo or documentation:\n\n```typescript\n// In src/demo.ts or your usage examples\nchatService.configure({ platform: 'anthropic', model: 'claude-3-5-sonnet-20241022' });\nconst anthropicResponse = await chatService.send('Hello from Anthropic!');\nconsole.log('[Anthropic]', anthropicResponse.content);\n```\n\n### Step 9: Verify Integration\n\n```pwsh\n# Rebuild TypeScript\nnpm run build\n\n# Run tests\nnpm test\n\n# Run linter\nnpm run lint\n\n# Test the demo\nnpm run demo\n```\n\n### Common Pitfalls\n\n1. **Authentication**: Remember to handle API keys/credentials in the client constructor.\n2. **Response Mapping**: Carefully map all fields from the provider's response to `ChatResponse`.\n3. **Error Handling**: Wrap SDK calls in try-catch and throw descriptive errors.\n4. **Streaming**: Not all platforms support streaming; document limitations.\n5. **Token Counting**: Different platforms count tokens differently; document this variance.\n6. **Model Availability**: Some models are region-specific or require special access.\n7. **Rate Limiting**: Implement retry logic with exponential backoff for production use.\n\n### Error Handling Best Practices\n\nHandle common error scenarios in your strategy implementation:\n\n```typescript\nasync sendMessage(prompt: string, options: ChatOptions = {}): Promise\u003cChatResponse\u003e {\n  try {\n    const response = await this.client.messages.create({...});\n    return this.adaptResponse(response);\n  } catch (error) {\n    // Authentication errors\n    if (error.status === 401) {\n      throw new Error('Authentication failed: Invalid API key');\n    }\n    \n    // Rate limiting\n    if (error.status === 429) {\n      throw new Error('Rate limit exceeded. Retry after delay.');\n    }\n    \n    // Model not found\n    if (error.status === 404) {\n      throw new Error(`Model \"${this.model}\" not found or not accessible`);\n    }\n    \n    // Token limit exceeded\n    if (error.type === 'invalid_request_error') {\n      throw new Error('Request exceeds model context window');\n    }\n    \n    // Network/timeout errors\n    if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {\n      throw new Error('Network error: Unable to reach API endpoint');\n    }\n    \n    // Generic fallback\n    throw new Error(`API call failed: ${error.message}`);\n  }\n}\n```\n\n### Best Practices\n\n- **Separation of Concerns**: Keep the strategy focused on API translation only\n- **Business Logic**: Put business logic in the service layer, not adapters\n- **Dependency Injection**: Use DI for SDK clients to enable testing\n- **Documentation**: Document platform-specific quirks in strategy comments\n- **Logging**: Add structured logging for debugging and monitoring\n- **Configuration**: Make model lists and settings configurable rather than hardcoded\n- **Observability**: Instrument with metrics (latency, token usage, error rates)\n\n```typescript\n// Example: Adding logging\nexport class AnthropicStrategy implements LLMStrategy {\n  constructor(\n    private readonly client: AnthropicClient,\n    private readonly model: string,\n    private readonly logger?: Logger\n  ) {}\n\n  async sendMessage(prompt: string, options: ChatOptions = {}): Promise\u003cChatResponse\u003e {\n    this.logger?.info('Sending message to Anthropic', { model: this.model });\n    const startTime = Date.now();\n    \n    try {\n      const response = await this.client.messages.create({...});\n      const duration = Date.now() - startTime;\n      \n      this.logger?.info('Anthropic response received', {\n        model: this.model,\n        duration,\n        tokens: response.usage.input_tokens + response.usage.output_tokens\n      });\n      \n      return this.adaptResponse(response);\n    } catch (error) {\n      this.logger?.error('Anthropic API call failed', { model: this.model, error });\n      throw error;\n    }\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliteobject%2Fdemo-llm-integration","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliteobject%2Fdemo-llm-integration","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliteobject%2Fdemo-llm-integration/lists"}