Skip to content

Tool Use

Language models are great at generating text, but often we need them to take real-world actions, such as querying an API, accessing a database, or calling a service. Most LLM providers support this through tool use (also known as function calling), where you expose specific operations in your application that the model can invoke.

Based on the input it receives, a model may choose to invoke (or call) one or more tools to augment its response. Your application then runs the corresponding logic for the tool using the parameters provided by the model. You then return the result to the model, allowing it to include the output in its final response.

The AiToolkit simplifies tool integration by offering a structured, type-safe approach to defining tools. It takes care of all the wiring between the model and your application - all you have to do is define the tool and implement its behavior.

Let’s walk through a complete example of how to define, implement, and use a tool that fetches a dad joke from the icanhazdadjoke.com API.

We start by defining a tool that the language model will have access to using the AiTool.make constructor.

This constructor accepts several parameters that allow us to fully describe the tool to the language model:

  • description: Provides an optional description of the tool
  • success: The type of value the tool will return if it succeeds
  • failure: The type of value the tool will return if it fails
  • parameters: The parameters that the tool should be called with

Example (Defining a Tool)

import {
import AiTool
AiTool
} from "@effect/ai"
import {
import Schema
Schema
} from "effect"
const
const GetDadJoke: AiTool.AiTool<"GetDadJoke", Schema.Struct<{
searchTerm: Schema.SchemaClass<string, string, never>;
}>, typeof Schema.String, typeof Schema.Never, never>
GetDadJoke
=
import AiTool
AiTool
.
const make: <"GetDadJoke", {
searchTerm: Schema.SchemaClass<string, string, never>;
}, typeof Schema.String, typeof Schema.Never>(name: "GetDadJoke", options?: {
readonly description?: string | undefined;
readonly parameters?: {
...;
};
readonly success?: typeof Schema.String;
readonly failure?: typeof Schema.Never;
} | undefined) => AiTool.AiTool<...>

Constructs an AiTool from a name and, optionally, a specification for the tool call's protocol.

@since1.0.0

make
("GetDadJoke", {
description?: string | undefined

An optional description of the tool.

description
: "Get a hilarious dad joke from the ICanHazDadJoke API",
success?: typeof Schema.String

A Schema representing the type that a tool returns from its handler if successful.

success
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
failure?: typeof Schema.Never

A Schema representing the type that a tool returns from its handler if it fails.

failure
:
import Schema
Schema
.
class Never

@since3.10.0

Never
,
parameters?: {
searchTerm: Schema.SchemaClass<string, string, never>;
}

A Schema representing the type of the parameters that a tool call handler must be provided with.

parameters
: {
searchTerm: Schema.SchemaClass<string, string, never>
searchTerm
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
.
Annotable<SchemaClass<string, string, never>, string, string, never>.annotations(annotations: Schema.Annotations.GenericSchema<string>): Schema.SchemaClass<string, string, never>

Merges a set of new annotations with existing ones, potentially overwriting any duplicates.

annotations
({
Annotations.Doc<string>.description?: string
description
: "The search term to use to find dad jokes"
})
}
})

Based on the above, a request to call the GetDadJoke tool:

  • Takes a single searchTerm parameter
  • Will return a string if it succeeds (i.e. the joke)
  • Does not have any expected failure scenarios

Once we have a tool request defined, we can create an AiToolkit, which is a collection of tools that the model will have access to.

Example (Creating an AiToolkit)

import {
import AiTool
AiTool
,
import AiToolkit
AiToolkit
} from "@effect/ai"
import {
import Schema
Schema
} from "effect"
class
class DadJokeTools
DadJokeTools
extends
import AiToolkit
AiToolkit
.
const make: <readonly [AiTool.AiTool<"GetDadJoke", Schema.Struct<{
searchTerm: Schema.SchemaClass<string, string, never>;
}>, typeof Schema.String, typeof Schema.Never, never>]>(tools_0: AiTool.AiTool<...>) => AiToolkit.AiToolkit<...>

Constructs a new AiToolkit from the specified tools.

@since1.0.0

make
(
import AiTool
AiTool
.
const make: <"GetDadJoke", {
searchTerm: Schema.SchemaClass<string, string, never>;
}, typeof Schema.String, typeof Schema.Never>(name: "GetDadJoke", options?: {
readonly description?: string | undefined;
readonly parameters?: {
...;
};
readonly success?: typeof Schema.String;
readonly failure?: typeof Schema.Never;
} | undefined) => AiTool.AiTool<...>

Constructs an AiTool from a name and, optionally, a specification for the tool call's protocol.

@since1.0.0

make
("GetDadJoke", {
description?: string | undefined

An optional description of the tool.

description
: "Get a hilarious dad joke from the ICanHazDadJoke API",
success?: typeof Schema.String

A Schema representing the type that a tool returns from its handler if successful.

success
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
failure?: typeof Schema.Never

A Schema representing the type that a tool returns from its handler if it fails.

failure
:
import Schema
Schema
.
class Never

@since3.10.0

Never
,
parameters?: {
searchTerm: Schema.SchemaClass<string, string, never>;
}

A Schema representing the type of the parameters that a tool call handler must be provided with.

parameters
: {
searchTerm: Schema.SchemaClass<string, string, never>
searchTerm
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
.
Annotable<SchemaClass<string, string, never>, string, string, never>.annotations(annotations: Schema.Annotations.GenericSchema<string>): Schema.SchemaClass<string, string, never>

Merges a set of new annotations with existing ones, potentially overwriting any duplicates.

annotations
({
Annotations.Doc<string>.description?: string
description
: "The search term to use to find dad jokes"
})
}
})
) {}

