1---
2title: Bedrock & Effect AI
3description: Drive Effect's LanguageModel service with Amazon Bedrock models from a Lambda Function — IAM-scoped bindings, streaming, and tool calling over the Converse API.
4sidebar:
5 order: 1
6---
7
8Effect's AI surface gives you a single `LanguageModel` service that
9every model provider ships as a `Layer`. Once that layer is in scope,
10the rest of the surface (`generateText`, `streamText`, `Toolkit`,
11`Chat`, …) is provider-agnostic.
12
13`AWS.Bedrock.LanguageModel` builds that layer on top of Amazon
14Bedrock. It speaks Bedrock's [Converse
15API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html)
16— the unified messages API that works across all conversational
17foundation models (Amazon Nova, Anthropic Claude, Meta Llama,
18Mistral, …) — so one binding covers every model on Bedrock. Requests
19are signed with the Function's IAM role: there is no API key to
20provision, store, or rotate.
21
22## Bind a model
23
24`yield* AWS.Bedrock.LanguageModel(model)` in the Function's constructor
25phase returns a `LanguageModel` `Layer` and grants the Function
26`bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`
27scoped to exactly that model:
28
29```typescript
30// src/Api.ts
31import * as AWS from "alchemy/AWS";
32import * as Effect from "effect/Effect";
33import { LanguageModel } from "effect/ai";
34import * as HttpServerResponse from "effect/http/HttpServerResponse";
35
36export default class Api extends AWS.Lambda.Function<Api>()(
37 "Api",
38 { main: import.meta.url, functionUrl: true },
39 Effect.gen(function* () {
40 const model = yield* AWS.Bedrock.LanguageModel(
41 "us.amazon.nova-micro-v1:0",
42 { parameters: { maxTokens: 1024, temperature: 0.7 } },
43 );
44
45 return {
46 fetch: Effect.gen(function* () {
47 const response = yield* LanguageModel.generateText({
48 prompt: "Say hello.",
49 }).pipe(Effect.orDie);
50 return yield* HttpServerResponse.json({ text: response.text });
51 }).pipe(Effect.provide(model)),
52 };
53 }).pipe(Effect.provide(AWS.Bedrock.LanguageModelHttp)),
54) {}
55```
56
57`AWS.Bedrock.LanguageModelHttp` is the binding's implementation
58layer. Provide it once at the bottom of the constructor's layer chain — it composes
59the `Converse` and `ConverseStream` bindings, so it is bundled into
60the Lambda and signs requests with the invocation role's credentials.
61
62A model reference may be a foundation-model id
63(`anthropic.claude-sonnet-4-20250514-v1:0`), a cross-region inference
64profile id (`us.amazon.nova-micro-v1:0`), or a full Bedrock ARN.
65Model access is an account entitlement — enable the model in the
66Bedrock console (Model access) before invoking, otherwise calls fail
67with `AccessDeniedException`. Many newer models are only invocable
68through their cross-region inference profile id, not the bare
69foundation-model id.
70
71## Stream text
72
73`streamText` maps Bedrock's `ConverseStream` events onto Effect AI's
74stream parts (`text-start`, `text-delta`, …, `finish`), one part per
75Bedrock event:
76
77```typescript
78import * as Stream from "effect/Stream";
79
80const parts = LanguageModel.streamText({
81 prompt: "Write a haiku about infrastructure.",
82}).pipe(Stream.provide(model));
83
84yield* parts.pipe(
85 Stream.runForEach((part) =>
86 part.type === "text-delta"
87 ? Effect.sync(() => process.stdout.write(part.delta))
88 : Effect.void,
89 ),
90);
91```
92
93The terminal `finish` part carries the mapped stop reason
94(`end_turn` → `stop`, `tool_use` → `tool-calls`, `max_tokens` →
95`length`, …) and real token usage from the stream's `metadata` event,
96including prompt-cache read/write counts.
97
98## Call tools
99
100Effect AI toolkits work unchanged — tool schemas are translated to
101Converse `toolConfig` and Bedrock's structured tool-use blocks are
102decoded back into typed tool calls and results:
103
104```typescript
105import * as Schema from "effect/Schema";
106import { Tool, Toolkit } from "effect/ai";
107
108const GetWeather = Tool.make("get_weather", {
109 description: "Get the current weather for a city.",
110 parameters: Schema.Struct({ city: Schema.String }),
111 success: Schema.Struct({ temperatureF: Schema.Number }),
112});
113const WeatherToolkit = Toolkit.make(GetWeather);
114
115const response = yield* LanguageModel.generateText({
116 prompt: "What's the weather in Seattle?",
117 toolkit: WeatherToolkit,
118}).pipe(
119 Effect.provide(
120 WeatherToolkit.toLayer({
121 get_weather: ({ city }) => Effect.succeed({ temperatureF: 72 }),
122 }),
123 ),
124 Effect.provide(model),
125);
126
127response.toolResults; // typed results, executed by your handlers
128```
129
130## Configure parameters at runtime
131
132Binding is the IAM boundary — *which models* the Function may call
133is fixed at deploy time. Everything else is a runtime decision. The
134`parameters` passed to the binding are only defaults; scope per-call
135overrides onto any call with `AWS.Bedrock.withModelParameters`:
136
137```typescript
138const response = yield* LanguageModel.generateText({ prompt }).pipe(
139 AWS.Bedrock.withModelParameters({ temperature: 0, maxTokens: 64 }),
140);
141```
142
143It works on streams the same way:
144
145```typescript
146const parts = LanguageModel.streamText({ prompt }).pipe(
147 Stream.provide(model),
148 AWS.Bedrock.withModelParameters({ temperature: 0.9 }),
149);
150```
151
152The override is fiber-scoped (a `Context.Reference`), so it applies
153to exactly the calls inside the piped region — concurrent requests
154with different settings never interfere. Defined fields win over the
155binding's defaults; omitted fields fall through.
156
157`parameters` covers the Converse API's portable `inferenceConfig`
158(`maxTokens`, `temperature`, `topP`, `stopSequences`). Anything
159model-specific passes through `additionalModelRequestFields`
160verbatim — e.g. `{ top_k: 50 }` for Anthropic models.
161
162## Multiple models
163
164Bind a list of models to grant IAM for all of them in one layer —
165the first is the default, and `modelId` picks per call:
166
167```typescript
168// Construction: IAM for both models, Nova Micro is the default
169const model = yield* AWS.Bedrock.LanguageModel([
170 "us.amazon.nova-micro-v1:0",
171 "us.anthropic.claude-sonnet-4-20250514-v1:0",
172]);
173
174// runtime: route this request to Claude, tuned for this call
175const response = yield* LanguageModel.generateText({ prompt }).pipe(
176 AWS.Bedrock.withModelParameters({
177 modelId: "us.anthropic.claude-sonnet-4-20250514-v1:0",
178 maxTokens: 2048,
179 }),
180);
181```
182
183Separate `LanguageModel(...)` calls also work — each is its own
184layer with its own grant — when you'd rather select a model by
185providing a different layer per route.
186
187## Why not an OpenAI-compatible endpoint?
188
189Bedrock also exposes an OpenAI-compatible Chat Completions endpoint,
190but it authenticates with long-lived Bedrock API keys and only covers
191a subset of models. The Converse-backed binding uses the Function's
192IAM role (SigV4) with a least-privilege grant scoped to the bound
193model, works with every conversational model on Bedrock, and needs no
194secret material at all.
195
196## Lower-level access
197
198The `LanguageModel` binding is built on the raw Converse bindings,
199which remain available when you want the wire-level API:
200
201- [`AWS.Bedrock.Converse`](/providers/aws/bedrock/converse) — single-shot
202 messages API
203- [`AWS.Bedrock.ConverseStream`](/providers/aws/bedrock/conversestream) —
204 streaming event API
205- [`AWS.Bedrock.InvokeModel`](/providers/aws/bedrock/invokemodel) — raw
206 model-native payloads
207
208## Where to go next
209
210- [Effect AI documentation](https://effect.website/docs/ai/introduction)
211 — the API surface (`generateText`, `streamText`, `Toolkit`,
212 structured outputs).
213- [Lambda](/aws/compute/lambda) — the Function runtime this binding
214 attaches to.
215- [Effect AI on Cloudflare](/cloudflare/ai/effect-ai) — the same
216 `LanguageModel` programs running on Workers AI.