@neutrome/lilsdk 0.3.5 → 0.4.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/package.json +2 -2
- package/src/index.ts +3 -2
- package/src/loops/index.ts +24 -18
- package/src/managed/capabilities.ts +215 -0
- package/src/managed/index.ts +4 -6
- package/src/managed/{twoPassExecutor.ts → two-pass.ts} +19 -48
- package/src/stream/index.ts +1 -0
- package/src/stream/stages.ts +25 -0
- package/src/tools.ts +10 -5
- package/src/types.ts +18 -30
- package/test/lilsdk-ts.test.ts +149 -32
- package/test/tools.test.ts +190 -194
- package/src/managed/target-executor.ts +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neutrome/lilsdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./managed": "./src/managed/index.ts"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@neutrome/lil-engine": "0.
|
|
16
|
+
"@neutrome/lil-engine": "0.4.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^25.9.3",
|
package/src/index.ts
CHANGED
package/src/loops/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createProgram, deltaText, type Program } from "@neutrome/lil-engine";
|
|
2
|
-
import type { Executor, ExecutorContext } from "../types.ts";
|
|
2
|
+
import type { Executor, ExecutorContext, ExecutorInput } from "../types.ts";
|
|
3
|
+
import { completeStream, streamStage } from "../stream/stages.ts";
|
|
3
4
|
import { appendAssistantMessage } from "../synthetic/index.ts";
|
|
4
5
|
|
|
5
6
|
export type RetryOptions = {
|
|
@@ -17,8 +18,8 @@ export type FallbackOptions = {
|
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export type GoalExecutorOptions = {
|
|
20
|
-
draft:
|
|
21
|
-
review:
|
|
21
|
+
draft: ExecutorInput;
|
|
22
|
+
review: ExecutorInput;
|
|
22
23
|
refine: (
|
|
23
24
|
request: Program,
|
|
24
25
|
answer: Program,
|
|
@@ -30,7 +31,7 @@ export type GoalExecutorOptions = {
|
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
export function retry(
|
|
33
|
-
executor:
|
|
34
|
+
executor: ExecutorInput,
|
|
34
35
|
options: RetryOptions = {},
|
|
35
36
|
): Executor {
|
|
36
37
|
const attempts = positiveInteger(options.attempts ?? 2, "retry attempts");
|
|
@@ -38,7 +39,7 @@ export function retry(
|
|
|
38
39
|
return {
|
|
39
40
|
execute(request, ctx) {
|
|
40
41
|
return runRetry(
|
|
41
|
-
(attempt) =>
|
|
42
|
+
(attempt) => childContext(ctx, attempt).invoke(executor, request),
|
|
42
43
|
attempts,
|
|
43
44
|
options,
|
|
44
45
|
);
|
|
@@ -50,9 +51,9 @@ export function retry(
|
|
|
50
51
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
51
52
|
let emitted = false;
|
|
52
53
|
try {
|
|
53
|
-
for await (const chunk of
|
|
54
|
+
for await (const chunk of childContext(ctx, attempt).invokeStream(
|
|
55
|
+
executor,
|
|
54
56
|
request,
|
|
55
|
-
childContext(ctx, attempt),
|
|
56
57
|
)) {
|
|
57
58
|
emitted = true;
|
|
58
59
|
yield chunk;
|
|
@@ -77,7 +78,7 @@ export function retry(
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export function fallback(
|
|
80
|
-
executors: readonly
|
|
81
|
+
executors: readonly ExecutorInput[],
|
|
81
82
|
options: FallbackOptions = {},
|
|
82
83
|
): Executor {
|
|
83
84
|
if (executors.length === 0) {
|
|
@@ -88,7 +89,7 @@ export function fallback(
|
|
|
88
89
|
execute(request, ctx) {
|
|
89
90
|
return runFallback(
|
|
90
91
|
(executor, index) =>
|
|
91
|
-
|
|
92
|
+
childContext(ctx, index + 1).invoke(executor, request),
|
|
92
93
|
executors,
|
|
93
94
|
options,
|
|
94
95
|
);
|
|
@@ -100,9 +101,9 @@ export function fallback(
|
|
|
100
101
|
for (const [index, executor] of executors.entries()) {
|
|
101
102
|
let emitted = false;
|
|
102
103
|
try {
|
|
103
|
-
for await (const chunk of
|
|
104
|
+
for await (const chunk of childContext(ctx, index + 1).invokeStream(
|
|
105
|
+
executor,
|
|
104
106
|
request,
|
|
105
|
-
childContext(ctx, index + 1),
|
|
106
107
|
)) {
|
|
107
108
|
emitted = true;
|
|
108
109
|
yield chunk;
|
|
@@ -139,8 +140,8 @@ export function createGoalExecutor(options: GoalExecutorOptions): Executor {
|
|
|
139
140
|
|
|
140
141
|
for (let attempt = 1; attempt <= maxIterations; attempt += 1) {
|
|
141
142
|
const attemptContext = childContext(ctx, attempt);
|
|
142
|
-
answer = await options.draft
|
|
143
|
-
const review = await options.review
|
|
143
|
+
answer = await attemptContext.invoke(options.draft, current);
|
|
144
|
+
const review = await attemptContext.invoke(options.review, answer);
|
|
144
145
|
if (await options.satisfied(review, attempt)) return answer;
|
|
145
146
|
if (attempt < maxIterations) {
|
|
146
147
|
current = await options.refine(current, answer, review, attempt);
|
|
@@ -157,19 +158,24 @@ export function createGoalExecutor(options: GoalExecutorOptions): Executor {
|
|
|
157
158
|
for (let attempt = 1; attempt <= maxIterations; attempt += 1) {
|
|
158
159
|
const attemptContext = childContext(ctx, attempt);
|
|
159
160
|
answer = yield* streamGoalStage(
|
|
160
|
-
options.draft
|
|
161
|
+
streamStage(attemptContext.invokeStream(options.draft, current)),
|
|
161
162
|
current,
|
|
162
163
|
);
|
|
163
164
|
const review = yield* streamGoalStage(
|
|
164
|
-
options.review
|
|
165
|
+
streamStage(attemptContext.invokeStream(options.review, answer)),
|
|
165
166
|
answer,
|
|
166
167
|
);
|
|
167
168
|
|
|
168
|
-
if (await options.satisfied(review, attempt))
|
|
169
|
+
if (await options.satisfied(review, attempt)) {
|
|
170
|
+
yield completeStream();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
169
173
|
if (attempt < maxIterations) {
|
|
170
174
|
current = await options.refine(current, answer, review, attempt);
|
|
171
175
|
}
|
|
172
176
|
}
|
|
177
|
+
|
|
178
|
+
yield completeStream();
|
|
173
179
|
},
|
|
174
180
|
};
|
|
175
181
|
}
|
|
@@ -218,8 +224,8 @@ async function runRetry<T>(
|
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
async function runFallback<T>(
|
|
221
|
-
operation: (executor:
|
|
222
|
-
executors: readonly
|
|
227
|
+
operation: (executor: ExecutorInput, index: number) => Promise<T>,
|
|
228
|
+
executors: readonly ExecutorInput[],
|
|
223
229
|
options: FallbackOptions,
|
|
224
230
|
): Promise<T> {
|
|
225
231
|
let lastError: unknown;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addTool,
|
|
3
|
+
clearIndices,
|
|
4
|
+
toolDefinitions,
|
|
5
|
+
viewProgram,
|
|
6
|
+
type Program,
|
|
7
|
+
type ProgramTool,
|
|
8
|
+
} from "@neutrome/lil-engine";
|
|
9
|
+
import { connectTools } from "../tools.ts";
|
|
10
|
+
import type {
|
|
11
|
+
Executor,
|
|
12
|
+
ExecutorContext,
|
|
13
|
+
ExecutorInput,
|
|
14
|
+
Tool,
|
|
15
|
+
} from "../types.ts";
|
|
16
|
+
|
|
17
|
+
const capabilityToolName = "learn_capability";
|
|
18
|
+
const encoder = new TextEncoder();
|
|
19
|
+
|
|
20
|
+
export type CapabilitiesExecutorOptions = {
|
|
21
|
+
enabledIterations?: number;
|
|
22
|
+
cacheKey?: (ctx: ExecutorContext) => string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type CapabilitySelection = { toolName: string; remaining: number };
|
|
26
|
+
|
|
27
|
+
export function createCapabilitiesExecutor(
|
|
28
|
+
inner: ExecutorInput,
|
|
29
|
+
options: CapabilitiesExecutorOptions = {},
|
|
30
|
+
): Executor {
|
|
31
|
+
const enabledIterations = positiveInteger(options.enabledIterations ?? 5);
|
|
32
|
+
const cacheKey = options.cacheKey ?? ((ctx) => ctx.requestId);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
async execute(request, ctx) {
|
|
36
|
+
const tools = viewProgram(request).tools;
|
|
37
|
+
if (tools.length === 0) return ctx.invoke(inner, request);
|
|
38
|
+
|
|
39
|
+
const key = selectionKey(cacheKey(ctx));
|
|
40
|
+
await discardMissingSelection(ctx, key, tools);
|
|
41
|
+
return connectCapabilities(inner, tools, key, enabledIterations).execute(
|
|
42
|
+
withoutTools(request),
|
|
43
|
+
ctx,
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async *stream(request, ctx) {
|
|
48
|
+
const tools = viewProgram(request).tools;
|
|
49
|
+
if (tools.length === 0) {
|
|
50
|
+
yield* ctx.invokeStream(inner, request);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const key = selectionKey(cacheKey(ctx));
|
|
55
|
+
await discardMissingSelection(ctx, key, tools);
|
|
56
|
+
yield* connectCapabilities(inner, tools, key, enabledIterations).stream(
|
|
57
|
+
withoutTools(request),
|
|
58
|
+
ctx,
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function connectCapabilities(
|
|
65
|
+
inner: ExecutorInput,
|
|
66
|
+
tools: readonly ProgramTool[],
|
|
67
|
+
key: string,
|
|
68
|
+
enabledIterations: number,
|
|
69
|
+
): Executor {
|
|
70
|
+
const selectedInner: Executor = {
|
|
71
|
+
async execute(request, ctx) {
|
|
72
|
+
return ctx.invoke(
|
|
73
|
+
inner,
|
|
74
|
+
await withSelectedTool(request, ctx, key, tools),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
async *stream(request, ctx) {
|
|
78
|
+
yield* ctx.invokeStream(
|
|
79
|
+
inner,
|
|
80
|
+
await withSelectedTool(request, ctx, key, tools),
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return connectTools(
|
|
86
|
+
[learnCapabilityTool(tools, key, enabledIterations)],
|
|
87
|
+
selectedInner,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function learnCapabilityTool(
|
|
92
|
+
tools: readonly ProgramTool[],
|
|
93
|
+
key: string,
|
|
94
|
+
enabledIterations: number,
|
|
95
|
+
): Tool {
|
|
96
|
+
return {
|
|
97
|
+
name: capabilityToolName,
|
|
98
|
+
description: `Enable one capability for the next ${enabledIterations} upstream iterations. Available capabilities: ${tools.map((tool) => tool.name).join(", ")}.`,
|
|
99
|
+
schema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: { capability: { type: "string" } },
|
|
102
|
+
required: ["capability"],
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
},
|
|
105
|
+
async execute(args, ctx) {
|
|
106
|
+
const name = typeof args.capability === "string" ? args.capability : "";
|
|
107
|
+
if (!tools.some((tool) => tool.name === name)) {
|
|
108
|
+
return `Unknown capability: ${name || "(missing)"}.`;
|
|
109
|
+
}
|
|
110
|
+
await writeSelection(ctx, key, {
|
|
111
|
+
toolName: name,
|
|
112
|
+
remaining: enabledIterations,
|
|
113
|
+
});
|
|
114
|
+
return `Enabled ${name} for the next ${enabledIterations} upstream iterations.`;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function withSelectedTool(
|
|
120
|
+
request: Program,
|
|
121
|
+
ctx: ExecutorContext,
|
|
122
|
+
key: string,
|
|
123
|
+
tools: readonly ProgramTool[],
|
|
124
|
+
): Promise<Program> {
|
|
125
|
+
const selection = await readSelection(ctx, key);
|
|
126
|
+
if (!selection || selection.remaining < 1) return request;
|
|
127
|
+
|
|
128
|
+
const tool = tools.find((candidate) => candidate.name === selection.toolName);
|
|
129
|
+
if (!tool) {
|
|
130
|
+
await ctx.cache.delete(key);
|
|
131
|
+
return request;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (selection.remaining === 1) {
|
|
135
|
+
await ctx.cache.delete(key);
|
|
136
|
+
} else {
|
|
137
|
+
await writeSelection(ctx, key, {
|
|
138
|
+
...selection,
|
|
139
|
+
remaining: selection.remaining - 1,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return addProgramTool(request, tool);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function discardMissingSelection(
|
|
146
|
+
ctx: ExecutorContext,
|
|
147
|
+
key: string,
|
|
148
|
+
tools: readonly ProgramTool[],
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const selection = await readSelection(ctx, key);
|
|
151
|
+
if (selection && !tools.some((tool) => tool.name === selection.toolName)) {
|
|
152
|
+
await ctx.cache.delete(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function withoutTools(request: Program): Program {
|
|
157
|
+
const indices: number[] = [];
|
|
158
|
+
for (const definition of toolDefinitions(request)) {
|
|
159
|
+
for (let index = definition.start; index <= definition.end; index += 1) {
|
|
160
|
+
indices.push(index);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return clearIndices(request, indices);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function addProgramTool(request: Program, tool: ProgramTool): Program {
|
|
167
|
+
return addTool(
|
|
168
|
+
request,
|
|
169
|
+
tool.name,
|
|
170
|
+
tool.description,
|
|
171
|
+
encoder.encode(JSON.stringify(tool.schema)),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function readSelection(
|
|
176
|
+
ctx: ExecutorContext,
|
|
177
|
+
key: string,
|
|
178
|
+
): Promise<CapabilitySelection | null> {
|
|
179
|
+
const value = await ctx.cache.get(key);
|
|
180
|
+
if (!value) return null;
|
|
181
|
+
try {
|
|
182
|
+
const parsed: unknown = JSON.parse(value);
|
|
183
|
+
if (
|
|
184
|
+
parsed &&
|
|
185
|
+
typeof parsed === "object" &&
|
|
186
|
+
typeof (parsed as CapabilitySelection).toolName === "string" &&
|
|
187
|
+
Number.isInteger((parsed as CapabilitySelection).remaining)
|
|
188
|
+
) {
|
|
189
|
+
return parsed as CapabilitySelection;
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Treat malformed cache data as an expired selection.
|
|
193
|
+
}
|
|
194
|
+
await ctx.cache.delete(key);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function writeSelection(
|
|
199
|
+
ctx: ExecutorContext,
|
|
200
|
+
key: string,
|
|
201
|
+
selection: CapabilitySelection,
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
return ctx.cache.put(key, JSON.stringify(selection));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function selectionKey(key: string): string {
|
|
207
|
+
return `capabilities:${key}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function positiveInteger(value: number): number {
|
|
211
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
212
|
+
throw new Error("enabledIterations must be a positive integer");
|
|
213
|
+
}
|
|
214
|
+
return value;
|
|
215
|
+
}
|
package/src/managed/index.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createCapabilitiesExecutor } from "./capabilities.ts";
|
|
2
|
+
export type { CapabilitiesExecutorOptions } from "./capabilities.ts";
|
|
2
3
|
|
|
3
4
|
export {
|
|
4
5
|
appendInternalDraft,
|
|
5
6
|
createTwoPassExecutor,
|
|
6
7
|
INTERNAL_DRAFT_CALL_ID,
|
|
7
8
|
INTERNAL_DRAFT_TOOL_NAME,
|
|
8
|
-
|
|
9
|
-
streamExecutor,
|
|
10
|
-
} from "./twoPassExecutor.ts";
|
|
9
|
+
} from "./two-pass.ts";
|
|
11
10
|
|
|
12
11
|
export type {
|
|
13
12
|
InternalDraft,
|
|
14
|
-
ModelExecutor,
|
|
15
13
|
TwoPassExecutorOptions,
|
|
16
14
|
TwoPassSettings,
|
|
17
15
|
TwoPassSettingsResolver,
|
|
18
|
-
} from "./
|
|
16
|
+
} from "./two-pass.ts";
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
callData,
|
|
3
3
|
contentText,
|
|
4
|
+
createProgram,
|
|
4
5
|
deltaText,
|
|
5
6
|
finishReason,
|
|
6
7
|
hasToolDelta,
|
|
7
8
|
lastMessageRole,
|
|
8
|
-
setModel,
|
|
9
9
|
type Program,
|
|
10
10
|
} from "@neutrome/lil-engine";
|
|
11
11
|
import { streamReasoningDelta } from "../output.ts";
|
|
12
|
-
import
|
|
12
|
+
import { completeStream, streamStage } from "../stream/stages.ts";
|
|
13
|
+
import type { Executor, ExecutorContext, ExecutorInput } from "../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
appendInternalDraft,
|
|
15
16
|
buildDraftRequest,
|
|
@@ -34,41 +35,15 @@ export {
|
|
|
34
35
|
INTERNAL_DRAFT_TOOL_NAME,
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
export type ModelExecutor = string | Executor;
|
|
38
|
-
|
|
39
38
|
export type TwoPassExecutorOptions = TwoPassRequestOptions & {
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
draft: ExecutorInput;
|
|
40
|
+
final: ExecutorInput;
|
|
42
41
|
resolveDraftSettings?: TwoPassSettingsResolver;
|
|
43
42
|
resolveFinalSettings?: TwoPassSettingsResolver;
|
|
44
43
|
reasoningIntro?: string;
|
|
45
44
|
reasoningSeparator?: string;
|
|
46
45
|
};
|
|
47
46
|
|
|
48
|
-
export function invokeExecutor(
|
|
49
|
-
ctx: ExecutorContext,
|
|
50
|
-
request: Program,
|
|
51
|
-
executor: ModelExecutor,
|
|
52
|
-
options?: InvokeOptions,
|
|
53
|
-
): Promise<Program> {
|
|
54
|
-
return typeof executor === "string"
|
|
55
|
-
? ctx.invoke(setModel(request, executor), options)
|
|
56
|
-
: executor.execute(request, ctx);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function* streamExecutor(
|
|
60
|
-
ctx: ExecutorContext,
|
|
61
|
-
request: Program,
|
|
62
|
-
executor: ModelExecutor,
|
|
63
|
-
options?: InvokeOptions,
|
|
64
|
-
): AsyncIterable<Program> {
|
|
65
|
-
if (typeof executor === "string") {
|
|
66
|
-
yield* ctx.invokeStream(setModel(request, executor), options);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
yield* executor.stream(request, ctx);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
47
|
export function createTwoPassExecutor(
|
|
73
48
|
options: TwoPassExecutorOptions,
|
|
74
49
|
): Executor {
|
|
@@ -84,14 +59,10 @@ export function createTwoPassExecutor(
|
|
|
84
59
|
}
|
|
85
60
|
const draftRequest = buildDraftRequest(request, settings.draft);
|
|
86
61
|
if (!settings.final || lastMessageRole(request) === "tool") {
|
|
87
|
-
return
|
|
62
|
+
return ctx.invoke(options.draft, draftRequest);
|
|
88
63
|
}
|
|
89
64
|
|
|
90
|
-
const draftResponse = await
|
|
91
|
-
ctx,
|
|
92
|
-
draftRequest,
|
|
93
|
-
options.draftModel,
|
|
94
|
-
);
|
|
65
|
+
const draftResponse = await ctx.invoke(options.draft, draftRequest);
|
|
95
66
|
if (callData(draftResponse).length > 0) return draftResponse;
|
|
96
67
|
|
|
97
68
|
return invokeFinal(
|
|
@@ -111,17 +82,15 @@ export function createTwoPassExecutor(
|
|
|
111
82
|
}
|
|
112
83
|
const draftRequest = buildDraftRequest(request, settings.draft);
|
|
113
84
|
if (!settings.final || lastMessageRole(request) === "tool") {
|
|
114
|
-
yield*
|
|
85
|
+
yield* ctx.invokeStream(options.draft, draftRequest);
|
|
115
86
|
return;
|
|
116
87
|
}
|
|
117
88
|
|
|
118
89
|
let transcript = "";
|
|
119
90
|
let emittedReasoning = false;
|
|
120
91
|
let toolMode = false;
|
|
121
|
-
for await (const chunk of
|
|
122
|
-
ctx,
|
|
123
|
-
draftRequest,
|
|
124
|
-
options.draftModel,
|
|
92
|
+
for await (const chunk of streamStage(
|
|
93
|
+
ctx.invokeStream(options.draft, draftRequest),
|
|
125
94
|
)) {
|
|
126
95
|
if (ctx.signal.aborted) return;
|
|
127
96
|
if (toolMode) {
|
|
@@ -143,7 +112,11 @@ export function createTwoPassExecutor(
|
|
|
143
112
|
yield streamReasoningDelta(text);
|
|
144
113
|
}
|
|
145
114
|
|
|
146
|
-
if (
|
|
115
|
+
if (ctx.signal.aborted) return;
|
|
116
|
+
if (toolMode) {
|
|
117
|
+
yield completeStream("tool_calls");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
147
120
|
if (emittedReasoning && reasoningSeparator) {
|
|
148
121
|
yield streamReasoningDelta(reasoningSeparator);
|
|
149
122
|
}
|
|
@@ -178,10 +151,9 @@ function invokeFinal(
|
|
|
178
151
|
settings: TwoPassSettings,
|
|
179
152
|
draft = "",
|
|
180
153
|
): Promise<Program> {
|
|
181
|
-
return
|
|
182
|
-
|
|
154
|
+
return ctx.invoke(
|
|
155
|
+
options.final,
|
|
183
156
|
buildFinalRequest(options, request, settings, draft),
|
|
184
|
-
options.finalModel,
|
|
185
157
|
);
|
|
186
158
|
}
|
|
187
159
|
|
|
@@ -192,9 +164,8 @@ async function* streamFinal(
|
|
|
192
164
|
settings: TwoPassSettings,
|
|
193
165
|
draft = "",
|
|
194
166
|
): AsyncIterable<Program> {
|
|
195
|
-
yield*
|
|
196
|
-
|
|
167
|
+
yield* ctx.invokeStream(
|
|
168
|
+
options.final,
|
|
197
169
|
buildFinalRequest(options, request, settings, draft),
|
|
198
|
-
options.finalModel,
|
|
199
170
|
);
|
|
200
171
|
}
|
package/src/stream/index.ts
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createProgram, Opcode, type Program } from "@neutrome/lil-engine";
|
|
2
|
+
|
|
3
|
+
/** Streams one stage without allowing it to complete the enclosing stream. */
|
|
4
|
+
export async function* streamStage(
|
|
5
|
+
source: AsyncIterable<Program>,
|
|
6
|
+
): AsyncGenerator<Program> {
|
|
7
|
+
for await (const chunk of source) {
|
|
8
|
+
const code = chunk.code.filter(
|
|
9
|
+
(instruction) =>
|
|
10
|
+
instruction.opcode !== Opcode.RESP_DONE &&
|
|
11
|
+
instruction.opcode !== Opcode.STREAM_END,
|
|
12
|
+
);
|
|
13
|
+
if (code.length === 0) continue;
|
|
14
|
+
yield code.length === chunk.code.length ? chunk : { ...chunk, code };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function completeStream(reason = "stop"): Program {
|
|
19
|
+
return createProgram({
|
|
20
|
+
code: [
|
|
21
|
+
{ opcode: Opcode.RESP_DONE, value: { kind: "string", value: reason } },
|
|
22
|
+
{ opcode: Opcode.STREAM_END, value: { kind: "none" } },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
type StreamToolDelta,
|
|
11
11
|
} from "@neutrome/lil-engine";
|
|
12
12
|
import { appendToolInteraction } from "./synthetic/index.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type Executor,
|
|
15
|
+
type ExecutorContext,
|
|
16
|
+
type ExecutorInput,
|
|
17
|
+
type Tool,
|
|
18
|
+
} from "./types.ts";
|
|
14
19
|
import {
|
|
15
20
|
buildCallExecutor,
|
|
16
21
|
buildToolAugmenter,
|
|
@@ -176,7 +181,7 @@ async function* streamToolLoop(
|
|
|
176
181
|
|
|
177
182
|
export function connectTools(
|
|
178
183
|
tools: Tool[],
|
|
179
|
-
inner:
|
|
184
|
+
inner: ExecutorInput,
|
|
180
185
|
options: WithToolsOptions = {},
|
|
181
186
|
): Executor {
|
|
182
187
|
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
@@ -192,7 +197,7 @@ export function connectTools(
|
|
|
192
197
|
);
|
|
193
198
|
|
|
194
199
|
while (canRunToolLoop(state, maxIterations)) {
|
|
195
|
-
const response = await
|
|
200
|
+
const response = await ctx.invoke(inner, state.request);
|
|
196
201
|
const calls = callData(response);
|
|
197
202
|
const decision = decideToolLoop(calls, toolMap);
|
|
198
203
|
if (decision.kind !== "continue") return response;
|
|
@@ -205,7 +210,7 @@ export function connectTools(
|
|
|
205
210
|
);
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
return
|
|
213
|
+
return ctx.invoke(inner, state.request);
|
|
209
214
|
},
|
|
210
215
|
|
|
211
216
|
async *stream(request, ctx) {
|
|
@@ -216,7 +221,7 @@ export function connectTools(
|
|
|
216
221
|
executeConnectedCalls,
|
|
217
222
|
request,
|
|
218
223
|
ctx,
|
|
219
|
-
(req) =>
|
|
224
|
+
(req) => ctx.invokeStream(inner, req),
|
|
220
225
|
);
|
|
221
226
|
},
|
|
222
227
|
};
|