Claude Code Plugins

Community-maintained marketplace

Feedback
7
0

Configure and compose AI provider layers using @effect/ai packages. Covers Anthropic, OpenAI, OpenRouter, Google, and Amazon Bedrock providers with config management, model abstraction, and runtime overrides for language model integration.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name effect-ai-provider
description Configure and compose AI provider layers using @effect/ai packages. Covers Anthropic, OpenAI, OpenRouter, Google, and Amazon Bedrock providers with config management, model abstraction, and runtime overrides for language model integration.

Effect AI Provider

Configure AI provider layers for language model integration using Effect's @effect/ai ecosystem.

When to Use This Skill

Use this skill when:

  • Integrating AI language models (Anthropic, OpenAI, Google, etc.) into Effect applications
  • Setting up multi-provider AI architectures with runtime switching
  • Implementing stateful chat conversations with context history
  • Working with embeddings for semantic search or RAG systems
  • Managing AI provider configuration and API keys securely
  • Composing AI capabilities with other Effect services

Import Patterns

CRITICAL: Always use namespace imports:

// Core
import * as LanguageModel from "@effect/ai/LanguageModel"
import * as Chat from "@effect/ai/Chat"
import * as EmbeddingModel from "@effect/ai/EmbeddingModel"
import * as Model from "@effect/ai/Model"

// Anthropic
import * as AnthropicClient from "@effect/ai-anthropic/AnthropicClient"
import * as AnthropicLanguageModel from "@effect/ai-anthropic/AnthropicLanguageModel"
import * as AnthropicTokenizer from "@effect/ai-anthropic/AnthropicTokenizer"

// OpenAI
import * as OpenAiClient from "@effect/ai-openai/OpenAiClient"
import * as OpenAiLanguageModel from "@effect/ai-openai/OpenAiLanguageModel"
import * as OpenAiEmbeddingModel from "@effect/ai-openai/OpenAiEmbeddingModel"

// OpenRouter
import * as OpenRouterClient from "@effect/ai-openrouter/OpenRouterClient"
import * as OpenRouterLanguageModel from "@effect/ai-openrouter/OpenRouterLanguageModel"

// Google
import * as GoogleClient from "@effect/ai-google/GoogleClient"
import * as GoogleLanguageModel from "@effect/ai-google/GoogleLanguageModel"

// Amazon Bedrock
import * as BedrockClient from "@effect/ai-amazon-bedrock/BedrockClient"
import * as BedrockLanguageModel from "@effect/ai-amazon-bedrock/BedrockLanguageModel"

// Effect
import * as Config from "effect/Config"
import * as Layer from "effect/Layer"
import * as Effect from "effect/Effect"

Provider Layer Pattern

providerLayer :: Model → Layer LanguageModel Client
providerLayer model = ProviderLanguageModel.layer({ model })
  |> Layer.provide(ProviderClient.layerConfig(...))

configLayer :: Config → Layer Client ∅
configLayer = Client.layerConfig({ apiKey: Config.redacted("API_KEY") })

Anthropic Provider

import * as AnthropicClient from "@effect/ai-anthropic/AnthropicClient"
import * as AnthropicLanguageModel from "@effect/ai-anthropic/AnthropicLanguageModel"
import * as Config from "effect/Config"
import * as Layer from "effect/Layer"

const AnthropicLive = AnthropicLanguageModel.layer({
  model: "claude-sonnet-4-20250514"
}).pipe(
  Layer.provide(
    AnthropicClient.layerConfig({
      apiKey: Config.redacted("ANTHROPIC_API_KEY")
    })
  )
)

OpenAI Provider

import * as OpenAiClient from "@effect/ai-openai/OpenAiClient"
import * as OpenAiLanguageModel from "@effect/ai-openai/OpenAiLanguageModel"

const OpenAiLive = OpenAiLanguageModel.layer({
  model: "gpt-4o"
}).pipe(
  Layer.provide(
    OpenAiClient.layerConfig({
      apiKey: Config.redacted("OPENAI_API_KEY")
    })
  )
)

OpenRouter Provider

Multi-provider access through unified interface:

