The AI module is marked as unstable, meaning its APIs may change in minor version releases. Use caution when upgrading Effect versions.
Overview
The effect/unstable/ai module provides comprehensive tools for building AI-powered applications with large language models (LLMs). It offers provider-agnostic interfaces for text generation, structured output, tool calling, and conversation management.
Installation
Key Modules
LanguageModel
The core interface for interacting with large language models, supporting both streaming and non-streaming text generation.
import { Effect } from "effect"
import { LanguageModel } from "effect/unstable/ai"
// Basic text generation
const program = Effect.gen(function*() {
const response = yield* LanguageModel.generateText({
prompt: "Explain quantum computing in simple terms"
})
console.log(response.text)
return response
})
Key Functions:
generateText(options) - Generate text from a prompt
streamText(options) - Stream text generation in real-time
generateObject(options) - Generate structured output conforming to a schema
Chat
Stateful conversation interface that maintains chat history and supports multi-turn interactions.
import { Effect, Stream } from "effect"
import { Chat } from "effect/unstable/ai"
// Create a chat session
const program = Effect.gen(function*() {
const chat = yield* Chat.empty
// Send first message
const response1 = yield* chat.generateText({
prompt: "What is Effect?"
})
// Chat maintains history for follow-up
const response2 = yield* chat.generateText({
prompt: "Can you give me an example?"
})
return { response1, response2 }
})
Define and manage tools that language models can call to extend their capabilities.
import { Schema } from "effect"
import { Tool, Toolkit } from "effect/unstable/ai"
// Define a calculator tool
const Calculator = Tool.make("Calculator", {
description: "Performs basic arithmetic operations",
parameters: Schema.Struct({
operation: Schema.Literals("add", "subtract", "multiply", "divide"),
a: Schema.Number,
b: Schema.Number
}),
success: Schema.Number
})
// Create a toolkit with multiple tools
const GetWeather = Tool.make("GetWeather", {
description: "Get weather for a location",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const MyToolkit = Toolkit.make(Calculator, GetWeather)
// Implement tool handlers
const MyToolkitLayer = MyToolkit.toLayer({
Calculator: ({ operation, a, b }) => {
switch (operation) {
case "add": return Effect.succeed(a + b)
case "subtract": return Effect.succeed(a - b)
case "multiply": return Effect.succeed(a * b)
case "divide": return Effect.succeed(a / b)
}
},
GetWeather: ({ location }) =>
Effect.succeed({
temperature: 72,
condition: "sunny"
})
})
Prompt & Response
Data structures for constructing prompts and handling model responses.
import { Prompt, Response } from "effect/unstable/ai"
// Create structured conversation
const conversation = Prompt.make([
{
role: "system",
content: "You are a helpful coding assistant."
},
{
role: "user",
content: [{
type: "text",
text: "How do I create an Effect?"
}]
}
])
// Concatenate prompts
const systemPrompt = Prompt.make([{
role: "system",
content: "You are a coding assistant."
}])
const userPrompt = Prompt.make("Help me write a function")
const combined = Prompt.concat(systemPrompt, userPrompt)
AiError
Comprehensive error handling for AI operations with semantic error categories.
import { Effect, Match } from "effect"
import type { AiError } from "effect/unstable/ai"
// Handle errors using Match on the reason
const handleAiError = Match.type<AiError.AiError>().pipe(
Match.when(
{ reason: { _tag: "RateLimitError" } },
(err) => Effect.logWarning(`Rate limited, retry after ${err.retryAfter}`)
),
Match.when(
{ reason: { _tag: "AuthenticationError" } },
(err) => Effect.logError(`Auth failed: ${err.reason.kind}`)
),
Match.when(
{ reason: { isRetryable: true } },
(err) => Effect.logWarning(`Transient error, retrying: ${err.message}`)
),
Match.orElse((err) => Effect.logError(`Permanent error: ${err.message}`))
)
Error Categories:
RateLimitError - Request throttled (429s, provider limits)
QuotaExhaustedError - Account/billing limits reached
AuthenticationError - Invalid/expired credentials
ContentPolicyError - Content violated provider policy
InvalidRequestError - Malformed request parameters
NetworkError - Transport-level failures
InvalidOutputError - Output parsing/validation failures
ToolNotFoundError - Model requested non-existent tool
ToolParameterValidationError - Tool params failed validation
Model
Unified interface for AI service providers.
import type { Layer } from "effect"
import { Effect } from "effect"
import { LanguageModel, Model } from "effect/unstable/ai"
declare const myAnthropicLayer: Layer.Layer<LanguageModel.LanguageModel>
const anthropicModel = Model.make("anthropic", "claude-3-5-haiku", myAnthropicLayer)
const program = Effect.gen(function*() {
const response = yield* LanguageModel.generateText({
prompt: "Hello, world!"
})
return response.text
}).pipe(Effect.provide(anthropicModel))
Tokenizer
Tokenization and text truncation for managing context length constraints.
import { Effect } from "effect"
import { Tokenizer } from "effect/unstable/ai"
const tokenizeText = Effect.gen(function*() {
const tokenizer = yield* Tokenizer.Tokenizer
const tokens = yield* tokenizer.tokenize("Hello, world!")
console.log(`Token count: ${tokens.length}`)
return tokens
})
// Truncate a prompt to fit token limits
const truncatePrompt = Effect.gen(function*() {
const tokenizer = yield* Tokenizer.Tokenizer
const longPrompt = "This is a very long prompt..."
const truncated = yield* tokenizer.truncate(longPrompt, 100)
return truncated
})
Telemetry
OpenTelemetry integration following GenAI semantic conventions.
import { Effect } from "effect"
import { Telemetry } from "effect/unstable/ai"
// Add telemetry attributes to a span
const addTelemetry = Effect.gen(function*() {
const span = yield* Effect.currentSpan
Telemetry.addGenAIAnnotations(span, {
system: "openai",
operation: { name: "chat" },
request: {
model: "gpt-4",
temperature: 0.7,
maxTokens: 1000
},
usage: {
inputTokens: 100,
outputTokens: 50
}
})
})
IdGenerator
Pluggable ID generation for tool calls and other AI operations.
import { Effect } from "effect"
import { IdGenerator } from "effect/unstable/ai"
// Custom ID generator for tool calls
const customLayer = IdGenerator.layer({
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
prefix: "tool_call",
separator: "-",
size: 12
})
const program = Effect.gen(function*() {
const idGen = yield* IdGenerator.IdGenerator
const id = yield* idGen.generateId()
console.log(id) // "tool_call-A7XK9MP2QR5T"
return id
}).pipe(Effect.provide(customLayer))
Additional Modules
- AnthropicStructuredOutput - Transform Effect schemas for Anthropic’s API constraints
- OpenAiStructuredOutput - Transform schemas for OpenAI structured output
MCP Integration
- McpSchema - Model Context Protocol schema definitions
- McpServer - MCP server implementation for Effect
Best Practices
- Error Handling - Always handle AI errors using the semantic error types
- Streaming - Use
streamText for better user experience with long responses
- Tool Safety - Validate tool parameters with Effect schemas
- Token Management - Use Tokenizer to stay within model limits
- Telemetry - Add observability for production AI applications
- CLI - Build command-line interfaces
- Cluster - Distributed computing primitives
- SQL - Database integration