@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 +189 -129
- package/dist/index.d.mts +154 -579
- package/dist/index.d.ts +154 -579
- package/dist/index.js +1057 -1207
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1055 -1190
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -5
- package/tests/README.md +0 -31
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Mozaik
|
|
2
2
|
|
|
3
|
-
**Mozaik** is TypeScript framework for building
|
|
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
|
  
|
|
6
6
|
|
|
7
|
-
|
|
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[
|
|
67
|
-
Agent[
|
|
68
|
-
Observer[
|
|
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
|
-
|
|
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
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
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
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
ModelName,
|
|
147
|
+
Tool,
|
|
148
|
+
runInference,
|
|
149
|
+
executeFunctionCall,
|
|
135
150
|
} from "@mozaik-ai/core"
|
|
136
151
|
|
|
137
|
-
export class ReactiveAgent extends
|
|
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
|
|
156
|
+
private readonly tools: Tool[] = [],
|
|
144
157
|
) {
|
|
145
|
-
super(
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 (`
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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 {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
model.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
258
|
-
|
|
|
259
|
-
| OpenAI
|
|
260
|
-
| Anthropic | claude-opus-4-7, claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5 | Yes
|
|
261
|
-
| Gemini
|
|
262
|
-
| DeepSeek
|
|
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
|
-
|
|
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
|
|
303
|
+
export class TeamAgent extends BaseParticipant {
|
|
274
304
|
// Called when this participant joins an environment.
|
|
275
|
-
onJoined(
|
|
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(
|
|
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
|
|
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
|
|
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 `
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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 `
|
|
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 {
|
|
397
|
-
|
|
398
|
-
export class ToolRegistryFunctionCallRunner implements FunctionCallRunner {
|
|
399
|
-
constructor(private readonly tools: Tool[]) {}
|
|
454
|
+
import { Tool } from "@mozaik-ai/core"
|
|
400
455
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|