import * as OpenRouterClient from "@effect/ai-openrouter/OpenRouterClient"
import * as OpenRouterLanguageModel from "@effect/ai-openrouter/OpenRouterLanguageModel"

const OpenRouterLive = OpenRouterLanguageModel.layer({
  model: "anthropic/claude-sonnet-4"
}).pipe(
  Layer.provide(
    OpenRouterClient.layerConfig({
      apiKey: Config.redacted("OPENROUTER_API_KEY")
    })
  )
)

Google Provider

import * as GoogleClient from "@effect/ai-google/GoogleClient"
import * as GoogleLanguageModel from "@effect/ai-google/GoogleLanguageModel"

const GoogleLive = GoogleLanguageModel.layer({
  model: "gemini-2.0-flash-exp"
}).pipe(
  Layer.provide(
    GoogleClient.layerConfig({
      apiKey: Config.redacted("GOOGLE_API_KEY")
    })
  )
)

Amazon Bedrock Provider

import * as BedrockClient from "@effect/ai-amazon-bedrock/BedrockClient"
import * as BedrockLanguageModel from "@effect/ai-amazon-bedrock/BedrockLanguageModel"

const BedrockLive = BedrockLanguageModel.layer({
  model: "anthropic.claude-3-5-sonnet-20241022-v2:0"
}).pipe(
  Layer.provide(
    BedrockClient.layerConfig({
      region: "us-east-1"
    })
  )
)

Tokenizer Integration

Count and manage tokens for model context limits:

import * as AnthropicTokenizer from "@effect/ai-anthropic/AnthropicTokenizer"
import * as Effect from "effect/Effect"

const program = Effect.gen(function* () {
  // Count tokens
  const count = yield* AnthropicTokenizer.countTokens("Hello world")

  // Truncate to limit
  const text = "Very long text that exceeds token limit..."
  const truncated = yield* AnthropicTokenizer.truncate(text, 4000)

  return { count, truncated }
})

EmbeddingModel Service

Generate vector embeddings for semantic search and RAG:

import * as EmbeddingModel from "@effect/ai/EmbeddingModel"
import * as OpenAiEmbeddingModel from "@effect/ai-openai/OpenAiEmbeddingModel"
import * as OpenAiClient from "@effect/ai-openai/OpenAiClient"
import * as Config from "effect/Config"
import * as Layer from "effect/Layer"

const EmbeddingLive = OpenAiEmbeddingModel.layer({
  model: "text-embedding-3-small"
}).pipe(
  Layer.provide(
    OpenAiClient.layerConfig({
      apiKey: Config.redacted("OPENAI_API_KEY")
    })
  )
)

const program = Effect.gen(function* () {
  // Single embedding
  const embedding = yield* EmbeddingModel.embed("Search query")

  // Batch embeddings
  const embeddings = yield* EmbeddingModel.embedAll([
    "Document 1",
    "Document 2",
    "Document 3"
  ])

  return embeddings
})

Effect.runPromise(program.pipe(Effect.provide(EmbeddingLive)))

Chat Service (Stateful Conversations)

Maintain conversation history with automatic context management:

import * as Chat from "@effect/ai/Chat"
import * as AnthropicLanguageModel from "@effect/ai-anthropic/AnthropicLanguageModel"
import * as AnthropicClient from "@effect/ai-anthropic/AnthropicClient"
import * as Config from "effect/Config"
import * as Layer from "effect/Layer"
import * as Effect from "effect/Effect"

const AnthropicLive = AnthropicLanguageModel.layer({
  model: "claude-sonnet-4-20250514"
}).pipe(
  Layer.provide(
    AnthropicClient.layerConfig({
      apiKey: Config.redacted("ANTHROPIC_API_KEY")
    })
  )
)