The .toLayer(...) method on an AiToolkit allows you to define the handlers for each tool in the toolkit. Because .toLayer(...) takes an Effect, we can access services from our application to implement the tool call handlers.

Example (Implementing an AiToolkit)

import {
import AiTool
AiTool
,
import AiToolkit
AiToolkit
} from "@effect/ai"
import {
import HttpClient
HttpClient
,
import HttpClientRequest
HttpClientRequest
,
import HttpClientResponse
HttpClientResponse
} from "@effect/platform"
import {
import NodeHttpClient
NodeHttpClient
} from "@effect/platform-node"
import {
import Array
Array
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Schema
Schema
} from "effect"
38 collapsed lines
class
class DadJoke
DadJoke
extends
import Schema
Schema
.
const Class: <DadJoke>(identifier: string) => <Fields>(fieldsOr: Fields | HasFields<Fields>, annotations?: ClassAnnotations<DadJoke, { [K in keyof Schema.Struct<Fields extends Schema.Struct.Fields>.Type<Fields>]: Schema.Struct.Type<...>[K]; }> | undefined) => Schema.Class<...>

@example

import { Schema } from "effect"
class MyClass extends Schema.Class<MyClass>("MyClass")({
someField: Schema.String
}) {
someMethod() {
return this.someField + "bar"
}
}

@since3.10.0

Class
<
class DadJoke
DadJoke
>("DadJoke")({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
joke: typeof Schema.String
joke
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
}) {}
class
class SearchResponse
SearchResponse
extends
import Schema
Schema
.
const Class: <SearchResponse>(identifier: string) => <Fields>(fieldsOr: Fields | HasFields<Fields>, annotations?: ClassAnnotations<SearchResponse, { [K in keyof Schema.Struct<Fields extends Schema.Struct.Fields>.Type<...>]: Schema.Struct.Type<...>[K]; }> | undefined) => Schema.Class<...>

@example

import { Schema } from "effect"
class MyClass extends Schema.Class<MyClass>("MyClass")({
someField: Schema.String
}) {
someMethod() {
return this.someField + "bar"
}
}

@since3.10.0

Class
<
class SearchResponse
SearchResponse
>("SearchResponse")({
results: Schema.Array$<typeof DadJoke>
results
:
import Schema
Schema
.
Array<typeof DadJoke>(value: typeof DadJoke): Schema.Array$<typeof DadJoke>
export Array

@since3.10.0

Array
(
class DadJoke
DadJoke
)
}) {}
class
class ICanHazDadJoke
ICanHazDadJoke
extends
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const Service: <ICanHazDadJoke>() => {
<Key, Make>(key: Key, make: Make): Effect.Service.Class<ICanHazDadJoke, Key, Make>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
}

Simplifies the creation and management of services in Effect by defining both a Tag and a Layer.

Details

This function allows you to streamline the creation of services by combining the definition of a Context.Tag and a Layer in a single step. It supports various ways of providing the service implementation:

  • Using an effect to define the service dynamically.
  • Using sync or succeed to define the service statically.
  • Using scoped to create services with lifecycle management.

It also allows you to specify dependencies for the service, which will be provided automatically when the service is used. Accessors can be optionally generated for the service, making it more convenient to use.

Example

import { Effect } from 'effect';
class Prefix extends Effect.Service<Prefix>()("Prefix", {
sync: () => ({ prefix: "PRE" })
}) {}
class Logger extends Effect.Service<Logger>()("Logger", {
accessors: true,
effect: Effect.gen(function* () {
const { prefix } = yield* Prefix
return {
info: (message: string) =>
Effect.sync(() => {
console.log(`[${prefix}][${message}]`)
})
}
}),
dependencies: [Prefix.Default]
}) {}

@since3.9.0

