Agent skill
effect-ts-ai
@effect/ai integration patterns for categorical AI composition, typed error handling, and production prompt pipelines. Use when building AI applications with Effect-TS, composing LLM calls with typed errors, creating tool-augmented AI systems, implementing structured output generation, or integrating multiple AI providers (OpenAI, Anthropic) with categorical composition patterns.
Install this agent skill to your Project
npx add-skill https://github.com/bphkns/dotfiles/tree/main/opencode/.config/opencode/skills/effect-ts-ai
SKILL.md
Effect-TS AI Integration
Production-ready categorical AI composition using @effect/ai and the Effect ecosystem.
Installation
npm install effect @effect/ai @effect/ai-openai @effect/ai-anthropic @effect/platform
Core Architecture
The @effect/ai package provides categorical abstractions for AI operations:
- AiLanguageModel: Functor over text/structured generation
- AiToolkit: Product type of available tools
- AiResponse: Coproduct capturing success/failure outcomes
- AiTool: Exponential object
Parameters → Effect<Success, Failure>
Provider Setup
OpenAI Configuration
import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai"
import { Layer, Config } from "effect"
const OpenAiLive = OpenAiClient.layerConfig({
apiKey: Config.secret("OPENAI_API_KEY")
})
const ModelLive = OpenAiLanguageModel.model("gpt-4o").pipe(
Layer.provide(OpenAiLive)
)
Anthropic Configuration
import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic"
import { Layer, Config } from "effect"
const AnthropicLive = AnthropicClient.layerConfig({
apiKey: Config.secret("ANTHROPIC_API_KEY")
})
const ModelLive = AnthropicLanguageModel.model("claude-sonnet-4-20250514").pipe(
Layer.provide(AnthropicLive)
)
Text Generation
Basic Generation
import { AiLanguageModel } from "@effect/ai"
import { Effect } from "effect"
const generateText = Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
const response = yield* model.generateText({
prompt: "Explain monads in one sentence"
})
return response.text
})
Streaming Generation
import { AiLanguageModel } from "@effect/ai"
import { Effect, Stream } from "effect"
const streamText = Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
const stream = yield* model.streamText({
prompt: "Write a haiku about functional programming"
})
yield* Stream.runForEach(stream.textStream, (chunk) =>
Effect.sync(() => process.stdout.write(chunk))
)
})
Structured Output Generation
Generate typed objects using Schema validation:
import { AiLanguageModel } from "@effect/ai"
import { Effect, Schema } from "effect"
class Sentiment extends Schema.Class<Sentiment>("Sentiment")({
score: Schema.Number.pipe(
Schema.greaterThanOrEqualTo(-1),
Schema.lessThanOrEqualTo(1)
),
label: Schema.Literal("positive", "negative", "neutral"),
confidence: Schema.Number.pipe(
Schema.greaterThanOrEqualTo(0),
Schema.lessThanOrEqualTo(1)
)
}) {}
const analyzeSentiment = (text: string) =>
Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
const response = yield* model.generateObject({
prompt: `Analyze sentiment: "${text}"`,
schema: Sentiment
})
return response.value
})
Tool-Augmented Generation
Defining Tools
import { AiTool, AiToolkit } from "@effect/ai"
import { Schema, Effect } from "effect"
const WeatherTool = AiTool.make("get_weather", {
description: "Get current weather for a location",
parameters: Schema.Struct({
location: Schema.String,
unit: Schema.optional(Schema.Literal("celsius", "fahrenheit"))
}),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const CalculatorTool = AiTool.make("calculate", {
description: "Perform mathematical calculations",
parameters: Schema.Struct({
expression: Schema.String
}),
success: Schema.Number
})
Creating Toolkits
const MyToolkit = AiToolkit.make(WeatherTool, CalculatorTool)
const ToolkitLive = MyToolkit.toLayer(
Effect.succeed({
get_weather: ({ location, unit }) =>
Effect.succeed({
temperature: 22,
condition: "sunny"
}),
calculate: ({ expression }) =>
Effect.try(() => eval(expression) as number)
})
)
Using Tools in Generation
const generateWithTools = Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
const response = yield* model.generateText({
prompt: "What's the weather in Tokyo and what is 42 * 17?",
toolkit: MyToolkit
})
return response.text
})
// Execute with all layers
const program = generateWithTools.pipe(
Effect.provide(ModelLive),
Effect.provide(ToolkitLive)
)
Categorical Composition Patterns
Functor: Mapping Over Responses
import { AiResponse } from "@effect/ai"
import { Effect } from "effect"
const mapResponse = <A, B>(
response: Effect.Effect<AiResponse.AiResponse, Error>,
f: (text: string) => B
) =>
Effect.map(response, (r) => f(r.text))
Monad: Sequencing AI Operations
const chainedGeneration = Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
// First call: generate outline
const outline = yield* model.generateText({
prompt: "Create an outline for an essay on category theory"
})
// Second call: expand each section (dependent on first)
const expanded = yield* model.generateText({
prompt: `Expand this outline into full paragraphs:\n${outline.text}`
})
return expanded.text
})
Applicative: Parallel AI Operations
import { Effect } from "effect"
const parallelGeneration = Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
const [summary, keywords, sentiment] = yield* Effect.all([
model.generateText({ prompt: "Summarize: ..." }),
model.generateObject({ prompt: "Extract keywords", schema: KeywordsSchema }),
model.generateObject({ prompt: "Analyze sentiment", schema: Sentiment })
], { concurrency: 3 })
return { summary: summary.text, keywords: keywords.value, sentiment: sentiment.value }
})
Natural Transformation: Provider Switching
import { Layer } from "effect"
// Natural transformation: OpenAI → Anthropic
const switchProvider = <R, E, A>(
program: Effect.Effect<A, E, R | AiLanguageModel.AiLanguageModel>
): Effect.Effect<A, E, R | AnthropicClient.AnthropicClient> =>
program.pipe(
Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514"))
)
Error Handling
Typed AI Errors
import { AiError } from "@effect/ai"
import { Effect, Match } from "effect"
const handleAiErrors = <A>(effect: Effect.Effect<A, AiError.AiError>) =>
effect.pipe(
Effect.catchTag("AiError", (error) =>
Match.value(error.reason).pipe(
Match.when({ _tag: "RateLimitExceeded" }, () =>
Effect.fail(new Error("Rate limited, retry later"))
),
Match.when({ _tag: "InvalidRequest" }, ({ message }) =>
Effect.fail(new Error(`Invalid request: ${message}`))
),
Match.orElse(() => Effect.fail(new Error("Unknown AI error")))
)
)
)
Retry with Exponential Backoff
import { Effect, Schedule } from "effect"
const withRetry = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
effect.pipe(
Effect.retry(
Schedule.exponential("100 millis").pipe(
Schedule.compose(Schedule.recurs(3))
)
)
)
Production Patterns
Telemetry Integration
import { AiTelemetry } from "@effect/ai"
import { Effect } from "effect"
const withTelemetry = <A, E, R>(
operation: string,
effect: Effect.Effect<A, E, R>
) =>
effect.pipe(
Effect.tap(() =>
Effect.logInfo(`AI operation: ${operation}`)
),
Effect.withSpan(`ai.${operation}`)
)
Resource Management
import { Effect, Scope } from "effect"
const managedAiSession = Effect.scoped(
Effect.gen(function*() {
const model = yield* AiLanguageModel.AiLanguageModel
// Resources automatically cleaned up
yield* Effect.addFinalizer(() =>
Effect.logInfo("AI session closed")
)
return yield* model.generateText({
prompt: "Hello, world!"
})
})
)
Configuration Management
import { Config, Effect, Layer } from "effect"
const AiConfigLive = Layer.effect(
AiConfig,
Effect.gen(function*() {
return {
maxTokens: yield* Config.integer("AI_MAX_TOKENS").pipe(
Config.withDefault(4096)
),
temperature: yield* Config.number("AI_TEMPERATURE").pipe(
Config.withDefault(0.7)
),
model: yield* Config.string("AI_MODEL").pipe(
Config.withDefault("gpt-4o")
)
}
})
)
MCP Integration
Register tools with MCP servers:
import { McpServer } from "@effect/ai"
import { Layer } from "effect"
const McpToolsLive = McpServer.toolkit(MyToolkit).pipe(
Layer.provide(ToolkitLive)
)
Testing
Mock Language Model
import { AiLanguageModel } from "@effect/ai"
import { Layer, Effect } from "effect"
const MockModelLive = Layer.succeed(
AiLanguageModel.AiLanguageModel,
AiLanguageModel.make({
generateText: (options) =>
Effect.succeed({
text: `Mock response for: ${options.prompt}`,
toolCalls: [],
finishReason: "stop"
}),
generateObject: (options) =>
Effect.succeed({
value: { mocked: true },
finishReason: "stop"
})
})
)
Categorical Guarantees
The @effect/ai library preserves these categorical properties:
- Functor Laws:
map(id) ≡ id,map(f ∘ g) ≡ map(f) ∘ map(g) - Monad Laws:
flatMap(pure) ≡ id,pure(a).flatMap(f) ≡ f(a) - Natural Transformation: Provider switching preserves structure
- Resource Safety: Scoped effects guarantee cleanup via finalizers
- Type Safety: Schema validation at compile-time and runtime
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
effect-ts-ai
@effect/ai integration patterns for categorical AI composition, typed error handling, and production prompt pipelines. Use when building AI applications with Effect-TS, composing LLM calls with typed errors, creating tool-augmented AI systems, implementing structured output generation, or integrating multiple AI providers (OpenAI, Anthropic) with categorical composition patterns.
react-modernization
Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use when building web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
react-modernization
Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use when building web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
edit-article
Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.
Didn't find tool you were looking for?