const program = Effect.gen(function* () {
  // Create empty chat
  const chat = yield* Chat.empty

  // Or with initial system prompt
  const chatWithSystem = yield* Chat.make({
    system: "You are a helpful assistant"
  })

  // Methods mirror LanguageModel but maintain history
  const response1 = yield* chat.generateText({
    prompt: "What is Effect?"
  })

  // Follow-up has context from previous message
  const response2 = yield* chat.generateText({
    prompt: "Can you elaborate on that?"
  })

  // Export for persistence
  const exported = yield* chat.exportJson

  // Import from persisted state
  const restored = yield* Chat.fromJson(exported)

  return { response1, response2, exported }
})

Effect.runPromise(program.pipe(Effect.provide(AnthropicLive)))

Config Override Pattern

Runtime configuration adjustment without rebuilding layers:

withConfigOverride :: Partial<ProviderConfig> → Layer LanguageModel Client
const CustomAnthropicLive = AnthropicLanguageModel.layer({
  model: "claude-sonnet-4-20250514"
}).pipe(
  AnthropicLanguageModel.withConfigOverride({
    temperature: 0.7,
    max_tokens: 4096
  }),
  Layer.provide(
    AnthropicClient.layerConfig({
      apiKey: Config.redacted("ANTHROPIC_API_KEY")
    })
  )
)

Model Abstraction

Wrap provider layers with metadata:

Model.make :: { name :: String, layer :: Layer LanguageModel Client } → Model
Model.ProviderName :: Model → String
import * as Model from "@effect/ai/Model"

const Claude = Model.make({
  name: "claude-sonnet-4",
  layer: AnthropicLive
})

const provider = Model.ProviderName(Claude) // "anthropic"

Multi-Provider Setup

import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as LanguageModel from "@effect/ai/LanguageModel"

const AppLive = Layer.mergeAll(
  AnthropicLive,
  OpenAiLive,
  OpenRouterLive
)

const program = Effect.gen(function* () {
  const model = yield* LanguageModel.LanguageModel
  const response = yield* model.generate("Hello")
  return response
})

Effect.runPromise(program.pipe(Effect.provide(AnthropicLive)))

Available Providers

Package Provider Models
@effect/ai-anthropic Anthropic Claude 3.x, Claude Sonnet 4
@effect/ai-openai OpenAI GPT-4, GPT-4o, o1
@effect/ai-openrouter OpenRouter Multi-provider proxy
@effect/ai-google Google Gemini 1.5, 2.0
@effect/ai-amazon-bedrock AWS Bedrock Cross-provider on AWS

Patterns

-- Layer composition
providerLayer ∘ configLayer :: Layer LanguageModel ∅

-- Config management
Config.redacted :: String → Config Redacted<String>

-- Runtime override
layer |> withConfigOverride(config) :: Layer A R

-- Provider selection
Model.make({ name, layer }) :: Model

Anti-Patterns

// ❌ Hardcoded API keys
AnthropicClient.layerConfig({ apiKey: "sk-..." })

// ✅ Config.redacted for secrets
AnthropicClient.layerConfig({
  apiKey: Config.redacted("ANTHROPIC_API_KEY")
})

// ❌ Rebuilding layer for config changes
const newLayer = AnthropicLanguageModel.layer({
  model: "claude-sonnet-4-20250514",
  temperature: 0.9
})

// ✅ Use withConfigOverride
AnthropicLive.pipe(
  AnthropicLanguageModel.withConfigOverride({ temperature: 0.9 })
)

// ❌ Direct client construction
new AnthropicClient({ apiKey: "..." })

// ✅ Layer-based dependency injection
AnthropicClient.layerConfig({ apiKey: Config.redacted("...") })

Quality Checklist

  • Use Config.redacted for API keys (never hardcode)
  • Compose with Layer.provide for dependencies
  • Use withConfigOverride for runtime config changes
  • Wrap in Model.make for metadata abstraction
  • Layer.mergeAll for multi-provider setups
  • Include Tokenizer for token counting
  • Use Chat service for stateful conversations
  • Use EmbeddingModel for vector embeddings

Complete Working Example

Full application with language model, chat, embeddings, and tokenization:

