@mozaik-ai/core 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Mozaik
2
2
 
3
- **Mozaik** is TypeScript framework for building reactive agents. It provides the easiest way to build collaborative, **event-driven agents** that can work together in parallel.
3
+ **Mozaik** is a TypeScript framework for building agentic teams. It enables AI agents to work in parallel, stay aware of each other, and collaborate through event-driven communication.
4
4
 
5
5
  ![npm downloads](https://img.shields.io/npm/dt/@mozaik-ai/core) ![npm downloads weekly](https://img.shields.io/npm/dw/@mozaik-ai/core) ![npm version](https://img.shields.io/npm/v/@mozaik-ai/core)
6
6
 
7
- In Mozaik, humans, agents, observers, and tools are all `Participant`s of the same `AgenticEnvironment`. Each participant runs **non-blocking** and produces events into the environment: plain-text **messages** for conversational input/output, and typed `ContextItem`s for model internals (function calls, function call outputs, reasoning, model messages). Every other participant sees those events in real time and can react, intercept, or stay silent — without any central scheduler.
7
+ Instead of forcing agents into sequential workflows, Mozaik makes collaboration a first-class concept — creating the foundation for moving from autonomous agents to autonomous teams of AI agents.
8
8
 
9
9
  ---
10
10
 
@@ -30,11 +30,17 @@ pnpm add @mozaik-ai/core
30
30
 
31
31
  ## API Key Configuration
32
32
 
33
+ Mozaik picks a provider from the model name you pass to `runInference`, and each provider's SDK reads its credential from the environment. Set the keys for the providers you use:
34
+
33
35
  ```env
34
36
  # .env
35
37
  OPENAI_API_KEY=your-openai-key-here
38
+ ANTHROPIC_API_KEY=your-anthropic-key-here
39
+ GEMINI_API_KEY=your-gemini-key-here
36
40
  ```
37
41
 
42
+ DeepSeek models run through the OpenAI-compatible chat-completions endpoint, so they use an OpenAI-style credential and base URL (`OPENAI_API_KEY` / `OPENAI_BASE_URL`) pointed at DeepSeek.
43
+
38
44
  ---
39
45
 
40
46
  ## The agentic environment
@@ -58,14 +64,16 @@ OPENAI_API_KEY=your-openai-key-here
58
64
  | `onExternalModelMessage` | another agent's inference returns an assistant message |
59
65
  | `onInternalEvent` | its own inference emits a semantic stream event |
60
66
  | `onExternalEvent` | another participant emits a semantic stream event |
67
+ | `onError` | one of its own handlers throws |
68
+ | `onParticipantError` | another participant's handler throws |
61
69
 
62
- Every handler defaults to a no-op — override only the ones you care about.
70
+ Every handler defaults to a no-op on `BaseParticipant` — override only the ones you care about.
63
71
 
64
72
  ```mermaid
65
73
  flowchart LR
66
- Human[BaseHuman] -->|sendMessage| Env(("AgenticEnvironment"))
67
- Agent[BaseAgent] -->|"runInference / executeFunctionCall"| Env
68
- Observer[Custom Participant] -->|join| Env
74
+ Human[Participant] -->|"sendMessage(env, text, caller)"| Env(("AgenticEnvironment"))
75
+ Agent[Participant] -->|"runInference / executeFunctionCall"| Env
76
+ Observer[Participant] -->|join| Env
69
77
  Env -->|"onMessage / onExternal*"| Human
70
78
  Env -->|"onFunctionCall / onReasoning / …"| Agent
71
79
  Env -->|"onExternal*"| Observer
@@ -76,52 +84,58 @@ flowchart LR
76
84
 
77
85
  ## Non-blocking participants
78
86
 
79
- Mozaik ships three ready-to-use participants:
87
+ A participant is any subclass of `Participant`. Use `BaseParticipant` as a base when you only want to override a few handlers — every handler it defines is a no-op. The _role_ (human, agent, observer) is just which capability functions a participant calls and which handlers it overrides:
80
88
 
81
- | Participant | Role |
82
- | -------------- | -------------------------------------------------------------------------------- |
83
- | `BaseHuman` | Sends messages with `sendMessage(environment, text)` |
84
- | `BaseAgent` | Runs inference and function calls via `InferenceRunner` and `FunctionCallRunner` |
85
- | `BaseObserver` | Listens only; no inference |
89
+ | Role | How to build it |
90
+ | -------- | --------------------------------------------------------------------------- |
91
+ | Human | A participant that calls `sendMessage(environment, text, caller)` |
92
+ | Agent | A participant that calls `runInference(...)` and `executeFunctionCall(...)` |
93
+ | Observer | A participant that only overrides handlers and never runs inference |
86
94
 
87
95
  ```ts
88
96
  import {
89
97
  AgenticEnvironment,
90
- BaseAgent,
91
- BaseHuman,
92
- OpenAIInferenceRunner,
93
- DefaultFunctionCallRunner,
94
- Gpt54Mini,
98
+ BaseParticipant,
95
99
  ModelContext,
100
+ UserMessageItem,
101
+ runInference,
102
+ sendMessage,
96
103
  } from "@mozaik-ai/core"
97
104
 
98
105
  const environment = new AgenticEnvironment()
99
106
 
100
- const human = new BaseHuman()
101
- const agent = new BaseAgent(new OpenAIInferenceRunner(), new DefaultFunctionCallRunner())
102
- const observer = new BaseObserver()
107
+ const human = new BaseParticipant()
108
+
109
+ class Agent extends BaseParticipant {
110
+ private readonly context = ModelContext.create("demo")
111
+
112
+ async onMessage(message: string): Promise<void> {
113
+ this.context.addContextItem(UserMessageItem.create(message))
114
+ runInference({ model: "gpt-5.4", context: this.context, caller: this, environment })
115
+ }
116
+ }
117
+
118
+ const agent = new Agent()
119
+ const observer = new BaseParticipant()
103
120
 
104
121
  human.join(environment)
105
122
  agent.join(environment)
106
123
  observer.join(environment)
107
124
 
108
- environment.start()
109
-
110
- human.sendMessage(environment, "Hello")
125
+ sendMessage(environment, "Hello", human)
111
126
  ```
112
127
 
113
- The environment fans every item out to every subscriber synchronously and without awaiting them, so a slow listener never blocks producers or other listeners.
128
+ Participants react as soon as they `join()` agentic environment. The environment fans every item out to every subscriber synchronously and without awaiting them, so a slow listener never blocks producers or other listeners.
114
129
 
115
130
  ---
116
131
 
117
132
  ## Reactive agent
118
133
 
119
- A reactive agent extends `BaseAgent` and overrides the handlers it wants to react on. Each handler is already a no-op in the base class, so only the relevant ones need bodies:
134
+ A reactive agent extends `BaseParticipant` and overrides the handlers it wants to react on. Each handler is already a no-op in the base class, so only the relevant ones need bodies. Capabilities are the free functions `runInference` and `executeFunctionCall` — the participant passes itself as `caller`:
120
135
 
121
136
  ```ts
122
137
  import {
123
- BaseAgent,
124
- Participant,
138
+ BaseParticipant,
125
139
  UserMessageItem,
126
140
  FunctionCallItem,
127
141
  FunctionCallOutputItem,
@@ -129,38 +143,50 @@ import {
129
143
  ModelMessageItem,
130
144
  AgenticEnvironment,
131
145
  ModelContext,
132
- GenerativeModel,
133
- InferenceRunner,
134
- FunctionCallRunner,
146
+ ModelName,
147
+ Tool,
148
+ runInference,
149
+ executeFunctionCall,
135
150
  } from "@mozaik-ai/core"
136
151
 
137
- export class ReactiveAgent extends BaseAgent {
152
+ export class ReactiveAgent extends BaseParticipant {
138
153
  constructor(
139
- inferenceRunner: InferenceRunner,
140
- functionCallRunner: FunctionCallRunner,
141
154
  private readonly environment: AgenticEnvironment,
142
155
  private readonly context: ModelContext,
143
- private readonly model: GenerativeModel,
156
+ private readonly tools: Tool[] = [],
144
157
  ) {
145
- super(inferenceRunner, functionCallRunner)
158
+ super()
146
159
  }
147
160
 
148
161
  // A message from a human (or any other participant) → record it and think.
149
162
  async onMessage(message: string): Promise<void> {
150
163
  this.context.addContextItem(UserMessageItem.create(message))
151
- this.runInference(this.environment, this.context, this.model)
164
+ runInference({
165
+ model: 'gpt-5.5',
166
+ context: this.context,
167
+ tools: this.tools,
168
+ caller: this,
169
+ environment: this.environment,
170
+ })
152
171
  }
153
172
 
154
173
  // The agent just produced a function call → execute it.
155
174
  async onFunctionCall(item: FunctionCallItem): Promise<void> {
156
175
  this.context.addContextItem(item)
157
- this.executeFunctionCall(this.environment, item)
176
+ const tool = this.tools.find((t) => t.name === item.name)
177
+ if (tool) executeFunctionCall(this.environment, item, tool, this)
158
178
  }
159
179
 
160
180
  // The tool just produced an output → feed it back and run inference again.
161
181
  async onFunctionCallOutput(item: FunctionCallOutputItem): Promise<void> {
162
182
  this.context.addContextItem(item)
163
- this.runInference(this.environment, this.context, this.model)
183
+ runInference({
184
+ model: 'gpt-5.5',
185
+ context: this.context,
186
+ tools: this.tools,
187
+ caller: this,
188
+ environment: this.environment,
189
+ })
164
190
  }
165
191
 
166
192
  // Keep the local context in sync with model-emitted reasoning and replies.
@@ -177,22 +203,22 @@ export class ReactiveAgent extends BaseAgent {
177
203
  Three things to note:
178
204
 
179
205
  1. The split between self handlers and `onExternal*` handlers means a participant can encode "act on my own outputs" separately from "observe others", without inspecting `source` by hand.
180
- 2. The agent never `await`s its own capability calls inside the handlers — those methods are non-blocking, so the environment keeps delivering events while inference and tool execution run in the background.
206
+ 2. The agent never `await`s its capability calls inside the handlers — `runInference` and `executeFunctionCall` are fire-and-forget (they return `void`), so the environment keeps delivering events while inference and tool execution run in the background.
181
207
  3. Behaviors compose by **reaction**, not orchestration. Add a second agent that overrides `onExternalModelMessage` and you get a critique loop. Add a `TranscriptLogger` and you get a UI stream. Neither change touches the existing participants.
182
208
 
183
209
  ---
184
210
 
185
211
  ## Streaming and semantic events
186
212
 
187
- When inference runs with streaming enabled (`model.setStreaming(true)` on a model that supports it), the runner does not wait for the full response. As the provider emits chunks, `OpenAIInferenceRunner` yields **`SemanticEvent`** items (`type` + `data`) and the environment delivers each one to **every** joined participant immediately — the same fan-out as messages and context items. Participants react in real time by overriding the stream handlers; no participant needs to poll or share a callback.
213
+ When inference runs with streaming enabled (`streaming: true` on the `runInference` params, for a model that supports it), the runner does not wait for the full response. As the provider emits chunks, the endpoint yields **`SemanticEvent`** items (`type` + `data`) and the environment delivers each one to **every** joined participant immediately — the same fan-out as messages and context items. Participants react in real time by overriding the stream handlers; no participant needs to poll or share a callback.
188
214
 
189
215
  The producing participant receives `onInternalEvent`; everyone else receives `onExternalEvent(source, event)`:
190
216
 
191
217
  ```ts
192
- import { BaseAgent, Participant, SemanticEvent } from "@mozaik-ai/core"
218
+ import { BaseParticipant, Participant, SemanticEvent } from "@mozaik-ai/core"
193
219
 
194
220
  // Agent that runs streaming inference — can observe its own stream chunks.
195
- export class StreamingAgent extends BaseAgent {
221
+ export class StreamingAgent extends BaseParticipant {
196
222
  async onInternalEvent(event: SemanticEvent<unknown>): Promise<void> {
197
223
  if (event.type === "response.output_text.delta") {
198
224
  // e.g. keep a local buffer of partial output
@@ -201,44 +227,52 @@ export class StreamingAgent extends BaseAgent {
201
227
  }
202
228
 
203
229
  // Any other participant — UI, logger, second agent — reacts to another's stream.
204
- export class LiveTranscript extends Participant {
230
+ export class LiveTranscript extends BaseParticipant {
205
231
  async onExternalEvent(source: Participant, event: SemanticEvent<unknown>): Promise<void> {
206
232
  if (event.type === "response.output_text.delta") {
207
233
  const { delta } = event.data as { delta: string }
208
234
  process.stdout.write(delta)
209
235
  }
210
236
  }
211
-
212
- // Self-emitted stream events are unused for a pure observer.
213
- async onInternalEvent(): Promise<void> {}
214
237
  }
215
238
  ```
216
239
 
217
- Enable streaming on the model before calling `runInference` as usual. `setStreaming(true)` on a model without `supportStreaming` throws before the API is called.
240
+ Enable streaming by passing `streaming: true` to `runInference`:
241
+
242
+ ```ts
243
+ runInference({ model: "gpt-5.4", context, caller: this, environment, streaming: true })
244
+ ```
245
+
246
+ Requesting streaming for a model whose specification has `supportsStreaming: false` fails request validation before the API is called.
218
247
 
219
248
  ---
220
249
 
221
250
  ## Structured output
222
251
 
223
- When you need the model to respond with a specific JSON shape instead of free-form text, use structured output. Set a JSON Schema on the model and the provider will enforce it:
252
+ When you need the model to respond with a specific JSON shape instead of free-form text, use structured output. Pass a `structuredOutput` (a `StructuredOutputFormat`) on the `runInference` params and the provider will enforce the JSON Schema:
224
253
 
225
254
  ```ts
226
- import { Gpt54, ModelContext, UserMessageItem } from "@mozaik-ai/core"
227
-
228
- const model = new Gpt54()
229
- model.setStructuredOutput({
230
- name: "weather",
231
- schema: {
232
- type: "object",
233
- properties: {
234
- city: { type: "string" },
235
- temperature: { type: "number" },
236
- condition: { type: "string" },
255
+ import { runInference } from "@mozaik-ai/core"
256
+
257
+ runInference({
258
+ model: "gpt-5.4",
259
+ context,
260
+ caller: this,
261
+ environment,
262
+ structuredOutput: {
263
+ name: "weather",
264
+ schema: {
265
+ type: "object",
266
+ properties: {
267
+ city: { type: "string" },
268
+ temperature: { type: "number" },
269
+ condition: { type: "string" },
270
+ },
271
+ required: ["city", "temperature", "condition"],
272
+ additionalProperties: false,
237
273
  },
238
- required: ["city", "temperature", "condition"],
239
- additionalProperties: false,
274
+ strict: true,
240
275
  },
241
- strict: true,
242
276
  })
243
277
  ```
244
278
 
@@ -246,22 +280,18 @@ The response comes back as a `ModelMessageItem` with valid JSON in the text fiel
246
280
 
247
281
  Structured output works alongside tools and streaming. When streaming is enabled, partial JSON chunks arrive as `SemanticEvent`s and the final event contains the complete response.
248
282
 
249
- To clear structured output and return to free-form text:
250
-
251
- ```ts
252
- model.setStructuredOutput(undefined)
253
- ```
283
+ To return to free-form text, simply omit `structuredOutput` on the next `runInference` call.
254
284
 
255
285
  ### Provider support
256
286
 
257
- | Provider | Models | Strict schema enforcement |
258
- | -------- | ------ | ------------------------- |
259
- | OpenAI | gpt-5.4, gpt-5.4-mini, gpt-5.4-nano, gpt-5.5 | Yes |
260
- | Anthropic | claude-opus-4-7, claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5 | Yes |
261
- | Gemini | gemini-3.1-pro, gemini-3.5-flash | Yes |
262
- | DeepSeek | deepseek-v4-flash, deepseek-v4-pro | Not supported — use prompt-based JSON guidance instead |
287
+ | Provider | Models | Strict schema enforcement |
288
+ | --------- | --------------------------------------------------------------------- | ------------------------------------------------------ |
289
+ | OpenAI | gpt-5.4, gpt-5.4-mini, gpt-5.4-nano, gpt-5.5 | Yes |
290
+ | Anthropic | claude-opus-4-7, claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5 | Yes |
291
+ | Gemini | gemini-3.1-pro-preview, gemini-3.5-flash | Yes |
292
+ | DeepSeek | deepseek-v4-flash, deepseek-v4-pro | Not supported — use prompt-based JSON guidance instead |
263
293
 
264
- Setting structured output on a model that does not support it throws before the API call.
294
+ Requesting structured output for a model whose specification has `supportsStructuredOutput: false` fails request validation before the API call.
265
295
 
266
296
  ---
267
297
 
@@ -270,24 +300,24 @@ Setting structured output on a model that does not support it throws before the
270
300
  Every participant receives lifecycle notifications when it or others join/leave an environment:
271
301
 
272
302
  ```ts
273
- export class TeamAgent extends BaseAgent {
303
+ export class TeamAgent extends BaseParticipant {
274
304
  // Called when this participant joins an environment.
275
- onJoined(environment: AgenticEnvironment): void {
305
+ onJoined(): void {
276
306
  console.log("I joined the environment")
277
307
  }
278
308
 
279
309
  // Called when this participant leaves an environment.
280
- onLeft(environment: AgenticEnvironment): void {
310
+ onLeft(): void {
281
311
  console.log("I left the environment")
282
312
  }
283
313
 
284
314
  // Called when another participant joins the same environment.
285
- onParticipantJoined(participant: Participant, environment: AgenticEnvironment): void {
315
+ onParticipantJoined(participant: Participant): void {
286
316
  console.log(`${participant.constructor.name} joined`)
287
317
  }
288
318
 
289
319
  // Called when another participant leaves the same environment.
290
- onParticipantLeft(participant: Participant, environment: AgenticEnvironment): void {
320
+ onParticipantLeft(participant: Participant): void {
291
321
  console.log(`${participant.constructor.name} left`)
292
322
  }
293
323
  }
@@ -301,39 +331,77 @@ This lets participants react to membership changes — for example, an agent cou
301
331
 
302
332
  Participants can listen to external events and react by overriding methods like `onMessage`, `onExternalFunctionCall`, `onExternalFunctionCallOutput`, `onExternalReasoning`, and `onExternalModelMessage`.
303
333
 
334
+ ### Selective listening
335
+
336
+ By default a participant reacts to events from every other participant. To scope a participant so it only reacts to specific participant _types_, populate its `listens` list with those classes. When `listens` is non-empty, the environment only delivers external events whose source is an instance of one of the listed classes:
337
+
338
+ ```ts
339
+ import { BaseParticipant } from "@mozaik-ai/core"
340
+
341
+ export class Critic extends BaseParticipant {
342
+ // Only react to events produced by Writer participants.
343
+ protected listens = [Writer]
344
+ }
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Error handling
350
+
351
+ When any handler throws, the environment catches it and routes it as an `AgenticError` instead of crashing the run. The participant whose handler threw receives `onError(error)`; every other participant receives `onParticipantError(source, error)`. After its own `onError`, the failing participant is marked inactive in that environment so it stops receiving further events.
352
+
353
+ ```ts
354
+ import { BaseParticipant, Participant, AgenticError } from "@mozaik-ai/core"
355
+
356
+ export class ResilientAgent extends BaseParticipant {
357
+ onError(error: AgenticError): void {
358
+ console.error("my handler threw:", error.message)
359
+ }
360
+
361
+ onParticipantError(source: Participant, error: AgenticError): void {
362
+ console.warn(`${source.constructor.name} failed:`, error.message)
363
+ }
364
+ }
365
+ ```
366
+
367
+ `AgenticError` carries the originating participant (`getSource()`) and environment (`getEnvironment()`).
368
+
369
+ ---
370
+
304
371
  ## Passive observer
305
372
 
306
- You can create observers that don't run inference themselves but watch what's happening in the conversation and take side actions (logging, metrics, persistence, etc.). Subclass `Participant` and override only the handlers you care about:
373
+ You can create observers that don't run inference themselves but watch what's happening in the conversation and take side actions (logging, metrics, persistence, etc.). Subclass `BaseParticipant` and override only the handlers you care about — everything else stays a no-op:
307
374
 
308
375
  ```ts
309
- import { Participant, FunctionCallItem, FunctionCallOutputItem, ReasoningItem, ModelMessageItem } from "@mozaik-ai/core"
376
+ import {
377
+ BaseParticipant,
378
+ Participant,
379
+ FunctionCallItem,
380
+ FunctionCallOutputItem,
381
+ ReasoningItem,
382
+ ModelMessageItem,
383
+ } from "@mozaik-ai/core"
310
384
 
311
- export class TranscriptLogger extends Participant {
385
+ export class TranscriptLogger extends BaseParticipant {
312
386
  async onMessage(message: string): Promise<void> {
313
387
  console.log("[message]", message)
314
388
  }
315
389
 
316
390
  async onExternalFunctionCall(source: Participant, item: FunctionCallItem): Promise<void> {
317
- console.log(`[${source.constructor.name}] function_call`, item.toJSON())
391
+ console.log(`[${source.constructor.name}] function_call`, item)
318
392
  }
319
393
 
320
394
  async onExternalFunctionCallOutput(source: Participant, item: FunctionCallOutputItem): Promise<void> {
321
- console.log(`[${source.constructor.name}] function_call_output`, item.toJSON())
395
+ console.log(`[${source.constructor.name}] function_call_output`, item)
322
396
  }
323
397
 
324
398
  async onExternalReasoning(source: Participant, item: ReasoningItem): Promise<void> {
325
- console.log(`[${source.constructor.name}] reasoning`, item.toJSON())
399
+ console.log(`[${source.constructor.name}] reasoning`, item)
326
400
  }
327
401
 
328
402
  async onExternalModelMessage(source: Participant, item: ModelMessageItem): Promise<void> {
329
- console.log(`[${source.constructor.name}] model_message`, item.toJSON())
403
+ console.log(`[${source.constructor.name}] model_message`, item)
330
404
  }
331
-
332
- // Self-emitted handlers (onFunctionCall, onReasoning, …) can be no-ops for a pure observer.
333
- async onFunctionCall(): Promise<void> {}
334
- async onFunctionCallOutput(): Promise<void> {}
335
- async onReasoning(): Promise<void> {}
336
- async onModelMessage(): Promise<void> {}
337
405
  }
338
406
  ```
339
407
 
@@ -341,7 +409,7 @@ export class TranscriptLogger extends Participant {
341
409
 
342
410
  ## Context and models (reference)
343
411
 
344
- `ModelContext` is the ordered list of `ContextItem`s a `GenerativeModel` is asked to reason over. It is constructed and mutated explicitly — typically inside a participant in response to delivered items.
412
+ `ModelContext` is the ordered list of `ContextItem`s a model is asked to reason over. It is constructed and mutated explicitly — typically inside a participant in response to delivered items.
345
413
 
346
414
  ```ts
347
415
  import { ModelContext, DeveloperMessageItem, UserMessageItem, InMemoryModelContextRepository } from "@mozaik-ai/core"
@@ -356,55 +424,47 @@ await repo.save(context)
356
424
 
357
425
  Implement `ModelContextRepository` to plug in any storage backend.
358
426
 
359
- The default inference path is `OpenAIInferenceRunner`, which maps `ModelContext` to the OpenAI Responses API and back into typed `ContextItem`s (and `SemanticEvent`s when streaming). Bundled models: `Gpt54`, `Gpt54Mini`, `Gpt54Nano`, `Gpt55`.
427
+ A model is selected by its `ModelName` string. Mozaik resolves the name to a provider `Endpoint` and a `ModelSpecification` internally, maps the `ModelContext` to that provider's API, and returns typed `ContextItem`s (and `SemanticEvent`s when streaming). Bundled model names:
428
+
429
+ | Provider | `ModelName` values |
430
+ | --------- | ------------------------------------------------------------------------------------- |
431
+ | OpenAI | `"gpt-5.4"`, `"gpt-5.4-mini"`, `"gpt-5.4-nano"`, `"gpt-5.5"` |
432
+ | Anthropic | `"claude-haiku-4-5"`, `"claude-sonnet-4-6"`, `"claude-opus-4-7"`, `"claude-opus-4-8"` |
433
+ | Gemini | `"gemini-3.5-flash"`, `"gemini-3.1-pro-preview"` |
434
+ | DeepSeek | `"deepseek-v4-flash"`, `"deepseek-v4-pro"` |
435
+
436
+ You drive inference with the `runInference` capability; it streams the resulting items into the environment for participants to react to:
360
437
 
361
438
  ```ts
362
- import { OpenAIInferenceRunner, DefaultFunctionCallRunner, Gpt54, ModelContext } from "@mozaik-ai/core"
439
+ import { runInference, ModelContext } from "@mozaik-ai/core"
363
440
 
364
- const runner = new OpenAIInferenceRunner()
365
441
  const context = ModelContext.create("demo")
366
442
 
367
- for await (const item of runner.run(context, new Gpt54())) {
368
- // ReasoningItem | FunctionCallItem | ModelMessageItem | SemanticEvent
369
- }
443
+ runInference({ model: "gpt-5.4", context, caller: this, environment })
444
+ // → environment delivers ReasoningItem | FunctionCallItem | ModelMessageItem (and SemanticEvent when streaming)
370
445
  ```
371
446
 
372
447
  ---
373
448
 
374
- ## Overriding Generators
375
-
376
- Mozaik uses async generators for inference and function calls — that's what lets the system emit events incrementally so participants can react to them as they arrive. Swap any runner to change _how_ events are produced.
377
-
378
- Humans send text with `sendMessage(environment, message)`; other participants receive it via `onMessage`.
379
-
380
- ### Custom `InferenceRunner`
381
-
382
- An `InferenceRunner` can yield `ReasoningItem`, `FunctionCallItem`, `ModelMessageItem`, and `SemanticEvent` (when streaming).
383
-
384
- ```ts
385
- import { InferenceRunner, ModelContext, GenerativeModel, OpenAIInferenceRunner } from "@mozaik-ai/core"
386
-
387
- // Use the bundled runner, or implement InferenceRunner for another provider.
388
- const runner: InferenceRunner = new OpenAIInferenceRunner()
389
- ```
390
-
391
- ### Custom `FunctionCallRunner`
449
+ ### Tools
392
450
 
393
- A `FunctionCallRunner` can only produce `FunctionCallOutputItem`.
451
+ A `Tool` is a function declaration with its own executor: `name`, `description`, JSON Schema `parameters`, `strict`, and an `invoke(args)` that runs the call. Pass tools on the `runInference` params; when the model emits a `FunctionCallItem`, run it with `executeFunctionCall`, which calls the matching tool's `invoke` and emits a `FunctionCallOutputItem`.
394
452
 
395
453
  ```ts
396
- import { FunctionCallRunner, FunctionCallItem, FunctionCallOutputItem, Tool } from "@mozaik-ai/core"
397
-
398
- export class ToolRegistryFunctionCallRunner implements FunctionCallRunner {
399
- constructor(private readonly tools: Tool[]) {}
454
+ import { Tool } from "@mozaik-ai/core"
400
455
 
401
- async *run(call: FunctionCallItem, signal?: AbortSignal): AsyncIterable<FunctionCallOutputItem> {
402
- const tool = this.tools.find((t) => t.name === call.name)
403
- if (!tool) throw new Error(`Unknown tool: ${call.name}`)
404
-
405
- const result = await tool.invoke(JSON.parse(call.args))
406
- yield FunctionCallOutputItem.create(call.callId, JSON.stringify(result))
407
- }
456
+ const getWeather: Tool = {
457
+ type: "function",
458
+ name: "get_weather",
459
+ description: "Get the current weather for a city",
460
+ parameters: {
461
+ type: "object",
462
+ properties: { city: { type: "string" } },
463
+ required: ["city"],
464
+ additionalProperties: false,
465
+ },
466
+ strict: true,
467
+ invoke: async ({ city }: { city: string }) => ({ city, temperature: 21, condition: "sunny" }),
408
468
  }
409
469
  ```
410
470