Service
<
class ICanHazDadJoke
ICanHazDadJoke
>()("ICanHazDadJoke", {
dependencies: readonly [Layer<HttpClient.HttpClient, never, never>]
dependencies
: [
import NodeHttpClient
NodeHttpClient
.
const layerUndici: Layer<HttpClient.HttpClient, never, never>

@since1.0.0

layerUndici
],
effect: Effect.Effect<{
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}, never, HttpClient.HttpClient>
effect
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<HttpClient.HttpClient, HttpClient.HttpClient>>, {
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function*() {
const
const httpClient: HttpClient.HttpClient
httpClient
= yield*
import HttpClient
HttpClient
.
const HttpClient: Tag<HttpClient.HttpClient, HttpClient.HttpClient>

@since1.0.0

@since1.0.0

@since1.0.0

HttpClient
const
const httpClientOk: HttpClient.HttpClient.With<HttpClientError, never>
httpClientOk
=
const httpClient: HttpClient.HttpClient
httpClient
.
Pipeable.pipe<HttpClient.HttpClient, HttpClient.HttpClient.With<HttpClientError, never>, HttpClient.HttpClient.With<HttpClientError, never>>(this: HttpClient.HttpClient, ab: (_: HttpClient.HttpClient) => HttpClient.HttpClient.With<...>, bc: (_: HttpClient.HttpClient.With<...>) => HttpClient.HttpClient.With<...>): HttpClient.HttpClient.With<...> (+21 overloads)
pipe
(
import HttpClient
HttpClient
.
const filterStatusOk: <E, R>(self: HttpClient.HttpClient.With<E, R>) => HttpClient.HttpClient.With<E | ResponseError, R>

Filters responses that return a 2xx status code.

@since1.0.0

filterStatusOk
,
import HttpClient
HttpClient
.
const mapRequest: (f: (a: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest) => <E, R>(self: HttpClient.HttpClient.With<E, R>) => HttpClient.HttpClient.With<E, R> (+1 overload)

Appends a transformation of the request object before sending it.

@since1.0.0

mapRequest
(
import HttpClientRequest
HttpClientRequest
.
const prependUrl: (path: string) => (self: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest (+1 overload)

@since1.0.0

prependUrl
("https://icanhazdadjoke.com"))
)
const
const search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fn: (name: string, options?: SpanOptions) => Effect.fn.Gen & Effect.fn.NonGen (+20 overloads)
fn
("ICanHazDadJoke.search")(
function*(
searchTerm: string
searchTerm
: string) {
return yield*
const httpClientOk: HttpClient.HttpClient.With<HttpClientError, never>
httpClientOk
.
HttpClient.With<HttpClientError, never>.get: (url: string | URL, options?: HttpClientRequest.Options.NoBody) => Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError, never>
get
("/search", {
acceptJson?: boolean | undefined
acceptJson
: true,
urlParams?: Input | undefined
urlParams
: {
searchTerm: string
searchTerm
}
}).
Pipeable.pipe<Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError, never>, Effect.Effect<SearchResponse, HttpClientError | ParseError, never>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>, cd: (_: Effect.Effect<...>) => Effect.Effect<...>, de: (_: Effect.Effect<...>) => Effect.Effect<...>, ef: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <HttpClientResponse.HttpClientResponse, SearchResponse, ResponseError | ParseError, never>(f: (a: HttpClientResponse.HttpClientResponse) => Effect.Effect<...>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(
import HttpClientResponse
HttpClientResponse
.
schemaBodyJson<SearchResponse, {
readonly results: readonly {
readonly id: string;
readonly joke: string;
}[];
}, never>(schema: Schema.Schema<SearchResponse, {
readonly results: readonly {
readonly id: string;
readonly joke: string;
}[];
}, never>, options?: ParseOptions | undefined): <E>(self: HttpIncomingMessage<...>) => Effect.Effect<...>
export schemaBodyJson

@since1.0.0

schemaBodyJson
(
class SearchResponse
SearchResponse
)),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <SearchResponse, DadJoke, NoSuchElementException, never>(f: (a: SearchResponse) => Effect.Effect<DadJoke, NoSuchElementException, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(({
results: readonly DadJoke[]
results
}) =>
import Array
Array
.
const head: <DadJoke>(self: readonly DadJoke[]) => Option<DadJoke>

Get the first element of a ReadonlyArray, or None if the ReadonlyArray is empty.

@since2.0.0

head
(
results: readonly DadJoke[]
results
)),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <DadJoke, string>(f: (a: DadJoke) => string) => <E, R>(self: Effect.Effect<DadJoke, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
((
joke: DadJoke
joke
) =>
joke: DadJoke
joke
.
joke: string
joke
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const scoped: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Scopes all resources used in an effect to the lifetime of the effect.

Details

This function ensures that all resources used within an effect are tied to its lifetime. Finalizers for these resources are executed automatically when the effect completes, whether through success, failure, or interruption. This guarantees proper resource cleanup without requiring explicit management.

@since2.0.0

scoped
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>

Converts an effect's failure into a fiber termination, removing the error from the effect's type.

Details

The orDie function is used when you encounter errors that you do not want to handle or recover from. It removes the error type from the effect and ensures that any failure will terminate the fiber. This is useful for propagating failures as defects, signaling that they should not be handled within the effect.

*When to Use

Use orDie when failures should be treated as unrecoverable defects and no error handling is required.

Example (Propagating an Error as a Defect)

import { Effect } from "effect"
const divide = (a: number, b: number) =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = Effect.orDie(divide(1, 0))
Effect.runPromise(program).catch(console.error)
// Output:
// (FiberFailure) Error: Cannot divide by zero
// ...stack trace...

@seeorDieWith if you need to customize the error.

@since2.0.0

orDie
)
}
)
return {
search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
} as
type const = {
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}
const
})
}) {}
class
class DadJokeTools
DadJokeTools
extends
import AiToolkit
AiToolkit
.
const make: <readonly [AiTool.AiTool<"GetDadJoke", Schema.Struct<{
searchTerm: Schema.SchemaClass<string, string, never>;
}>, typeof Schema.String, typeof Schema.Never, never>]>(tools_0: AiTool.AiTool<...>) => AiToolkit.AiToolkit<...>

Constructs a new AiToolkit from the specified tools.

@since1.0.0

make
(
import AiTool
AiTool
.
const make: <"GetDadJoke", {
searchTerm: Schema.SchemaClass<string, string, never>;
}, typeof Schema.String, typeof Schema.Never>(name: "GetDadJoke", options?: {
readonly description?: string | undefined;
readonly parameters?: {
...;
};
readonly success?: typeof Schema.String;
readonly failure?: typeof Schema.Never;
} | undefined) => AiTool.AiTool<...>

Constructs an AiTool from a name and, optionally, a specification for the tool call's protocol.

@since1.0.0

make
("GetDadJoke", {
description?: string | undefined

An optional description of the tool.

description
: "Get a hilarious dad joke from the ICanHazDadJoke API",
success?: typeof Schema.String

A Schema representing the type that a tool returns from its handler if successful.

success
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
failure?: typeof Schema.Never

A Schema representing the type that a tool returns from its handler if it fails.

failure
:
import Schema
Schema
.
class Never

@since3.10.0

Never
,
parameters?: {
searchTerm: Schema.SchemaClass<string, string, never>;
}

A Schema representing the type of the parameters that a tool call handler must be provided with.

parameters
: {
searchTerm: Schema.SchemaClass<string, string, never>
searchTerm
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
.
Annotable<SchemaClass<string, string, never>, string, string, never>.annotations(annotations: Schema.Annotations.GenericSchema<string>): Schema.SchemaClass<string, string, never>

Merges a set of new annotations with existing ones, potentially overwriting any duplicates.

annotations
({
Annotations.Doc<string>.description?: string
description
: "The search term to use to find dad jokes"
})
}
})
) {}
const
const DadJokeToolHandlers: Layer<AiTool.Handler<"GetDadJoke">, never, ICanHazDadJoke>
DadJokeToolHandlers
=
class DadJokeTools
DadJokeTools
.
AiToolkit<AiTool<"GetDadJoke", Struct<{ searchTerm: SchemaClass<string, string, never>; }>, typeof String$, typeof Never, never>>.toLayer<{
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
}, never, ICanHazDadJoke>(build: {
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
} | Effect.Effect<...>): Layer<...>

Converts this toolkit into a Layer containing the handlers for all tools in the toolkit.

toLayer
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<ICanHazDadJoke, ICanHazDadJoke>>, {
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
}>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function*() {
// Access the `ICanHazDadJoke` service
const
const icanhazdadjoke: ICanHazDadJoke
icanhazdadjoke
= yield*
class ICanHazDadJoke
ICanHazDadJoke
return {
// Implement the handler for the `GetDadJoke` tool call request
type GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>
GetDadJoke
: ({
searchTerm: string
searchTerm
}) =>
const icanhazdadjoke: ICanHazDadJoke
icanhazdadjoke
.
search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
(
searchTerm: string
searchTerm
)
}
})
)

In the code above:

  • We access the ICanHazDadJoke service from our application
  • Register a handler for the GetDadJoke tool using .handle("GetDadJoke", ...)
  • Use the .search method on our ICanHazDadJoke service to search for a dad joke based on the tool call parameters

The result of calling .toLayer on an AiToolkit is a Layer that contains the handlers for all the tools in our toolkit.

Because of this, it is quite simple to test an AiToolkit by using .toLayer to create a separate Layer specifically for testing.

Once the tools are defined and implemented, you can pass them along to the model at request time. Behind the scenes, the model is given a structured description of each tool and can choose to call one or more of them when responding to input.

Example (Using an AiToolkit in Completions.toolkit)

import {
import AiLanguageModel
AiLanguageModel
,
import AiTool
AiTool
,
import AiToolkit
AiToolkit
} from "@effect/ai"
import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Schema
Schema
} from "effect"
class
class DadJokeTools
DadJokeTools
extends
import AiToolkit
AiToolkit
.
const make: <readonly [AiTool.AiTool<"GetDadJoke", Schema.Struct<{
searchTerm: Schema.SchemaClass<string, string, never>;
}>, typeof Schema.String, typeof Schema.Never, never>]>(tools_0: AiTool.AiTool<...>) => AiToolkit.AiToolkit<...>

Constructs a new AiToolkit from the specified tools.

@since1.0.0

make
(
import AiTool
AiTool
.
const make: <"GetDadJoke", {
searchTerm: Schema.SchemaClass<string, string, never>;
}, typeof Schema.String, typeof Schema.Never>(name: "GetDadJoke", options?: {
readonly description?: string | undefined;
readonly parameters?: {
...;
};
readonly success?: typeof Schema.String;
readonly failure?: typeof Schema.Never;
} | undefined) => AiTool.AiTool<...>

Constructs an AiTool from a name and, optionally, a specification for the tool call's protocol.

@since1.0.0

make
("GetDadJoke", {
description?: string | undefined

An optional description of the tool.

description
: "Get a hilarious dad joke from the ICanHazDadJoke API",
success?: typeof Schema.String

A Schema representing the type that a tool returns from its handler if successful.

success
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
failure?: typeof Schema.Never

A Schema representing the type that a tool returns from its handler if it fails.

failure
:
import Schema
Schema
.
class Never

@since3.10.0

Never
,
parameters?: {
searchTerm: Schema.SchemaClass<string, string, never>;
}

A Schema representing the type of the parameters that a tool call handler must be provided with.

parameters
: {
searchTerm: Schema.SchemaClass<string, string, never>
searchTerm
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
.
Annotable<SchemaClass<string, string, never>, string, string, never>.annotations(annotations: Schema.Annotations.GenericSchema<string>): Schema.SchemaClass<string, string, never>

Merges a set of new annotations with existing ones, potentially overwriting any duplicates.

annotations
({
Annotations.Doc<string>.description?: string
description
: "The search term to use to find dad jokes"
})
}
})
) {}
const
const generateDadJoke: Effect.Effect<AiResponse, AiError, AiLanguageModel.AiLanguageModel>
generateDadJoke
=
import AiLanguageModel
AiLanguageModel
.
const generateText: <AiTool.Any, {
prompt: string;
tools: typeof DadJokeTools;
}>(options: {
prompt: string;
tools: typeof DadJokeTools;
} & AiLanguageModel.GenerateTextOptions<AiTool.Any>) => Effect.Effect<...>

Generate text using a large language model for the specified prompt.

If a toolkit is specified, the large language model will additionally be able to perform tool calls to augment its response.

@since1.0.0

generateText
({
prompt: string & Raw

The prompt input to use to generate text.

prompt
: "Generate a dad joke about pirates",
tools: typeof DadJokeTools
tools
:
class DadJokeTools
DadJokeTools
})

To make the program executable, we must provide the implementation of our tool call handlers:

Example (Providing the Tool Call Handlers to a Program)

import {
import AiLanguageModel
AiLanguageModel
,
import AiTool
AiTool
,
import AiToolkit
AiToolkit
} from "@effect/ai"
import {
import OpenAiClient
OpenAiClient
,
import OpenAiLanguageModel
OpenAiLanguageModel
} from "@effect/ai-openai"
import {
import HttpClient
HttpClient
,
import HttpClientRequest
HttpClientRequest
,
import HttpClientResponse
HttpClientResponse
} from "@effect/platform"
import {
import NodeHttpClient
NodeHttpClient
} from "@effect/platform-node"
import {
import Array
Array
,
import Config
Config
,
import Console
Console
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Layer
Layer
,
import Schema
Schema
} from "effect"
51 collapsed lines
class
class DadJoke
DadJoke
extends
import Schema
Schema
.
const Class: <DadJoke>(identifier: string) => <Fields>(fieldsOr: Fields | HasFields<Fields>, annotations?: ClassAnnotations<DadJoke, { [K in keyof Schema.Struct<Fields extends Schema.Struct.Fields>.Type<Fields>]: Schema.Struct.Type<...>[K]; }> | undefined) => Schema.Class<...>

@example

import { Schema } from "effect"
class MyClass extends Schema.Class<MyClass>("MyClass")({
someField: Schema.String
}) {
someMethod() {
return this.someField + "bar"
}
}

@since3.10.0

Class
<
class DadJoke
DadJoke
>("DadJoke")({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
joke: typeof Schema.String
joke
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
}) {}
class
class SearchResponse
SearchResponse
extends
import Schema
Schema
.
const Class: <SearchResponse>(identifier: string) => <Fields>(fieldsOr: Fields | HasFields<Fields>, annotations?: ClassAnnotations<SearchResponse, { [K in keyof Schema.Struct<Fields extends Schema.Struct.Fields>.Type<...>]: Schema.Struct.Type<...>[K]; }> | undefined) => Schema.Class<...>

@example

import { Schema } from "effect"
class MyClass extends Schema.Class<MyClass>("MyClass")({
someField: Schema.String
}) {
someMethod() {
return this.someField + "bar"
}
}

@since3.10.0

Class
<
class SearchResponse
SearchResponse
>("SearchResponse")({
results: Schema.Array$<typeof DadJoke>
results
:
import Schema
Schema
.
Array<typeof DadJoke>(value: typeof DadJoke): Schema.Array$<typeof DadJoke>
export Array

@since3.10.0

Array
(
class DadJoke
DadJoke
)
}) {}
class
class ICanHazDadJoke
ICanHazDadJoke
extends
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const Service: <ICanHazDadJoke>() => {
<Key, Make>(key: Key, make: Make): Effect.Service.Class<ICanHazDadJoke, Key, Make>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
<Key, Make>(key: Key, make: Make): Effect.Service.Class<...>;
}

Simplifies the creation and management of services in Effect by defining both a Tag and a Layer.

Details

This function allows you to streamline the creation of services by combining the definition of a Context.Tag and a Layer in a single step. It supports various ways of providing the service implementation:

  • Using an effect to define the service dynamically.
  • Using sync or succeed to define the service statically.
  • Using scoped to create services with lifecycle management.

It also allows you to specify dependencies for the service, which will be provided automatically when the service is used. Accessors can be optionally generated for the service, making it more convenient to use.

Example

import { Effect } from 'effect';
class Prefix extends Effect.Service<Prefix>()("Prefix", {
sync: () => ({ prefix: "PRE" })
}) {}
class Logger extends Effect.Service<Logger>()("Logger", {
accessors: true,
effect: Effect.gen(function* () {
const { prefix } = yield* Prefix
return {
info: (message: string) =>
Effect.sync(() => {
console.log(`[${prefix}][${message}]`)
})
}
}),
dependencies: [Prefix.Default]
}) {}

@since3.9.0

Service
<
class ICanHazDadJoke
ICanHazDadJoke
>()("ICanHazDadJoke", {
dependencies: readonly [Layer.Layer<HttpClient.HttpClient, never, never>]
dependencies
: [
import NodeHttpClient
NodeHttpClient
.
const layerUndici: Layer.Layer<HttpClient.HttpClient, never, never>

@since1.0.0

layerUndici
],
effect: Effect.Effect<{
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}, never, HttpClient.HttpClient>
effect
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<HttpClient.HttpClient, HttpClient.HttpClient>>, {
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function*() {
const
const httpClient: HttpClient.HttpClient
httpClient
= yield*
import HttpClient
HttpClient
.
const HttpClient: Tag<HttpClient.HttpClient, HttpClient.HttpClient>

@since1.0.0

@since1.0.0

@since1.0.0

HttpClient
const
const httpClientOk: HttpClient.HttpClient.With<HttpClientError, never>
httpClientOk
=
const httpClient: HttpClient.HttpClient
httpClient
.
Pipeable.pipe<HttpClient.HttpClient, HttpClient.HttpClient.With<HttpClientError, never>, HttpClient.HttpClient.With<HttpClientError, never>>(this: HttpClient.HttpClient, ab: (_: HttpClient.HttpClient) => HttpClient.HttpClient.With<...>, bc: (_: HttpClient.HttpClient.With<...>) => HttpClient.HttpClient.With<...>): HttpClient.HttpClient.With<...> (+21 overloads)
pipe
(
import HttpClient
HttpClient
.
const filterStatusOk: <E, R>(self: HttpClient.HttpClient.With<E, R>) => HttpClient.HttpClient.With<E | ResponseError, R>

Filters responses that return a 2xx status code.

@since1.0.0

filterStatusOk
,
import HttpClient
HttpClient
.
const mapRequest: (f: (a: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest) => <E, R>(self: HttpClient.HttpClient.With<E, R>) => HttpClient.HttpClient.With<E, R> (+1 overload)

Appends a transformation of the request object before sending it.

@since1.0.0

mapRequest
(
import HttpClientRequest
HttpClientRequest
.
const prependUrl: (path: string) => (self: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest (+1 overload)

@since1.0.0

prependUrl
("https://icanhazdadjoke.com"))
)
const
const search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fn: (name: string, options?: SpanOptions) => Effect.fn.Gen & Effect.fn.NonGen (+20 overloads)
fn
("ICanHazDadJoke.search")(
function*(
searchTerm: string
searchTerm
: string) {
return yield*
const httpClientOk: HttpClient.HttpClient.With<HttpClientError, never>
httpClientOk
.
HttpClient.With<HttpClientError, never>.get: (url: string | URL, options?: HttpClientRequest.Options.NoBody) => Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError, never>
get
("/search", {
acceptJson?: boolean | undefined
acceptJson
: true,
urlParams?: Input | undefined
urlParams
: {
searchTerm: string
searchTerm
}
}).
Pipeable.pipe<Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError, never>, Effect.Effect<SearchResponse, HttpClientError | ParseError, never>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>, cd: (_: Effect.Effect<...>) => Effect.Effect<...>, de: (_: Effect.Effect<...>) => Effect.Effect<...>, ef: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <HttpClientResponse.HttpClientResponse, SearchResponse, ResponseError | ParseError, never>(f: (a: HttpClientResponse.HttpClientResponse) => Effect.Effect<...>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(
import HttpClientResponse
HttpClientResponse
.
schemaBodyJson<SearchResponse, {
readonly results: readonly {
readonly id: string;
readonly joke: string;
}[];
}, never>(schema: Schema.Schema<SearchResponse, {
readonly results: readonly {
readonly id: string;
readonly joke: string;
}[];
}, never>, options?: ParseOptions | undefined): <E>(self: HttpIncomingMessage<...>) => Effect.Effect<...>
export schemaBodyJson

@since1.0.0

schemaBodyJson
(
class SearchResponse
SearchResponse
)),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <SearchResponse, DadJoke, NoSuchElementException, never>(f: (a: SearchResponse) => Effect.Effect<DadJoke, NoSuchElementException, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(({
results: readonly DadJoke[]
results
}) =>
import Array
Array
.
const head: <DadJoke>(self: readonly DadJoke[]) => Option<DadJoke>

Get the first element of a ReadonlyArray, or None if the ReadonlyArray is empty.

@since2.0.0

head
(
results: readonly DadJoke[]
results
)),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <DadJoke, string>(f: (a: DadJoke) => string) => <E, R>(self: Effect.Effect<DadJoke, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
((
joke: DadJoke
joke
) =>
joke: DadJoke
joke
.
joke: string
joke
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const scoped: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Scopes all resources used in an effect to the lifetime of the effect.

Details

This function ensures that all resources used within an effect are tied to its lifetime. Finalizers for these resources are executed automatically when the effect completes, whether through success, failure, or interruption. This guarantees proper resource cleanup without requiring explicit management.

@since2.0.0

scoped
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const orDie: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>

Converts an effect's failure into a fiber termination, removing the error from the effect's type.

Details

The orDie function is used when you encounter errors that you do not want to handle or recover from. It removes the error type from the effect and ensures that any failure will terminate the fiber. This is useful for propagating failures as defects, signaling that they should not be handled within the effect.

*When to Use

Use orDie when failures should be treated as unrecoverable defects and no error handling is required.

Example (Propagating an Error as a Defect)

import { Effect } from "effect"
const divide = (a: number, b: number) =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = Effect.orDie(divide(1, 0))
Effect.runPromise(program).catch(console.error)
// Output:
// (FiberFailure) Error: Cannot divide by zero
// ...stack trace...

@seeorDieWith if you need to customize the error.

@since2.0.0

orDie
)
}
)
return {
search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
} as
type const = {
readonly search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>;
}
const
})
}) {}
class
class DadJokeTools
DadJokeTools
extends
import AiToolkit
AiToolkit
.
const make: <readonly [AiTool.AiTool<"GetDadJoke", Schema.Struct<{
searchTerm: Schema.SchemaClass<string, string, never>;
}>, typeof Schema.String, typeof Schema.Never, never>]>(tools_0: AiTool.AiTool<...>) => AiToolkit.AiToolkit<...>

Constructs a new AiToolkit from the specified tools.

@since1.0.0

make
(
import AiTool
AiTool
.
const make: <"GetDadJoke", {
searchTerm: Schema.SchemaClass<string, string, never>;
}, typeof Schema.String, typeof Schema.Never>(name: "GetDadJoke", options?: {
readonly description?: string | undefined;
readonly parameters?: {
...;
};
readonly success?: typeof Schema.String;
readonly failure?: typeof Schema.Never;
} | undefined) => AiTool.AiTool<...>

Constructs an AiTool from a name and, optionally, a specification for the tool call's protocol.

@since1.0.0

make
("GetDadJoke", {
description?: string | undefined

An optional description of the tool.

description
: "Get a hilarious dad joke from the ICanHazDadJoke API",
success?: typeof Schema.String

A Schema representing the type that a tool returns from its handler if successful.

success
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
failure?: typeof Schema.Never

A Schema representing the type that a tool returns from its handler if it fails.

failure
:
import Schema
Schema
.
class Never

@since3.10.0

Never
,
parameters?: {
searchTerm: Schema.SchemaClass<string, string, never>;
}

A Schema representing the type of the parameters that a tool call handler must be provided with.

parameters
: {
searchTerm: Schema.SchemaClass<string, string, never>
searchTerm
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
.
Annotable<SchemaClass<string, string, never>, string, string, never>.annotations(annotations: Schema.Annotations.GenericSchema<string>): Schema.SchemaClass<string, string, never>

Merges a set of new annotations with existing ones, potentially overwriting any duplicates.

annotations
({
Annotations.Doc<string>.description?: string
description
: "The search term to use to find dad jokes"
})
}
})
) {}
const
const DadJokeToolHandlers: Layer.Layer<AiTool.Handler<"GetDadJoke">, never, never>
DadJokeToolHandlers
=
class DadJokeTools
DadJokeTools
.
AiToolkit<AiTool<"GetDadJoke", Struct<{ searchTerm: SchemaClass<string, string, never>; }>, typeof String$, typeof Never, never>>.toLayer<{
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
}, never, ICanHazDadJoke>(build: {
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
} | Effect.Effect<...>): Layer.Layer<...>

Converts this toolkit into a Layer containing the handlers for all tools in the toolkit.

toLayer
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<ICanHazDadJoke, ICanHazDadJoke>>, {
GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>;
}>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function*() {
const
const icanhazdadjoke: ICanHazDadJoke
icanhazdadjoke
= yield*
class ICanHazDadJoke
ICanHazDadJoke
return {
type GetDadJoke: ({ searchTerm }: {
readonly searchTerm: string;
}) => Effect.Effect<string, never, never>
GetDadJoke
: ({
searchTerm: string
searchTerm
}) =>
const icanhazdadjoke: ICanHazDadJoke
icanhazdadjoke
.
search: (this: unknown, searchTerm: string) => Effect.Effect<string, never, never>
search
(
searchTerm: string
searchTerm
)
}
})
).
Pipeable.pipe<Layer.Layer<AiTool.Handler<"GetDadJoke">, never, ICanHazDadJoke>, Layer.Layer<AiTool.Handler<"GetDadJoke">, never, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, ICanHazDadJoke>(that: Layer.Layer<ICanHazDadJoke, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<...> (+3 overloads)

Feeds the output services of this builder into the input of the specified builder, resulting in a new builder with the inputs of this builder as well as any leftover inputs, and the outputs of the specified builder.

@since2.0.0

provide
(
class ICanHazDadJoke
ICanHazDadJoke
.
type Default: Layer.Layer<ICanHazDadJoke, never, never>
Default
))
const
const program: Effect.Effect<void, AiError, OpenAiClient.OpenAiClient>
program
=
import AiLanguageModel
AiLanguageModel
.
const generateText: <AiTool.Any, {
prompt: string;
tools: typeof DadJokeTools;
}>(options: {
prompt: string;
tools: typeof DadJokeTools;
} & AiLanguageModel.GenerateTextOptions<AiTool.Any>) => Effect.Effect<...>

Generate text using a large language model for the specified prompt.

If a toolkit is specified, the large language model will additionally be able to perform tool calls to augment its response.

@since1.0.0

generateText
({
prompt: string & Raw

The prompt input to use to generate text.

prompt
: "Generate a dad joke about pirates",
tools: typeof DadJokeTools
tools
:
class DadJokeTools
DadJokeTools
}).
Pipeable.pipe<Effect.Effect<AiResponse, AiError, AiLanguageModel.AiLanguageModel>, Effect.Effect<void, AiError, AiLanguageModel.AiLanguageModel>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <AiResponse, void, never, never>(f: (a: AiResponse) => Effect.Effect<void, never, never>) => <E, R>(self: Effect.Effect<AiResponse, E, R>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
((
response: AiResponse
response
) =>
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>

@since2.0.0

log
(
response: AiResponse
response
.
AiResponse.text: string

Returns the generated text content of the response.

text
)),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <AiLanguageModel.AiLanguageModel, never, OpenAiClient.OpenAiClient>(layer: Layer.Layer<AiLanguageModel.AiLanguageModel, never, OpenAiClient.OpenAiClient>) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+9 overloads)

Provides necessary dependencies to an effect, removing its environmental requirements.

Details

This function allows you to supply the required environment for an effect. The environment can be provided in the form of one or more Layers, a Context, a Runtime, or a ManagedRuntime. Once the environment is provided, the effect can run without requiring external dependencies.

You can compose layers to create a modular and reusable way of setting up the environment for effects. For example, layers can be used to configure databases, logging services, or any other required dependencies.

Example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@seeprovideService for providing a service to an effect.

@since2.0.0

provide
(
import OpenAiLanguageModel
OpenAiLanguageModel
.
const model: (model: (string & {}) | OpenAiLanguageModel.Model, config?: Omit<OpenAiLanguageModel.Config.Service, "model">) => AiModel<AiLanguageModel.AiLanguageModel, OpenAiClient.OpenAiClient>

@since1.0.0

model
("gpt-4o"))
)
const
const OpenAi: Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, never>
OpenAi
=
import OpenAiClient
OpenAiClient
.
const layerConfig: (options: Config.Config.Wrap<{
readonly apiKey?: Redacted | undefined;
readonly apiUrl?: string | undefined;
readonly organizationId?: Redacted | undefined;
readonly projectId?: Redacted | undefined;
readonly transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
}>) => Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, HttpClient.HttpClient>

@since1.0.0

layerConfig
({
apiKey?: Config.Config<Redacted<string> | undefined>
apiKey
:
import Config
Config
.
const redacted: (name?: string) => Config.Config<Redacted> (+1 overload)

Constructs a config for a redacted value.

@since2.0.0

redacted
("OPENAI_API_KEY")
}).
Pipeable.pipe<Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, HttpClient.HttpClient>, Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient.HttpClient>(that: Layer.Layer<HttpClient.HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<...> (+3 overloads)

Feeds the output services of this builder into the input of the specified builder, resulting in a new builder with the inputs of this builder as well as any leftover inputs, and the outputs of the specified builder.

@since2.0.0

provide
(
import NodeHttpClient
NodeHttpClient
.
const layerUndici: Layer.Layer<HttpClient.HttpClient, never, never>

@since1.0.0

layerUndici
))
const program: Effect.Effect<void, AiError, OpenAiClient.OpenAiClient>
program
.
Pipeable.pipe<Effect.Effect<void, AiError, OpenAiClient.OpenAiClient>, Effect.Effect<void, AiError | ConfigError, never>, Promise<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <[Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, never>, Layer.Layer<AiTool.Handler<"GetDadJoke">, never, never>]>(layers: [...]) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+9 overloads)

Provides necessary dependencies to an effect, removing its environmental requirements.

Details

This function allows you to supply the required environment for an effect. The environment can be provided in the form of one or more Layers, a Context, a Runtime, or a ManagedRuntime. Once the environment is provided, the effect can run without requiring external dependencies.

You can compose layers to create a modular and reusable way of setting up the environment for effects. For example, layers can be used to configure databases, logging services, or any other required dependencies.

Example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@seeprovideService for providing a service to an effect.

@since2.0.0

provide
([
const OpenAi: Layer.Layer<OpenAiClient.OpenAiClient, ConfigError, never>
OpenAi
,
const DadJokeToolHandlers: Layer.Layer<AiTool.Handler<"GetDadJoke">, never, never>
DadJokeToolHandlers
]),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<A>

Executes an effect and returns the result as a Promise.

Details

This function runs an effect and converts its result into a Promise. If the effect succeeds, the Promise will resolve with the successful result. If the effect fails, the Promise will reject with an error, which includes the failure details of the effect.

The optional options parameter allows you to pass an AbortSignal for cancellation, enabling more fine-grained control over asynchronous tasks.

When to Use

Use this function when you need to execute an effect and work with its result in a promise-based system, such as when integrating with third-party libraries that expect Promise results.

Example (Running a Successful Effect as a Promise)

import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

Example (Handling a Failing Effect as a Rejected Promise)

import { Effect } from "effect"
Effect.runPromise(Effect.fail("my error")).catch(console.error)
// Output:
// (FiberFailure) Error: my error

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@since2.0.0

runPromise
)

Type Safe

Every tool is fully described using Effect’s Schema, including inputs, outputs, and descriptions.

Effect Native

Tool call behavior is defined using Effect, so they can leverage all the power of Effect. This is especially useful when you need to access other services to support the implementation of your tool call handlers.

Injectable

Because implementing the handlers for an AiToolkit results in a Layer, providing alternate implementation of tool call handlers in different environments is as simple as providing a different Layer to your program.

Separation of Concerns

The definition of a tool call request is cleanly separated from both the implementation of the tool behavior, as well as the business logic that calls the model.