import * as LanguageModel from "@effect/ai/LanguageModel"
import * as Chat from "@effect/ai/Chat"
import * as EmbeddingModel from "@effect/ai/EmbeddingModel"
import * as Model from "@effect/ai/Model"
import * as AnthropicClient from "@effect/ai-anthropic/AnthropicClient"
import * as AnthropicLanguageModel from "@effect/ai-anthropic/AnthropicLanguageModel"
import * as AnthropicTokenizer from "@effect/ai-anthropic/AnthropicTokenizer"
import * as OpenAiClient from "@effect/ai-openai/OpenAiClient"
import * as OpenAiEmbeddingModel from "@effect/ai-openai/OpenAiEmbeddingModel"
import * as Config from "effect/Config"
import * as Layer from "effect/Layer"
import * as Effect from "effect/Effect"
import * as Console from "effect/Console"

// Provider Layers
const AnthropicLive = AnthropicLanguageModel.layer({
  model: "claude-sonnet-4-20250514"
}).pipe(
  AnthropicLanguageModel.withConfigOverride({
    temperature: 0.7,
    max_tokens: 4096
  }),
  Layer.provide(
    AnthropicClient.layerConfig({
      apiKey: Config.redacted("ANTHROPIC_API_KEY")
    })
  )
)

const EmbeddingLive = OpenAiEmbeddingModel.layer({
  model: "text-embedding-3-small"
}).pipe(
  Layer.provide(
    OpenAiClient.layerConfig({
      apiKey: Config.redacted("OPENAI_API_KEY")
    })
  )
)

// Combined application layer
const AppLive = Layer.mergeAll(AnthropicLive, EmbeddingLive)

// Program using all AI capabilities
const program = Effect.gen(function* () {
  // Token counting
  const prompt = "Explain functional programming"
  const tokenCount = yield* AnthropicTokenizer.countTokens(prompt)
  yield* Console.log(`Prompt tokens: ${tokenCount}`)

  // Simple generation
  const model = yield* LanguageModel.LanguageModel
  const simple = yield* model.generateText({ prompt })
  yield* Console.log(`Simple response: ${simple}`)

  // Stateful chat
  const chat = yield* Chat.make({
    system: "You are a functional programming expert"
  })

  const response1 = yield* chat.generateText({
    prompt: "What is Effect?"
  })

  const response2 = yield* chat.generateText({
    prompt: "How does it handle errors?"
  })

  // Export chat for persistence
  const chatState = yield* chat.exportJson

  // Generate embeddings
  const embedding = yield* EmbeddingModel.embed(
    "functional programming concepts"
  )

  const batchEmbeddings = yield* EmbeddingModel.embedAll([
    "monads",
    "functors",
    "effect systems"
  ])

  return {
    tokenCount,
    simple,
    chatResponses: { response1, response2 },
    chatState,
    embeddingDimensions: embedding.length,
    batchCount: batchEmbeddings.length
  }
})

// Run with environment variables:
// ANTHROPIC_API_KEY=sk-... OPENAI_API_KEY=sk-... node program.js
Effect.runPromise(program.pipe(Effect.provide(AppLive)))

Model Abstraction Pattern

Create reusable model configurations:

import * as Model from "@effect/ai/Model"

const Claude = Model.make({
  name: "claude-sonnet-4",
  layer: AnthropicLive
})

const GPT4 = Model.make({
  name: "gpt-4o",
  layer: OpenAiLive
})

// Runtime model selection
const selectModel = (useGPT: boolean) =>
  useGPT ? GPT4.layer : Claude.layer

const program = Effect.gen(function* () {
  const model = yield* LanguageModel.LanguageModel
  return yield* model.generateText({ prompt: "Hello" })
})

// Switch providers at runtime
Effect.runPromise(
  program.pipe(Effect.provide(selectModel(false)))
)

Related Skills

  • effect-ai-language-model - Using LanguageModel service with providers
  • effect-ai-prompt - Provider-specific message options
  • effect-ai-tool - Provider-defined tools (Anthropic bash, OpenAI web search)
  • effect-ai-streaming - Provider-specific streaming behavior
  • layer-design - General Effect layer composition patterns

References

  • .context/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts
  • .context/effect/packages/ai/openai/src/OpenAiLanguageModel.ts
  • apps/ui/src/lib/AppLive.ts