Basic usage
Recipe: Basic usage
Scenario. Ask the model “What time is it in Lisbon and Tokyo right
now?”, let it call a get_current_time tool, run the tool, and feed the
output back so the model can produce a final answer. Deltas stream as they
arrive.
This is the smallest end-to-end shape an agent built on effect-uai takes:
state is a plain record, the body is a Stream, and the loop continues
until the model stops asking for tools.
What it shows
- Defining a tool with
Tool.makeand an EffectSchemafor its input. - Building a
Toolkitand projecting it to provider-shaped descriptors withToolkit.toDescriptors. - Driving a turn with
Responses.streamTurnand piping the raw delta stream throughLoop.streamUntilCompleteto forward deltas to the consumer and decide what to do once the turn lands. - Continuing the loop with
Loop.nextAfterafter running tools, or ending it withLoop.stopwhen the model produced its final message. - The downstream consumer sees the natural protocol shapes
(
Turn.TurnDelta,Items.FunctionCallOutput) and pattern-matches on the existingtypediscriminator. No recipe-defined event taxonomy.
The loop, in shape
pipe( initial, loop((state) => Effect.gen(function* () { const oai = yield* Responses
return oai.streamTurn(state.history, { tools }).pipe( streamUntilComplete((turn) => Effect.gen(function* () { const next = Turn.cursor(state, turn) const calls = Turn.functionCalls(turn) if (calls.length === 0) return stop
const outputs = yield* Toolkit.executeAllSafe(toolkit, calls) return nextAfter(Stream.fromIterable(outputs), { ...next, history: [...next.history, ...outputs], index: state.index + 1, }) }), ), ) }), ),)Turn.cursor(state, turn) extends state.history with turn.items and
stamps the turn. Loop.stop is a one-element stream that ends the loop;
Loop.nextAfter(s, state) emits the values in s then advances with
the new state.
If the upstream ends without a turn_complete, the resulting stream
fails with AiError.IncompleteTurn - catch it via Stream.catchTag
if you want to recover.
Run it
OPENAI_API_KEY=sk-... pnpm tsx recipes/basic-usage/index.tsThe full source lives next to this README at
index.ts.