@neutrome/lilsdk 0.4.5 → 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/README.md +24 -21
- package/etc/api/index.api.md +96 -0
- package/etc/api/loops.api.md +108 -0
- package/etc/api/managed.api.md +102 -0
- package/etc/api/primitives.api.md +39 -0
- package/etc/api/stream.api.md +46 -0
- package/etc/api/tools.api.md +103 -0
- package/package.json +8 -9
- package/src/index.ts +41 -15
- package/src/loops/goal.ts +111 -0
- package/src/loops/index.ts +41 -110
- package/src/loops/shared.ts +27 -0
- package/src/managed/attachment-to-text.ts +35 -11
- package/src/managed/capabilities.ts +65 -25
- package/src/managed/index.ts +0 -14
- package/src/observe.ts +21 -6
- package/src/output.ts +13 -25
- package/src/primitives/index.ts +69 -0
- package/src/stream/index.ts +2 -6
- package/src/stream/stages.ts +15 -11
- package/src/tools-support.ts +61 -30
- package/src/tools.ts +37 -20
- package/src/types.ts +129 -4
- package/test/lilsdk-ts.test.ts +71 -341
- package/test/tools.test.ts +69 -21
- package/src/managed/two-pass-request.ts +0 -164
- package/src/managed/two-pass.ts +0 -171
- package/src/synthetic/index.ts +0 -134
package/test/tools.test.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
callData,
|
|
4
3
|
createProgram,
|
|
4
|
+
extractToolCalls,
|
|
5
5
|
Opcode,
|
|
6
|
+
ProgramView,
|
|
6
7
|
type Instruction,
|
|
7
8
|
type Program,
|
|
8
9
|
} from "@neutrome/lil-engine";
|
|
9
|
-
import {
|
|
10
|
+
import { createToolsExecutor } from "../src/tools.ts";
|
|
10
11
|
import type { ExecutorContext, Tool } from "../src/types.ts";
|
|
11
12
|
|
|
12
13
|
function buildToolCallResponse(
|
|
@@ -63,7 +64,7 @@ const calculatorTool: Tool = {
|
|
|
63
64
|
},
|
|
64
65
|
};
|
|
65
66
|
|
|
66
|
-
describe("
|
|
67
|
+
describe("createToolsExecutor", () => {
|
|
67
68
|
it("passes the executor cache to connected tools", async () => {
|
|
68
69
|
let callCount = 0;
|
|
69
70
|
const cache = {
|
|
@@ -82,7 +83,7 @@ describe("connectTools", () => {
|
|
|
82
83
|
return (await ctx.cache.get("tool-key")) ?? "missing";
|
|
83
84
|
},
|
|
84
85
|
};
|
|
85
|
-
const executor =
|
|
86
|
+
const executor = createToolsExecutor("test-model", [tool]);
|
|
86
87
|
const ctx = buildCtx({
|
|
87
88
|
async invoke() {
|
|
88
89
|
callCount += 1;
|
|
@@ -104,7 +105,7 @@ describe("connectTools", () => {
|
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
it("passes through when LLM returns no tool calls", async () => {
|
|
107
|
-
const executor =
|
|
108
|
+
const executor = createToolsExecutor("test-model", [calculatorTool]);
|
|
108
109
|
|
|
109
110
|
const request = createProgram({
|
|
110
111
|
code: [
|
|
@@ -135,7 +136,7 @@ describe("connectTools", () => {
|
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
it("executes connected tool and loops until text response", async () => {
|
|
138
|
-
const executor =
|
|
139
|
+
const executor = createToolsExecutor("test-model", [calculatorTool]);
|
|
139
140
|
|
|
140
141
|
let callCount = 0;
|
|
141
142
|
const request = createProgram({
|
|
@@ -173,22 +174,69 @@ describe("connectTools", () => {
|
|
|
173
174
|
expect(hasAnswer).toBe(true);
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
it("
|
|
177
|
-
const executor =
|
|
177
|
+
it("returns malformed connected-tool arguments to the model", async () => {
|
|
178
|
+
const executor = createToolsExecutor("test-model", [calculatorTool]);
|
|
179
|
+
let retryRequest: Program | undefined;
|
|
180
|
+
let invocations = 0;
|
|
178
181
|
const ctx = buildCtx({
|
|
179
|
-
async invoke() {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
async invoke(_executor, request) {
|
|
183
|
+
invocations += 1;
|
|
184
|
+
if (invocations === 1) {
|
|
185
|
+
return buildToolCallResponse([
|
|
186
|
+
{ id: "call_1", name: "calculator", args: "{not json}" },
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
189
|
+
retryRequest = request;
|
|
190
|
+
return buildTextResponse("I need valid calculator arguments.");
|
|
183
191
|
},
|
|
184
192
|
async *invokeStream() {
|
|
185
193
|
return;
|
|
186
194
|
},
|
|
187
195
|
});
|
|
188
196
|
|
|
189
|
-
await
|
|
190
|
-
|
|
197
|
+
const result = await executor.execute(createProgram(), ctx);
|
|
198
|
+
|
|
199
|
+
expect(invocations).toBe(2);
|
|
200
|
+
expect(
|
|
201
|
+
new ProgramView(retryRequest!).messages.at(-1)?.toolResult?.text,
|
|
202
|
+
).toBe(
|
|
203
|
+
'Tool "calculator" failed: Tool "calculator" received invalid JSON object arguments',
|
|
191
204
|
);
|
|
205
|
+
expect(extractToolCalls(result)).toHaveLength(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("returns thrown connected-tool errors to the model", async () => {
|
|
209
|
+
const failingTool: Tool = {
|
|
210
|
+
...calculatorTool,
|
|
211
|
+
async execute() {
|
|
212
|
+
throw new Error("Calculator service is unavailable");
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
const executor = createToolsExecutor("test-model", [failingTool]);
|
|
216
|
+
let retryRequest: Program | undefined;
|
|
217
|
+
let invocations = 0;
|
|
218
|
+
const ctx = buildCtx({
|
|
219
|
+
async invoke(_executor, request) {
|
|
220
|
+
invocations += 1;
|
|
221
|
+
if (invocations === 1) {
|
|
222
|
+
return buildToolCallResponse([
|
|
223
|
+
{ id: "call_1", name: "calculator", args: '{"expr":"2+2"}' },
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
retryRequest = request;
|
|
227
|
+
return buildTextResponse("I cannot calculate that right now.");
|
|
228
|
+
},
|
|
229
|
+
async *invokeStream() {
|
|
230
|
+
return;
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await executor.execute(createProgram(), ctx);
|
|
235
|
+
|
|
236
|
+
expect(invocations).toBe(2);
|
|
237
|
+
expect(
|
|
238
|
+
new ProgramView(retryRequest!).messages.at(-1)?.toolResult?.text,
|
|
239
|
+
).toBe('Tool "calculator" failed: Calculator service is unavailable');
|
|
192
240
|
});
|
|
193
241
|
|
|
194
242
|
it("handles multiple tool calls in single response (parallel execution)", async () => {
|
|
@@ -203,7 +251,7 @@ describe("connectTools", () => {
|
|
|
203
251
|
return `value_of_${key}`;
|
|
204
252
|
},
|
|
205
253
|
};
|
|
206
|
-
const executor =
|
|
254
|
+
const executor = createToolsExecutor("test-model", [multiTool]);
|
|
207
255
|
|
|
208
256
|
let callCount = 0;
|
|
209
257
|
const request = createProgram();
|
|
@@ -230,7 +278,7 @@ describe("connectTools", () => {
|
|
|
230
278
|
});
|
|
231
279
|
|
|
232
280
|
it("returns immediately when outer (non-connected) tool calls are present", async () => {
|
|
233
|
-
const executor =
|
|
281
|
+
const executor = createToolsExecutor("test-model", [calculatorTool]);
|
|
234
282
|
|
|
235
283
|
const request = createProgram();
|
|
236
284
|
const ctx = buildCtx({
|
|
@@ -252,17 +300,17 @@ describe("connectTools", () => {
|
|
|
252
300
|
return calculatorTool.execute(args, ctx);
|
|
253
301
|
},
|
|
254
302
|
};
|
|
255
|
-
const guardedExecutor =
|
|
303
|
+
const guardedExecutor = createToolsExecutor("test-model", [tool]);
|
|
256
304
|
const result = await guardedExecutor.execute(request, ctx);
|
|
257
305
|
|
|
258
|
-
const calls =
|
|
306
|
+
const calls = extractToolCalls(result);
|
|
259
307
|
expect(calls).toHaveLength(2);
|
|
260
308
|
expect(calls.some((c) => c.name === "external_tool")).toBe(true);
|
|
261
309
|
expect(executions).toBe(0);
|
|
262
310
|
});
|
|
263
311
|
|
|
264
312
|
it("respects maxIterations", async () => {
|
|
265
|
-
const executor =
|
|
313
|
+
const executor = createToolsExecutor("test-model", [calculatorTool], {
|
|
266
314
|
maxIterations: 2,
|
|
267
315
|
});
|
|
268
316
|
|
|
@@ -290,7 +338,7 @@ describe("connectTools", () => {
|
|
|
290
338
|
});
|
|
291
339
|
|
|
292
340
|
it("streams final response after tool loop completes", async () => {
|
|
293
|
-
const executor =
|
|
341
|
+
const executor = createToolsExecutor("test-model", [calculatorTool]);
|
|
294
342
|
|
|
295
343
|
let callCount = 0;
|
|
296
344
|
const request = createProgram();
|
|
@@ -349,7 +397,7 @@ describe("connectTools", () => {
|
|
|
349
397
|
return "4";
|
|
350
398
|
},
|
|
351
399
|
};
|
|
352
|
-
const executor =
|
|
400
|
+
const executor = createToolsExecutor("test-model", [tool]);
|
|
353
401
|
let invocation = 0;
|
|
354
402
|
const ctx = buildCtx({
|
|
355
403
|
async invoke() {
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
appendInstructions,
|
|
3
|
-
contentText,
|
|
4
|
-
insertBefore,
|
|
5
|
-
messageText,
|
|
6
|
-
messages,
|
|
7
|
-
removeRange,
|
|
8
|
-
type Program,
|
|
9
|
-
} from "@neutrome/lil-engine";
|
|
10
|
-
import { Opcode } from "@neutrome/lil-engine";
|
|
11
|
-
import {
|
|
12
|
-
appendToolInteraction,
|
|
13
|
-
makeMessage,
|
|
14
|
-
prependSystemPrompt,
|
|
15
|
-
} from "../synthetic/index.ts";
|
|
16
|
-
import type { ExecutorContext } from "../types.ts";
|
|
17
|
-
|
|
18
|
-
export const INTERNAL_DRAFT_TOOL_NAME = "knowledge";
|
|
19
|
-
export const INTERNAL_DRAFT_CALL_ID = "knowledge_0";
|
|
20
|
-
|
|
21
|
-
export type InternalDraft =
|
|
22
|
-
| string
|
|
23
|
-
| readonly string[]
|
|
24
|
-
| readonly { text: string }[];
|
|
25
|
-
|
|
26
|
-
export type TwoPassSettings = {
|
|
27
|
-
reasoningLevel?: string;
|
|
28
|
-
systemPrompt?: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type TwoPassSettingsResolver = (
|
|
32
|
-
request: Program,
|
|
33
|
-
ctx: ExecutorContext,
|
|
34
|
-
) => TwoPassSettings | null | Promise<TwoPassSettings | null>;
|
|
35
|
-
|
|
36
|
-
export type TwoPassRequestOptions = {
|
|
37
|
-
maxTotalContextLength?: number;
|
|
38
|
-
buildFinalRequest?: (request: Program, draft: string) => Program;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export function appendInternalDraft(
|
|
42
|
-
request: Program,
|
|
43
|
-
draft: InternalDraft,
|
|
44
|
-
options: { callId?: string } = {},
|
|
45
|
-
): Program {
|
|
46
|
-
const callId = options.callId ?? INTERNAL_DRAFT_CALL_ID;
|
|
47
|
-
const drafts = normalizeDrafts(draft);
|
|
48
|
-
if (drafts.length === 0) return request;
|
|
49
|
-
return drafts.reduce(
|
|
50
|
-
(program, text, index) =>
|
|
51
|
-
appendToolInteraction(program, {
|
|
52
|
-
callId: drafts.length === 1 ? callId : `${callId}_${index}`,
|
|
53
|
-
name: INTERNAL_DRAFT_TOOL_NAME,
|
|
54
|
-
args: {},
|
|
55
|
-
result: text,
|
|
56
|
-
}),
|
|
57
|
-
request,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function buildDraftRequest(
|
|
62
|
-
request: Program,
|
|
63
|
-
settings: TwoPassSettings,
|
|
64
|
-
): Program {
|
|
65
|
-
const withPrompt = settings.systemPrompt
|
|
66
|
-
? replaceSystemPrompt(request, settings.systemPrompt)
|
|
67
|
-
: request;
|
|
68
|
-
return applySettings(withPrompt, settings);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function buildFinalRequest(
|
|
72
|
-
options: TwoPassRequestOptions,
|
|
73
|
-
request: Program,
|
|
74
|
-
settings: TwoPassSettings,
|
|
75
|
-
draft: string,
|
|
76
|
-
): Program {
|
|
77
|
-
const limitedDraft = limitDraft(options, request, settings, draft);
|
|
78
|
-
const withDraft = options.buildFinalRequest
|
|
79
|
-
? options.buildFinalRequest(request, limitedDraft)
|
|
80
|
-
: appendInternalDraft(request, limitedDraft);
|
|
81
|
-
const withPrompt = settings.systemPrompt
|
|
82
|
-
? prependSystemPrompt(withDraft, settings.systemPrompt)
|
|
83
|
-
: withDraft;
|
|
84
|
-
return applySettings(withPrompt, settings);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function validateMaxContextLength(value: number | undefined): void {
|
|
88
|
-
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
89
|
-
throw new Error("maxTotalContextLength must be a non-negative integer");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function replaceSystemPrompt(request: Program, systemPrompt: string): Program {
|
|
94
|
-
const systemSpans = messages(request).filter(
|
|
95
|
-
(span) => span.role === "system",
|
|
96
|
-
);
|
|
97
|
-
const previousSystemPrompt = systemSpans
|
|
98
|
-
.map((span) => messageText(request, span).trim())
|
|
99
|
-
.filter(Boolean)
|
|
100
|
-
.join("\n\n");
|
|
101
|
-
let draft = request;
|
|
102
|
-
for (const span of systemSpans.slice().reverse()) {
|
|
103
|
-
draft = removeRange(draft, span.start, span.end);
|
|
104
|
-
}
|
|
105
|
-
draft = prependSystemPrompt(draft, systemPrompt);
|
|
106
|
-
if (!previousSystemPrompt) return draft;
|
|
107
|
-
const firstNonSystem = messages(draft).find((span) => span.role !== "system");
|
|
108
|
-
return insertBefore(
|
|
109
|
-
draft,
|
|
110
|
-
firstNonSystem?.start ?? draft.code.length,
|
|
111
|
-
makeMessage("user", previousSystemPrompt).code,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function applySettings(request: Program, settings: TwoPassSettings): Program {
|
|
116
|
-
if (!settings.reasoningLevel) return request;
|
|
117
|
-
return appendInstructions(
|
|
118
|
-
{
|
|
119
|
-
...request,
|
|
120
|
-
code: request.code.filter(
|
|
121
|
-
(instruction) => instruction.opcode !== Opcode.SET_REASON_EFFORT,
|
|
122
|
-
),
|
|
123
|
-
},
|
|
124
|
-
[
|
|
125
|
-
{
|
|
126
|
-
opcode: Opcode.SET_REASON_EFFORT,
|
|
127
|
-
value: { kind: "string", value: settings.reasoningLevel },
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function limitDraft(
|
|
134
|
-
options: TwoPassRequestOptions,
|
|
135
|
-
request: Program,
|
|
136
|
-
settings: TwoPassSettings,
|
|
137
|
-
draft: string,
|
|
138
|
-
): string {
|
|
139
|
-
if (options.maxTotalContextLength === undefined) return draft;
|
|
140
|
-
const base = buildFinalRequestWithoutDraft(options, request, settings);
|
|
141
|
-
const available = options.maxTotalContextLength - contentText(base).length;
|
|
142
|
-
return available > 0 ? draft.slice(0, available) : "";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function buildFinalRequestWithoutDraft(
|
|
146
|
-
options: TwoPassRequestOptions,
|
|
147
|
-
request: Program,
|
|
148
|
-
settings: TwoPassSettings,
|
|
149
|
-
): Program {
|
|
150
|
-
const withDraft = options.buildFinalRequest
|
|
151
|
-
? options.buildFinalRequest(request, "")
|
|
152
|
-
: request;
|
|
153
|
-
const withPrompt = settings.systemPrompt
|
|
154
|
-
? prependSystemPrompt(withDraft, settings.systemPrompt)
|
|
155
|
-
: withDraft;
|
|
156
|
-
return applySettings(withPrompt, settings);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function normalizeDrafts(draft: InternalDraft): string[] {
|
|
160
|
-
const drafts = Array.isArray(draft)
|
|
161
|
-
? draft.map((item) => (typeof item === "string" ? item : item.text))
|
|
162
|
-
: [draft];
|
|
163
|
-
return drafts.map((text) => text.trim()).filter((text) => text.length > 0);
|
|
164
|
-
}
|
package/src/managed/two-pass.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
callData,
|
|
3
|
-
contentText,
|
|
4
|
-
createProgram,
|
|
5
|
-
deltaText,
|
|
6
|
-
finishReason,
|
|
7
|
-
hasToolDelta,
|
|
8
|
-
lastMessageRole,
|
|
9
|
-
type Program,
|
|
10
|
-
} from "@neutrome/lil-engine";
|
|
11
|
-
import { streamReasoningDelta } from "../output.ts";
|
|
12
|
-
import { completeStream, streamStage } from "../stream/stages.ts";
|
|
13
|
-
import type { Executor, ExecutorContext, ExecutorInput } from "../types.ts";
|
|
14
|
-
import {
|
|
15
|
-
appendInternalDraft,
|
|
16
|
-
buildDraftRequest,
|
|
17
|
-
buildFinalRequest,
|
|
18
|
-
INTERNAL_DRAFT_CALL_ID,
|
|
19
|
-
INTERNAL_DRAFT_TOOL_NAME,
|
|
20
|
-
validateMaxContextLength,
|
|
21
|
-
type InternalDraft,
|
|
22
|
-
type TwoPassRequestOptions,
|
|
23
|
-
type TwoPassSettings,
|
|
24
|
-
type TwoPassSettingsResolver,
|
|
25
|
-
} from "./two-pass-request.ts";
|
|
26
|
-
|
|
27
|
-
export type {
|
|
28
|
-
InternalDraft,
|
|
29
|
-
TwoPassSettings,
|
|
30
|
-
TwoPassSettingsResolver,
|
|
31
|
-
} from "./two-pass-request.ts";
|
|
32
|
-
export {
|
|
33
|
-
appendInternalDraft,
|
|
34
|
-
INTERNAL_DRAFT_CALL_ID,
|
|
35
|
-
INTERNAL_DRAFT_TOOL_NAME,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type TwoPassExecutorOptions = TwoPassRequestOptions & {
|
|
39
|
-
draft: ExecutorInput;
|
|
40
|
-
final: ExecutorInput;
|
|
41
|
-
resolveDraftSettings?: TwoPassSettingsResolver;
|
|
42
|
-
resolveFinalSettings?: TwoPassSettingsResolver;
|
|
43
|
-
reasoningIntro?: string;
|
|
44
|
-
reasoningSeparator?: string;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export function createTwoPassExecutor(
|
|
48
|
-
options: TwoPassExecutorOptions,
|
|
49
|
-
): Executor {
|
|
50
|
-
const reasoningIntro = options.reasoningIntro ?? "Let me think...\n\n";
|
|
51
|
-
const reasoningSeparator = options.reasoningSeparator ?? "\n\n";
|
|
52
|
-
validateMaxContextLength(options.maxTotalContextLength);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
async execute(request, ctx) {
|
|
56
|
-
const settings = await resolveSettings(options, request, ctx);
|
|
57
|
-
if (!settings.draft) {
|
|
58
|
-
return invokeFinal(options, request, ctx, settings.final!);
|
|
59
|
-
}
|
|
60
|
-
const draftRequest = buildDraftRequest(request, settings.draft);
|
|
61
|
-
if (!settings.final || lastMessageRole(request) === "tool") {
|
|
62
|
-
return ctx.invoke(options.draft, draftRequest);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const draftResponse = await ctx.invoke(options.draft, draftRequest);
|
|
66
|
-
if (callData(draftResponse).length > 0) return draftResponse;
|
|
67
|
-
|
|
68
|
-
return invokeFinal(
|
|
69
|
-
options,
|
|
70
|
-
request,
|
|
71
|
-
ctx,
|
|
72
|
-
settings.final,
|
|
73
|
-
contentText(draftResponse),
|
|
74
|
-
);
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
async *stream(request, ctx) {
|
|
78
|
-
const settings = await resolveSettings(options, request, ctx);
|
|
79
|
-
if (!settings.draft) {
|
|
80
|
-
yield* streamFinal(options, request, ctx, settings.final!);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const draftRequest = buildDraftRequest(request, settings.draft);
|
|
84
|
-
if (!settings.final || lastMessageRole(request) === "tool") {
|
|
85
|
-
yield* ctx.invokeStream(options.draft, draftRequest);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let transcript = "";
|
|
90
|
-
let emittedReasoning = false;
|
|
91
|
-
let toolMode = false;
|
|
92
|
-
for await (const chunk of streamStage(
|
|
93
|
-
ctx.invokeStream(options.draft, draftRequest),
|
|
94
|
-
)) {
|
|
95
|
-
if (ctx.signal.aborted) return;
|
|
96
|
-
if (toolMode) {
|
|
97
|
-
yield chunk;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (hasToolDelta(chunk) || finishReason(chunk) === "tool_calls") {
|
|
101
|
-
toolMode = true;
|
|
102
|
-
yield chunk;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const text = deltaText(chunk);
|
|
106
|
-
if (!text) continue;
|
|
107
|
-
transcript += text;
|
|
108
|
-
if (!emittedReasoning) {
|
|
109
|
-
emittedReasoning = true;
|
|
110
|
-
yield streamReasoningDelta(reasoningIntro);
|
|
111
|
-
}
|
|
112
|
-
yield streamReasoningDelta(text);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (ctx.signal.aborted) return;
|
|
116
|
-
if (toolMode) {
|
|
117
|
-
yield completeStream("tool_calls");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (emittedReasoning && reasoningSeparator) {
|
|
121
|
-
yield streamReasoningDelta(reasoningSeparator);
|
|
122
|
-
}
|
|
123
|
-
yield* streamFinal(options, request, ctx, settings.final, transcript);
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function resolveSettings(
|
|
129
|
-
options: TwoPassExecutorOptions,
|
|
130
|
-
request: Program,
|
|
131
|
-
ctx: ExecutorContext,
|
|
132
|
-
): Promise<{ draft: TwoPassSettings | null; final: TwoPassSettings | null }> {
|
|
133
|
-
const [draft, final] = await Promise.all([
|
|
134
|
-
options.resolveDraftSettings
|
|
135
|
-
? options.resolveDraftSettings(request, ctx)
|
|
136
|
-
: {},
|
|
137
|
-
options.resolveFinalSettings
|
|
138
|
-
? options.resolveFinalSettings(request, ctx)
|
|
139
|
-
: {},
|
|
140
|
-
]);
|
|
141
|
-
if (!draft && !final) {
|
|
142
|
-
throw new Error("Two-pass executor requires at least one enabled pass");
|
|
143
|
-
}
|
|
144
|
-
return { draft, final };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function invokeFinal(
|
|
148
|
-
options: TwoPassExecutorOptions,
|
|
149
|
-
request: Program,
|
|
150
|
-
ctx: ExecutorContext,
|
|
151
|
-
settings: TwoPassSettings,
|
|
152
|
-
draft = "",
|
|
153
|
-
): Promise<Program> {
|
|
154
|
-
return ctx.invoke(
|
|
155
|
-
options.final,
|
|
156
|
-
buildFinalRequest(options, request, settings, draft),
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function* streamFinal(
|
|
161
|
-
options: TwoPassExecutorOptions,
|
|
162
|
-
request: Program,
|
|
163
|
-
ctx: ExecutorContext,
|
|
164
|
-
settings: TwoPassSettings,
|
|
165
|
-
draft = "",
|
|
166
|
-
): AsyncIterable<Program> {
|
|
167
|
-
yield* ctx.invokeStream(
|
|
168
|
-
options.final,
|
|
169
|
-
buildFinalRequest(options, request, settings, draft),
|
|
170
|
-
);
|
|
171
|
-
}
|
package/src/synthetic/index.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addTool,
|
|
3
|
-
appendInstructions,
|
|
4
|
-
createProgram,
|
|
5
|
-
insertBefore,
|
|
6
|
-
messages,
|
|
7
|
-
Opcode,
|
|
8
|
-
setModel,
|
|
9
|
-
type Instruction,
|
|
10
|
-
type Opcode as OpcodeValue,
|
|
11
|
-
type Program,
|
|
12
|
-
} from "@neutrome/lil-engine";
|
|
13
|
-
|
|
14
|
-
const encoder = new TextEncoder();
|
|
15
|
-
|
|
16
|
-
export type TextMessageRole = "system" | "user" | "assistant";
|
|
17
|
-
|
|
18
|
-
export type ToolInteraction = {
|
|
19
|
-
callId: string;
|
|
20
|
-
name: string;
|
|
21
|
-
args?: unknown;
|
|
22
|
-
result: unknown;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export function createModelProgram(model: string): Program {
|
|
26
|
-
return setModel(createProgram(), model);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function makeMessage(role: TextMessageRole, text: string): Program {
|
|
30
|
-
return createProgram({ code: buildMessage(role, text) });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function prependSystemPrompt(program: Program, text: string): Program {
|
|
34
|
-
const systemMessage = buildMessage("system", text);
|
|
35
|
-
const spans = messages(program);
|
|
36
|
-
if (spans.length === 0) {
|
|
37
|
-
return appendInstructions(program, systemMessage);
|
|
38
|
-
}
|
|
39
|
-
const firstSpan = spans[0];
|
|
40
|
-
if (!firstSpan) return appendInstructions(program, systemMessage);
|
|
41
|
-
return insertBefore(program, firstSpan.start, systemMessage);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function appendUserMessage(program: Program, text: string): Program {
|
|
45
|
-
return appendInstructions(program, buildMessage("user", text));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function appendAssistantMessage(
|
|
49
|
-
program: Program,
|
|
50
|
-
text: string,
|
|
51
|
-
): Program {
|
|
52
|
-
return appendInstructions(program, buildMessage("assistant", text));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function makeToolCall(
|
|
56
|
-
name: string,
|
|
57
|
-
args: unknown = {},
|
|
58
|
-
callId: string = crypto.randomUUID(),
|
|
59
|
-
): Program {
|
|
60
|
-
return createProgram({
|
|
61
|
-
code: [
|
|
62
|
-
{ opcode: Opcode.MSG_START, value: { kind: "none" } },
|
|
63
|
-
{ opcode: Opcode.ROLE_AST, value: { kind: "none" } },
|
|
64
|
-
{ opcode: Opcode.CALL_START, value: { kind: "string", value: callId } },
|
|
65
|
-
{ opcode: Opcode.CALL_NAME, value: { kind: "string", value: name } },
|
|
66
|
-
{
|
|
67
|
-
opcode: Opcode.CALL_ARGS,
|
|
68
|
-
value: { kind: "json", value: encodeJson(args) },
|
|
69
|
-
},
|
|
70
|
-
{ opcode: Opcode.CALL_END, value: { kind: "none" } },
|
|
71
|
-
{ opcode: Opcode.MSG_END, value: { kind: "none" } },
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function makeToolResponse(callId: string, result: unknown): Program {
|
|
77
|
-
return createProgram({ code: buildToolResponse(callId, result) });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function appendToolInteraction(
|
|
81
|
-
program: Program,
|
|
82
|
-
interaction: ToolInteraction,
|
|
83
|
-
): Program {
|
|
84
|
-
return appendInstructions(program, [
|
|
85
|
-
...makeToolCall(
|
|
86
|
-
interaction.name,
|
|
87
|
-
interaction.args ?? {},
|
|
88
|
-
interaction.callId,
|
|
89
|
-
).code,
|
|
90
|
-
...buildToolResponse(interaction.callId, interaction.result),
|
|
91
|
-
]);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function buildMessage(role: TextMessageRole, text: string): Instruction[] {
|
|
95
|
-
return [
|
|
96
|
-
{ opcode: Opcode.MSG_START, value: { kind: "none" } },
|
|
97
|
-
{ opcode: roleOpcode(role), value: { kind: "none" } },
|
|
98
|
-
{ opcode: Opcode.TXT_CHUNK, value: { kind: "string", value: text } },
|
|
99
|
-
{ opcode: Opcode.MSG_END, value: { kind: "none" } },
|
|
100
|
-
];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function buildToolResponse(callId: string, result: unknown): Instruction[] {
|
|
104
|
-
return [
|
|
105
|
-
{ opcode: Opcode.MSG_START, value: { kind: "none" } },
|
|
106
|
-
{ opcode: Opcode.ROLE_TOOL, value: { kind: "none" } },
|
|
107
|
-
{ opcode: Opcode.RESULT_START, value: { kind: "string", value: callId } },
|
|
108
|
-
{
|
|
109
|
-
opcode: Opcode.RESULT_DATA,
|
|
110
|
-
value: { kind: "string", value: stringifyResult(result) },
|
|
111
|
-
},
|
|
112
|
-
{ opcode: Opcode.RESULT_END, value: { kind: "none" } },
|
|
113
|
-
{ opcode: Opcode.MSG_END, value: { kind: "none" } },
|
|
114
|
-
];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function roleOpcode(role: TextMessageRole): OpcodeValue {
|
|
118
|
-
switch (role) {
|
|
119
|
-
case "system":
|
|
120
|
-
return Opcode.ROLE_SYS;
|
|
121
|
-
case "user":
|
|
122
|
-
return Opcode.ROLE_USR;
|
|
123
|
-
case "assistant":
|
|
124
|
-
return Opcode.ROLE_AST;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function encodeJson(value: unknown): Uint8Array {
|
|
129
|
-
return encoder.encode(JSON.stringify(value));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function stringifyResult(result: unknown): string {
|
|
133
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
134
|
-
}
|