Effect AI enables you to build sophisticated AI agents by combining tool calling with stateful chat sessions. This page covers defining tools, implementing handlers, and managing conversations.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/effect-TS/effect-smol/llms.txt
Use this file to discover all available pages before exploring further.
Tools
Tools allow AI models to perform actions like calling APIs, querying databases, or executing code within your application context.Defining Tools
Create a tool usingTool.make with a name, description, parameters schema, and success schema:
import { Schema } from "effect"
import { Tool } from "effect/unstable/ai"
const GetWeather = Tool.make("GetWeather", {
description: "Get current weather for a location",
parameters: Schema.Struct({
location: Schema.String.pipe(
Schema.annotations({ description: "City name, e.g. 'San Francisco'" })
),
units: Schema.Literals("celsius", "fahrenheit").pipe(
Schema.withDecodingDefault(() => "celsius" as const)
)
}),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String,
humidity: Schema.Number
})
})
Tool Options
Description: Helps the model understand when to use the toolconst SearchDatabase = Tool.make("SearchDatabase", {
description: "Search the product database by keyword. Use this when the user asks about products, inventory, or pricing.",
parameters: Schema.Struct({ query: Schema.String }),
success: Schema.Array(ProductSchema)
})
const DeleteUser = Tool.make("DeleteUser", {
description: "Delete a user account",
parameters: Schema.Struct({ userId: Schema.String }),
success: Schema.Struct({ deleted: Schema.Boolean }),
failure: Schema.Struct({
error: Schema.Literals("not_found", "permission_denied")
}),
failureMode: "return" // Return failures instead of throwing
})
const SendEmail = Tool.make("SendEmail", {
description: "Send an email",
parameters: Schema.Struct({
to: Schema.String,
subject: Schema.String,
body: Schema.String
}),
success: Schema.Struct({ sent: Schema.Boolean }),
needsApproval: true // Always require approval
})
// Or conditional approval
const TransferMoney = Tool.make("TransferMoney", {
description: "Transfer money between accounts",
parameters: Schema.Struct({
amount: Schema.Number,
from: Schema.String,
to: Schema.String
}),
success: Schema.Struct({ transactionId: Schema.String }),
needsApproval: ({ amount }) => amount > 1000 // Only large transfers
})
Toolkits
Group related tools into a toolkit:import { Effect } from "effect"
import { Tool, Toolkit } from "effect/unstable/ai"
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Get the current time",
success: Schema.String
})
const GetWeather = Tool.make("GetWeather", {
description: "Get current weather",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const SearchWeb = Tool.make("SearchWeb", {
description: "Search the web",
parameters: Schema.Struct({ query: Schema.String }),
success: Schema.Array(Schema.Struct({
title: Schema.String,
url: Schema.String,
snippet: Schema.String
}))
})
// Create a toolkit
const AssistantToolkit = Toolkit.make(
GetCurrentTime,
GetWeather,
SearchWeb
)
Implementing Handlers
Convert a toolkit to a Layer with handler implementations:import { DateTime, Effect, Layer } from "effect"
const AssistantToolkitLayer = AssistantToolkit.toLayer(
Effect.gen(function*() {
// Access any services you need
const weatherService = yield* WeatherService
const searchService = yield* SearchService
return AssistantToolkit.of({
GetCurrentTime: Effect.fn("AssistantToolkit.GetCurrentTime")(
function*() {
const now = yield* DateTime.now
return DateTime.formatIso(now)
}
),
GetWeather: Effect.fn("AssistantToolkit.GetWeather")(
function*({ location }) {
return yield* weatherService.getCurrentWeather(location)
}
),
SearchWeb: Effect.fn("AssistantToolkit.SearchWeb")(
function*({ query }) {
const results = yield* searchService.search(query)
return results.slice(0, 5)
}
)
})
})
).pipe(
Layer.provide(WeatherServiceLive),
Layer.provide(SearchServiceLive)
)
Using Tools with LanguageModel
Pass a toolkit to enable tool calling:import { Effect } from "effect"
import { LanguageModel } from "effect/unstable/ai"
const program = Effect.gen(function*() {
const toolkit = yield* AssistantToolkit
const response = yield* LanguageModel.generateText({
prompt: "What's the weather in San Francisco and what time is it?",
toolkit
})
console.log("Response:", response.text)
console.log("Tool calls made:", response.toolCalls.length)
// Inspect tool calls
for (const call of response.toolCalls) {
console.log(`Called ${call.name} with:`, call.params)
}
// Inspect tool results
for (const result of response.toolResults) {
console.log(`Result from ${result.name}:`, result.result)
console.log(`Failed: ${result.isFailure}`)
}
})
const runnable = program.pipe(
Effect.provide(modelLayer),
Effect.provide(AssistantToolkitLayer)
)
Tool Choice Control
Control how the model uses tools:// Auto (default): Model decides whether to call tools
const response1 = yield* LanguageModel.generateText({
prompt: "Hello!",
toolkit,
toolChoice: "auto"
})
// None: Disable tool calling
const response2 = yield* LanguageModel.generateText({
prompt: "Hello!",
toolkit,
toolChoice: "none"
})
// Required: Force the model to call at least one tool
const response3 = yield* LanguageModel.generateText({
prompt: "What's the weather?",
toolkit,
toolChoice: "required"
})
// Specific tool: Force a particular tool
const response4 = yield* LanguageModel.generateText({
prompt: "Weather please",
toolkit,
toolChoice: { tool: "GetWeather" }
})
// Subset of tools: Restrict to specific tools
const response5 = yield* LanguageModel.generateText({
prompt: "Help me",
toolkit,
toolChoice: {
mode: "required",
oneOf: ["GetWeather", "GetCurrentTime"]
}
})
Provider-Defined Tools
Some providers offer built-in tools like web search or code execution:import { OpenAiTool } from "@effect/ai-openai"
import { AnthropicTool } from "@effect/ai-anthropic"
// OpenAI tools
const webSearch = OpenAiTool.WebSearch({
search_context_size: "medium"
})
const codeInterpreter = OpenAiTool.CodeInterpreter()
const fileSearch = OpenAiTool.FileSearch({
vector_store_ids: ["vs_abc123"]
})
// Anthropic tools
const computerUse = AnthropicTool.ComputerUse({
display_width_px: 1920,
display_height_px: 1080
})
const bash = AnthropicTool.Bash()
// Combine with user-defined tools
const mixedToolkit = Toolkit.make(
GetWeather,
GetCurrentTime,
webSearch,
codeInterpreter
)
Chat
TheChat module provides stateful conversation sessions with automatic history management.
Creating a Chat Session
Create an empty chat or initialize with a prompt:import { Effect } from "effect"
import { Chat, Prompt } from "effect/unstable/ai"
// Empty chat
const chat1 = yield* Chat.empty
// With initial prompt
const chat2 = yield* Chat.fromPrompt("Hello!")
// With system message
const chat3 = yield* Chat.fromPrompt([
{
role: "system",
content: "You are a helpful coding assistant."
}
])
// Using Prompt utilities
const systemPrompt = Prompt.empty.pipe(
Prompt.setSystem("You are a helpful assistant.")
)
const chat4 = yield* Chat.fromPrompt(systemPrompt)
Multi-Turn Conversations
The chat automatically maintains history:import { Effect } from "effect"
import { Chat } from "effect/unstable/ai"
const program = Effect.gen(function*() {
const chat = yield* Chat.fromPrompt([
{
role: "system",
content: "You are a helpful assistant."
}
])
// First turn
const response1 = yield* chat.generateText({
prompt: "What's the capital of France?"
})
console.log("Assistant:", response1.text)
// Second turn - chat remembers previous context
const response2 = yield* chat.generateText({
prompt: "What's its population?"
})
console.log("Assistant:", response2.text)
// Third turn
const response3 = yield* chat.generateText({
prompt: "What are some famous landmarks there?"
})
console.log("Assistant:", response3.text)
})
const runnable = program.pipe(
Effect.provide(modelLayer)
)
Streaming Chat
Stream responses while maintaining history:import { Effect, Stream } from "effect"
import { Chat } from "effect/unstable/ai"
const program = Effect.gen(function*() {
const chat = yield* Chat.empty
const stream = chat.streamText({
prompt: "Write a short story about space"
})
yield* Stream.runForEach(stream, (part) => {
if (part.type === "text-delta") {
return Effect.sync(() => process.stdout.write(part.delta))
}
return Effect.void
})
// History is updated after streaming completes
const response2 = yield* chat.generateText({
prompt: "What was the main character's name?"
})
console.log("\nAssistant:", response2.text)
})
Structured Output with Chat
Generate validated objects while maintaining history:import { Schema } from "effect"
import { Chat } from "effect/unstable/ai"
const ContactSchema = Schema.Struct({
name: Schema.String,
email: Schema.String,
phone: Schema.optional(Schema.String)
})
const program = Effect.gen(function*() {
const chat = yield* Chat.empty
const response = yield* chat.generateObject({
prompt: "Extract contact: John Doe, john@example.com",
schema: ContactSchema
})
console.log("Contact:", response.value)
// Continue conversation
const response2 = yield* chat.generateText({
prompt: "Format that as a business card"
})
console.log("Card:", response2.text)
})
Building Agentic Loops
Create AI agents that use tools iteratively:import { Effect } from "effect"
import { Chat, Tool, Toolkit } from "effect/unstable/ai"
const tools = Toolkit.make(
Tool.make("SearchDatabase", {
description: "Search the database",
parameters: Schema.Struct({ query: Schema.String }),
success: Schema.Array(Schema.Unknown)
}),
Tool.make("AnalyzeData", {
description: "Analyze data",
parameters: Schema.Struct({ data: Schema.Unknown }),
success: Schema.String
})
)
const agent = Effect.gen(function*() {
const toolkit = yield* tools
// Initialize chat with system prompt
const chat = yield* Chat.fromPrompt([
{
role: "system",
content: "You are an AI agent that helps analyze data. Use tools to gather and analyze information."
},
{
role: "user",
content: "Find and analyze recent sales data"
}
])
// Run agent loop until no more tool calls
let maxIterations = 10
while (maxIterations-- > 0) {
const response = yield* chat.generateText({
prompt: [], // Empty prompt - uses chat history
toolkit
})
if (response.toolCalls.length === 0) {
// Agent returned final answer
return response.text
}
// Tool calls were executed and added to history automatically
// Continue the loop
}
return "Agent exceeded max iterations"
})
const runnable = agent.pipe(
Effect.provide(modelLayer),
Effect.provide(toolsLayer)
)
Persisting Chat History
Export and restore chat sessions:import { Effect } from "effect"
import { Chat } from "effect/unstable/ai"
// Export to JSON
const saveChat = Effect.gen(function*() {
const chat = yield* Chat.empty
yield* chat.generateText({ prompt: "Hello!" })
yield* chat.generateText({ prompt: "How are you?" })
// Export as JSON string
const json = yield* chat.exportJson
// Save to storage
yield* Effect.sync(() => localStorage.setItem("chat-history", json))
return json
})
// Restore from JSON
const loadChat = Effect.gen(function*() {
const json = yield* Effect.sync(() => localStorage.getItem("chat-history"))
if (!json) {
return yield* Chat.empty
}
// Restore chat with full history
const chat = yield* Chat.fromJson(json)
// Continue conversation
const response = yield* chat.generateText({
prompt: "Let's continue our discussion"
})
return chat
})
Inspecting History
Access the conversation history directly:import { Effect, Ref } from "effect"
import { Chat } from "effect/unstable/ai"
const program = Effect.gen(function*() {
const chat = yield* Chat.empty
yield* chat.generateText({ prompt: "Hello!" })
// Get current history
const history = yield* Ref.get(chat.history)
console.log("Messages:", history.content.length)
for (const message of history.content) {
console.log(`${message.role}:`, message.content)
}
// Manually modify history if needed
yield* Ref.update(chat.history, (h) => {
// Transform history
return h
})
})
Complete Example: Customer Support Agent
Here’s a complete example combining tools and chat:import { Effect, Schema, Layer, Config } from "effect"
import { Chat, Tool, Toolkit } from "effect/unstable/ai"
import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai"
import { FetchHttpClient } from "effect/unstable/http"
const OpenAiClientLayer = OpenAiClient.layerConfig({
apiKey: Config.redacted("OPENAI_API_KEY")
}).pipe(Layer.provide(FetchHttpClient.layer))
// Define tools
const tools = Toolkit.make(
Tool.make("GetCustomerInfo", {
description: "Get customer information by ID",
parameters: Schema.Struct({ customerId: Schema.String }),
success: Schema.Struct({
name: Schema.String,
email: Schema.String,
tier: Schema.Literals("free", "pro", "enterprise")
})
}),
Tool.make("GetOrderStatus", {
description: "Get order status by order ID",
parameters: Schema.Struct({ orderId: Schema.String }),
success: Schema.Struct({
status: Schema.Literals("pending", "shipped", "delivered"),
estimatedDelivery: Schema.String
})
}),
Tool.make("CreateTicket", {
description: "Create a support ticket",
parameters: Schema.Struct({
customerId: Schema.String,
issue: Schema.String,
priority: Schema.Literals("low", "medium", "high")
}),
success: Schema.Struct({ ticketId: Schema.String })
})
)
const toolsLayer = tools.toLayer(
Effect.gen(function*() {
return tools.of({
GetCustomerInfo: ({ customerId }) =>
Effect.succeed({
name: "John Doe",
email: "john@example.com",
tier: "pro" as const
}),
GetOrderStatus: ({ orderId }) =>
Effect.succeed({
status: "shipped" as const,
estimatedDelivery: "2024-03-15"
}),
CreateTicket: ({ customerId, issue, priority }) =>
Effect.succeed({ ticketId: `TKT-${Date.now()}` })
})
})
)
const supportAgent = Effect.gen(function*() {
const toolkit = yield* tools
const modelLayer = yield* OpenAiLanguageModel.model("gpt-4")
const chat = yield* Chat.fromPrompt([
{
role: "system",
content:
"You are a helpful customer support agent. Use tools to look up customer " +
"information, check order status, and create support tickets when needed."
}
])
// Handle a customer inquiry
const response1 = yield* chat.generateText({
prompt: "Hi, I'm customer C123 and want to check my order O456",
toolkit
}).pipe(Effect.provide(modelLayer))
console.log("Agent:", response1.text)
// Continue conversation
const response2 = yield* chat.generateText({
prompt: "It's taking too long, can you help?",
toolkit
}).pipe(Effect.provide(modelLayer))
console.log("Agent:", response2.text)
})
const runnable = supportAgent.pipe(
Effect.provide(toolsLayer),
Effect.provide(OpenAiClientLayer)
)
Effect.runPromise(runnable)
Next Steps
Language Models
Learn about text generation and structured output
AI Overview
Understand the full AI framework architecture
