@procedures/llm 0.7.0 → 0.8.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/src/tools.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  /**
2
- * Procedures as tools of the model (`llm.complete` with `procedures`).
2
+ * Procedures as tools of the model (`llm.complete` and `llm.stream` with `procedures`).
3
3
  *
4
- * The caller names the procedures. `llm.complete` gives the model a tool for each one. The name
4
+ * The caller names the procedures. The procedure gives the model a tool for each one. The name
5
5
  * is the key with `_` in place of each `.`. The description and the input schema come from the
6
- * procedure. When the model asks for a tool, `llm.complete` calls the procedure through
7
- * `ctx.client` with the input of the model. The output goes back as a `tool_result`.
6
+ * procedure. When the model asks for a tool, the procedure starts it through `ctx.client` with
7
+ * the input of the model. The output goes back as a `tool_result`.
8
8
  *
9
- * The procedure `llm.complete` is data-driven (`markDataDriven`): its input chooses its callees.
10
- * Thus the admission of the host examines each nested call: its capability and its expose rule.
11
- * Before the first request, the same rules examine each named procedure, so a refused tool costs
12
- * no request.
9
+ * `loop.ts` has the loop of the two procedures.
10
+ *
11
+ * The procedures `llm.complete` and `llm.stream` are data-driven (`markDataDriven`): the input
12
+ * chooses the callees. Thus the admission of the host examines each nested call: its capability
13
+ * and its expose rule. Before the first request, the same rules examine each named procedure,
14
+ * so a refused tool costs no request.
13
15
  */
14
16
 
15
17
  import {
@@ -38,7 +40,7 @@ export interface ProcedureTool {
38
40
  readonly param: ToolParam;
39
41
  }
40
42
 
41
- /** The default of `maxRounds`: the most requests to the model in one call of `llm.complete`. */
43
+ /** The default of `maxRounds`: the most requests to the model in one call of `llm.complete` or `llm.stream`. */
42
44
  export const DEFAULT_MAX_ROUNDS = 8;
43
45
  /** The most characters of the output of a tool that the model gets. */
44
46
  export const MAX_TOOL_RESULT_CHARS = 200_000;
@@ -152,6 +154,29 @@ function resultText(output: unknown): string {
152
154
  return `${text.slice(0, MAX_TOOL_RESULT_CHARS)}\n[llm: the output has ${text.length} characters, and this result has the first ${MAX_TOOL_RESULT_CHARS}]`;
153
155
  }
154
156
 
157
+ /**
158
+ * This function waits for a nested call. The abort of the signal stops the wait at once with
159
+ * `ABORTED`. The nested call gets the same signal, so a procedure that reads it stops too.
160
+ */
161
+ function untilAbort<T>(work: Promise<T>, signal: AbortSignal | undefined, path: ProcedurePath): Promise<T> {
162
+ if (signal === undefined) return work;
163
+ return new Promise<T>((resolve, reject) => {
164
+ const onAbort = (): void => reject(new InvocationError("ABORTED", `Procedure was aborted: ${path.join(".")}`, path));
165
+ if (signal.aborted) onAbort();
166
+ else signal.addEventListener("abort", onAbort, { once: true });
167
+ work.then(
168
+ (value) => {
169
+ signal.removeEventListener("abort", onAbort);
170
+ resolve(value);
171
+ },
172
+ (error: unknown) => {
173
+ signal.removeEventListener("abort", onAbort);
174
+ reject(error);
175
+ },
176
+ );
177
+ });
178
+ }
179
+
155
180
  /**
156
181
  * This function runs one `tool_use` block with the procedure of its tool, through `ctx.client`.
157
182
  * It gives the record of the call and the `tool_result` block. An error of the procedure goes to
@@ -160,7 +185,7 @@ function resultText(output: unknown): string {
160
185
  export async function runToolUse(use: LlmToolUse, tool: ProcedureTool, ctx: ToolContext): Promise<{ call: LlmToolCall; result: ContentBlock }> {
161
186
  const base = { id: use.id, name: use.name, procedure: tool.key, input: use.input };
162
187
  try {
163
- const output = await ctx.client!.call(tool.path, use.input);
188
+ const output = await untilAbort(ctx.client!.call(tool.path, use.input), ctx.signal, tool.path);
164
189
  return {
165
190
  call: { ...base, isError: false, output },
166
191
  result: { type: "tool_result", tool_use_id: use.id, content: resultText(output) },
package/src/types.ts CHANGED
@@ -79,6 +79,13 @@ export interface LlmToolInput {
79
79
  toolChoice?: LlmToolChoice | undefined;
80
80
  /** The number of new tries after an error with `retryable: true`: 0 to 10. The default is the option of the module (2). */
81
81
  retries?: number | undefined;
82
+ /**
83
+ * The keys of the procedures that the model can use as tools, for example `weather.current`.
84
+ * The procedure starts each one through `ctx.client`, sends the result to the model, and asks again.
85
+ */
86
+ procedures?: string[] | undefined;
87
+ /** The most requests to the model in one call: 1 to 64. The default is 8. */
88
+ maxRounds?: number | undefined;
82
89
  }
