@procedures/llm 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +114 -9
- package/dist/.tsbuildinfo +1 -1
- package/dist/anthropic.d.ts +64 -5
- package/dist/anthropic.d.ts.map +1 -1
- package/dist/anthropic.js +109 -50
- package/dist/anthropic.js.map +1 -1
- package/dist/errors.d.ts +15 -8
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +218 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +8 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +127 -12
- package/dist/module.js.map +1 -1
- package/dist/procedures/llm/complete.d.ts +9 -3
- package/dist/procedures/llm/complete.d.ts.map +1 -1
- package/dist/procedures/llm/complete.js +50 -10
- package/dist/procedures/llm/complete.js.map +1 -1
- package/dist/procedures/llm/index.d.ts +1 -0
- package/dist/procedures/llm/index.d.ts.map +1 -1
- package/dist/procedures/llm/index.js +1 -0
- package/dist/procedures/llm/index.js.map +1 -1
- package/dist/procedures/llm/stream.d.ts +14 -0
- package/dist/procedures/llm/stream.d.ts.map +1 -0
- package/dist/procedures/llm/stream.js +20 -0
- package/dist/procedures/llm/stream.js.map +1 -0
- package/dist/procedures/llm/structured.d.ts +1 -1
- package/dist/procedures/llm/structured.d.ts.map +1 -1
- package/dist/procedures/llm/structured.js +6 -4
- package/dist/procedures/llm/structured.js.map +1 -1
- package/dist/request.d.ts +15 -4
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +47 -2
- package/dist/request.js.map +1 -1
- package/dist/retry.d.ts +45 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +106 -0
- package/dist/retry.js.map +1 -0
- package/dist/tools.d.ts +50 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +162 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +138 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -5
- package/src/anthropic.ts +149 -49
- package/src/errors.ts +17 -5
- package/src/events.ts +218 -0
- package/src/index.ts +5 -2
- package/src/module.ts +143 -12
- package/src/procedures/llm/complete.ts +53 -12
- package/src/procedures/llm/index.ts +1 -0
- package/src/procedures/llm/stream.ts +23 -0
- package/src/procedures/llm/structured.ts +6 -4
- package/src/request.ts +47 -4
- package/src/retry.ts +110 -0
- package/src/tools.ts +178 -0
- package/src/types.ts +148 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @procedures/llm
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 11f9aa9: Streaming, tool use and an automatic retry for the procedures of `@procedures/llm`:
|
|
8
|
+
|
|
9
|
+
- The new procedure `llm.stream` sends the request with `"stream": true`. It gives the server-sent events of the Messages API as stream items: `start`, `text`, `tool_use` and `stop`. The `stop` item has the stop reason, the usage and the full text. `sseEvents` of `@procedures/http` parses the stream. The procedure declares `streaming: true` and `stream: { unbounded: false }`.
|
|
10
|
+
- `llm.complete` and `llm.stream` take `tools` (name, description, input schema, strict) and `toolChoice`. The output gives each `tool_use` block.
|
|
11
|
+
- With `procedures`, `llm.complete` gives the model the named procedures as tools. It calls each one through `ctx.client`, sends the `tool_result` back, and asks again until the model stops. `maxRounds` (default 8) ends the loop. The output has `toolCalls`, `rounds` and `roundLimit`.
|
|
12
|
+
- `llm.complete` is data-driven (`markDataDriven`), so the host examines each nested call with its capability and its expose rule. A refused procedure gives `FORBIDDEN` or `NOT_EXPOSED` before a request goes out.
|
|
13
|
+
- A request that fails with `retryable: true` (408, 409, 429, 529 and the other 5xx statuses, a connection error, an early error event of a stream) gets new tries. The wait is the `retry-after-ms` or `retry-after` header, or `backoffDelay` of `@mark1russell7/service/backoff`. The deadline stops the retry, and a stream tries again only before its first item.
|
|
14
|
+
- The input `retries` (0 to 10, default 2) of `llm.complete` and `llm.stream` sets the new tries. `retries: 0` turns them off. `llm.structured` keeps `retries` for the answers that are not valid, and gets `requestRetries`.
|
|
15
|
+
- `createLlmModule()` takes `retries` and `backoff`. `LlmError` has `retryAfterMs` and the new code `BAD_TOOL`.
|
|
16
|
+
- `timeoutMs` limits each request to the API.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 0f75644: The dependencies go to `@mark1russell7/algebra` `^0.7.0` and `@mark1russell7/service` `^0.4.1`. A host that uses algebra 0.7.0 gets one copy of it. Algebra 0.7.0 only adds to 0.6.0.
|
|
21
|
+
- Updated dependencies [0f75644]
|
|
22
|
+
- Updated dependencies [09e5566]
|
|
23
|
+
- Updated dependencies [ab77f75]
|
|
24
|
+
- Updated dependencies [a47bee6]
|
|
25
|
+
- Updated dependencies [bc74091]
|
|
26
|
+
- @procedures/client@0.5.0
|
|
27
|
+
- @procedures/http@0.5.0
|
|
28
|
+
- @procedures/json@0.5.0
|
|
29
|
+
|
|
3
30
|
## 0.4.0
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -6,20 +6,21 @@
|
|
|
6
6
|
npm install @procedures/llm
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
A language model as procedures: a completion, and a structured answer that a JSON Schema accepts. The procedures send each request to the Anthropic Messages API.
|
|
9
|
+
A language model as procedures: a completion, a stream of the answer, and a structured answer that a JSON Schema accepts. The procedures send each request to the Anthropic Messages API.
|
|
10
10
|
|
|
11
11
|
## The procedures
|
|
12
12
|
|
|
13
13
|
| Procedure | What it does | Effects |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `llm.complete` | It sends a prompt and a system text, and gives the text, the model, the stop reason and the usage. | reads and writes `network` |
|
|
15
|
+
| `llm.complete` | It sends a prompt and a system text, and gives the text, the model, the stop reason and the usage. The model can use tools and procedures. | reads and writes `network` |
|
|
16
|
+
| `llm.stream` | It sends the same request with `"stream": true`, and gives the events of the answer as stream items. | reads and writes `network` |
|
|
16
17
|
| `llm.structured` | It sends a prompt and a JSON Schema, and gives a value that the schema accepts. | reads and writes `network` |
|
|
17
18
|
|
|
18
19
|
- Each procedure declares `reads: ["network"]` and `writes: ["network"]`, with `destructive: false` and `idempotent: false`. A call sends data out of the host and costs money.
|
|
19
20
|
- The input has `prompt`, `system`, `model`, `maxTokens`, `temperature`, `effort`, `timeoutMs` and `fallback`.
|
|
20
21
|
- The default model is `claude-sonnet-5-5`. The default of `maxTokens` is 16000, and the limit counts the tokens of the thinking too.
|
|
21
22
|
- Some models accept only the default temperature. For such a model, the API refuses another value with an error.
|
|
22
|
-
- The text of `llm.complete` is the text blocks of the answer, joined. The thinking blocks are not in it.
|
|
23
|
+
- The text of `llm.complete` is the text blocks of the last answer, joined. The thinking blocks are not in it.
|
|
23
24
|
|
|
24
25
|
```ts
|
|
25
26
|
import "@procedures/llm/register";
|
|
@@ -40,6 +41,106 @@ The key comes from an environment variable. The default variable is `ANTHROPIC_A
|
|
|
40
41
|
- An error message does not contain the key.
|
|
41
42
|
- The procedures send the request with `fetch`, not through `http.request`. The input of a nested call is visible to the interceptors and to the traces of the host, and the key is in a header.
|
|
42
43
|
|
|
44
|
+
## Streaming
|
|
45
|
+
|
|
46
|
+
`llm.stream` sends the request with `"stream": true` and reads the server-sent events of the answer. `sseEvents` of `@procedures/http` parses the event stream. Each item has a `type`:
|
|
47
|
+
|
|
48
|
+
| Item | Event of the API | Fields |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `start` | `message_start` | `id`, `model`, and the `usage` at the start: the tokens of the input |
|
|
51
|
+
| `text` | `content_block_delta` with `text_delta` | `index` of the content block, and the new `text` |
|
|
52
|
+
| `tool_use` | `content_block_stop` of a `tool_use` block | `index`, `id`, `name`, and the parsed `input` |
|
|
53
|
+
| `stop` | `message_stop` | `model`, `stopReason`, the full `usage`, the full `text` and the `toolUses` |
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
for await (const item of client.stream(["llm", "stream"], { prompt: "Write a haiku." })) {
|
|
57
|
+
if (item.type === "text") process.stdout.write(item.text);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- The stream has the key rule, the injectable `fetch`, the time limit, the deadline and the abort of the other procedures.
|
|
62
|
+
- The usage of a `message_delta` event is cumulative. The `stop` item has the last value.
|
|
63
|
+
- `client.call` of a stream gives its last item. For `llm.stream`, that is the `stop` item, with the full text.
|
|
64
|
+
- The procedure ignores the `ping` events, the thinking deltas and the event types that it does not know.
|
|
65
|
+
- A `tool_use` item gives `input: null` and the text in `partialJson` when the JSON of the input does not parse. An answer that stops at `maxTokens` can do this.
|
|
66
|
+
- After a server-side fallback, the `stop` item gives the fallback model.
|
|
67
|
+
- An `error` event gives `API_ERROR`. An overload, a rate limit and a server error are retryable.
|
|
68
|
+
- A stream that ends before `message_stop` gives `CONNECTION`, with `retryable: true`.
|
|
69
|
+
- A refusal at the end of the stream gives `REFUSAL`, as in `llm.complete`.
|
|
70
|
+
- The procedure declares `streaming: true` and `stream: { unbounded: false }`. The answer ends: `maxTokens` and `timeoutMs` limit it.
|
|
71
|
+
|
|
72
|
+
## Tools
|
|
73
|
+
|
|
74
|
+
`llm.complete` and `llm.stream` take `tools` and `toolChoice`.
|
|
75
|
+
|
|
76
|
+
- Each tool has `name`, `description`, `inputSchema` and `strict`. The name has 1 to 128 letters, digits, `_` and `-`. The API gets the schema as `input_schema`.
|
|
77
|
+
- `toolChoice` is `{ type: "auto" }` (the default), `{ type: "any" }`, `{ type: "tool", name }` or `{ type: "none" }`. The API gets it as `tool_choice`.
|
|
78
|
+
- `disableParallelToolUse: true` on `auto`, `any` or `tool` lets the model use at most one tool in an answer.
|
|
79
|
+
- The output of `llm.complete` gives each `tool_use` block of the answer in `toolUses`: `id`, `name` and `input`. The caller starts these tools.
|
|
80
|
+
- Claude Sonnet 5.5, Claude Opus 5.5 and Claude Fable 5.1 refuse `any` and `tool` with a 400 error. For these models, use `auto`, a clear prompt and `strict: true`.
|
|
81
|
+
- Two tools with the same name, a `toolChoice` without tools, and a `toolChoice` with an unknown name give `BAD_TOOL`, before a request goes out.
|
|
82
|
+
|
|
83
|
+
## Procedures as tools
|
|
84
|
+
|
|
85
|
+
With `procedures`, `llm.complete` starts the tools itself. The input names the keys of the procedures, for example `weather.current`.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const { text, toolCalls, rounds } = await client.call(["llm", "complete"], {
|
|
89
|
+
prompt: "What is the weather in Paris?",
|
|
90
|
+
procedures: ["weather.current"],
|
|
91
|
+
maxRounds: 4,
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
1. Each procedure becomes a tool. The name is the key with `_` in place of each `.`, for example `weather_current`.
|
|
96
|
+
2. The description of the procedure and the JSON Schema of its input go to the API. The input of the procedure must be an object.
|
|
97
|
+
3. When the model asks for a tool, `llm.complete` starts the procedure through `ctx.client` with the input of the model.
|
|
98
|
+
4. The output goes back as a `tool_result`, and the next request has the answer and the results.
|
|
99
|
+
5. The loop continues until the model stops, or until `maxRounds` requests went out. The default of `maxRounds` is 8, and the maximum is 64.
|
|
100
|
+
|
|
101
|
+
The output has these fields:
|
|
102
|
+
|
|
103
|
+
| Field | Meaning |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `toolCalls` | Each procedure call: `id`, `name`, `procedure`, `input`, `isError`, and `output` or `error` |
|
|
106
|
+
| `rounds` | The number of requests to the model |
|
|
107
|
+
| `roundLimit` | True when `maxRounds` stopped the loop. Then `stopReason` is `tool_use`, and `toolUses` has the blocks that no procedure got. |
|
|
108
|
+
| `usage` | The tokens of all the requests |
|
|
109
|
+
|
|
110
|
+
- The procedures run one at a time, in the sequence of the answer. All the results of one answer go back in one message.
|
|
111
|
+
- An error of a procedure goes to the model as a `tool_result` with `is_error: true`. An input that the schema refuses gives `VALIDATION_ERROR` there. The model can try again.
|
|
112
|
+
- A forced `toolChoice` (`any` or `tool`) applies to the first request only. The next requests use `auto`, so the model can stop.
|
|
113
|
+
- When an answer asks for a tool of `tools` too, no procedure runs. The output gives all the `tool_use` blocks.
|
|
114
|
+
- A `tool_result` has at most 200000 characters of the output. A longer output gets a note with its length.
|
|
115
|
+
- A procedure without an end (`stream: { unbounded: true }`) gives `BAD_TOOL`.
|
|
116
|
+
|
|
117
|
+
### The admission of the host
|
|
118
|
+
|
|
119
|
+
`llm.complete` is a data-driven procedure (`markDataDriven` of `@procedures/client`), because its input chooses its callees. Thus the host examines each nested call, as it examines the calls of `client.chain`.
|
|
120
|
+
|
|
121
|
+
- The capability of the call applies to each procedure tool. A host that permits only `writes: ["network"]` permits only read-only tools.
|
|
122
|
+
- The expose rule of the host applies too. A host with `expose` lets the model call only the procedures that it exposes.
|
|
123
|
+
- Before the first request, `llm.complete` examines each named procedure with the same rules. A refused procedure gives `FORBIDDEN` or `NOT_EXPOSED`, and no request goes out.
|
|
124
|
+
- A procedure that is not in the registry gives `NOT_FOUND`. A refused path tells nothing about the procedure.
|
|
125
|
+
- When the host refuses a nested call later (an interceptor can narrow the capability), the call stops with that error.
|
|
126
|
+
- The abort of the caller and the deadline also stop the call during a tool.
|
|
127
|
+
- The procedure is not `runs-refs`: the refs of its input still run before the call. It declares no `calls`, because its callees come from the input.
|
|
128
|
+
|
|
129
|
+
## Automatic retry
|
|
130
|
+
|
|
131
|
+
A request that fails with `retryable: true` gets a new try.
|
|
132
|
+
|
|
133
|
+
- The retryable errors are the statuses 408, 409, 429 and 5xx (529 too), a connection error, and an early error event of a stream.
|
|
134
|
+
- The header `x-should-retry` of the API overrides the rule of the status.
|
|
135
|
+
- The input `retries` of `llm.complete` and `llm.stream` sets the number of new tries, from 0 to 10. The default is 2, the default of the Anthropic SDKs. `retries: 0` turns the retry off.
|
|
136
|
+
- `llm.structured` has `retries` already, for the answers that are not valid. Its input `requestRetries` sets the new tries of a request.
|
|
137
|
+
- The wait before a try is the `retry-after-ms` or `retry-after` header. Without a header, the wait comes from `backoffDelay` of `@mark1russell7/service/backoff`.
|
|
138
|
+
- The default backoff starts at 500 ms, doubles each time, and stops at 8000 ms. A jitter removes up to a quarter of each wait.
|
|
139
|
+
- When the wait ends after the deadline of the call, or `retry-after` asks for more than 60 seconds, the call gives the error at once.
|
|
140
|
+
- The abort of the caller stops a wait with `ABORTED`.
|
|
141
|
+
- A stream tries again only before its first item. After the first item, an error goes to the caller.
|
|
142
|
+
- A `TIMEOUT` does not get a new try.
|
|
143
|
+
|
|
43
144
|
## Structured answers
|
|
44
145
|
|
|
45
146
|
`llm.structured` gives a value that the schema accepts, or an error.
|
|
@@ -54,7 +155,7 @@ The key comes from an environment variable. The default variable is `ANTHROPIC_A
|
|
|
54
155
|
|
|
55
156
|
## Limits and time
|
|
56
157
|
|
|
57
|
-
- Each
|
|
158
|
+
- Each request to the API has `timeoutMs`. The default is 10 minutes, and the maximum is 1 hour. A stream gets the same limit for all its events.
|
|
58
159
|
- The deadline of the call (`ctx.capability.deadline`) and the abort of the caller (`ctx.signal`) also stop the call. The earliest event wins.
|
|
59
160
|
- A stop gives `TIMEOUT`, or `ABORTED` for an abort of the caller.
|
|
60
161
|
|
|
@@ -86,20 +187,24 @@ const registry = createRegistry(createLlmModule({ apiKeyEnv: "WORK_ANTHROPIC_KEY
|
|
|
86
187
|
| `fallback` | The default of the `fallback` input. The default is `true`. |
|
|
87
188
|
| `fetch` | The `fetch` of the requests. A test gives a fake one. |
|
|
88
189
|
| `env` | The variables: a record, or a function that gives them at each call. The default is `process.env`. |
|
|
190
|
+
| `retries` | The default number of new tries after a retryable error, from 0 to 10. The default is 2. |
|
|
191
|
+
| `backoff` | The `Backoff` policy of `@mark1russell7/service/backoff`. A test gives a short one. |
|
|
89
192
|
|
|
90
193
|
## Errors
|
|
91
194
|
|
|
92
195
|
| Error | Code | Cause |
|
|
93
196
|
|---|---|---|
|
|
94
197
|
| `LlmError` | `NO_API_KEY` | The variable of the key has no value. |
|
|
95
|
-
| `LlmError` | `API_ERROR` | The API answered with an error status. `status` has
|
|
96
|
-
| `LlmError` | `CONNECTION` | The connection failed
|
|
97
|
-
| `LlmError` | `BAD_RESPONSE` | The answer of the API is not a message. |
|
|
198
|
+
| `LlmError` | `API_ERROR` | The API answered with an error status, or a stream sent an `error` event. `status` has the status. `retryable` is true for 408, 409, 429 and 5xx. |
|
|
199
|
+
| `LlmError` | `CONNECTION` | The connection failed, or a stream ended before `message_stop`. `retryable` is true. |
|
|
200
|
+
| `LlmError` | `BAD_RESPONSE` | The answer of the API is not a message or an event stream. |
|
|
98
201
|
| `LlmError` | `REFUSAL` | A safety classifier declined the request. |
|
|
202
|
+
| `LlmError` | `BAD_TOOL` | A tool definition is not valid. |
|
|
99
203
|
| `LlmError` | `MAX_TOKENS`, `INVALID_OUTPUT` | `llm.structured` got no valid value. |
|
|
100
204
|
| `InvocationError` | `TIMEOUT`, `ABORTED` | The time limit, the deadline or the abort of the caller stopped the call. |
|
|
205
|
+
| `InvocationError` | `FORBIDDEN`, `NOT_EXPOSED`, `NOT_FOUND` | The host refused a procedure tool. |
|
|
101
206
|
|
|
102
|
-
|
|
207
|
+
`retryAfterMs` of an `LlmError` gives the wait that the API asked for.
|
|
103
208
|
|
|
104
209
|
## MCP
|
|
105
210
|
|
|
@@ -107,4 +212,4 @@ These procedures are not in a bundle of MCP tools. A call costs money and sends
|
|
|
107
212
|
|
|
108
213
|
## Tests
|
|
109
214
|
|
|
110
|
-
`pnpm --filter @procedures/llm test` examines the procedures with a fake `fetch`. No test sends a request to the network.
|
|
215
|
+
`pnpm --filter @procedures/llm test` examines the procedures with a fake `fetch`. The fake gives recorded JSON answers and recorded event streams. No test sends a request to the network.
|