llmz 0.9.0 → 1.0.2
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/DOCS.md +232 -1794
- package/ERRORS.md +101 -0
- package/README.md +96 -582
- package/dist/chat/chat.d.ts +63 -0
- package/dist/chat/component.d.ts +31 -0
- package/dist/chat/component.default.d.ts +58 -0
- package/dist/chat/response.d.ts +15 -0
- package/dist/compiler/compiler.d.ts +2 -7
- package/dist/compiler/plugins/dynamic-code.d.ts +7 -0
- package/dist/compiler/plugins/replace-comment.d.ts +2 -3
- package/dist/compiler/plugins/return-async.d.ts +5 -4
- package/dist/compiler/plugins/termination.d.ts +8 -0
- package/dist/compiler/plugins/tool-resolution.d.ts +4 -0
- package/dist/compiler/plugins/variable-extraction.d.ts +2 -8
- package/dist/component-CMFoCHyu.cjs +649 -0
- package/dist/component-DncqP4fB.js +614 -0
- package/dist/context.d.ts +71 -378
- package/dist/custom-client-C-WXvLD9.js +1655 -0
- package/dist/custom-client-Cgt9_0r1.cjs +1732 -0
- package/dist/custom-client.d.ts +12 -2
- package/dist/errors/catalog.d.ts +294 -0
- package/dist/errors/hooks.d.ts +2 -0
- package/dist/errors-C6ppQUZX.cjs +893 -0
- package/dist/errors-Crk5P8jo.js +636 -0
- package/dist/errors.d.ts +24 -36
- package/dist/execute-CiYvPs5Q.js +2061 -0
- package/dist/execute-DTTfacsi.cjs +2062 -0
- package/dist/{exit-DUM96DN1.cjs → exit-BOtKWPuT.cjs} +38 -28
- package/dist/{exit-CDdxDBRT.js → exit-DMPwCSba.js} +37 -27
- package/dist/exit.d.ts +19 -16
- package/dist/index.cjs +177 -1237
- package/dist/index.d.ts +25 -28
- package/dist/index.js +137 -1213
- package/dist/index.workerd.js +8974 -11145
- package/dist/inspect.d.ts +18 -4
- package/dist/inspection.d.ts +40 -0
- package/dist/native-0bq5ATZM.cjs +212 -0
- package/dist/native-n_718Z-T.js +177 -0
- package/dist/objects.d.ts +2 -57
- package/dist/prompts/native.d.ts +2 -0
- package/dist/prompts/prompt.d.ts +10 -101
- package/dist/quickjs-variant-21IO97PF.cjs +1080 -0
- package/dist/quickjs-variant-CtuGagsb.js +985 -0
- package/dist/result.d.ts +30 -861
- package/dist/runtime/execute.d.ts +3 -2
- package/dist/runtime/execution-activity.d.ts +11 -0
- package/dist/runtime/execution-diagnostics.d.ts +11 -0
- package/dist/runtime/execution-report.d.ts +51 -0
- package/dist/runtime/fixtures/chat.d.ts +8 -0
- package/dist/runtime/fixtures/long-search.d.ts +1 -1
- package/dist/runtime/fixtures/native-client.d.ts +33 -0
- package/dist/runtime/fixtures/protocol-languages.d.ts +1 -1
- package/dist/runtime/forced-inspection.d.ts +13 -0
- package/dist/runtime/forced-inspection.test.d.ts +1 -0
- package/dist/runtime/generate.d.ts +23 -20
- package/dist/runtime/generate.test.d.ts +1 -0
- package/dist/runtime/hook-provenance.test.d.ts +1 -0
- package/dist/runtime/inspection-hook.test.d.ts +1 -0
- package/dist/runtime/inspection-values.d.ts +14 -0
- package/dist/runtime/inspection-values.test.d.ts +1 -0
- package/dist/runtime/interruption-control-flow.test.d.ts +1 -0
- package/dist/runtime/interruption-settings.test.d.ts +1 -0
- package/dist/runtime/javascript-api.d.ts +62 -0
- package/dist/runtime/javascript-api.test.d.ts +1 -0
- package/dist/runtime/native-review.test.d.ts +1 -0
- package/dist/runtime/native-tools.d.ts +18 -0
- package/dist/runtime/native-tools.test.d.ts +1 -0
- package/dist/runtime/object-memory.test.d.ts +1 -0
- package/dist/runtime/single-tool.test.d.ts +1 -0
- package/dist/runtime/think-signal.test.d.ts +1 -0
- package/dist/runtime/token-budget.d.ts +22 -0
- package/dist/runtime/token-budget.test.d.ts +1 -0
- package/dist/runtime/tool-wrapper.d.ts +6 -5
- package/dist/runtime/traces.test.d.ts +1 -0
- package/dist/runtime/truncation.test.d.ts +1 -0
- package/dist/runtime/types.d.ts +47 -31
- package/dist/runtime/variable-recovery.test.d.ts +1 -0
- package/dist/runtime/vm-context.d.ts +6 -1
- package/dist/runtime/worker-completion.test.d.ts +1 -0
- package/dist/runtime-names.d.ts +4 -0
- package/dist/schema-CXlnbb3n.cjs +68 -0
- package/dist/schema-GE4ISPCR.js +45 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.test.d.ts +1 -0
- package/dist/session/compactor.d.ts +51 -0
- package/dist/session/compactor.test.d.ts +1 -0
- package/dist/session/history.d.ts +36 -0
- package/dist/session/history.test.d.ts +1 -0
- package/dist/session/json.d.ts +3 -0
- package/dist/session/json.test.d.ts +1 -0
- package/dist/session/memory-codec.d.ts +9 -0
- package/dist/session/memory-codec.test.d.ts +1 -0
- package/dist/session/memory-render.d.ts +15 -0
- package/dist/session/memory-render.test.d.ts +1 -0
- package/dist/session/memory.d.ts +112 -0
- package/dist/session/memory.test.d.ts +1 -0
- package/dist/session/messages.d.ts +19 -0
- package/dist/session/messages.test.d.ts +1 -0
- package/dist/session/serialization.d.ts +36 -0
- package/dist/session/serialization.test.d.ts +1 -0
- package/dist/session/session-memory.test.d.ts +1 -0
- package/dist/session/session.d.ts +121 -0
- package/dist/session/session.test.d.ts +1 -0
- package/dist/session/speaker.test.d.ts +1 -0
- package/dist/{transcript.d.ts → session/transcript.d.ts} +4 -8
- package/dist/session/transcript.test.d.ts +1 -0
- package/dist/{tool-Bv8N4IPj.js → tool-DstssiAs.js} +144 -92
- package/dist/{tool-Dy6AJpJH.cjs → tool-_wK9hQOb.cjs} +146 -94
- package/dist/tool.d.ts +25 -17
- package/dist/truncate-BEKbdo3t.js +79 -0
- package/dist/truncate-uomAMGPs.cjs +102 -0
- package/dist/truncate.d.ts +24 -0
- package/dist/truncate.test.d.ts +1 -0
- package/dist/types.d.ts +33 -9
- package/dist/{typings-3lsqCbkn.cjs → typings-BA_XxAcY.cjs} +38 -193
- package/dist/{typings-CrBa7Tyy.js → typings-BEVDvtuk.js} +38 -175
- package/dist/typings-formatter.d.ts +1 -1
- package/dist/typings.d.ts +3 -0
- package/dist/{uniq-BYQ7GWwb.js → uniq-DfUeSNaX.js} +2 -2
- package/dist/{uniq-G872RWmE.cjs → uniq-DiXgysNV.cjs} +6 -18
- package/dist/{_baseIteratee-P_firB7b.js → utils-BOFzb8Ok.js} +1134 -16
- package/dist/{_baseIteratee-CF4qP2a8.cjs → utils-CTlsXvdj.cjs} +1281 -84
- package/dist/vm/drivers/node.d.ts +1 -1
- package/dist/vm/drivers/quickjs.d.ts +1 -1
- package/dist/vm/errors.d.ts +4 -4
- package/dist/vm/index.d.ts +3 -4
- package/dist/vm/instrument.d.ts +14 -6
- package/dist/vm/member-proxy.d.ts +4 -0
- package/dist/vm/member-proxy.test.d.ts +1 -0
- package/dist/vm/memory.test.d.ts +1 -0
- package/dist/vm/return-semantics.test.d.ts +1 -0
- package/dist/vm/types.d.ts +20 -3
- package/dist/{vm-BLsHw1da.js → vm-C7SfutHn.js} +1187 -180
- package/dist/{vm-D6s1zF6M.cjs → vm-gmekHc0i.cjs} +1221 -207
- package/package.json +13 -6
- package/dist/_baseForOwn-BfWDTL8L.cjs +0 -53
- package/dist/_baseForOwn-CFhWe_W6.js +0 -48
- package/dist/_getPrototype-CG7Dxrrz.js +0 -6
- package/dist/_getPrototype-DQDlNVVM.cjs +0 -10
- package/dist/_overArg-C5Hy7oCi.cjs +0 -196
- package/dist/_overArg-CRYbj1NH.js +0 -155
- package/dist/chat.d.ts +0 -401
- package/dist/common-BlRFtdf2.js +0 -807
- package/dist/common-BnvrDnIT.cjs +0 -860
- package/dist/compiler/plugins/track-tool-calls.d.ts +0 -33
- package/dist/compiler-C-OI9sim.cjs +0 -547
- package/dist/compiler-CW1VjQsn.js +0 -473
- package/dist/component-C6Agl2ns.cjs +0 -73
- package/dist/component-DGG7d_Mz.js +0 -44
- package/dist/component.d.ts +0 -88
- package/dist/component.default.d.ts +0 -278
- package/dist/custom-client-ChuZ63zp.js +0 -2658
- package/dist/custom-client-DAsGRnYc.cjs +0 -2729
- package/dist/dual-modes-DDkoCp3R.js +0 -3006
- package/dist/dual-modes-DPXiuvNe.cjs +0 -3090
- package/dist/errors-C962uJdS.cjs +0 -220
- package/dist/errors-Cg1G0Awu.js +0 -161
- package/dist/example-format.d.ts +0 -8
- package/dist/example.d.ts +0 -45
- package/dist/execute-BWcdv9so.js +0 -1059
- package/dist/execute-DM2-7y86.cjs +0 -1060
- package/dist/exit-parser.d.ts +0 -39
- package/dist/handlers.d.ts +0 -10
- package/dist/hoist.d.ts +0 -2
- package/dist/isEmpty-BhJRCqIF.js +0 -52
- package/dist/isEmpty-LY533Wmh.cjs +0 -57
- package/dist/isPlainObject-CSxXVKCk.cjs +0 -56
- package/dist/isPlainObject-D39AzPO2.js +0 -51
- package/dist/jsx-CIgdGGVs.js +0 -52
- package/dist/jsx-owqxrquU.cjs +0 -75
- package/dist/jsx.d.ts +0 -11
- package/dist/message-stream/index.d.ts +0 -9
- package/dist/message-stream/instructions.d.ts +0 -30
- package/dist/message-stream/normalizer.d.ts +0 -21
- package/dist/message-stream/parser.d.ts +0 -89
- package/dist/message-stream/registry.d.ts +0 -14
- package/dist/message-stream/replayable.d.ts +0 -27
- package/dist/message-stream/response-parser.d.ts +0 -24
- package/dist/message-stream/sanitize.d.ts +0 -10
- package/dist/message-stream/stream.d.ts +0 -54
- package/dist/message-stream/types.d.ts +0 -97
- package/dist/message-stream/validator.d.ts +0 -23
- package/dist/prompts/chat-mode/protocol.d.ts +0 -2
- package/dist/prompts/chat-mode/system.d.ts +0 -2
- package/dist/prompts/chat-mode/system.md.d.ts +0 -2
- package/dist/prompts/chat-mode/user.d.ts +0 -2
- package/dist/prompts/chat-mode/user.md.d.ts +0 -2
- package/dist/prompts/code-examples.d.ts +0 -2
- package/dist/prompts/common.d.ts +0 -7
- package/dist/prompts/dual-modes.d.ts +0 -2
- package/dist/prompts/execution-history.d.ts +0 -6
- package/dist/prompts/execution-state.d.ts +0 -3
- package/dist/prompts/protocol-basics.d.ts +0 -5
- package/dist/prompts/protocol.d.ts +0 -31
- package/dist/prompts/worker-mode/protocol.d.ts +0 -2
- package/dist/prompts/worker-mode/system.d.ts +0 -2
- package/dist/prompts/worker-mode/system.md.d.ts +0 -2
- package/dist/prompts/worker-mode/user.d.ts +0 -2
- package/dist/prompts/worker-mode/user.md.d.ts +0 -2
- package/dist/quickjs-variant-BYhMBpwU.js +0 -45
- package/dist/quickjs-variant-CsIwTVFC.cjs +0 -56
- package/dist/rolldown-runtime-EAsCxrDo.js +0 -14
- package/dist/runtime/fixtures/qwen-protocol-failures.d.ts +0 -29
- package/dist/runtime/interpret-result.d.ts +0 -22
- package/dist/snapshots.d.ts +0 -249
- package/dist/stack-traces-BIE_5Xwf.js +0 -21
- package/dist/stack-traces-ntw_qhoy.cjs +0 -26
- package/dist/truncator-CLkouTCO.cjs +0 -276
- package/dist/truncator-DZqEqnpD.js +0 -253
- package/dist/truncator.d.ts +0 -118
- package/dist/utils-3ESwP3yg.cjs +0 -1075
- package/dist/utils-UBeAFeEI.js +0 -950
- /package/dist/{citations.d.ts → chat/citations.d.ts} +0 -0
- /package/dist/{citations.test.d.ts → chat/citations.test.d.ts} +0 -0
- /package/dist/{component.default.test.d.ts → chat/component.default.test.d.ts} +0 -0
- /package/dist/{compiler/plugins/track-tool-calls.test.d.ts → chat/component.test.d.ts} +0 -0
- /package/dist/{example.test.d.ts → chat/response.test.d.ts} +0 -0
- /package/dist/{exit-parser.test.d.ts → compiler/plugins/dynamic-code.test.d.ts} +0 -0
- /package/dist/{handlers.test.d.ts → compiler/plugins/tool-resolution.test.d.ts} +0 -0
- /package/dist/{hoist.test.d.ts → errors/catalog.test.d.ts} +0 -0
- /package/dist/{message-stream/instructions.test.d.ts → exit.test.d.ts} +0 -0
- /package/dist/{message-stream/normalizer.test.d.ts → inspection.test.d.ts} +0 -0
- /package/dist/{message-stream/parser.test.d.ts → prompts/native.test.d.ts} +0 -0
- /package/dist/{message-stream/response-parser.test.d.ts → result.test.d.ts} +0 -0
- /package/dist/{message-stream/stream.test.d.ts → runtime/chat-completion.test.d.ts} +0 -0
- /package/dist/{message-stream/validator.test.d.ts → runtime/chat-delivery.test.d.ts} +0 -0
- /package/dist/{prompts/adherence.test.d.ts → runtime/chat-last-speaker.test.d.ts} +0 -0
- /package/dist/{prompts/catalogue-examples.test.d.ts → runtime/compatibility-regressions.test.d.ts} +0 -0
- /package/dist/{prompts/code-examples.test.d.ts → runtime/context-registration.test.d.ts} +0 -0
- /package/dist/{prompts/common.test.d.ts → runtime/empty-completion.test.d.ts} +0 -0
- /package/dist/{prompts/concrete-patterns.test.d.ts → runtime/error-recovery.test.d.ts} +0 -0
- /package/dist/{prompts/long-search.test.d.ts → runtime/errors.test.d.ts} +0 -0
- /package/dist/{prompts/protocol-simple.test.d.ts → runtime/execution-activity.test.d.ts} +0 -0
- /package/dist/{prompts/protocol.test.d.ts → runtime/execution-diagnostics.test.d.ts} +0 -0
- /package/dist/{prompts/sections.test.d.ts → runtime/execution-report.test.d.ts} +0 -0
- /package/dist/runtime/{interpret-result.test.d.ts → execution-safety.test.d.ts} +0 -0
- /package/dist/{transcript.test.d.ts → runtime/exit-control-flow.test.d.ts} +0 -0
- /package/dist/{truncator.test.d.ts → runtime/fixtures/long-search.test.d.ts} +0 -0
package/DOCS.md
CHANGED
|
@@ -1,1923 +1,361 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LLMZ API guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Quickstart](./README.md) · [Sessions](#sessions-and-messages) · [Memory](#execution-and-memory) · [Tools](#tools-and-exits) · [Components](#components) · [Streaming](#streaming-and-delivery) · [Budgets](#token-budgets-and-compaction) · [Inspection](#inspection) · [Errors](./ERRORS.md) · [Hooks](#hooks) · [Migration](#migrating-from-the-previous-api)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## The public API
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
| API | Responsibility |
|
|
8
|
+
| ----------------------------- | --------------------------------------------------------------------------------- |
|
|
9
|
+
| `execute(props)` | Run model iterations until completion or failure. |
|
|
10
|
+
| `Session` | Own input queues, native conversation history, and exact retained memory. |
|
|
11
|
+
| `Chat` | Configure assistant text and rich component delivery. |
|
|
12
|
+
| `Tool` | Expose a host function with optional input and output schemas. |
|
|
13
|
+
| `Exit` | Define a named completion and its output schema. |
|
|
14
|
+
| `Component` | Define a rich message schema, exact JavaScript method name, and delivery handler. |
|
|
15
|
+
| `ExecutionResult` | Read one run's outcome, provider usage, and diagnostics. |
|
|
16
|
+
| `truncate` | Attach a display budget to a value without changing its data. |
|
|
17
|
+
| `inspect` / `createInspector` | Format bounded previews for host integrations. |
|
|
8
18
|
|
|
9
|
-
##
|
|
19
|
+
## Sessions and messages
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
2. [Core Philosophy](#core-philosophy)
|
|
13
|
-
3. [Quick Start](#quick-start)
|
|
14
|
-
4. [Core Concepts](#core-concepts)
|
|
15
|
-
5. [Execution Modes](#execution-modes)
|
|
16
|
-
6. [Tools](#tools)
|
|
17
|
-
7. [Objects and Variables](#objects-and-variables)
|
|
18
|
-
8. [Execution Results](#execution-results)
|
|
19
|
-
9. [Hooks System](#hooks-system)
|
|
20
|
-
10. [Advanced Features](#advanced-features)
|
|
21
|
-
11. [API Reference](#api-reference)
|
|
22
|
-
12. [Examples](#examples)
|
|
21
|
+
Create one `Session` per conversation or worker context. Append input before calling `execute({ session, ... })`. Omit `session` to create a fresh one, then read it from `result.session`.
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
```ts
|
|
24
|
+
import { Session } from 'llmz'
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **Multiple expensive roundtrips** for each tool call
|
|
37
|
-
- **Unreliable beyond simple scenarios**
|
|
38
|
-
|
|
39
|
-
LLMz leverages the fact that models have been trained extensively on millions of TypeScript codebases, making them incredibly reliable at generating working code. This enables:
|
|
40
|
-
|
|
41
|
-
- **Complex logic and multi-tool orchestration** in **one call**
|
|
42
|
-
- **Native LLM thinking** via comments and code structure
|
|
43
|
-
- **Complete type safety** and predictable schemas
|
|
44
|
-
- **Seamless scaling** in production environments
|
|
45
|
-
|
|
46
|
-
### Battle-Tested at Scale
|
|
47
|
-
|
|
48
|
-
LLMz operates as an LLM-native TypeScript VM built on top of Zui (Botpress's internal schema library), battle-tested in production powering millions of AI agents worldwide.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Core Philosophy
|
|
53
|
-
|
|
54
|
-
### Code Generation > Tool Calling
|
|
55
|
-
|
|
56
|
-
Traditional tool-calling agents are fundamentally limited by the JSON interface between the LLM and tools. This requires multiple roundtrips for complex tasks and cannot handle conditional logic, loops, or sophisticated data processing.
|
|
57
|
-
|
|
58
|
-
LLMz solves this by letting LLMs do what they do best: **generate code**. Since models are trained extensively on code, they can reliably generate TypeScript that:
|
|
59
|
-
|
|
60
|
-
- Calls multiple tools in sequence
|
|
61
|
-
- Handles conditional logic and error cases
|
|
62
|
-
- Processes and transforms data between tool calls
|
|
63
|
-
- Implements complex business logic
|
|
64
|
-
- Maintains type safety throughout execution
|
|
65
|
-
|
|
66
|
-
### Example: Traditional vs LLMz
|
|
67
|
-
|
|
68
|
-
**Traditional Tool Calling:**
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
LLM → JSON: {"tool": "getPrice", "params": {"from": "quebec", "to": "new york"}}
|
|
72
|
-
System → Response: {"price": 600}
|
|
73
|
-
LLM → JSON: {"tool": "checkBudget", "params": {"amount": 600}}
|
|
74
|
-
System → Response: {"canAfford": false}
|
|
75
|
-
LLM → JSON: {"tool": "notifyUser", "params": {"message": "Price too high"}}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**LLMz Code Generation:**
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// Check the ticket price and user's budget in one go
|
|
82
|
-
const price = await getTicketPrice({ from: 'quebec', to: 'new york' })
|
|
83
|
-
const budget = await getUserBudget()
|
|
84
|
-
|
|
85
|
-
if (price > budget) {
|
|
86
|
-
await notifyUser({ message: `Price $${price} exceeds budget $${budget}` })
|
|
87
|
-
return { action: 'budget_exceeded', price, budget }
|
|
88
|
-
} else {
|
|
89
|
-
const ticketId = await buyTicket({ from: 'quebec', to: 'new york' })
|
|
90
|
-
return { action: 'done', result: ticketId }
|
|
91
|
-
}
|
|
26
|
+
const session = new Session()
|
|
27
|
+
session.append([
|
|
28
|
+
{ role: 'user', content: 'Hello' },
|
|
29
|
+
{ role: 'assistant', content: 'How can I help?' },
|
|
30
|
+
{
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: 'What does this receipt say?',
|
|
33
|
+
attachments: [{ type: 'image', url: 'https://example.com/receipt.png', id: 'receipt', alt: 'Store receipt' }],
|
|
34
|
+
},
|
|
35
|
+
])
|
|
92
36
|
```
|
|
93
37
|
|
|
94
|
-
|
|
38
|
+
`append` accepts a native Cognitive message, a `Transcript.Message`, or an array of either. It validates the whole batch before enqueueing any of it. Identical messages remain distinct inputs. The session copies its inputs and returns copies of its messages.
|
|
95
39
|
|
|
96
|
-
|
|
40
|
+
| Input | Cognitive representation |
|
|
41
|
+
| --------------------------------------- | -------------------------------------------------------------------------- |
|
|
42
|
+
| Native text or multipart message | Preserves content, part order, and opaque JSON provider fields. |
|
|
43
|
+
| User/assistant text | Retains its role and text. |
|
|
44
|
+
| `attachments` | Adds image/audio parts to that message, with adjacent ID/alt descriptions. |
|
|
45
|
+
| `modality: 'voice'` or audio attachment | Labels convenience input as a voice transcript. |
|
|
46
|
+
| `role: 'event'` | User-role data labeled with the event name and a bounded payload preview. |
|
|
47
|
+
| `role: 'summary'` | User-role data labeled as a conversation summary. |
|
|
97
48
|
|
|
98
|
-
|
|
49
|
+
Native multipart inputs already describe their content; do not combine them with convenience `attachments` or `modality`. Events and summaries do not become system instructions. Pass system instructions through `execute({ instructions })`.
|
|
99
50
|
|
|
100
|
-
|
|
101
|
-
npm install llmz @botpress/client
|
|
102
|
-
```
|
|
51
|
+
`session.transcript` returns the retained conversation and queued input, preserving `event` and `summary` roles. `session.messages` exposes processed native messages; `session.pendingMessages` exposes queued native input. All three return detached copies.
|
|
103
52
|
|
|
104
|
-
|
|
53
|
+
External events start turns just like user messages. Append them, then execute:
|
|
105
54
|
|
|
106
|
-
```
|
|
107
|
-
import { execute } from 'llmz'
|
|
55
|
+
```ts
|
|
108
56
|
import { Client } from '@botpress/client'
|
|
57
|
+
import { Chat, Session, execute } from 'llmz'
|
|
109
58
|
|
|
110
|
-
const client = new Client(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
})
|
|
59
|
+
const client = new Client()
|
|
60
|
+
const session = new Session()
|
|
61
|
+
const chat = new Chat({ response: { handler: (text) => console.log(text) } })
|
|
114
62
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
63
|
+
session.append({
|
|
64
|
+
role: 'event',
|
|
65
|
+
name: 'button.clicked',
|
|
66
|
+
payload: { button: 'confirm', orderId: 'order-42' },
|
|
118
67
|
})
|
|
119
|
-
|
|
120
|
-
if (result.isSuccess()) {
|
|
121
|
-
console.log('Result:', result.output)
|
|
122
|
-
console.log('Generated code:', result.iteration.code)
|
|
123
|
-
}
|
|
68
|
+
await execute({ client, session, chat, instructions: 'Respond to order actions.' })
|
|
124
69
|
```
|
|
125
70
|
|
|
126
|
-
|
|
71
|
+
Appending an event does not start execution automatically. Events arriving during a run stay queued for the next call; repeated clicks remain separate events. Event payloads are data, so validate authorization and business rules in your tool handlers.
|
|
127
72
|
|
|
128
|
-
|
|
129
|
-
import { execute } from 'llmz'
|
|
130
|
-
import { Client } from '@botpress/client'
|
|
131
|
-
import { CLIChat } from './utils/cli-chat'
|
|
73
|
+
Input cannot inject native tool calls or results. Restore a serialized session to continue existing tool history. The runtime retains assistant calls and their matching results together, including provider continuation metadata. It never appends memory instructions to signed assistant output.
|
|
132
74
|
|
|
133
|
-
|
|
134
|
-
botId: process.env.BOTPRESS_BOT_ID!,
|
|
135
|
-
token: process.env.BOTPRESS_TOKEN!,
|
|
136
|
-
})
|
|
75
|
+
Convenience event payloads receive a 5,000-token preview in native model input. The original payload remains in `session.transcript` and serialized session state until that event is compacted. Ordinary user text is retained in full. Keep authoritative application events in the host when they must outlive conversation compaction.
|
|
137
76
|
|
|
138
|
-
|
|
77
|
+
Session status is `idle`, `pending`, or `active`. A turn claims queued input once. Input arriving during execution remains queued until that turn completes. Concurrent `execute` calls on one session are rejected.
|
|
139
78
|
|
|
140
|
-
|
|
141
|
-
await execute({
|
|
142
|
-
instructions: 'You are a helpful assistant',
|
|
143
|
-
chat,
|
|
144
|
-
client,
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## Core Concepts
|
|
152
|
-
|
|
153
|
-
### Execution Loop
|
|
154
|
-
|
|
155
|
-
At its core, LLMz exposes a single method (`execute`) that runs in a loop until one of these conditions is met:
|
|
156
|
-
|
|
157
|
-
1. **An Exit is returned** - Agent completes with structured result
|
|
158
|
-
2. **Agent waits for user input** (Chat Mode) - Returns control to user
|
|
159
|
-
3. **Maximum iterations reached** - Safety limit to prevent infinite loops
|
|
160
|
-
|
|
161
|
-
The loop automatically handles:
|
|
162
|
-
|
|
163
|
-
- Tool calling and result processing
|
|
164
|
-
- Thinking about outputs and context
|
|
165
|
-
- Error recovery and retry logic
|
|
166
|
-
- Variable state persistence across iterations
|
|
79
|
+
`session.messages` is canonical history. `session.pendingMessages` contains unclaimed input. `session.requestMessages()` produces a copy with an ephemeral memory overview; `requestMessages({ memory: false })` omits the overview. Runtime lifecycle methods such as `beginTurn`, `nextIteration`, and `settleIteration` are used by `execute`; applications normally only need `append` and persistence.
|
|
167
80
|
|
|
168
|
-
|
|
81
|
+
## Execution and memory
|
|
169
82
|
|
|
170
|
-
|
|
83
|
+
The model receives one native tool, `run_javascript`, and declarations for the registered host tools, objects, components, and exits. A response may contain at most one JavaScript call. Its program can compose multiple business operations.
|
|
171
84
|
|
|
172
|
-
|
|
85
|
+
These examples are JavaScript generated by the model, not host TypeScript:
|
|
173
86
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Chat mode - give turn back to user
|
|
178
|
-
return { action: 'listen' }
|
|
179
|
-
|
|
180
|
-
// Worker mode - complete with result
|
|
181
|
-
return { action: 'done', result: calculatedValue }
|
|
87
|
+
```js
|
|
88
|
+
const account = await getAccount({ id: 'customer-42' })
|
|
89
|
+
return inspect(account)
|
|
182
90
|
```
|
|
183
91
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
Unlike traditional tool calling, LLMz enables complex logic impossible with JSON:
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
// Complex conditional logic and error handling
|
|
190
|
-
const price = await getTicketPrice({ from: 'quebec', to: 'new york' })
|
|
191
|
-
|
|
192
|
-
if (price > 500) {
|
|
193
|
-
throw new Error('Price too high')
|
|
194
|
-
} else {
|
|
195
|
-
const ticketId = await buyTicket({ from: 'quebec', to: 'new york' })
|
|
196
|
-
return { action: 'done', result: ticketId }
|
|
197
|
-
}
|
|
92
|
+
```js
|
|
93
|
+
return exit('done', { accountId: account.id })
|
|
198
94
|
```
|
|
199
95
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Comments help LLMs think step-by-step and plan ahead:
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
// Check user's budget first before proceeding with purchase
|
|
206
|
-
const budget = await getUserBudget()
|
|
207
|
-
|
|
208
|
-
// Only proceed if we have enough funds
|
|
209
|
-
if (budget >= price) {
|
|
210
|
-
// Purchase the ticket
|
|
211
|
-
const ticket = await buyTicket(ticketDetails)
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### React Components (Chat Mode Only)
|
|
216
|
-
|
|
217
|
-
In Chat Mode, agents can yield React components for rich user interaction:
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
// Multi-line text support
|
|
221
|
-
yield <Text>
|
|
222
|
-
Hello, world!
|
|
223
|
-
This is a second line.
|
|
224
|
-
</Text>
|
|
225
|
-
|
|
226
|
-
// Composed/nested components
|
|
227
|
-
yield <Message>
|
|
228
|
-
<Text>What do you prefer?</Text>
|
|
229
|
-
<Button>Cats</Button>
|
|
230
|
-
<Button>Dogs</Button>
|
|
231
|
-
</Message>
|
|
232
|
-
|
|
233
|
-
return { action: 'listen' }
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Compilation Pipeline
|
|
237
|
-
|
|
238
|
-
LLMz uses a sophisticated Babel-based compilation system to transform generated code:
|
|
239
|
-
|
|
240
|
-
1. **AST Parsing**: TypeScript/JSX code parsed into Abstract Syntax Tree
|
|
241
|
-
2. **Plugin Transformation**: Custom plugins modify the AST for execution
|
|
242
|
-
3. **Code Generation**: Modified AST compiled back to executable JavaScript
|
|
243
|
-
4. **Source Maps**: Generated for debugging and error tracking
|
|
244
|
-
|
|
245
|
-
Key transformations include:
|
|
246
|
-
|
|
247
|
-
- Tool call instrumentation for monitoring
|
|
248
|
-
- Variable extraction and tracking
|
|
249
|
-
- JSX component handling
|
|
250
|
-
- Line number preservation for stack traces
|
|
251
|
-
|
|
252
|
-
### Virtual Machine Execution
|
|
96
|
+
`inspect(value)` exposes a preview in the next model request. Exact retained values remain available to JavaScript. `exit(name, payload)` validates a completion payload against its registered exit. Normal returned data does not complete a worker.
|
|
253
97
|
|
|
254
|
-
|
|
98
|
+
Worker prompts require every response to be a `run_javascript` call with empty assistant text, including during recovery and completion. Workers must not emit preambles, progress updates, or acknowledgements such as “Done.” Results are inspected or returned through a registered exit.
|
|
255
99
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
100
|
+
| Value | Lifetime |
|
|
101
|
+
| ----------------- | ---------------------------------------------------------------------------- |
|
|
102
|
+
| Named variables | Remain in session memory across executions and history compaction. |
|
|
103
|
+
| `$return` | Latest available returned result; read-only. |
|
|
104
|
+
| `$iterations` | Retained iteration records, newest first; records and results are read-only. |
|
|
105
|
+
| Object properties | Host-backed values with declared read/write access. |
|
|
106
|
+
| Prompt previews | Derived display text; never the source of stored values. |
|
|
259
107
|
|
|
260
|
-
|
|
108
|
+
A record's `hasResult` distinguishes an absent result from a result whose exact value is `undefined`. A failed iteration without a new result preserves the previous `$return`. A new result that cannot be retained clears `$return` rather than exposing stale evidence. Compaction removes discarded iteration results; it does not fall back to an older `$return`.
|
|
261
109
|
|
|
262
|
-
|
|
263
|
-
- Execution timeouts
|
|
264
|
-
- Secure context separation
|
|
265
|
-
- Stack trace sanitization
|
|
110
|
+
Memory accepts finite numbers, booleans, strings, null, undefined, dense arrays, and plain data objects. Its codec preserves `undefined` and negative zero through JSON persistence. Functions, accessors, symbols, cycles, and custom class instances cannot be stored as exact memory values.
|
|
266
111
|
|
|
267
|
-
|
|
112
|
+
```ts
|
|
113
|
+
import { Session } from 'llmz'
|
|
268
114
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
### Chat Mode
|
|
274
|
-
|
|
275
|
-
**Enabled when**: `chat` parameter is provided to `execute()`
|
|
276
|
-
|
|
277
|
-
Chat Mode is designed for interactive conversational agents that need to:
|
|
278
|
-
|
|
279
|
-
- Maintain conversation history
|
|
280
|
-
- Respond to user messages
|
|
281
|
-
- Yield UI components for rich interaction
|
|
282
|
-
- Handle turn-taking between agent and user
|
|
283
|
-
|
|
284
|
-
Key characteristics:
|
|
285
|
-
|
|
286
|
-
- Agent can yield React components to user
|
|
287
|
-
- Special `ListenExit` automatically available
|
|
288
|
-
- Transcript management for conversation history
|
|
289
|
-
- Turn-based execution flow
|
|
290
|
-
|
|
291
|
-
```typescript
|
|
292
|
-
const result = await execute({
|
|
293
|
-
instructions: 'You are a helpful assistant',
|
|
294
|
-
chat: myChatInstance,
|
|
295
|
-
tools: [searchTool, calculatorTool],
|
|
296
|
-
client,
|
|
115
|
+
const session = new Session({
|
|
116
|
+
variables: { accountId: 'customer-42', attempts: 0 },
|
|
117
|
+
maxBytes: 4 * 1024 * 1024,
|
|
297
118
|
})
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// Agent is waiting for user input
|
|
301
|
-
}
|
|
119
|
+
console.log(session.memory.variables)
|
|
120
|
+
console.log(session.iterations)
|
|
302
121
|
```
|
|
303
122
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
**Enabled when**: `chat` parameter is omitted from `execute()`
|
|
307
|
-
|
|
308
|
-
Worker Mode is designed for automated execution environments that need to:
|
|
123
|
+
The default memory limit is 16 MiB. It covers encoded named variables, object memory, and retained result records; it is not a byte limit on all conversation messages. Token budgeting controls the request sent to the model. Successful writes may be retained when later writes exceed capacity; reports describe unavailable values. Host side effects are not rolled back.
|
|
309
124
|
|
|
310
|
-
|
|
311
|
-
- Execute multi-step workflows
|
|
312
|
-
- Return structured results
|
|
313
|
-
- Run without human interaction
|
|
125
|
+
## Persistence and recovery
|
|
314
126
|
|
|
315
|
-
|
|
127
|
+
Persist `session.toJSON()` after execution has settled. It rejects serialization while a run holds the session lock or an iteration is pending.
|
|
316
128
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
- Sandboxed execution with security isolation
|
|
320
|
-
- Automated completion without user interaction
|
|
129
|
+
```ts
|
|
130
|
+
import { Session } from 'llmz'
|
|
321
131
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
client,
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
if (result.is(dataProcessedExit)) {
|
|
331
|
-
console.log('Analysis complete:', result.output)
|
|
332
|
-
}
|
|
132
|
+
const session = new Session()
|
|
133
|
+
session.append({ role: 'user', content: 'Queued request' })
|
|
134
|
+
const encoded = JSON.stringify(session.toJSON())
|
|
135
|
+
const restored = Session.fromJSON(JSON.parse(encoded))
|
|
136
|
+
console.log(restored.status) // pending
|
|
333
137
|
```
|
|
334
138
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
| Feature | Chat Mode | Worker Mode |
|
|
338
|
-
| -------------------- | ------------------- | --------------- |
|
|
339
|
-
| User Interaction | ✅ Interactive | ❌ Automated |
|
|
340
|
-
| UI Components | ✅ React components | ❌ No UI |
|
|
341
|
-
| Conversation History | ✅ Full transcript | ❌ No history |
|
|
342
|
-
| Default Exits | `ListenExit` | `DefaultExit` |
|
|
343
|
-
| Primary Use Case | Conversational AI | Data processing |
|
|
344
|
-
| Execution Pattern | Turn-based | Continuous |
|
|
139
|
+
Restoration checks format versions, chronology, identities, call/result pairing, exact result encodings, and memory capacity. Unknown versions are rejected. Conversation/provider fields must be acyclic JSON data; encode binary values explicitly. This is conversation and memory persistence, not a paused JavaScript stack.
|
|
345
140
|
|
|
346
|
-
|
|
141
|
+
After a failed execution, the session retains the active input batch and any completed effects/results. Executing it again continues that turn. Newly appended messages remain queued. A successful retry can therefore leave `session.status === "pending"`; call `execute()` again to process that next batch. Its user messages reset the chat response guard when the next turn starts. Applications should inspect failures before retrying operations that produce external effects.
|
|
347
142
|
|
|
348
|
-
|
|
143
|
+
`result.toJSON()` is a compact outcome summary. `result.diagnostics()` exports run diagnostics. Neither replaces `session.toJSON()`.
|
|
349
144
|
|
|
350
|
-
|
|
145
|
+
Chat requires the assistant to be the last participant who spoke before `exit("listen")` can complete. Successfully delivered text and components count across iterations, executions, and saved sessions. A new user message requires a new response; events, tool results, failed deliveries, and whitespace-only content do not change the last speaker. An empty or whitespace-only model response with `stopReason: "stop"` and no tool calls follows the same listen flow and guard. Silent completion raises a recoverable `MissingChatResponseError` before `onExit`, preserving tool results for the next attempt. Set `options: { requireChatResponse: false }` for intentionally silent handling. Custom task exits and worker mode are unaffected. Older session snapshots without delivery tracking restore with an unknown last speaker and require a response by default.
|
|
351
146
|
|
|
352
|
-
|
|
147
|
+
## Tools and exits
|
|
353
148
|
|
|
354
|
-
|
|
149
|
+
A `Tool` has a name, async handler, optional description, input/output schemas, aliases, metadata, and retry callback. Its original input schema validates and normalizes arguments before the handler runs. Its output schema describes likely returned data for TypeScript and the model: results are never validated, normalized, stripped, or rejected against that schema. Dynamic tools, objects, exits, instructions, and model configuration can be supplied as getters evaluated for each iteration.
|
|
355
150
|
|
|
356
|
-
```
|
|
357
|
-
import { Tool } from 'llmz'
|
|
151
|
+
```ts
|
|
358
152
|
import { z } from '@bpinternal/zui'
|
|
153
|
+
import { Tool, Exit } from 'llmz'
|
|
359
154
|
|
|
360
|
-
const
|
|
361
|
-
name: '
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
units: z.enum(['celsius', 'fahrenheit']).optional().default('celsius'),
|
|
366
|
-
}),
|
|
367
|
-
output: z.object({
|
|
368
|
-
temperature: z.number(),
|
|
369
|
-
conditions: z.string(),
|
|
370
|
-
humidity: z.number(),
|
|
371
|
-
}),
|
|
372
|
-
handler: async ({ location, units }) => {
|
|
373
|
-
// Implementation here
|
|
374
|
-
return {
|
|
375
|
-
temperature: 22,
|
|
376
|
-
conditions: 'sunny',
|
|
377
|
-
humidity: 65,
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
})
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Tool Usage in Generated Code
|
|
384
|
-
|
|
385
|
-
The LLM generates TypeScript code that calls tools naturally:
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
// Simple tool call
|
|
389
|
-
const weather = await getWeather({ location: 'New York' })
|
|
390
|
-
|
|
391
|
-
// Complex logic with multiple tools
|
|
392
|
-
const weather = await getWeather({ location: userLocation })
|
|
393
|
-
if (weather.temperature < 0) {
|
|
394
|
-
const clothing = await getSuggestions({ type: 'winter', temperature: weather.temperature })
|
|
395
|
-
yield <Text>It's {weather.temperature}°C! {clothing.suggestion}</Text>
|
|
396
|
-
} else {
|
|
397
|
-
yield <Text>Nice weather! {weather.conditions} at {weather.temperature}°C</Text>
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return { action: 'listen' }
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### Advanced Tool Features
|
|
404
|
-
|
|
405
|
-
#### Tool Aliases
|
|
406
|
-
|
|
407
|
-
Tools can have multiple names for flexible calling:
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
const tool = new Tool({
|
|
411
|
-
name: 'calculatePrice',
|
|
412
|
-
aliases: ['getPrice', 'checkCost'],
|
|
413
|
-
// ... rest of definition
|
|
155
|
+
const getStock = new Tool({
|
|
156
|
+
name: 'getStock',
|
|
157
|
+
input: z.object({ sku: z.string() }),
|
|
158
|
+
output: z.number(),
|
|
159
|
+
handler: async ({ sku }) => (sku === 'coffee' ? 20 : 0),
|
|
414
160
|
})
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// await checkCost(params)
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
#### Static Inputs
|
|
423
|
-
|
|
424
|
-
Force specific inputs to be always included:
|
|
425
|
-
|
|
426
|
-
```typescript
|
|
427
|
-
const tool = new Tool({
|
|
428
|
-
name: 'logEvent',
|
|
429
|
-
input: z.object({
|
|
430
|
-
event: z.string(),
|
|
431
|
-
userId: z.string(),
|
|
432
|
-
timestamp: z.number(),
|
|
433
|
-
}),
|
|
434
|
-
staticInputs: {
|
|
435
|
-
userId: 'user-123',
|
|
436
|
-
timestamp: () => Date.now(), // Dynamic static input
|
|
437
|
-
},
|
|
438
|
-
handler: async ({ event, userId, timestamp }) => {
|
|
439
|
-
// userId and timestamp are automatically provided
|
|
440
|
-
},
|
|
161
|
+
const done = new Exit({
|
|
162
|
+
name: 'done',
|
|
163
|
+
description: 'Return available stock.',
|
|
164
|
+
schema: z.object({ available: z.number() }),
|
|
441
165
|
})
|
|
442
166
|
```
|
|
443
167
|
|
|
444
|
-
|
|
168
|
+
See the [complete typed error catalogue and recovery contract](./ERRORS.md) for stable codes, import-safe guards, iteration diagnostics, and critical failures.
|
|
445
169
|
|
|
446
|
-
|
|
170
|
+
LLMz retains the original schemas separately from their model-facing JSON descriptions. Tool inputs, exit payloads, component props, and object-property assignments preserve `.trim()`, `.refine()`, `.superRefine()`, `.preprocess()`, `.transform()`, and pipelines. Rejected refinements produce the corresponding typed input error and feedback for the next iteration. Successful parsing runs effects once; handlers, exit hooks, deliveries, and stored properties receive the parsed value. Cloning tools and exits preserves their validators.
|
|
447
171
|
|
|
448
|
-
|
|
449
|
-
const originalTool = new Tool({/* definition */})
|
|
450
|
-
|
|
451
|
-
const wrappedTool = originalTool.clone({
|
|
452
|
-
name: 'wrappedVersion',
|
|
453
|
-
description: 'Enhanced version with logging',
|
|
454
|
-
handler: async (input) => {
|
|
455
|
-
console.log('Tool called with:', input)
|
|
456
|
-
const result = await originalTool.execute(input)
|
|
457
|
-
console.log('Tool returned:', result)
|
|
458
|
-
return result
|
|
459
|
-
},
|
|
460
|
-
})
|
|
461
|
-
```
|
|
172
|
+
Tool input effects may be asynchronous. Exit validation, component rendering, and object-property setters are synchronous: async effects on those surfaces raise `InvalidConfigurationError`. Put asynchronous checks in a tool input schema. Exceptions thrown by schema callbacks are configuration failures; use refinement issues to report correctable input errors.
|
|
462
173
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
Use `tool.getTypings()` to see the TypeScript definitions generated for the LLM:
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
console.log(weatherTool.getTypings())
|
|
469
|
-
// Output:
|
|
470
|
-
// /**
|
|
471
|
-
// * Get current weather for a location
|
|
472
|
-
// */
|
|
473
|
-
// declare function getWeather(input: {
|
|
474
|
-
// location: string; // City name or coordinates
|
|
475
|
-
// units?: "celsius" | "fahrenheit";
|
|
476
|
-
// }): Promise<{
|
|
477
|
-
// temperature: number;
|
|
478
|
-
// conditions: string;
|
|
479
|
-
// humidity: number;
|
|
480
|
-
// }>;
|
|
481
|
-
```
|
|
174
|
+
For a type-changing tool input, `execute()` accepts `z.input<I>` and the handler receives `z.output<I>`. Exit results and component handlers use the parsed output type. Tool output schemas describe raw data, so their type hint uses `z.input<O>`; output effects never execute. A handler may return data outside that hint. Memory and inspection retain their own supported-value constraints.
|
|
482
175
|
|
|
483
|
-
|
|
176
|
+
Model-facing schemas describe accepted input shapes, not arbitrary JavaScript behavior. Use descriptions to explain custom constraints. Object property schemas describe assignments; host-provided initial values are already-normalized state and are not parsed again when loaded or restored. Plain session variables have no user-supplied validation schema.
|
|
484
177
|
|
|
485
|
-
|
|
486
|
-
2. **Type Safety**: Use strict Zui schemas for predictable behavior
|
|
487
|
-
3. **Error Handling**: Tools should handle errors gracefully
|
|
488
|
-
4. **Performance**: Keep tool execution fast to avoid timeouts
|
|
489
|
-
5. **Documentation**: Clear descriptions improve code generation quality
|
|
178
|
+
`toJSON()` exports descriptions, not executable refinements or transforms. Persist sessions separately and reattach the original tool, exit, component, and object definitions when resuming. Reconstructing a schema from JSON cannot restore its JavaScript callbacks.
|
|
490
179
|
|
|
491
|
-
|
|
180
|
+
Use `result.is(done)` to narrow the result and its output type. `result.isError()` exposes an execution failure. Without custom exits, workers receive `DefaultExit`. An explicit empty `exits` array supplies no completion exits. Chat adds `ListenExit`; an accepted plain assistant answer can complete the chat turn.
|
|
492
181
|
|
|
493
|
-
|
|
182
|
+
Tools return business data. Generated code sends rich messages through registered `chat.<component>(props)` methods. A tool may throw or return `new ThinkSignal(reason, context)` to require inspection of a successful result. JavaScript receives `context` as the tool return value: assignments, subsequent calls, and awaited parallel tools continue normally. At the end of that iteration, inspection takes precedence over an attempted exit or a later recoverable error; exit hooks do not run. The next model turn receives every signaling tool's name, source line, reason, and result together inside `<forced_inspection>`, and is instructed to reuse those results rather than repeat successful calls. Explicit `inspect()` output is also preserved. Tool-result display budgets and `truncate()` policies still apply. Only the business tool handler may produce a `ThinkSignal`; tool hooks must return their documented input/output overrides. Forced inspection uses the output accepted by `onAfterTool`. If that hook rejects or cancels, the rejected result is withheld from guest memory and inspection.
|
|
494
183
|
|
|
495
|
-
|
|
184
|
+
Forced inspection delays completion, not business actions: code after a signaling tool still runs, including other tools and chat component deliveries. It is not an authorization barrier. Cancellation and critical failures still stop execution completely. `ThinkSignal` is not durable pause/resume.
|
|
496
185
|
|
|
497
|
-
|
|
186
|
+
## Components
|
|
498
187
|
|
|
499
|
-
|
|
188
|
+
Component names are exact JavaScript identifiers. Object and array schemas are supported, including root effects and pipelines; transformed props must still be an object or array. Aliases and generation metadata are not component options. Text names such as `text` and `message` are reserved for native assistant output.
|
|
500
189
|
|
|
501
|
-
```
|
|
502
|
-
import { ObjectInstance } from 'llmz'
|
|
190
|
+
```ts
|
|
503
191
|
import { z } from '@bpinternal/zui'
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
type: z.string(),
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
name: 'age',
|
|
516
|
-
value: 30,
|
|
517
|
-
writable: false, // Read-only
|
|
518
|
-
type: z.number(),
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
name: 'preferences',
|
|
522
|
-
value: { theme: 'dark', language: 'en' },
|
|
523
|
-
writable: true,
|
|
524
|
-
type: z.object({
|
|
525
|
-
theme: z.enum(['light', 'dark']),
|
|
526
|
-
language: z.string(),
|
|
527
|
-
}),
|
|
528
|
-
},
|
|
529
|
-
],
|
|
530
|
-
tools: [
|
|
531
|
-
new Tool({
|
|
532
|
-
name: 'updateProfile',
|
|
533
|
-
input: z.object({ name: z.string() }),
|
|
534
|
-
handler: async ({ name }) => {
|
|
535
|
-
// This tool is scoped to the user object
|
|
536
|
-
return { success: true }
|
|
537
|
-
},
|
|
538
|
-
}),
|
|
539
|
-
],
|
|
540
|
-
})
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### Variables in Generated Code
|
|
544
|
-
|
|
545
|
-
The LLM can read and write object properties in generated code:
|
|
546
|
-
|
|
547
|
-
```typescript
|
|
548
|
-
// Reading variables
|
|
549
|
-
const userName = user.name // "John Doe"
|
|
550
|
-
const userAge = user.age // 30
|
|
551
|
-
|
|
552
|
-
// Writing to writable variables
|
|
553
|
-
user.name = 'Jane Smith' // ✅ Succeeds
|
|
554
|
-
user.preferences = { theme: 'light', language: 'es' } // ✅ Succeeds
|
|
555
|
-
|
|
556
|
-
// Attempting to write read-only variables
|
|
557
|
-
user.age = 25 // ❌ Throws AssignmentError
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
### Type Safety and Validation
|
|
561
|
-
|
|
562
|
-
Variables are validated against their schemas:
|
|
563
|
-
|
|
564
|
-
```typescript
|
|
565
|
-
// Valid assignment
|
|
566
|
-
user.preferences = { theme: 'dark', language: 'fr' } // ✅
|
|
567
|
-
|
|
568
|
-
// Invalid assignment - wrong type
|
|
569
|
-
user.preferences = { theme: 'blue', language: 'fr' } // ❌ Throws validation error
|
|
570
|
-
|
|
571
|
-
// Invalid assignment - missing required fields
|
|
572
|
-
user.preferences = { theme: 'dark' } // ❌ Missing language field
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
### Mutation Tracking
|
|
576
|
-
|
|
577
|
-
LLMz automatically tracks changes to object properties:
|
|
578
|
-
|
|
579
|
-
```typescript
|
|
580
|
-
// In generated code
|
|
581
|
-
user.name = 'Updated Name'
|
|
582
|
-
user.preferences.theme = 'light'
|
|
583
|
-
|
|
584
|
-
// After execution, mutations are available
|
|
585
|
-
console.log(result.iteration.mutations)
|
|
586
|
-
// [
|
|
587
|
-
// {
|
|
588
|
-
// object: 'user',
|
|
589
|
-
// property: 'name',
|
|
590
|
-
// before: 'John Doe',
|
|
591
|
-
// after: 'Updated Name'
|
|
592
|
-
// },
|
|
593
|
-
// {
|
|
594
|
-
// object: 'user',
|
|
595
|
-
// property: 'preferences',
|
|
596
|
-
// before: { theme: 'dark', language: 'en' },
|
|
597
|
-
// after: { theme: 'light', language: 'en' }
|
|
598
|
-
// }
|
|
599
|
-
// ]
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### Namespaced Tools
|
|
603
|
-
|
|
604
|
-
Tools within objects are called with object namespace:
|
|
605
|
-
|
|
606
|
-
```typescript
|
|
607
|
-
// Tool is scoped to the user object
|
|
608
|
-
await user.updateProfile({ name: 'New Name' })
|
|
609
|
-
|
|
610
|
-
// This automatically updates the user object's properties
|
|
611
|
-
// and is tracked as a mutation
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
### Object Sealing and Protection
|
|
615
|
-
|
|
616
|
-
Objects are automatically sealed to prevent unauthorized modifications:
|
|
617
|
-
|
|
618
|
-
```typescript
|
|
619
|
-
// In generated code - these will throw errors
|
|
620
|
-
user.newProperty = 'value' // ❌ Cannot add new properties
|
|
621
|
-
delete user.name // ❌ Cannot delete properties
|
|
622
|
-
|
|
623
|
-
// Only predefined properties can be modified (if writable)
|
|
624
|
-
user.name = 'New Name' // ✅ Allowed if writable: true
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Variable Persistence
|
|
628
|
-
|
|
629
|
-
Variables persist across iterations and thinking cycles:
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
// Iteration 1: Set a variable
|
|
633
|
-
user.preferences = { theme: 'dark', language: 'es' }
|
|
634
|
-
return { action: 'think' } // Trigger thinking
|
|
635
|
-
|
|
636
|
-
// Iteration 2: Variable is still available
|
|
637
|
-
const currentTheme = user.preferences.theme // 'dark'
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Best Practices
|
|
641
|
-
|
|
642
|
-
1. **Meaningful Names**: Use descriptive object and property names
|
|
643
|
-
2. **Appropriate Scope**: Group related functionality together
|
|
644
|
-
3. **Write Protection**: Mark properties as read-only when appropriate
|
|
645
|
-
4. **Type Safety**: Use strict schemas for predictable behavior
|
|
646
|
-
5. **Mutation Tracking**: Leverage mutation tracking for audit trails
|
|
647
|
-
|
|
648
|
-
---
|
|
649
|
-
|
|
650
|
-
## Execution Results
|
|
651
|
-
|
|
652
|
-
Every call to `execute()` returns an `ExecutionResult` that provides type-safe access to the execution outcome. LLMz execution can result in three different types of results.
|
|
653
|
-
|
|
654
|
-
### Result Types
|
|
655
|
-
|
|
656
|
-
#### SuccessExecutionResult
|
|
657
|
-
|
|
658
|
-
Agent completed successfully with an Exit. Contains the structured data produced by the agent.
|
|
659
|
-
|
|
660
|
-
```typescript
|
|
661
|
-
const result = await execute({
|
|
662
|
-
instructions: 'Calculate the sum',
|
|
663
|
-
client,
|
|
664
|
-
})
|
|
665
|
-
|
|
666
|
-
if (result.isSuccess()) {
|
|
667
|
-
console.log('Output:', result.output)
|
|
668
|
-
console.log('Exit used:', result.exit.name)
|
|
669
|
-
console.log('Generated code:', result.iteration.code)
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
#### ErrorExecutionResult
|
|
674
|
-
|
|
675
|
-
Execution failed with an unrecoverable error:
|
|
676
|
-
|
|
677
|
-
```typescript
|
|
678
|
-
if (result.isError()) {
|
|
679
|
-
console.error('Error:', result.error)
|
|
680
|
-
console.error('Failed iteration:', result.iteration?.error)
|
|
681
|
-
|
|
682
|
-
// Analyze failure progression
|
|
683
|
-
result.iterations.forEach((iter, i) => {
|
|
684
|
-
console.log(`Iteration ${i + 1}: ${iter.status.type}`)
|
|
685
|
-
})
|
|
686
|
-
}
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
#### PartialExecutionResult
|
|
690
|
-
|
|
691
|
-
Execution was interrupted by a SnapshotSignal for pauseable operations:
|
|
692
|
-
|
|
693
|
-
```typescript
|
|
694
|
-
if (result.isInterrupted()) {
|
|
695
|
-
console.log('Interrupted:', result.signal.message)
|
|
696
|
-
|
|
697
|
-
// Save snapshot for later resumption
|
|
698
|
-
const serialized = result.snapshot.toJSON()
|
|
699
|
-
await database.saveSnapshot(serialized)
|
|
700
|
-
}
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
### Type-Safe Exit Checking
|
|
704
|
-
|
|
705
|
-
Use `result.is(exit)` for type-safe access to specific exit data:
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
const successExit = new Exit({
|
|
709
|
-
name: 'success',
|
|
710
|
-
schema: z.object({
|
|
711
|
-
recordsProcessed: z.number(),
|
|
712
|
-
processingTime: z.number(),
|
|
713
|
-
}),
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
const errorExit = new Exit({
|
|
717
|
-
name: 'error',
|
|
718
|
-
schema: z.object({
|
|
719
|
-
errorCode: z.string(),
|
|
720
|
-
details: z.string(),
|
|
721
|
-
}),
|
|
722
|
-
})
|
|
723
|
-
|
|
724
|
-
const result = await execute({
|
|
725
|
-
instructions: 'Process the data',
|
|
726
|
-
exits: [successExit, errorExit],
|
|
727
|
-
client,
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
// Type-safe exit handling with automatic output typing
|
|
731
|
-
if (result.is(successExit)) {
|
|
732
|
-
// TypeScript knows result.output has the success schema
|
|
733
|
-
console.log(`Processed ${result.output.recordsProcessed} records`)
|
|
734
|
-
console.log(`Processing took ${result.output.processingTime}ms`)
|
|
735
|
-
} else if (result.is(errorExit)) {
|
|
736
|
-
// TypeScript knows result.output has the error schema
|
|
737
|
-
console.error(`Error ${result.output.errorCode}: ${result.output.details}`)
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
### Built-in Exits
|
|
742
|
-
|
|
743
|
-
```typescript
|
|
744
|
-
import { ListenExit, DefaultExit, ThinkExit } from 'llmz'
|
|
745
|
-
|
|
746
|
-
// Check for built-in exits
|
|
747
|
-
if (result.is(ListenExit)) {
|
|
748
|
-
console.log('Agent is waiting for user input')
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
if (result.is(DefaultExit)) {
|
|
752
|
-
// DefaultExit has success/failure discriminated union
|
|
753
|
-
if (result.output.success) {
|
|
754
|
-
console.log('Completed successfully:', result.output.result)
|
|
755
|
-
} else {
|
|
756
|
-
console.error('Completed with error:', result.output.error)
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (result.is(ThinkExit)) {
|
|
761
|
-
console.log('Agent requested thinking time')
|
|
762
|
-
console.log('Current variables:', result.output.variables)
|
|
763
|
-
}
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
### Accessing Execution Details
|
|
767
|
-
|
|
768
|
-
#### Iterations and Execution Flow
|
|
769
|
-
|
|
770
|
-
```typescript
|
|
771
|
-
// Access the final iteration
|
|
772
|
-
const lastIteration = result.iteration
|
|
773
|
-
if (lastIteration) {
|
|
774
|
-
console.log('Generated code:', lastIteration.code)
|
|
775
|
-
console.log('Status:', lastIteration.status.type)
|
|
776
|
-
console.log('Duration:', lastIteration.duration)
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Access all iterations to see full execution flow
|
|
780
|
-
result.iterations.forEach((iteration, index) => {
|
|
781
|
-
console.log(`Iteration ${index + 1}:`)
|
|
782
|
-
console.log(' Status:', iteration.status.type)
|
|
783
|
-
console.log(' Code length:', iteration.code?.length || 0)
|
|
784
|
-
console.log(' Variables:', Object.keys(iteration.variables).length)
|
|
785
|
-
})
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
#### Variables and Declarations
|
|
789
|
-
|
|
790
|
-
```typescript
|
|
791
|
-
// If agent generates: const hello = '1234'
|
|
792
|
-
const lastIteration = result.iteration
|
|
793
|
-
if (lastIteration) {
|
|
794
|
-
console.log(lastIteration.variables.hello) // '1234'
|
|
795
|
-
|
|
796
|
-
// Access all variables from the final iteration
|
|
797
|
-
Object.entries(lastIteration.variables).forEach(([name, value]) => {
|
|
798
|
-
console.log(`Variable ${name}:`, value)
|
|
799
|
-
})
|
|
800
|
-
}
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
#### Tool Calls and Traces
|
|
804
|
-
|
|
805
|
-
```typescript
|
|
806
|
-
// Access tool calls from all iterations
|
|
807
|
-
const allToolCalls = result.iterations.flatMap((iter) => iter.traces.filter((trace) => trace.type === 'tool_call'))
|
|
808
|
-
|
|
809
|
-
console.log('Total tool calls:', allToolCalls.length)
|
|
810
|
-
|
|
811
|
-
// Access other trace types
|
|
812
|
-
const lastIteration = result.iteration
|
|
813
|
-
if (lastIteration) {
|
|
814
|
-
const yields = lastIteration.traces.filter((trace) => trace.type === 'yield')
|
|
815
|
-
const comments = lastIteration.traces.filter((trace) => trace.type === 'comment')
|
|
816
|
-
const propertyAccess = lastIteration.traces.filter((trace) => trace.type === 'property')
|
|
817
|
-
}
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
#### Context and Metadata
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
823
|
-
if (result.isSuccess()) {
|
|
824
|
-
// Access original execution parameters
|
|
825
|
-
console.log('Instructions:', result.context.instructions)
|
|
826
|
-
console.log('Loop limit:', result.context.loop)
|
|
827
|
-
console.log('Temperature:', result.context.temperature)
|
|
828
|
-
console.log('Model:', result.context.model)
|
|
829
|
-
|
|
830
|
-
// Access tools and exits that were available
|
|
831
|
-
console.log(
|
|
832
|
-
'Available tools:',
|
|
833
|
-
result.context.tools?.map((t) => t.name)
|
|
834
|
-
)
|
|
835
|
-
console.log(
|
|
836
|
-
'Available exits:',
|
|
837
|
-
result.context.exits?.map((e) => e.name)
|
|
838
|
-
)
|
|
839
|
-
}
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
### Error Analysis
|
|
843
|
-
|
|
844
|
-
```typescript
|
|
845
|
-
if (result.isError()) {
|
|
846
|
-
console.error('Execution failed:', result.error)
|
|
847
|
-
|
|
848
|
-
// Analyze the failure progression
|
|
849
|
-
const failedIteration = result.iteration
|
|
850
|
-
if (failedIteration) {
|
|
851
|
-
switch (failedIteration.status.type) {
|
|
852
|
-
case 'execution_error':
|
|
853
|
-
console.error('Code execution failed:', failedIteration.status.execution_error.message)
|
|
854
|
-
console.error('Stack trace:', failedIteration.status.execution_error.stack)
|
|
855
|
-
console.error('Failed code:', failedIteration.code)
|
|
856
|
-
break
|
|
857
|
-
|
|
858
|
-
case 'generation_error':
|
|
859
|
-
console.error('LLM generation failed:', failedIteration.status.generation_error.message)
|
|
860
|
-
break
|
|
861
|
-
|
|
862
|
-
case 'invalid_code_error':
|
|
863
|
-
console.error('Invalid code generated:', failedIteration.status.invalid_code_error.message)
|
|
864
|
-
console.error('Invalid code:', failedIteration.code)
|
|
865
|
-
break
|
|
866
|
-
|
|
867
|
-
case 'aborted':
|
|
868
|
-
console.error('Execution aborted:', failedIteration.status.aborted.reason)
|
|
869
|
-
break
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Review all iterations to understand failure progression
|
|
874
|
-
console.log('Iterations before failure:', result.iterations.length)
|
|
875
|
-
result.iterations.forEach((iter, i) => {
|
|
876
|
-
console.log(`Iteration ${i + 1}: ${iter.status.type}`)
|
|
877
|
-
})
|
|
878
|
-
}
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
### Snapshot Handling
|
|
882
|
-
|
|
883
|
-
Handle interrupted executions with snapshot resumption:
|
|
884
|
-
|
|
885
|
-
```typescript
|
|
886
|
-
const result = await execute({
|
|
887
|
-
instructions: 'Process large dataset with pauseable operation',
|
|
888
|
-
tools: [snapshotCapableTool],
|
|
889
|
-
client,
|
|
890
|
-
})
|
|
891
|
-
|
|
892
|
-
if (result.isInterrupted()) {
|
|
893
|
-
console.log('Execution paused:', result.signal.message)
|
|
894
|
-
|
|
895
|
-
// Serialize snapshot for persistence
|
|
896
|
-
const serialized = result.snapshot.toJSON()
|
|
897
|
-
await database.saveSnapshot('execution-123', serialized)
|
|
898
|
-
|
|
899
|
-
// Later, resume from snapshot
|
|
900
|
-
const snapshot = Snapshot.fromJSON(serialized)
|
|
901
|
-
snapshot.resolve({ resumeData: 'Operation completed' })
|
|
902
|
-
|
|
903
|
-
const continuation = await execute({
|
|
904
|
-
snapshot,
|
|
905
|
-
instructions: result.context.instructions,
|
|
906
|
-
tools: result.context.tools,
|
|
907
|
-
exits: result.context.exits,
|
|
908
|
-
client,
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
if (continuation.isSuccess()) {
|
|
912
|
-
console.log('Resumed execution completed:', continuation.output)
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
---
|
|
918
|
-
|
|
919
|
-
## Hooks System
|
|
920
|
-
|
|
921
|
-
LLMz provides a comprehensive hook system that allows you to inject custom logic at various points during execution. Hooks are categorized as either blocking (execution waits) or non-blocking, and either mutation (can modify data) or non-mutation.
|
|
922
|
-
|
|
923
|
-
### Hook Types Overview
|
|
924
|
-
|
|
925
|
-
| Hook | Blocking | Mutation | Called When |
|
|
926
|
-
| ------------------- | -------- | -------- | -------------------------- |
|
|
927
|
-
| `onTrace` | ❌ | ❌ | Each trace generated |
|
|
928
|
-
| `onIterationEnd` | ✅ | ❌ | After iteration completion |
|
|
929
|
-
| `onExit` | ✅ | ❌ | When exit is reached |
|
|
930
|
-
| `onBeforeExecution` | ✅ | ✅ | Before code execution |
|
|
931
|
-
| `onBeforeTool` | ✅ | ✅ | Before tool execution |
|
|
932
|
-
| `onAfterTool` | ✅ | ✅ | After tool execution |
|
|
933
|
-
|
|
934
|
-
### onTrace (Non-blocking, Non-mutation)
|
|
935
|
-
|
|
936
|
-
Called for each trace generated during iteration. Useful for logging, debugging, or monitoring execution progress.
|
|
937
|
-
|
|
938
|
-
```typescript
|
|
939
|
-
await execute({
|
|
940
|
-
onTrace: ({ trace, iteration }) => {
|
|
941
|
-
console.log(`Iteration ${iteration}: ${trace.type}`, trace)
|
|
942
|
-
|
|
943
|
-
// Log specific trace types
|
|
944
|
-
if (trace.type === 'tool_call') {
|
|
945
|
-
console.log(`Tool ${trace.tool_name} called with:`, trace.input)
|
|
946
|
-
}
|
|
947
|
-
},
|
|
948
|
-
// ... other props
|
|
949
|
-
})
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
**Available Trace Types:**
|
|
953
|
-
|
|
954
|
-
- `abort_signal`: Abort signal received
|
|
955
|
-
- `comment`: Comment found in generated code
|
|
956
|
-
- `llm_call_success`: LLM generation completed successfully
|
|
957
|
-
- `property`: Object property accessed or modified
|
|
958
|
-
- `think_signal`: ThinkSignal thrown
|
|
959
|
-
- `tool_call`: Tool executed
|
|
960
|
-
- `yield`: Component yielded in chat mode
|
|
961
|
-
- `log`: General logging event
|
|
962
|
-
|
|
963
|
-
### onIterationEnd (Blocking, Non-mutation)
|
|
964
|
-
|
|
965
|
-
Called after each iteration ends, regardless of status. Useful for logging, cleanup, or controlling iteration timing.
|
|
966
|
-
|
|
967
|
-
```typescript
|
|
968
|
-
await execute({
|
|
969
|
-
onIterationEnd: async (iteration, controller) => {
|
|
970
|
-
console.log(`Iteration ${iteration.id} ended with status: ${iteration.status.type}`)
|
|
971
|
-
|
|
972
|
-
// Add delays, cleanup, or conditional logic
|
|
973
|
-
if (iteration.status.type === 'execution_error') {
|
|
974
|
-
await logError(iteration.error)
|
|
975
|
-
|
|
976
|
-
// Add delay before retry
|
|
977
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// Can use controller to abort execution if needed
|
|
981
|
-
if (shouldAbort(iteration)) {
|
|
982
|
-
controller.abort('Custom abort reason')
|
|
983
|
-
}
|
|
192
|
+
import { Chat, Component } from 'llmz'
|
|
193
|
+
|
|
194
|
+
const cards = new Component({
|
|
195
|
+
name: 'cards',
|
|
196
|
+
description: 'Show a list of products.',
|
|
197
|
+
props: z.array(z.object({ title: z.string(), price: z.number() })),
|
|
198
|
+
handler: (items, metadata) => {
|
|
199
|
+
console.log(metadata.id, items)
|
|
984
200
|
},
|
|
985
|
-
// ... other props
|
|
986
201
|
})
|
|
202
|
+
const chat = new Chat({ components: [cards] })
|
|
203
|
+
const rendered = cards.render([{ title: 'Coffee', price: 12 }])
|
|
204
|
+
console.log(rendered.props)
|
|
987
205
|
```
|
|
988
206
|
|
|
989
|
-
|
|
207
|
+
The model calls `chat.cards(...)`. `render` parses props once and captures an immutable delivery value. `withHandler` binds delivery without changing a reusable definition. `DefaultComponents` provides ready-made rich message definitions; bind handlers before using them for delivery.
|
|
990
208
|
|
|
991
|
-
|
|
209
|
+
`Chat.response` accepts `markdown`, `text`, or `speech`, or `{ preset, instructions, handler, onDelta }`. Speech is a prose style for text-to-speech; it does not synthesize audio.
|
|
992
210
|
|
|
993
|
-
|
|
994
|
-
await execute({
|
|
995
|
-
onExit: async (result) => {
|
|
996
|
-
console.log(`Exiting with: ${result.exit.name}`, result.result)
|
|
211
|
+
## Streaming and delivery
|
|
997
212
|
|
|
998
|
-
|
|
999
|
-
if (result.exit.name === 'approve_loan' && result.result.amount > 10000) {
|
|
1000
|
-
throw new Error('Manager approval required for loans over $10,000')
|
|
1001
|
-
}
|
|
213
|
+
`response.onDelta` receives previews. Its normal event has `delta`, accumulated `content`, `id`, and `iterationId`. A restart event has `restart: true`, an iteration ID, attempt number, models, and reason. Retract the prior preview before displaying replacement text.
|
|
1002
214
|
|
|
1003
|
-
|
|
1004
|
-
await notifyStakeholders(result)
|
|
215
|
+
`response.handler` receives the accepted complete text and its message metadata. Components use their own handlers. Without handlers, native text is still retained in session history, but does not count as delivered speech for `requireChatResponse`.
|
|
1005
216
|
|
|
1006
|
-
|
|
1007
|
-
await auditLog.record({
|
|
1008
|
-
action: result.exit.name,
|
|
1009
|
-
data: result.result,
|
|
1010
|
-
timestamp: Date.now(),
|
|
1011
|
-
})
|
|
1012
|
-
},
|
|
1013
|
-
// ... other props
|
|
1014
|
-
})
|
|
1015
|
-
```
|
|
217
|
+
Complete JavaScript calls can execute while streaming continues. If a stream fails after dispatch, the runtime retains completed effects and reports the interruption; it does not replay the program on another model. `options.midStreamFallback` enables restarts before dispatch and requires a preview consumer capable of handling retractions. Normal preview callback failures are ignored; failed retractions stop generation. A final delivery failure can fail execution.
|
|
1016
218
|
|
|
1017
|
-
|
|
219
|
+
## Token budgets and compaction
|
|
1018
220
|
|
|
1019
|
-
|
|
221
|
+
`options.maxTokens` caps the context window, not output alone. It must be a positive safe integer. The effective limit is the smaller of this cap and every configured model's input limit. A fallback list uses the smallest input and output limits so the same request fits every candidate.
|
|
1020
222
|
|
|
1021
|
-
|
|
1022
|
-
await execute({
|
|
1023
|
-
onBeforeExecution: async (iteration, controller) => {
|
|
1024
|
-
console.log('Generated code:', iteration.code)
|
|
223
|
+
The runtime reserves output space before fitting input: 10% of the effective limit, with a 256-token floor and 16,000-token ceiling, also bounded by the model's output limit and space for input. A window too small for the request fails before model dispatch.
|
|
1025
224
|
|
|
1026
|
-
|
|
1027
|
-
if (iteration.code?.includes('dangerousOperation')) {
|
|
1028
|
-
return {
|
|
1029
|
-
code: iteration.code.replace('dangerousOperation', 'safeOperation'),
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
225
|
+
Input measurement includes system instructions, message scaffolding, tools, JavaScript arguments, tool receipts, memory previews, and execution-budget guidance. Enforcement uses exact tokenization with the configured local tokenizer. This remains an estimate of the provider's request accounting: model tokenizers and provider framing can differ. Image/audio transport URLs and encoded bytes are excluded from text measurement; provider-specific media token costs are not available locally. Media-shaped business arguments and ordinary strings are counted as text.
|
|
1032
226
|
|
|
1033
|
-
|
|
1034
|
-
if (iteration.code?.includes('forbidden')) {
|
|
1035
|
-
throw new Error('Forbidden operation detected')
|
|
1036
|
-
}
|
|
227
|
+
Automatic compaction is enabled by default. At 85% of the available input budget, it aims for 65%, preferring to keep the two most recent iterations. A hard overflow can require retaining fewer iterations. The compactor asks an LLM to summarize the discarded conversation, including decisions, external events, confirmed effects, failures, and pending work. Large histories are summarized in bounded segments; each request reserves output space and includes the previous segment's summary.
|
|
1037
228
|
|
|
1038
|
-
|
|
1039
|
-
const securityIssues = await scanCodeForSecurity(iteration.code)
|
|
1040
|
-
if (securityIssues.length > 0) {
|
|
1041
|
-
throw new Error(`Security issues found: ${securityIssues.join(', ')}`)
|
|
1042
|
-
}
|
|
229
|
+
A successful compaction inserts a `{ role: 'summary', content }` event at the start of the retained transcript. It replaces earlier compaction summaries, preserving their information in the new summary. Native requests receive the summary as labeled user-role data. The summarizer cannot call tools. Summaries are lossy model output; named JavaScript memory stays exact and is never reconstructed from them.
|
|
1043
230
|
|
|
1044
|
-
|
|
1045
|
-
|
|
231
|
+
```ts
|
|
232
|
+
import { Client } from '@botpress/client'
|
|
233
|
+
import { Session } from 'llmz'
|
|
234
|
+
|
|
235
|
+
const client = new Client()
|
|
236
|
+
const session = new Session({
|
|
237
|
+
compaction: {
|
|
238
|
+
triggerRatio: 0.85,
|
|
239
|
+
targetRatio: 0.65,
|
|
240
|
+
keepRecentIterations: 2,
|
|
241
|
+
maxSummaryTokens: 1024,
|
|
242
|
+
model: 'fast',
|
|
1046
243
|
},
|
|
1047
|
-
// ... other props
|
|
1048
244
|
})
|
|
1049
|
-
```
|
|
1050
|
-
|
|
1051
|
-
### onBeforeTool (Blocking, Mutation)
|
|
1052
|
-
|
|
1053
|
-
Called before any tool execution. Allows input modification and tool execution control.
|
|
1054
|
-
|
|
1055
|
-
```typescript
|
|
1056
|
-
await execute({
|
|
1057
|
-
onBeforeTool: async ({ iteration, tool, input, controller }) => {
|
|
1058
|
-
console.log(`Executing tool: ${tool.name}`, input)
|
|
1059
|
-
|
|
1060
|
-
// Input modification
|
|
1061
|
-
if (tool.name === 'sendEmail') {
|
|
1062
|
-
return {
|
|
1063
|
-
input: {
|
|
1064
|
-
...input,
|
|
1065
|
-
subject: `[Automated] ${input.subject}`, // Add prefix
|
|
1066
|
-
from: 'noreply@company.com', // Override sender
|
|
1067
|
-
},
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
// Access control
|
|
1072
|
-
if (tool.name === 'deleteFile' && !hasPermission(input.path)) {
|
|
1073
|
-
throw new Error('Insufficient permissions to delete file')
|
|
1074
|
-
}
|
|
1075
245
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (rateLimit.exceeded) {
|
|
1079
|
-
throw new Error(`Rate limit exceeded for ${tool.name}`)
|
|
1080
|
-
}
|
|
246
|
+
// Read-only: returns a summary message without changing the conversation.
|
|
247
|
+
const summary = await session.summarize({ client })
|
|
1081
248
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
// ... other props
|
|
1086
|
-
})
|
|
249
|
+
// Replace older history now, preserving the most recent iteration.
|
|
250
|
+
await session.compact({ client, keepRecentIterations: 1 })
|
|
251
|
+
console.log(summary, session.transcript)
|
|
1087
252
|
```
|
|
1088
253
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
Called after tool execution. Allows output modification and post-processing.
|
|
1092
|
-
|
|
1093
|
-
```typescript
|
|
1094
|
-
await execute({
|
|
1095
|
-
onAfterTool: async ({ iteration, tool, input, output, controller }) => {
|
|
1096
|
-
console.log(`Tool ${tool.name} completed`, { input, output })
|
|
1097
|
-
|
|
1098
|
-
// Output modification
|
|
1099
|
-
if (tool.name === 'fetchUserData') {
|
|
1100
|
-
return {
|
|
1101
|
-
output: {
|
|
1102
|
-
...output,
|
|
1103
|
-
// Remove sensitive data before LLM sees it
|
|
1104
|
-
ssn: undefined,
|
|
1105
|
-
creditCard: undefined,
|
|
1106
|
-
// Add metadata
|
|
1107
|
-
fetchedAt: Date.now(),
|
|
1108
|
-
},
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
// Result enhancement
|
|
1113
|
-
if (tool.name === 'calculatePrice') {
|
|
1114
|
-
return {
|
|
1115
|
-
output: {
|
|
1116
|
-
...output,
|
|
1117
|
-
currency: 'USD',
|
|
1118
|
-
timestamp: Date.now(),
|
|
1119
|
-
exchangeRate: await getCurrentExchangeRate(),
|
|
1120
|
-
},
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
254
|
+
Without an explicit compaction model, automatic summaries use the execution model; standalone `summarize` and `compact` use `fast`. Summary generation makes additional model calls. `maxSummaryTokens` is a ceiling; model limits and remaining request space may reduce it. `signal` cancels explicit summarization or compaction.
|
|
1123
255
|
|
|
1124
|
-
|
|
1125
|
-
await Promise.all([
|
|
1126
|
-
cacheResult(tool.name, input, output),
|
|
1127
|
-
logToolExecution(tool.name, input, output),
|
|
1128
|
-
updateMetrics(tool.name, Date.now() - tool.startTime),
|
|
1129
|
-
])
|
|
1130
|
-
},
|
|
1131
|
-
// ... other props
|
|
1132
|
-
})
|
|
1133
|
-
```
|
|
256
|
+
Set `compaction: false` to disable automatic summarization; requests that cannot fit then fail without dropping history. A custom `compaction.summarize({ messages, maxTokens, signal })` can return summary text; the same validation and token limit still apply. Serializable controls survive `toJSON()` / `fromJSON()`. Reattach a custom callback with `Session.fromJSON(state, { compaction: { ...controls, summarize } })`.
|
|
1134
257
|
|
|
1135
|
-
|
|
258
|
+
Compaction retains complete call/result groups, current-turn input, pending iterations, queued input, and named memory. Discarded iteration results are removed from `$iterations` and `$return`. Summary failure, cancellation, an oversized summary, or a failing `onBeforeRequest` hook leaves the original history intact. A prepared summary is committed only after the final request fits and its hooks succeed. Explicit `compact` and `summarize` acquire the session lock and cannot overlap execution.
|
|
1136
259
|
|
|
1137
|
-
For
|
|
260
|
+
For deliberate deletion without summarization, `session.prune(retainedIterationIds)` keeps the specified complete iterations. `requestMessages({ retainedIterationIds })` previews that selection. Use these lower-level methods only when discarding history is intentional.
|
|
1138
261
|
|
|
1139
|
-
|
|
1140
|
-
2. **onBeforeExecution**: After code generation, before execution
|
|
1141
|
-
3. **onBeforeTool**: Before each tool call
|
|
1142
|
-
4. **onAfterTool**: After each tool call
|
|
1143
|
-
5. **onExit**: When exit is reached
|
|
1144
|
-
6. **onIterationEnd**: After iteration completes
|
|
262
|
+
If required instructions, current input, tool definitions, or retained memory previews still exceed the budget, shorten them or use a larger context limit. LLMZ does not silently truncate current user input.
|
|
1145
263
|
|
|
1146
|
-
|
|
264
|
+
`result.tokens` reports provider usage summed over this run. Each iteration's `tokens.context` describes the measured request after compaction and hook replacement; its categories sum to `context.total`. These estimates and provider usage have different purposes.
|
|
1147
265
|
|
|
1148
|
-
|
|
266
|
+
## Inspection
|
|
1149
267
|
|
|
1150
|
-
|
|
1151
|
-
await execute({
|
|
1152
|
-
onBeforeTool: async ({ tool, input }) => {
|
|
1153
|
-
// Apply different logic based on tool
|
|
1154
|
-
switch (tool.name) {
|
|
1155
|
-
case 'payment':
|
|
1156
|
-
return await handlePaymentValidation(input)
|
|
1157
|
-
case 'notification':
|
|
1158
|
-
return await handleNotificationThrottling(input)
|
|
1159
|
-
default:
|
|
1160
|
-
return // No modification
|
|
1161
|
-
}
|
|
1162
|
-
},
|
|
1163
|
-
})
|
|
1164
|
-
```
|
|
268
|
+
Use `truncate` in a tool handler to set a value's display budget while retaining its full data:
|
|
1165
269
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
throw new Error('Retrying due to recoverable error')
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
},
|
|
1183
|
-
})
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
#### Hook State Management
|
|
1187
|
-
|
|
1188
|
-
```typescript
|
|
1189
|
-
let executionMetrics = { toolCalls: 0, totalTime: 0 }
|
|
1190
|
-
|
|
1191
|
-
await execute({
|
|
1192
|
-
onBeforeTool: async ({ tool }) => {
|
|
1193
|
-
executionMetrics.toolCalls++
|
|
1194
|
-
tool.startTime = Date.now()
|
|
1195
|
-
},
|
|
1196
|
-
|
|
1197
|
-
onAfterTool: async ({ tool }) => {
|
|
1198
|
-
executionMetrics.totalTime += Date.now() - tool.startTime
|
|
1199
|
-
},
|
|
1200
|
-
|
|
1201
|
-
onIterationEnd: async () => {
|
|
1202
|
-
console.log('Execution metrics:', executionMetrics)
|
|
1203
|
-
},
|
|
270
|
+
```ts
|
|
271
|
+
import { z } from '@bpinternal/zui'
|
|
272
|
+
import { Tool, truncate } from 'llmz'
|
|
273
|
+
|
|
274
|
+
const getEvidence = new Tool({
|
|
275
|
+
name: 'getEvidence',
|
|
276
|
+
output: z.string(),
|
|
277
|
+
handler: async () =>
|
|
278
|
+
truncate({
|
|
279
|
+
value: 'First section\nDetailed evidence\nLast section',
|
|
280
|
+
maxTokens: 800,
|
|
281
|
+
preserve: 'both',
|
|
282
|
+
}),
|
|
1204
283
|
})
|
|
1205
284
|
```
|
|
1206
285
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
1. **Error Handling**: Always wrap hook logic in try-catch for production
|
|
1210
|
-
2. **Performance**: Keep hooks lightweight, especially `onTrace`
|
|
1211
|
-
3. **Security**: Use `onBeforeExecution` and `onBeforeTool` for security validation
|
|
1212
|
-
4. **Debugging**: Leverage `onTrace` for comprehensive execution monitoring
|
|
1213
|
-
5. **Guardrails**: Implement business logic validation in `onExit`
|
|
1214
|
-
6. **Data Transformation**: Use `onBeforeTool`/`onAfterTool` for input/output processing
|
|
1215
|
-
7. **Async Operations**: All hooks support async/await for external API calls
|
|
1216
|
-
8. **State Management**: Use closures or external state for cross-hook data sharing
|
|
1217
|
-
|
|
1218
|
-
---
|
|
1219
|
-
|
|
1220
|
-
## Advanced Features
|
|
1221
|
-
|
|
1222
|
-
### Snapshots (Pauseable Execution)
|
|
1223
|
-
|
|
1224
|
-
Snapshots allow you to pause and resume LLMz execution, enabling long-running workflows that can be interrupted and continued later.
|
|
286
|
+
`preserve` is `top`, `bottom`, or `both`. Budgets are nonnegative safe integers; zero hides the preview. `options.toolResultMaxTokens` defaults to 2,000 and accepts 0–2,000. An explicit `truncate` policy can raise or lower the inspected result budget. It cannot bypass the overall request limit. Tool-result preview budgets do not cap the entire execution report, which also contains status and activity.
|
|
1225
287
|
|
|
1226
|
-
|
|
288
|
+
Inventories and diagnostic previews keep their own small budgets even if a value carries a larger inspection override. Truncation markers are included in the final measured output. Unicode boundaries, cyclic structures, throwing formatters, and accessor properties are handled without changing retained data.
|
|
1227
289
|
|
|
1228
|
-
|
|
290
|
+
Both `return inspect(value)` and forced ThinkSignal inspection use the native inspector. Standalone text is displayed directly, preserving whitespace, code, and literal tags within its preview budget. Report sections add no XML entity escaping or CDATA. Strings nested in objects or arrays use JSON quoting so source indentation, line endings, and literal escapes remain distinct from the preview's formatting.
|
|
1229
291
|
|
|
1230
|
-
|
|
1231
|
-
import { SnapshotSignal, Tool } from 'llmz'
|
|
292
|
+
`onInspect` customizes previews by purpose and identity. Return `undefined` for the default formatter. The hook receives an isolated read-only snapshot; custom text is still bounded. A formatter failure falls back to default inspection.
|
|
1232
293
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
input: z.object({ datasetId: z.string() }),
|
|
1236
|
-
async handler({ datasetId }) {
|
|
1237
|
-
// Start processing
|
|
1238
|
-
const dataset = await loadDataset(datasetId)
|
|
294
|
+
```ts
|
|
295
|
+
import { createInspector, inspect, type ExecutionHooks } from 'llmz'
|
|
1239
296
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
'Processing will continue once background job completes'
|
|
1245
|
-
)
|
|
297
|
+
const hooks: ExecutionHooks = {
|
|
298
|
+
onInspect: (event) => {
|
|
299
|
+
if (event.purpose === 'variable' && event.identity?.variable === 'apiKey') {
|
|
300
|
+
return '[redacted]'
|
|
1246
301
|
}
|
|
1247
|
-
|
|
1248
|
-
return { processed: true }
|
|
302
|
+
return undefined
|
|
1249
303
|
},
|
|
1250
|
-
})
|
|
1251
|
-
```
|
|
1252
|
-
|
|
1253
|
-
#### Snapshot Handling
|
|
1254
|
-
|
|
1255
|
-
```typescript
|
|
1256
|
-
const result = await execute({
|
|
1257
|
-
instructions: 'Process the uploaded dataset',
|
|
1258
|
-
tools: [longRunningTool],
|
|
1259
|
-
client,
|
|
1260
|
-
})
|
|
1261
|
-
|
|
1262
|
-
if (result.isInterrupted()) {
|
|
1263
|
-
console.log('Execution paused:', result.signal.message)
|
|
1264
|
-
|
|
1265
|
-
// Serialize snapshot for persistence
|
|
1266
|
-
const serialized = result.snapshot.toJSON()
|
|
1267
|
-
await database.saveSnapshot('job-123', serialized)
|
|
1268
|
-
|
|
1269
|
-
// Start background processing
|
|
1270
|
-
await backgroundJobQueue.add('process-dataset', {
|
|
1271
|
-
snapshotId: 'job-123',
|
|
1272
|
-
datasetId: result.signal.toolCall?.input.datasetId,
|
|
1273
|
-
})
|
|
1274
304
|
}
|
|
305
|
+
const inspector = createInspector(hooks.onInspect)
|
|
306
|
+
console.log(inspector({ count: 3 }, { purpose: 'result', maxTokens: 40 }))
|
|
307
|
+
console.log(inspect({ count: 3 }, undefined, { tokens: 40, compact: true }))
|
|
1275
308
|
```
|
|
1276
309
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
```typescript
|
|
1280
|
-
// Later, when background job completes
|
|
1281
|
-
const serialized = await database.getSnapshot('job-123')
|
|
1282
|
-
const snapshot = Snapshot.fromJSON(serialized)
|
|
310
|
+
A redacted preview is not a security boundary: the underlying value still exists in memory and can be used by JavaScript or tools. Keep secrets out of model-accessible state.
|
|
1283
311
|
|
|
1284
|
-
|
|
1285
|
-
snapshot.resolve({
|
|
1286
|
-
processed: true,
|
|
1287
|
-
recordCount: 1000000,
|
|
1288
|
-
processingTime: 3600000,
|
|
1289
|
-
})
|
|
312
|
+
## Hooks
|
|
1290
313
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1302
|
-
```
|
|
314
|
+
| Hook | Purpose |
|
|
315
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
316
|
+
| `onIterationStart` | Observe the iteration before generation. Use getters for dynamic configuration. |
|
|
317
|
+
| `onBeforeRequest` | Return replacement native messages after tentative compaction. The replacement must fit the budget. |
|
|
318
|
+
| `onBeforeExecution` | Optionally return replacement JavaScript code. |
|
|
319
|
+
| `onBeforeTool` / `onAfterTool` | Inspect or replace a business tool's input/output. |
|
|
320
|
+
| `onInspect` | Format bounded previews without changing retained values. |
|
|
321
|
+
| `onTrace` | Observe execution traces. Observer exceptions do not alter completed actions. |
|
|
322
|
+
| `onIterationEnd` | Observe a settled iteration. |
|
|
323
|
+
| `onExit` | Validate or handle a proposed completion. |
|
|
1303
324
|
|
|
1304
|
-
|
|
325
|
+
```ts
|
|
326
|
+
import type { ExecutionHooks } from 'llmz'
|
|
1305
327
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
snapshot,
|
|
1313
|
-
// ... same parameters
|
|
1314
|
-
})
|
|
1315
|
-
|
|
1316
|
-
// The agent will receive the error and can handle it
|
|
1317
|
-
```
|
|
1318
|
-
|
|
1319
|
-
### Thinking (Agent Reflection)
|
|
1320
|
-
|
|
1321
|
-
The thinking system allows agents to pause and reflect on variables and context before proceeding.
|
|
1322
|
-
|
|
1323
|
-
#### ThinkSignal (Tool-Initiated)
|
|
1324
|
-
|
|
1325
|
-
Tools can force thinking by throwing a `ThinkSignal`:
|
|
1326
|
-
|
|
1327
|
-
```typescript
|
|
1328
|
-
const analysisTool = new Tool({
|
|
1329
|
-
name: 'analyzeData',
|
|
1330
|
-
input: z.object({ data: z.array(z.number()) }),
|
|
1331
|
-
async handler({ data }) {
|
|
1332
|
-
const result = performAnalysis(data)
|
|
1333
|
-
|
|
1334
|
-
// Force the agent to think about the results before responding
|
|
1335
|
-
throw new ThinkSignal(
|
|
1336
|
-
'Analysis complete, consider the implications',
|
|
1337
|
-
`Found ${result.anomalies.length} anomalies and ${result.patterns.length} patterns`
|
|
1338
|
-
)
|
|
1339
|
-
},
|
|
1340
|
-
})
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
#### Agent-Initiated Thinking
|
|
1344
|
-
|
|
1345
|
-
Agents can request thinking time in generated code:
|
|
1346
|
-
|
|
1347
|
-
```typescript
|
|
1348
|
-
// In generated code
|
|
1349
|
-
const analysisResult = await analyzeData({ data: userInputData })
|
|
1350
|
-
|
|
1351
|
-
// Think about the results before responding to user
|
|
1352
|
-
return { action: 'think' }
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
#### Thinking with Variables
|
|
1356
|
-
|
|
1357
|
-
Pass specific variables for reflection:
|
|
1358
|
-
|
|
1359
|
-
```typescript
|
|
1360
|
-
// In generated code
|
|
1361
|
-
const price = await calculatePrice({ items: cartItems })
|
|
1362
|
-
const budget = await getUserBudget()
|
|
1363
|
-
|
|
1364
|
-
// Think about pricing vs budget with specific context
|
|
1365
|
-
return {
|
|
1366
|
-
action: 'think',
|
|
1367
|
-
price,
|
|
1368
|
-
budget,
|
|
1369
|
-
recommendation: price > budget ? 'deny' : 'approve',
|
|
1370
|
-
}
|
|
1371
|
-
```
|
|
1372
|
-
|
|
1373
|
-
#### Handling Think Results
|
|
1374
|
-
|
|
1375
|
-
```typescript
|
|
1376
|
-
const result = await execute({
|
|
1377
|
-
instructions: 'Analyze the user data and provide recommendations',
|
|
1378
|
-
tools: [analysisTool],
|
|
1379
|
-
client,
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
if (result.is(ThinkExit)) {
|
|
1383
|
-
console.log('Agent is thinking about:', result.output.variables)
|
|
1384
|
-
|
|
1385
|
-
// Continue execution after thinking
|
|
1386
|
-
const continuation = await execute({
|
|
1387
|
-
instructions: result.context.instructions,
|
|
1388
|
-
tools: result.context.tools,
|
|
1389
|
-
// Variables from thinking are automatically preserved
|
|
1390
|
-
client,
|
|
1391
|
-
})
|
|
1392
|
-
}
|
|
1393
|
-
```
|
|
1394
|
-
|
|
1395
|
-
### Citations (RAG Support)
|
|
1396
|
-
|
|
1397
|
-
CitationsManager provides standardized source tracking and referencing for RAG (Retrieval-Augmented Generation) systems.
|
|
1398
|
-
|
|
1399
|
-
#### Core Concepts
|
|
1400
|
-
|
|
1401
|
-
Citations use rare Unicode symbols (`【】`) as markers that are unlikely to appear in natural text. The system supports:
|
|
1402
|
-
|
|
1403
|
-
- **Source Registration**: Register any object as a citation source
|
|
1404
|
-
- **Tag Generation**: Automatic creation of unique citation tags like `【0】`, `【1】`
|
|
1405
|
-
- **Content Processing**: Extract and clean citation tags from text
|
|
1406
|
-
- **Multiple Citations**: Support for multi-source citations like `【0,1,3】`
|
|
1407
|
-
|
|
1408
|
-
#### Basic Usage
|
|
1409
|
-
|
|
1410
|
-
```typescript
|
|
1411
|
-
import { CitationsManager } from 'llmz'
|
|
1412
|
-
|
|
1413
|
-
const citations = new CitationsManager()
|
|
1414
|
-
|
|
1415
|
-
// Register sources and get citation tags
|
|
1416
|
-
const source1 = citations.registerSource({
|
|
1417
|
-
file: 'document.pdf',
|
|
1418
|
-
page: 5,
|
|
1419
|
-
title: 'Company Policy',
|
|
1420
|
-
})
|
|
1421
|
-
|
|
1422
|
-
const source2 = citations.registerSource({
|
|
1423
|
-
url: 'https://example.com/article',
|
|
1424
|
-
title: 'Best Practices',
|
|
1425
|
-
})
|
|
1426
|
-
|
|
1427
|
-
console.log(source1.tag) // "【0】"
|
|
1428
|
-
console.log(source2.tag) // "【1】"
|
|
1429
|
-
|
|
1430
|
-
// Use tags in content
|
|
1431
|
-
const content = `The policy states employees must arrive on time${source1.tag}. However, best practices suggest flexibility${source2.tag}.`
|
|
1432
|
-
```
|
|
1433
|
-
|
|
1434
|
-
#### RAG Implementation Example
|
|
1435
|
-
|
|
1436
|
-
```typescript
|
|
1437
|
-
const ragTool = new Tool({
|
|
1438
|
-
name: 'search',
|
|
1439
|
-
description: 'Searches in the knowledge base for relevant information.',
|
|
1440
|
-
input: z.string().describe('The query to search in the knowledge base.'),
|
|
1441
|
-
async handler(query) {
|
|
1442
|
-
// Perform semantic search
|
|
1443
|
-
const { passages } = await client.searchFiles({
|
|
1444
|
-
query,
|
|
1445
|
-
limit: 20,
|
|
1446
|
-
contextDepth: 3,
|
|
1447
|
-
})
|
|
1448
|
-
|
|
1449
|
-
if (!passages.length) {
|
|
1450
|
-
throw new ThinkSignal(
|
|
1451
|
-
'No results found',
|
|
1452
|
-
'No results were found in the knowledge base. Try rephrasing your question.'
|
|
1453
|
-
)
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
// Build response with citations
|
|
1457
|
-
let message: string[] = ['Here are the search results:']
|
|
1458
|
-
let { tag: example } = chat.citations.registerSource({}) // Example citation
|
|
1459
|
-
|
|
1460
|
-
// Register each passage as a source
|
|
1461
|
-
for (const passage of passages) {
|
|
1462
|
-
const { tag } = chat.citations.registerSource({
|
|
1463
|
-
file: passage.file.key,
|
|
1464
|
-
title: passage.file.tags.title,
|
|
1465
|
-
})
|
|
1466
|
-
|
|
1467
|
-
message.push(`<${tag} file="${passage.file.key}">`)
|
|
1468
|
-
message.push(`**${passage.file.tags.title}**`)
|
|
1469
|
-
message.push(passage.content)
|
|
1470
|
-
message.push(`</${tag}>`)
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
// Provide context with citation instructions
|
|
1474
|
-
throw new ThinkSignal(
|
|
1475
|
-
`Got search results. When answering, you MUST add inline citations (eg: "The price is $10${example} ...")`,
|
|
1476
|
-
message.join('\n').trim()
|
|
1477
|
-
)
|
|
1478
|
-
},
|
|
1479
|
-
})
|
|
1480
|
-
```
|
|
1481
|
-
|
|
1482
|
-
#### Chat Integration
|
|
1483
|
-
|
|
1484
|
-
```typescript
|
|
1485
|
-
class CLIChat extends Chat {
|
|
1486
|
-
public citations: CitationsManager = new CitationsManager()
|
|
1487
|
-
|
|
1488
|
-
private async sendMessage(input: RenderedComponent) {
|
|
1489
|
-
if (input.type === 'Text') {
|
|
1490
|
-
let sources: string[] = []
|
|
1491
|
-
|
|
1492
|
-
// Extract citations and format them for display
|
|
1493
|
-
const { cleaned } = this.citations.extractCitations(input.text, (citation) => {
|
|
1494
|
-
let idx = chalk.bgGreenBright.black.bold(` ${sources.length + 1} `)
|
|
1495
|
-
sources.push(`${idx}: ${JSON.stringify(citation.source)}`)
|
|
1496
|
-
return `${idx}` // Replace 【0】 with [1]
|
|
1497
|
-
})
|
|
1498
|
-
|
|
1499
|
-
// Display cleaned text and sources
|
|
1500
|
-
console.log(`🤖 Agent: ${cleaned}`)
|
|
1501
|
-
|
|
1502
|
-
if (sources.length) {
|
|
1503
|
-
console.log(chalk.dim('Citations'))
|
|
1504
|
-
console.log(chalk.dim('========='))
|
|
1505
|
-
console.log(chalk.dim(sources.join('\n')))
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
```
|
|
1511
|
-
|
|
1512
|
-
#### Advanced Citation Features
|
|
1513
|
-
|
|
1514
|
-
**Multiple Citation Support:**
|
|
1515
|
-
|
|
1516
|
-
```typescript
|
|
1517
|
-
// Agent can reference multiple sources in one citation
|
|
1518
|
-
const content = 'This fact is supported by multiple studies【0,1,3】'
|
|
1519
|
-
|
|
1520
|
-
const { cleaned, citations } = manager.extractCitations(content)
|
|
1521
|
-
// citations array contains entries for sources 0, 1, and 3
|
|
1522
|
-
```
|
|
1523
|
-
|
|
1524
|
-
**Object Citation Processing:**
|
|
1525
|
-
|
|
1526
|
-
```typescript
|
|
1527
|
-
// Remove citations from complex objects
|
|
1528
|
-
const dataWithCitations = {
|
|
1529
|
-
summary: 'The report shows positive trends【0】',
|
|
1530
|
-
details: {
|
|
1531
|
-
revenue: 'Increased by 15%【1】',
|
|
1532
|
-
costs: 'Reduced by 8%【2】',
|
|
328
|
+
const hooks: ExecutionHooks = {
|
|
329
|
+
onBeforeRequest: ({ messages }) => ({
|
|
330
|
+
messages: [{ role: 'system', content: 'Use concise language.' }, ...messages],
|
|
331
|
+
}),
|
|
332
|
+
onTrace: ({ trace }) => {
|
|
333
|
+
console.debug(trace.type)
|
|
1533
334
|
},
|
|
1534
335
|
}
|
|
1535
|
-
|
|
1536
|
-
const [cleanData, extractedCitations] = manager.removeCitationsFromObject(dataWithCitations)
|
|
1537
|
-
// cleanData has citations removed, extractedCitations contains path + citation info
|
|
1538
|
-
```
|
|
1539
|
-
|
|
1540
|
-
**Citation Stripping:**
|
|
1541
|
-
|
|
1542
|
-
```typescript
|
|
1543
|
-
// Remove all citation tags from content
|
|
1544
|
-
const textWithCitations = 'This statement【0】 has multiple【1,2】 citations.'
|
|
1545
|
-
const cleaned = CitationsManager.stripCitationTags(textWithCitations)
|
|
1546
|
-
// Result: "This statement has multiple citations."
|
|
1547
|
-
```
|
|
1548
|
-
|
|
1549
|
-
### Dynamic Context
|
|
1550
|
-
|
|
1551
|
-
LLMz supports dynamic evaluation of most parameters, allowing context-aware configuration:
|
|
1552
|
-
|
|
1553
|
-
```typescript
|
|
1554
|
-
await execute({
|
|
1555
|
-
// Dynamic instructions based on context
|
|
1556
|
-
instructions: (ctx) => {
|
|
1557
|
-
const timeOfDay = new Date().getHours()
|
|
1558
|
-
const greeting = timeOfDay < 12 ? 'Good morning' : 'Good afternoon'
|
|
1559
|
-
return `${greeting}! You are a helpful assistant with access to ${ctx.tools?.length || 0} tools.`
|
|
1560
|
-
},
|
|
1561
|
-
|
|
1562
|
-
// Dynamic tools based on user permissions
|
|
1563
|
-
tools: async (ctx) => {
|
|
1564
|
-
const userPermissions = await getUserPermissions(ctx.userId)
|
|
1565
|
-
return allTools.filter((tool) => userPermissions.includes(tool.permission))
|
|
1566
|
-
},
|
|
1567
|
-
|
|
1568
|
-
// Dynamic objects with current state
|
|
1569
|
-
objects: async (ctx) => {
|
|
1570
|
-
const userPreferences = await loadUserPreferences(ctx.userId)
|
|
1571
|
-
return [
|
|
1572
|
-
new ObjectInstance({
|
|
1573
|
-
name: 'user',
|
|
1574
|
-
properties: [{ name: 'preferences', value: userPreferences, writable: true }],
|
|
1575
|
-
}),
|
|
1576
|
-
]
|
|
1577
|
-
},
|
|
1578
|
-
|
|
1579
|
-
client,
|
|
1580
|
-
})
|
|
1581
|
-
```
|
|
1582
|
-
|
|
1583
|
-
---
|
|
1584
|
-
|
|
1585
|
-
### Mid-Stream Model Fallback (midStreamFallback)
|
|
1586
|
-
|
|
1587
|
-
By default, LLMz streams responses: message bodies (`■send` blocks) and completed `Chat.handler` sends are dispatched as soon as they are produced, and the ■run block starts executing while the model is still generating the rest of the response. This gives the lowest time-to-first-token but runs code before the response is complete.
|
|
1588
|
-
|
|
1589
|
-
Set `options.midStreamFallback: true` to allow Cognitive to restart a failed stream on another model. There is **no buffered delivery and no final delivery queue**: with fallback enabled, delta text and completed `Chat.handler` sends both stream immediately during generation. Only **TOOL/CODE execution** waits for a stream to complete successfully. When Cognitive abandons an attempt, a reset-only delta (`restart: true`) is delivered through the existing `Chat.onMessageDelta`; it invalidates **all** messages of the current iteration — **including messages already committed through `handler`** — so consumers must retract and replace them:
|
|
1590
|
-
|
|
1591
|
-
```typescript
|
|
1592
|
-
await execute({
|
|
1593
|
-
// ...
|
|
1594
|
-
chat: new MyChat({
|
|
1595
|
-
onMessageDelta: (delta) => {
|
|
1596
|
-
if (delta.restart) {
|
|
1597
|
-
// invalidates ALL current-iteration messages, including completed handler
|
|
1598
|
-
// sends: retract/replace them before the replacement streams
|
|
1599
|
-
return retractIteration(delta.iterationId)
|
|
1600
|
-
} else {
|
|
1601
|
-
// stream this message's text as it arrives (same iterationId as below)
|
|
1602
|
-
updateMessage(delta)
|
|
1603
|
-
}
|
|
1604
|
-
},
|
|
1605
|
-
}),
|
|
1606
|
-
options: { midStreamFallback: true },
|
|
1607
|
-
})
|
|
1608
|
-
```
|
|
1609
|
-
|
|
1610
|
-
`retractIteration`/`updateMessage` are placeholder names for your consumer's message store. **This option is not safe to enable blindly**: a reset invalidates messages that were already delivered through `handler`. If your transport cannot retract or replace an already-sent message — an external irreversible send such as SMS, email, or a third-party webhook — those sends cannot be undone, so keep `midStreamFallback` disabled unless your consumer can retract and replace invalidated messages.
|
|
1611
|
-
|
|
1612
|
-
#### MessageDelta contract
|
|
1613
|
-
|
|
1614
|
-
`Chat.onMessageDelta` receives a single typed `MessageDelta` discriminated union:
|
|
1615
|
-
|
|
1616
|
-
- **Ordinary text chunk** (`restart: false`) — the next piece of one message's body; all fields are required:
|
|
1617
|
-
```typescript
|
|
1618
|
-
{
|
|
1619
|
-
restart: false,
|
|
1620
|
-
iterationId: string, // stable across ALL attempts of this LLM generation iteration
|
|
1621
|
-
id: string, // message id — DIFFERENT per attempt (unique when fallback is on)
|
|
1622
|
-
component: string,
|
|
1623
|
-
props: Record<string, unknown>, // final by the time the body starts streaming
|
|
1624
|
-
delta: string, // new chunk of body text
|
|
1625
|
-
content: string, // full body text accumulated so far, including this chunk
|
|
1626
|
-
}
|
|
1627
|
-
```
|
|
1628
|
-
- **Reset-only delta** (`restart: true`) — carries no text; signals the current generation attempt was abandoned and a replacement is being attempted:
|
|
1629
|
-
```typescript
|
|
1630
|
-
{
|
|
1631
|
-
restart: true,
|
|
1632
|
-
iterationId: string, // the iteration whose messages (incl. handler sends) must be retracted/replaced
|
|
1633
|
-
attempt: number,
|
|
1634
|
-
fromModel: string,
|
|
1635
|
-
toModel: string,
|
|
1636
|
-
reason: string,
|
|
1637
|
-
}
|
|
1638
|
-
```
|
|
1639
|
-
|
|
1640
|
-
IDs: `iterationId` is **stable across attempts** — every restart of the same iteration keeps the same `iterationId` — while `id` is **different per attempt**. After a reset, replacement chunks arrive under new `id`s within the same `iterationId`.
|
|
1641
|
-
|
|
1642
|
-
Consumer rules:
|
|
1643
|
-
|
|
1644
|
-
- On `restart: true`, retract/replace **all** current-iteration messages bearing that `iterationId` — streamed text **and** messages already delivered through `handler`.
|
|
1645
|
-
- On an ordinary chunk, update the message for `delta.id` (create it if new, otherwise append).
|
|
1646
|
-
- A reset is emitted and awaited **before** replacement output begins, **even if the replacement yields no message at all**. Return/await the retraction promise in your handler. If it throws or rejects, generation fails without delivering replacement output or executing code; ordinary text-preview errors remain best-effort.
|
|
1647
|
-
- A reset only invalidates the messages of its own `iterationId`; it **never** invalidates messages from earlier, completed iterations.
|
|
1648
|
-
- Only TOOL/CODE execution is gated on a successful stream: generated code and tool calls never run from an abandoned attempt.
|
|
1649
|
-
|
|
1650
|
-
Outside of fallback (the default), `onMessageDelta` behavior is unchanged: ordinary chunks carry `iterationId`/`id`/`component`/`props`/`delta`/`content` with a single stable message id, and no restart/reset deltas are produced.
|
|
1651
|
-
|
|
1652
|
-
#### Completed-send correlation
|
|
1653
|
-
|
|
1654
|
-
`Chat.handler(component, metadata)` receives `{ iterationId: string, id: string }` for **every send**, including empty-body and non-text components, without requiring `onMessageDelta`. For streamed messages, `metadata.id` matches the text delta's `id`; it changes across attempts while `iterationId` stays stable. Existing one-argument handlers continue to work.
|
|
1655
|
-
|
|
1656
|
-
Record **all** persisted message IDs created while awaiting that handler under its metadata (one component may produce multiple persisted messages). A restart can then retract everything for the matching iteration, even after a message's stream tracking has completed. Non-streaming sends also receive metadata. Tool-yielded messages get unique per-yield IDs and the runtime iteration ID; standalone `Tool.execute` calls without an iteration use their `callId` as the scope.
|
|
1657
|
-
|
|
1658
|
-
#### Migrating consumers
|
|
1659
|
-
|
|
1660
|
-
- **No final delivery queue**: messages are not withheld until the stream ends. Deltas and completed `handler` sends arrive during generation; drop any "promote on success" design — treat every delivered message as provisional until the iteration completes without a reset.
|
|
1661
|
-
- **Retract, don't just clear previews**: on `restart: true`, retraction must cascade to every current-iteration message, including ones already committed through `handler`. If your UI or transport cannot do that, keep `midStreamFallback` disabled.
|
|
1662
|
-
- **Clear on errors and cancellation**: a failed or cancelled generation can leave current-iteration messages on screen, incomplete, or unresettable. Explicitly clear/roll back current-iteration state when execution errors or ends — there is no exactly-once guarantee, and caller retries can duplicate effects.
|
|
1663
|
-
- **Irreversible transports**: external sends that cannot be undone (SMS, email, webhooks) must not blindly enable fallback without a reset-capable gateway.
|
|
1664
|
-
|
|
1665
|
-
#### Caveats
|
|
1666
|
-
|
|
1667
|
-
- **No extra retry loop**: Cognitive owns fallback, bounded by the model chain. LLMz does not retry abandoned attempts itself.
|
|
1668
|
-
- **Cancellation**: cancellation still aborts the original generation request as usual; consumers must clear current-iteration state on abort (see above). Cancellation deadlines are not reset, and the existing stream inactivity guard remains active during handoffs.
|
|
1669
|
-
|
|
1670
|
-
This option defaults to false and applies only to streaming clients. Non-streaming Cognitive requests already fall back transparently. It can be combined with `maxTimeToFirstToken` and `transcriptionModel`; do not enable it globally underneath LLMz without also enabling LLMz's fallback option and a reset-capable consumer.
|
|
1671
|
-
|
|
1672
|
-
Restarts emit `llm_call_restarted` traces with the attempt, source/destination models, and reason. Token timings remain relative to the original request and describe the surviving attempt (including time spent on prior attempts). Usage and cost retain Cognitive's final reported metadata; LLMz does not infer or sum abandoned-attempt billing.
|
|
1673
|
-
|
|
1674
|
-
## API Reference
|
|
1675
|
-
|
|
1676
|
-
### Core Functions
|
|
1677
|
-
|
|
1678
|
-
#### execute(props: ExecutionProps): Promise<ExecutionResult>
|
|
1679
|
-
|
|
1680
|
-
Main execution function that runs LLMz agents in either Chat Mode or Worker Mode.
|
|
1681
|
-
|
|
1682
|
-
**Parameters:**
|
|
1683
|
-
|
|
1684
|
-
- `props.client` - Botpress Client or Cognitive Client instance for LLM generation
|
|
1685
|
-
- `props.instructions` - System prompt/instructions for the LLM (static string or dynamic function)
|
|
1686
|
-
- `props.chat` - Optional Chat instance to enable Chat Mode with user interaction
|
|
1687
|
-
- `props.tools` - Array of Tool instances available to the agent (static or dynamic)
|
|
1688
|
-
- `props.objects` - Array of ObjectInstance for namespaced tools and variables (static or dynamic)
|
|
1689
|
-
- `props.exits` - Array of Exit definitions for structured completion (static or dynamic)
|
|
1690
|
-
- `props.snapshot` - Optional Snapshot to resume paused execution
|
|
1691
|
-
- `props.signal` - Optional AbortSignal to cancel execution
|
|
1692
|
-
- `props.options` - Optional execution options (loop limit, temperature, model, timeout)
|
|
1693
|
-
- `props.onTrace` - Optional non-blocking hook for monitoring traces during execution
|
|
1694
|
-
- `props.onIterationEnd` - Optional blocking hook called after each iteration
|
|
1695
|
-
- `props.onExit` - Optional blocking hook called when an exit is reached
|
|
1696
|
-
- `props.onBeforeExecution` - Optional blocking hook to modify code before VM execution
|
|
1697
|
-
- `props.onBeforeTool` - Optional blocking hook to modify tool inputs before execution
|
|
1698
|
-
- `props.onAfterTool` - Optional blocking hook to modify tool outputs after execution
|
|
1699
|
-
|
|
1700
|
-
**Returns:** `Promise<ExecutionResult>` - Result containing success/error/interrupted status with type-safe exit checking
|
|
1701
|
-
|
|
1702
|
-
### Tool Class
|
|
1703
|
-
|
|
1704
|
-
#### new Tool(config: ToolConfig)
|
|
1705
|
-
|
|
1706
|
-
Creates a new tool definition with type-safe schemas.
|
|
1707
|
-
|
|
1708
|
-
**Properties:**
|
|
1709
|
-
|
|
1710
|
-
- `name: string` - Tool name used in generated code
|
|
1711
|
-
- `description?: string` - Description for LLM understanding
|
|
1712
|
-
- `input?: ZuiSchema` - Input validation schema
|
|
1713
|
-
- `output?: ZuiSchema` - Output validation schema
|
|
1714
|
-
- `handler: (input: any) => Promise<any> | any` - Tool implementation
|
|
1715
|
-
- `aliases?: string[]` - Alternative names for the tool
|
|
1716
|
-
- `staticInputs?: Record<string, any>` - Force specific input values
|
|
1717
|
-
|
|
1718
|
-
**Methods:**
|
|
1719
|
-
|
|
1720
|
-
- `execute(input: any, context?: ToolContext): Promise<any>` - Execute the tool
|
|
1721
|
-
- `getTypings(): string` - Get TypeScript definitions for LLM
|
|
1722
|
-
- `clone(overrides: Partial<ToolConfig>): Tool` - Create a modified copy
|
|
1723
|
-
|
|
1724
|
-
### Exit Class
|
|
1725
|
-
|
|
1726
|
-
#### new Exit(config: ExitConfig)
|
|
1727
|
-
|
|
1728
|
-
Defines a structured exit point for agent execution.
|
|
1729
|
-
|
|
1730
|
-
**Properties:**
|
|
1731
|
-
|
|
1732
|
-
- `name: string` - Exit name used in generated code
|
|
1733
|
-
- `description?: string` - Description for LLM understanding
|
|
1734
|
-
- `schema?: ZuiSchema` - Output validation schema
|
|
1735
|
-
- `aliases?: string[]` - Alternative names for the exit
|
|
1736
|
-
|
|
1737
|
-
### ObjectInstance Class
|
|
1738
|
-
|
|
1739
|
-
#### new ObjectInstance(config: ObjectConfig)
|
|
1740
|
-
|
|
1741
|
-
Creates a namespaced container for tools and variables.
|
|
1742
|
-
|
|
1743
|
-
**Properties:**
|
|
1744
|
-
|
|
1745
|
-
- `name: string` - Object name used in generated code
|
|
1746
|
-
- `properties?: PropertyConfig[]` - Object properties/variables
|
|
1747
|
-
- `tools?: Tool[]` - Tools scoped to this object
|
|
1748
|
-
|
|
1749
|
-
**PropertyConfig:**
|
|
1750
|
-
|
|
1751
|
-
- `name: string` - Property name
|
|
1752
|
-
- `value: any` - Initial value
|
|
1753
|
-
- `writable: boolean` - Whether property can be modified
|
|
1754
|
-
- `type?: ZuiSchema` - Validation schema
|
|
1755
|
-
|
|
1756
|
-
### ExecutionResult Types
|
|
1757
|
-
|
|
1758
|
-
#### SuccessExecutionResult
|
|
1759
|
-
|
|
1760
|
-
**Properties:**
|
|
1761
|
-
|
|
1762
|
-
- `isSuccess(): boolean` - Type guard for success
|
|
1763
|
-
- `output: any` - The result data from the exit
|
|
1764
|
-
- `exit: Exit` - The exit that was used
|
|
1765
|
-
- `iteration: Iteration` - Final iteration details
|
|
1766
|
-
- `iterations: Iteration[]` - All iterations
|
|
1767
|
-
- `context: Context` - Execution context
|
|
1768
|
-
- `is(exit: Exit): boolean` - Type-safe exit checking
|
|
1769
|
-
|
|
1770
|
-
#### ErrorExecutionResult
|
|
1771
|
-
|
|
1772
|
-
**Properties:**
|
|
1773
|
-
|
|
1774
|
-
- `isError(): boolean` - Type guard for error
|
|
1775
|
-
- `error: Error | string` - The error that occurred
|
|
1776
|
-
- `iteration?: Iteration` - Failed iteration details
|
|
1777
|
-
- `iterations: Iteration[]` - All iterations before failure
|
|
1778
|
-
- `context: Context` - Execution context
|
|
1779
|
-
|
|
1780
|
-
#### PartialExecutionResult
|
|
1781
|
-
|
|
1782
|
-
**Properties:**
|
|
1783
|
-
|
|
1784
|
-
- `isInterrupted(): boolean` - Type guard for interruption
|
|
1785
|
-
- `signal: SnapshotSignal` - The signal that caused interruption
|
|
1786
|
-
- `snapshot: Snapshot` - Serializable execution state
|
|
1787
|
-
- `iterations: Iteration[]` - All iterations before interruption
|
|
1788
|
-
- `context: Context` - Execution context
|
|
1789
|
-
|
|
1790
|
-
### Chat Class
|
|
1791
|
-
|
|
1792
|
-
Abstract base class for implementing chat interfaces.
|
|
1793
|
-
|
|
1794
|
-
**Abstract Methods:**
|
|
1795
|
-
|
|
1796
|
-
- `getTranscript(): Promise<Transcript.Message[]> | Transcript.Message[]` - Get conversation history
|
|
1797
|
-
- `getComponents(): Promise<ComponentDefinition[]> | ComponentDefinition[]` - Get available UI components
|
|
1798
|
-
- `handler(component: RenderedComponent): Promise<void>` - Handle agent messages
|
|
1799
|
-
|
|
1800
|
-
### CitationsManager Class
|
|
1801
|
-
|
|
1802
|
-
Manages source citations for RAG systems.
|
|
1803
|
-
|
|
1804
|
-
**Methods:**
|
|
1805
|
-
|
|
1806
|
-
- `registerSource(source: any): { tag: string, id: number }` - Register a source and get citation tag
|
|
1807
|
-
- `extractCitations(text: string, replacer?: (citation) => string): { cleaned: string, citations: Citation[] }` - Extract and process citations
|
|
1808
|
-
- `removeCitationsFromObject(obj: any): [cleanedObj: any, citations: Citation[]]` - Remove citations from objects
|
|
1809
|
-
- `static stripCitationTags(text: string): string` - Remove all citation tags
|
|
1810
|
-
|
|
1811
|
-
### Snapshot Class
|
|
1812
|
-
|
|
1813
|
-
Manages pauseable execution state.
|
|
1814
|
-
|
|
1815
|
-
**Methods:**
|
|
1816
|
-
|
|
1817
|
-
- `toJSON(): string` - Serialize snapshot
|
|
1818
|
-
- `static fromJSON(json: string): Snapshot` - Deserialize snapshot
|
|
1819
|
-
- `resolve(data: any): void` - Resume with success
|
|
1820
|
-
- `reject(error: Error): void` - Resume with error
|
|
1821
|
-
|
|
1822
|
-
### Built-in Exits
|
|
1823
|
-
|
|
1824
|
-
- `ListenExit` - Automatically available in Chat Mode for user interaction
|
|
1825
|
-
- `DefaultExit` - Default exit for Worker Mode with success/failure discrimination
|
|
1826
|
-
- `ThinkExit` - Used when agent requests thinking time
|
|
1827
|
-
|
|
1828
|
-
### Signals
|
|
1829
|
-
|
|
1830
|
-
- `SnapshotSignal` - Thrown to pause execution for later resumption
|
|
1831
|
-
- `ThinkSignal` - Thrown to request agent reflection time
|
|
1832
|
-
- `LoopExceededError` - Thrown when maximum iterations reached
|
|
1833
|
-
|
|
1834
|
-
### Environment Variables
|
|
1835
|
-
|
|
1836
|
-
- `VM_DRIVER: 'isolated-vm' | 'node'` - Choose VM execution environment
|
|
1837
|
-
- `CI: boolean` - Automatically detected, affects VM driver selection
|
|
1838
|
-
|
|
1839
|
-
---
|
|
1840
|
-
|
|
1841
|
-
## Examples
|
|
1842
|
-
|
|
1843
|
-
The LLMz repository includes 20 comprehensive examples demonstrating different patterns and capabilities:
|
|
1844
|
-
|
|
1845
|
-
### Chat Examples (Interactive Patterns)
|
|
1846
|
-
|
|
1847
|
-
1. **01_chat_basic** - Basic conversational agent setup
|
|
1848
|
-
2. **02_chat_exits** - Custom exits for structured conversations
|
|
1849
|
-
3. **03_chat_conditional_tool** - Conditional tool usage based on context
|
|
1850
|
-
4. **04_chat_small_models** - Optimizations for smaller language models
|
|
1851
|
-
5. **05_chat_web_search** - Integration with web search capabilities
|
|
1852
|
-
6. **06_chat_confirm_tool** - User confirmation patterns for sensitive operations
|
|
1853
|
-
7. **07_chat_guardrails** - Safety mechanisms and content filtering
|
|
1854
|
-
8. **08_chat_multi_agent** - Multi-agent orchestration and delegation
|
|
1855
|
-
9. **09_chat_variables** - Object variables and state management
|
|
1856
|
-
10. **10_chat_components** - Rich UI components and interactive elements
|
|
1857
|
-
|
|
1858
|
-
### Worker Examples (Automated Patterns)
|
|
1859
|
-
|
|
1860
|
-
11. **11_worker_minimal** - Simplest worker mode execution
|
|
1861
|
-
12. **12_worker_fs** - File system operations and data processing
|
|
1862
|
-
13. **13_worker_sandbox** - Security isolation and sandboxing
|
|
1863
|
-
14. **14_worker_snapshot** - Pauseable execution and resumption
|
|
1864
|
-
15. **15_worker_stacktraces** - Error handling and debugging
|
|
1865
|
-
16. **16_worker_tool_chaining** - Complex multi-tool workflows
|
|
1866
|
-
17. **17_worker_error_recovery** - Graceful error recovery patterns
|
|
1867
|
-
18. **18_worker_security** - Security best practices and validation
|
|
1868
|
-
19. **19_worker_wrap_tool** - Tool modification and enhancement
|
|
1869
|
-
20. **20_chat_rag** - Retrieval-Augmented Generation with citations
|
|
1870
|
-
|
|
1871
|
-
### Running Examples
|
|
1872
|
-
|
|
1873
|
-
```bash
|
|
1874
|
-
# Install dependencies
|
|
1875
|
-
pnpm install
|
|
1876
|
-
|
|
1877
|
-
# Set up environment variables
|
|
1878
|
-
cp .env.example .env
|
|
1879
|
-
# Edit .env with your Botpress credentials
|
|
1880
|
-
|
|
1881
|
-
# Run a specific example
|
|
1882
|
-
pnpm start 01_chat_basic
|
|
1883
|
-
pnpm start chat_basic
|
|
1884
|
-
pnpm start 01
|
|
1885
|
-
|
|
1886
|
-
# List all available examples
|
|
1887
|
-
pnpm start
|
|
1888
336
|
```
|
|
1889
337
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
Create a `.env` file in the examples directory:
|
|
1893
|
-
|
|
1894
|
-
```env
|
|
1895
|
-
BOTPRESS_BOT_ID=your_bot_id_here
|
|
1896
|
-
BOTPRESS_TOKEN=your_token_here
|
|
1897
|
-
```
|
|
1898
|
-
|
|
1899
|
-
### Key Learning Paths
|
|
1900
|
-
|
|
1901
|
-
**Getting Started:**
|
|
1902
|
-
|
|
1903
|
-
- Start with `01_chat_basic` and `11_worker_minimal`
|
|
1904
|
-
- Understand the difference between Chat and Worker modes
|
|
1905
|
-
- Learn basic tool integration patterns
|
|
338
|
+
Request messages are copies. Hooks should preserve native call/result pairing and opaque provider fields. `onBeforeRequest` changes the model request, not canonical session history. Blocking hook failures can fail the iteration; observation hooks have the behavior documented by their types.
|
|
1906
339
|
|
|
1907
|
-
|
|
340
|
+
## Runtime configuration
|
|
1908
341
|
|
|
1909
|
-
|
|
1910
|
-
- Study `16_worker_tool_chaining` for complex workflows
|
|
1911
|
-
- Review `14_worker_snapshot` for pauseable execution
|
|
342
|
+
`options.loop` bounds model iterations; `options.timeout` bounds JavaScript execution. Supply `signal` to cancel a run. Cancellation preserves completed effects and any values captured before interruption.
|
|
1912
343
|
|
|
1913
|
-
|
|
344
|
+
`model` can be a model ID, an ordered fallback list, or a dynamic getter. `options.maxTimeToFirstToken` and `midStreamFallback` apply to streaming clients. `options.transcriptionModel` selects transcription for audio on models without native audio support.
|
|
1914
345
|
|
|
1915
|
-
|
|
1916
|
-
- Learn from `20_chat_rag` for knowledge integration
|
|
1917
|
-
- Study `18_worker_security` for production deployment
|
|
346
|
+
On platforms that prohibit runtime WASM compilation, configure a compatible QuickJS variant with `configureQuickJS` and a precompiled tokenizer with `configureTokenizer` before execution. [Examples](./examples) show host integration patterns.
|
|
1918
347
|
|
|
1919
|
-
|
|
348
|
+
## Migrating from the previous API
|
|
1920
349
|
|
|
1921
|
-
|
|
350
|
+
| Previous API | Current replacement |
|
|
351
|
+
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
352
|
+
| `Chat.transcript` or `execute.messages` | `session.append(...)`, then `execute({ session, ... })`. |
|
|
353
|
+
| `Chat.handler`, `onMessageDelta` | `response.handler`, `response.onDelta`; rich messages use component handlers. |
|
|
354
|
+
| Snapshot signals, snapshot results, `execute.snapshot` | Persist settled sessions. Durable paused-program resumption is no longer supported. |
|
|
355
|
+
| Persisting `ExecutionResult` as conversation state | `session.toJSON()` and `Session.fromJSON(...)`. |
|
|
356
|
+
| `Example`, `execute.examples`, response examples | Put guidance in instructions, or supply request messages with `onBeforeRequest`. |
|
|
357
|
+
| Component aliases, generated method normalization, generation metadata | Exact component names with object or array props. |
|
|
358
|
+
| `utils.wrapContent`, legacy truncation helpers | `truncate({ value, maxTokens, preserve })`. |
|
|
359
|
+
| Returning iteration mutations from `onIterationStart` | Dynamic configuration getters or `onBeforeRequest`. |
|
|
1922
360
|
|
|
1923
|
-
|
|
361
|
+
Stored state has explicit format versions. Migration from older serialized state is not automatic. Rebuild supported session inputs from your application's authoritative conversation records when upgrading.
|