83
90
 
84
91
  /** A `tool_use` block of an answer: a call of a tool that the model asks for. */
@@ -91,7 +98,7 @@ export interface LlmToolUse {
91
98
  input: unknown;
92
99
  }
93
100
 
94
- /** A call of a procedure tool that `llm.complete` made for the model. */
101
+ /** A call of a procedure tool that `llm.complete` or `llm.stream` made for the model. */
95
102
  export interface LlmToolCall {
96
103
  /** The ID of the `tool_use` block. */
97
104
  id: string;
@@ -113,15 +120,7 @@ export interface LlmToolCall {
113
120
  // llm.complete
114
121
  // =============================================================================
115
122
 
116
- export interface LlmCompleteInput extends LlmRequestInput, LlmToolInput {
117
- /**
118
- * The keys of the procedures that the model can use as tools, for example `weather.current`.
119
- * `llm.complete` calls each one through `ctx.client`, sends the result to the model, and asks again.
120
- */
121
- procedures?: string[] | undefined;
122
- /** The most requests to the model in one call: 1 to 64. The default is 8. */
123
- maxRounds?: number | undefined;
124
- }
123
+ export interface LlmCompleteInput extends LlmRequestInput, LlmToolInput {}
125
124
 
126
125
  export interface LlmCompleteOutput {
127
126
  /** The text of the last answer: its text blocks, joined. */
@@ -215,20 +214,57 @@ export interface LlmStreamToolUse {
215
214
  partialJson?: string | undefined;
216
215
  }
217
216
 
218
- /** The last item of a stream: the event `message_stop`, with the stop reason and the usage. */
217
+ /**
218
+ * The start of a procedure tool: `llm.stream` starts the procedure for a `tool_use` block of the
219
+ * model. Only an input with `procedures` gives this item.
220
+ */
221
+ export interface LlmStreamToolCall {
222
+ type: "tool_call";
223
+ /** The number of the request whose answer asked for the tool. The first request is 1. */
224
+ round: number;
225
+ /** The ID of the `tool_use` block. */
226
+ id: string;
227
+ /** The name of the tool. */
228
+ name: string;
229
+ /** The key of the procedure, for example `weather.current`. */
230
+ procedure: string;
231
+ /** The input that the model gave. */
232
+ input: unknown;
233
+ }
234
+
235
+ /**
236
+ * The end of a procedure tool: its output, or its error. The model gets it as a `tool_result`.
237
+ * Only an input with `procedures` gives this item.
238
+ */
239
+ export interface LlmStreamToolResult extends LlmToolCall {
240
+ type: "tool_result";
241
+ /** The number of the request whose answer asked for the tool. The first request is 1. */
242
+ round: number;
243
+ }
244
+
245
+ /**
246
+ * The last item of a stream, after the event `message_stop` of the last answer: the stop reason,
247
+ * the usage and the text.
248
+ */
219
249
  export interface LlmStreamStop {
220
250
  type: "stop";
221
- /** The model that answered. After a fallback, it is the fallback model. */
251
+ /** The model of the last answer. After a fallback, it is the fallback model. */
222
252
  model: string;
223
- /** Why the model stopped. */
253
+ /** Why the model stopped in the last answer. */
224
254
  stopReason: string;
225
- /** The tokens of the call. */
255
+ /** The tokens of all the requests, added together. */
226
256
  usage: LlmUsage;
227
- /** The text of the answer: all the text deltas, joined. */
257
+ /** The text of all the answers: all the text deltas, joined. */
228
258
  text: string;
229
- /** The `tool_use` blocks of the answer. */
259
+ /** The `tool_use` blocks of the last answer. No procedure started them. */
230
260
  toolUses: LlmToolUse[];
261
+ /** The calls of procedure tools, in their sequence. Only an input with `procedures` gives this field. */
262
+ toolCalls?: LlmToolCall[] | undefined;
263
+ /** The number of requests to the model. Only an input with `procedures` gives this field. */
264
+ rounds?: number | undefined;
265
+ /** True when `maxRounds` stopped the loop before the model stopped. Only an input with `procedures` gives this field. */
266
+ roundLimit?: boolean | undefined;
231
267
  }
232
268
 
233
269
  /** One item of `llm.stream`. */
234
- export type LlmStreamOutput = LlmStreamStart | LlmStreamText | LlmStreamToolUse | LlmStreamStop;
270
+ export type LlmStreamOutput = LlmStreamStart | LlmStreamText | LlmStreamToolUse | LlmStreamToolCall | LlmStreamToolResult | LlmStreamStop;