agency-lang 0.0.44 → 0.0.46
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 +5 -2
- package/dist/lib/backends/baseGenerator.d.ts +7 -3
- package/dist/lib/backends/baseGenerator.js +31 -0
- package/dist/lib/backends/graphGenerator.js +1 -1
- package/dist/lib/backends/typescriptGenerator.d.ts +0 -1
- package/dist/lib/backends/typescriptGenerator.js +38 -22
- package/dist/lib/templates/backends/graphGenerator/graphNode.d.ts +1 -1
- package/dist/lib/templates/backends/graphGenerator/graphNode.js +23 -1
- package/dist/lib/templates/backends/graphGenerator/imports.d.ts +1 -1
- package/dist/lib/templates/backends/graphGenerator/imports.js +36 -47
- package/dist/lib/templates/backends/typescriptGenerator/functionDefinition.d.ts +1 -1
- package/dist/lib/templates/backends/typescriptGenerator/functionDefinition.js +9 -4
- package/dist/lib/templates/backends/typescriptGenerator/promptFunction.d.ts +1 -1
- package/dist/lib/templates/backends/typescriptGenerator/promptFunction.js +4 -0
- package/dist/lib/utils.js +0 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,10 +25,13 @@ agency infile.agency
|
|
|
25
25
|
Note that the generated files use several other libraries that you will need to install as well:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
pnpm i nanoid openai
|
|
28
|
+
pnpm i nanoid openai piemachine statelog-client zod
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## troubleshooting
|
|
32
32
|
### Weird undefined error
|
|
33
33
|
|
|
34
|
-
A couple of times, I have tried to import a parser, and even though it exists, when I import it, the value that is `undefined`. This is due to a circular dependency issue. If I move that parser to its own file and then import it, it works.
|
|
34
|
+
A couple of times, I have tried to import a parser, and even though it exists, when I import it, the value that is `undefined`. This is due to a circular dependency issue. If I move that parser to its own file and then import it, it works.
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
[FSL](https://fsl.software).
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { SpecialVar } from "../types/specialVar.js";
|
|
2
2
|
import { AgencyComment, AgencyNode, AgencyProgram, Assignment, Literal, NewLine, PromptLiteral, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
|
|
3
|
+
import { AwaitStatement } from "../types/await.js";
|
|
4
|
+
import { TimeBlock } from "../types/timeBlock.js";
|
|
3
5
|
import { AccessExpression, DotFunctionCall, DotProperty, IndexAccess } from "../types/access.js";
|
|
4
6
|
import { AgencyArray, AgencyObject } from "../types/dataStructures.js";
|
|
5
7
|
import { FunctionCall, FunctionDefinition } from "../types/function.js";
|
|
6
8
|
import { GraphNodeDefinition } from "../types/graphNode.js";
|
|
9
|
+
import { IfElse } from "../types/ifElse.js";
|
|
7
10
|
import { ImportNodeStatement, ImportStatement, ImportToolStatement } from "../types/importStatement.js";
|
|
8
11
|
import { MatchBlock } from "../types/matchBlock.js";
|
|
9
12
|
import { ReturnStatement } from "../types/returnStatement.js";
|
|
10
13
|
import { UsesTool } from "../types/tools.js";
|
|
11
14
|
import { WhileLoop } from "../types/whileLoop.js";
|
|
12
|
-
import { IfElse } from "../types/ifElse.js";
|
|
13
|
-
import { TimeBlock } from "../types/timeBlock.js";
|
|
14
|
-
import { AwaitStatement } from "../types/await.js";
|
|
15
15
|
type Scope = "global" | "function" | "node";
|
|
16
16
|
export declare class BaseGenerator {
|
|
17
17
|
protected typeHints: TypeHintMap;
|
|
@@ -71,6 +71,10 @@ export declare class BaseGenerator {
|
|
|
71
71
|
protected postprocess(): string;
|
|
72
72
|
protected startScope(scope: Scope): void;
|
|
73
73
|
protected endScope(): void;
|
|
74
|
+
protected getCurrentScope(): Scope;
|
|
74
75
|
protected getScopeVar(): string;
|
|
76
|
+
protected generateScopedVariableName(variableName: string): string;
|
|
77
|
+
protected isImportedTool(functionName: string): boolean;
|
|
78
|
+
protected isInternalFunction(functionName: string): boolean;
|
|
75
79
|
}
|
|
76
80
|
export {};
|
|
@@ -261,6 +261,12 @@ export class BaseGenerator {
|
|
|
261
261
|
endScope() {
|
|
262
262
|
this.currentScope.pop();
|
|
263
263
|
}
|
|
264
|
+
getCurrentScope() {
|
|
265
|
+
return this.currentScope[this.currentScope.length - 1];
|
|
266
|
+
}
|
|
267
|
+
/* This function is used during assignment, so we can figure out whether this variable
|
|
268
|
+
should be assigned on local or global scope. There's a related function `generateScopedVariableName`,
|
|
269
|
+
which is used when retrieving the value of a variable. */
|
|
264
270
|
getScopeVar() {
|
|
265
271
|
const currentScope = this.currentScope[this.currentScope.length - 1];
|
|
266
272
|
switch (currentScope) {
|
|
@@ -271,4 +277,29 @@ export class BaseGenerator {
|
|
|
271
277
|
return "__stack.locals";
|
|
272
278
|
}
|
|
273
279
|
}
|
|
280
|
+
generateScopedVariableName(variableName) {
|
|
281
|
+
if (this.functionParameters.includes(variableName)) {
|
|
282
|
+
return `__stack.args.${variableName}`;
|
|
283
|
+
}
|
|
284
|
+
if (this.functionScopedVariables.includes(variableName)) {
|
|
285
|
+
return `__stack.locals.${variableName}`;
|
|
286
|
+
}
|
|
287
|
+
else if (this.globalScopedVariables.includes(variableName)) {
|
|
288
|
+
return `__stateStack.globals.${variableName}`;
|
|
289
|
+
}
|
|
290
|
+
return variableName;
|
|
291
|
+
}
|
|
292
|
+
isImportedTool(functionName) {
|
|
293
|
+
return this.importedTools
|
|
294
|
+
.map((node) => node.importedTools)
|
|
295
|
+
.flat()
|
|
296
|
+
.includes(functionName);
|
|
297
|
+
}
|
|
298
|
+
/* Internal function means the user defined this function in an Agency file,
|
|
299
|
+
as opposed to an external function, which was defined in an external TypeScript file
|
|
300
|
+
and imported into Agency. */
|
|
301
|
+
isInternalFunction(functionName) {
|
|
302
|
+
return (!!this.functionDefinitions[functionName] ||
|
|
303
|
+
this.isImportedTool(functionName));
|
|
304
|
+
}
|
|
274
305
|
}
|
|
@@ -3,8 +3,8 @@ import * as renderConditionalEdge from "../templates/backends/graphGenerator/con
|
|
|
3
3
|
import * as goToNode from "../templates/backends/graphGenerator/goToNode.js";
|
|
4
4
|
import * as renderGraphNode from "../templates/backends/graphGenerator/graphNode.js";
|
|
5
5
|
import * as renderImports from "../templates/backends/graphGenerator/imports.js";
|
|
6
|
-
import * as renderStartNode from "../templates/backends/graphGenerator/startNode.js";
|
|
7
6
|
import * as renderRunNodeFunction from "../templates/backends/graphGenerator/runNodeFunction.js";
|
|
7
|
+
import * as renderStartNode from "../templates/backends/graphGenerator/startNode.js";
|
|
8
8
|
import { TypeScriptGenerator } from "./typescriptGenerator.js";
|
|
9
9
|
import { mapFunctionName } from "./typescriptGenerator/builtins.js";
|
|
10
10
|
import { variableTypeToString } from "./typescriptGenerator/typeToString.js";
|
|
@@ -43,7 +43,6 @@ export declare class TypeScriptGenerator extends BaseGenerator {
|
|
|
43
43
|
* Generates TypeScript expression for a function call (without semicolon)
|
|
44
44
|
*/
|
|
45
45
|
protected generateFunctionCallExpression(node: FunctionCall): string;
|
|
46
|
-
protected generateScopedVariableName(variableName: string): string;
|
|
47
46
|
protected generateLiteral(literal: Literal): string;
|
|
48
47
|
protected generateImports(): string;
|
|
49
48
|
buildPromptString(segments: PromptSegment[], typeHints: TypeHintMap): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TYPES_THAT_DONT_TRIGGER_NEW_PART } from "../config.js";
|
|
1
2
|
import * as renderSpecialVar from "../templates/backends/graphGenerator/specialVar.js";
|
|
2
3
|
import * as renderTime from "../templates/backends/typescriptGenerator/builtinFunctions/time.js";
|
|
3
4
|
import * as builtinTools from "../templates/backends/typescriptGenerator/builtinTools.js";
|
|
@@ -11,7 +12,6 @@ import { BaseGenerator } from "./baseGenerator.js";
|
|
|
11
12
|
import { generateBuiltinHelpers, mapFunctionName, } from "./typescriptGenerator/builtins.js";
|
|
12
13
|
import { variableTypeToString } from "./typescriptGenerator/typeToString.js";
|
|
13
14
|
import { DEFAULT_SCHEMA, mapTypeToZodSchema, } from "./typescriptGenerator/typeToZodSchema.js";
|
|
14
|
-
import { TYPES_THAT_DONT_TRIGGER_NEW_PART } from "../config.js";
|
|
15
15
|
export class TypeScriptGenerator extends BaseGenerator {
|
|
16
16
|
constructor() {
|
|
17
17
|
super();
|
|
@@ -61,8 +61,14 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
61
61
|
const returnCode = this.processNode(node.value);
|
|
62
62
|
if (node.value.type === "functionCall" &&
|
|
63
63
|
node.value.functionName === "interrupt") {
|
|
64
|
+
/* In this case we're not popping off the stack, because we need to save the state,
|
|
65
|
+
because we will be restoring it (since this is an interrupt). However we do need to
|
|
66
|
+
advance the step so that the next time we come here, we start at the part after this
|
|
67
|
+
interrupt. */
|
|
64
68
|
return `__stack.step++;\nreturn ${returnCode}\n`;
|
|
65
69
|
}
|
|
70
|
+
/* Pop the state off the stack, we won't be coming back.
|
|
71
|
+
Doesn't matter if we update the step or not, since we won't be coming back here. */
|
|
66
72
|
return `__stateStack.pop();\nreturn ${returnCode}\n`;
|
|
67
73
|
}
|
|
68
74
|
processAccessExpression(node) {
|
|
@@ -115,8 +121,13 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
115
121
|
}
|
|
116
122
|
processAssignment(node) {
|
|
117
123
|
const { variableName, typeHint, value } = node;
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
const _currentScope = this.getCurrentScope();
|
|
125
|
+
if (_currentScope === "global") {
|
|
126
|
+
this.globalScopedVariables.push(variableName);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.functionScopedVariables.push(variableName);
|
|
130
|
+
}
|
|
120
131
|
const typeAnnotation = typeHint
|
|
121
132
|
? `: ${variableTypeToString(typeHint, this.typeAliases)}`
|
|
122
133
|
: "";
|
|
@@ -130,7 +141,11 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
130
141
|
throw new Error(`llm function call must have at least one argument for the prompt.`);
|
|
131
142
|
}
|
|
132
143
|
const promptArg = args[0];
|
|
133
|
-
|
|
144
|
+
const promptArgIsPrompt = promptArg.type === "prompt" ||
|
|
145
|
+
promptArg.type === "string" ||
|
|
146
|
+
promptArg.type === "multiLineString" ||
|
|
147
|
+
promptArg.type === "variableName"; // if variable name, assume its a string
|
|
148
|
+
if (!promptArgIsPrompt) {
|
|
134
149
|
throw new Error(`First argument to llm function must be a prompt literal.`);
|
|
135
150
|
}
|
|
136
151
|
const promptConfig = args[1];
|
|
@@ -139,7 +154,14 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
139
154
|
}
|
|
140
155
|
return this.processPromptLiteral(variableName, typeHint, {
|
|
141
156
|
type: "prompt",
|
|
142
|
-
segments: promptArg.
|
|
157
|
+
segments: promptArg.type === "variableName"
|
|
158
|
+
? [
|
|
159
|
+
{
|
|
160
|
+
type: "interpolation",
|
|
161
|
+
variableName: promptArg.value,
|
|
162
|
+
},
|
|
163
|
+
]
|
|
164
|
+
: promptArg.segments,
|
|
143
165
|
config: promptConfig,
|
|
144
166
|
});
|
|
145
167
|
}
|
|
@@ -290,11 +312,7 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
290
312
|
}
|
|
291
313
|
});
|
|
292
314
|
let argsString = "";
|
|
293
|
-
|
|
294
|
-
.map((node) => node.importedTools)
|
|
295
|
-
.flat()
|
|
296
|
-
.includes(node.functionName);
|
|
297
|
-
if (this.functionDefinitions[node.functionName] || isImportedTool) {
|
|
315
|
+
if (this.isInternalFunction(node.functionName)) {
|
|
298
316
|
argsString = parts.join(", ");
|
|
299
317
|
return `${functionName}([${argsString}])`;
|
|
300
318
|
}
|
|
@@ -304,18 +322,6 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
304
322
|
return `${functionName}(${argsString})`;
|
|
305
323
|
}
|
|
306
324
|
}
|
|
307
|
-
generateScopedVariableName(variableName) {
|
|
308
|
-
if (this.functionParameters.includes(variableName)) {
|
|
309
|
-
return `__stack.args.${variableName}`;
|
|
310
|
-
}
|
|
311
|
-
if (this.functionScopedVariables.includes(variableName)) {
|
|
312
|
-
return `__stack.locals.${variableName}`;
|
|
313
|
-
}
|
|
314
|
-
else if (this.globalScopedVariables.includes(variableName)) {
|
|
315
|
-
return `__stateStack.globals.${variableName}`;
|
|
316
|
-
}
|
|
317
|
-
return variableName;
|
|
318
|
-
}
|
|
319
325
|
generateLiteral(literal) {
|
|
320
326
|
switch (literal.type) {
|
|
321
327
|
case "number":
|
|
@@ -502,6 +508,16 @@ export class TypeScriptGenerator extends BaseGenerator {
|
|
|
502
508
|
const code = this.processNode(node.expression);
|
|
503
509
|
return `await ${code}`;
|
|
504
510
|
}
|
|
511
|
+
/* This generates the body of a node or function separated into multiple parts.
|
|
512
|
+
You can think of a part as roughly corresponding to a single statement
|
|
513
|
+
(although some statements don't need their own parts, such as a newlines or type definitions).
|
|
514
|
+
|
|
515
|
+
This is done so that we can keep track of what statement we're currently executing,
|
|
516
|
+
so that we can serialize that as part of the state if we return from an interrupt,
|
|
517
|
+
so that when we deserialize the state, we can pick up where we were and avoid having to
|
|
518
|
+
re-execute all the statements that we already executed.
|
|
519
|
+
|
|
520
|
+
Basically, this is part of the reason why agency can pick up exactly where you left off. */
|
|
505
521
|
processBodyAsParts(body) {
|
|
506
522
|
const parts = [[]];
|
|
507
523
|
for (const stmt of body) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "\ngraph.node(\"{{{name}}}\", async (state): Promise<any> => {\n const __messages: Message[] = state.messages || [];\n const __graph = state.__metadata?.graph || graph;\n const statelogClient = state.__metadata?.statelogClient || __statelogClient;\n if (state.__metadata?.__stateStack) {\n __stateStack = state.__metadata.__stateStack;\n }\n const __stack = __stateStack.getNewState();\n const __step = __stack.step;\n\n const __self: Record<string, any> = __stack.locals;\n\n const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;\n const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;\n\n if (state.__metadata?.state?.global) {\n __global = state.__metadata.state.global;\n }\n\n {{#hasParam}}\n \n const __params = {{{paramNames}}};\n if (state.data !== \"<from-stack>\") {\n (state.data).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n }\n {{/hasParam}}\n {{{body}}}\n \n // this is just here to have a default return value from a node if the user doesn't specify one\n return { ...state, data: undefined };\n});\n";
|
|
1
|
+
export declare const template = "\ngraph.node(\"{{{name}}}\", async (state): Promise<any> => {\n const __messages: Message[] = state.messages || [];\n const __graph = state.__metadata?.graph || graph;\n const statelogClient = state.__metadata?.statelogClient || __statelogClient;\n \n // if `state.__metadata?.__stateStack` is set, that means we are resuming execution\n // at this node after an interrupt. In that case, this is the line that restores the state.\n if (state.__metadata?.__stateStack) {\n __stateStack = structuredClone(state.__metadata.__stateStack);\n \n // clear the state stack from metadata so it doesn't propagate to other nodes.\n state.__metadata.__stateStack = undefined;\n }\n\n // either creates a new stack for this node,\n // or restores the stack if we're resuming after an interrupt,\n // depending on the mode of the state stack (serialize or deserialize).\n const __stack = __stateStack.getNewState();\n \n // We're going to modify __stack.step to keep track of what line we're on,\n // but first we save this value. This will help us figure out if we should execute\n // from the start of this node or from a specific line.\n const __step = __stack.step;\n\n const __self: Record<string, any> = __stack.locals;\n\n // If we're resuming after an interrupt, these will be set.\n // There should be a cleaner way to handle this,\n // instead of littering this scope with these variables\n const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;\n const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;\n\n // TODO pretty sure this isn't needed, check and remove\n if (state.__metadata?.state?.global) {\n __global = state.__metadata.state.global;\n }\n\n {{#hasParam}}\n \n const __params = {{{paramNames}}};\n \n // Any arguments that were passed into this node,\n // save them onto the stack, unless we are restoring the stack after an interrupt,\n // in which case leave as is\n if (state.data !== \"<from-stack>\") {\n (state.data).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n }\n {{/hasParam}}\n {{{body}}}\n \n // this is just here to have a default return value from a node if the user doesn't specify one\n return { ...state, data: undefined };\n});\n";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
name: string | boolean | number;
|
|
4
4
|
hasParam: boolean;
|
|
@@ -7,17 +7,35 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
|
|
|
7
7
|
const __messages: Message[] = state.messages || [];
|
|
8
8
|
const __graph = state.__metadata?.graph || graph;
|
|
9
9
|
const statelogClient = state.__metadata?.statelogClient || __statelogClient;
|
|
10
|
+
|
|
11
|
+
// if \`state.__metadata?.__stateStack\` is set, that means we are resuming execution
|
|
12
|
+
// at this node after an interrupt. In that case, this is the line that restores the state.
|
|
10
13
|
if (state.__metadata?.__stateStack) {
|
|
11
|
-
__stateStack = state.__metadata.__stateStack;
|
|
14
|
+
__stateStack = structuredClone(state.__metadata.__stateStack);
|
|
15
|
+
|
|
16
|
+
// clear the state stack from metadata so it doesn't propagate to other nodes.
|
|
17
|
+
state.__metadata.__stateStack = undefined;
|
|
12
18
|
}
|
|
19
|
+
|
|
20
|
+
// either creates a new stack for this node,
|
|
21
|
+
// or restores the stack if we're resuming after an interrupt,
|
|
22
|
+
// depending on the mode of the state stack (serialize or deserialize).
|
|
13
23
|
const __stack = __stateStack.getNewState();
|
|
24
|
+
|
|
25
|
+
// We're going to modify __stack.step to keep track of what line we're on,
|
|
26
|
+
// but first we save this value. This will help us figure out if we should execute
|
|
27
|
+
// from the start of this node or from a specific line.
|
|
14
28
|
const __step = __stack.step;
|
|
15
29
|
|
|
16
30
|
const __self: Record<string, any> = __stack.locals;
|
|
17
31
|
|
|
32
|
+
// If we're resuming after an interrupt, these will be set.
|
|
33
|
+
// There should be a cleaner way to handle this,
|
|
34
|
+
// instead of littering this scope with these variables
|
|
18
35
|
const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;
|
|
19
36
|
const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;
|
|
20
37
|
|
|
38
|
+
// TODO pretty sure this isn't needed, check and remove
|
|
21
39
|
if (state.__metadata?.state?.global) {
|
|
22
40
|
__global = state.__metadata.state.global;
|
|
23
41
|
}
|
|
@@ -25,6 +43,10 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
|
|
|
25
43
|
{{#hasParam}}
|
|
26
44
|
|
|
27
45
|
const __params = {{{paramNames}}};
|
|
46
|
+
|
|
47
|
+
// Any arguments that were passed into this node,
|
|
48
|
+
// save them onto the stack, unless we are restoring the stack after an interrupt,
|
|
49
|
+
// in which case leave as is
|
|
28
50
|
if (state.data !== "<from-stack>") {
|
|
29
51
|
(state.data).forEach((item, index) => {
|
|
30
52
|
__stack.args[__params[index]] = item;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "import { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { PieMachine, goToNode } from \"piemachine\";\nimport { StatelogClient } from \"statelog-client\";\nimport { nanoid } from \"nanoid\";\nimport { assistantMessage, getClient, userMessage, toolMessage, messageFromJSON } from \"smoltalk\";\nimport type { Message } from \"smoltalk\";\n\nconst statelogHost = \"https://statelog.adit.io\";\nconst traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: traceId,\n apiKey: process.env.STATELOG_API_KEY || \"\",\n projectId: \"agency-lang\",\n debugMode: false,\n };\nconst __statelogClient = new StatelogClient(statelogConfig);\nconst __model: ModelName = \"gpt-4o-mini\";\n\nconst getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n model: __model,\n logLevel: \"warn\",\n };\n\n return getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = getClientWithConfig();\n\ntype State = {\n messages: string[];\n data: any;\n}\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new PieMachine<State>(graphConfig);\n\n// builtins\n\nconst not = (val: any): boolean => !val;\nconst eq = (a: any, b: any): boolean => a === b;\nconst neq = (a: any, b: any): boolean => a !== b;\nconst lt = (a: any, b: any): boolean => a < b;\nconst lte = (a: any, b: any): boolean => a <= b;\nconst gt = (a: any, b: any): boolean => a > b;\nconst gte = (a: any, b: any): boolean => a >= b;\nconst and = (a: any, b: any): boolean => a && b;\nconst or = (a: any, b: any): boolean => a || b;\nconst head = <T>(arr: T[]): T | undefined => arr[0];\nconst tail = <T>(arr: T[]): T[] => arr.slice(1);\nconst empty = <T>(arr: T[]): boolean => arr.length === 0;\n\n// interrupts\n\nexport type Interrupt<T> = {\n type: \"interrupt\";\n data: T;\n __state?:
|
|
1
|
+
export declare const template = "import { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { PieMachine, goToNode } from \"piemachine\";\nimport { StatelogClient } from \"statelog-client\";\nimport { nanoid } from \"nanoid\";\nimport { assistantMessage, getClient, userMessage, toolMessage, messageFromJSON } from \"smoltalk\";\nimport type { Message } from \"smoltalk\";\n\nconst statelogHost = \"https://statelog.adit.io\";\nconst traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: traceId,\n apiKey: process.env.STATELOG_API_KEY || \"\",\n projectId: \"agency-lang\",\n debugMode: false,\n };\nconst __statelogClient = new StatelogClient(statelogConfig);\nconst __model: ModelName = \"gpt-4o-mini\";\n\nconst getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n model: __model,\n logLevel: \"warn\",\n };\n\n return getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = getClientWithConfig();\n\ntype State = {\n messages: string[];\n data: any;\n}\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new PieMachine<State>(graphConfig);\n\n// builtins\n\nconst not = (val: any): boolean => !val;\nconst eq = (a: any, b: any): boolean => a === b;\nconst neq = (a: any, b: any): boolean => a !== b;\nconst lt = (a: any, b: any): boolean => a < b;\nconst lte = (a: any, b: any): boolean => a <= b;\nconst gt = (a: any, b: any): boolean => a > b;\nconst gte = (a: any, b: any): boolean => a >= b;\nconst and = (a: any, b: any): boolean => a && b;\nconst or = (a: any, b: any): boolean => a || b;\nconst head = <T>(arr: T[]): T | undefined => arr[0];\nconst tail = <T>(arr: T[]): T[] => arr.slice(1);\nconst empty = <T>(arr: T[]): boolean => arr.length === 0;\n\n// interrupts\n\nexport type Interrupt<T> = {\n type: \"interrupt\";\n data: T;\n\n // JSONified StateStack, i.e. serialized execution state\n __state?: Record<string, any>;\n};\n\nexport function interrupt<T>(data: T): Interrupt<T> {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt<T>(obj: any): obj is Interrupt<T> {\n return obj && obj.type === \"interrupt\";\n}\n\nfunction printJSON(obj: any) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport type InterruptResponseType = InterruptResponseApprove | InterruptResponseReject | InterruptResponseModify;\nexport type InterruptResponseApprove = {\n type: \"approve\";\n};\nexport type InterruptResponseReject = {\n type: \"reject\";\n};\nexport type InterruptResponseModify = {\n type: \"modify\";\n newArguments: Record<string, any>;\n};\n\n\nexport async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {\n const interrupt = structuredClone(_interrupt);\n const interruptResponse = structuredClone(_interruptResponse);\n\n __stateStack = StateStack.fromJSON(interrupt.__state || {});\n __stateStack.deserializeMode();\n \n const messages = (__stateStack.other.messages || []).map((json: any) => {\n // create message objects from JSON\n return messageFromJSON(json);\n });\n\n // start at the last node we visited\n const nodesTraversed = __stateStack.other.nodesTraversed || [];\n const nodeName = nodesTraversed[nodesTraversed.length - 1];\n const result = await graph.run(nodeName, {\n messages: messages,\n __metadata: {\n graph: graph,\n statelogClient: __statelogClient,\n interruptResponse: interruptResponse,\n state: interrupt.__state,\n __stateStack: __stateStack,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\"\n });\n return result.data;\n}\n\nexport async function approveInterrupt(interrupt: Interrupt) {\n return await respondToInterrupt(interrupt, { type: \"approve\" });\n}\n\nexport async function rejectInterrupt(interrupt: Interrupt) {\n return await respondToInterrupt(interrupt, { type: \"reject\" });\n}\n\ntype StackFrame = {\n args: Record<string, any>;\n locals: Record<string, any>;\n step: number;\n};\n\n// See docs for notes on how this works.\nclass StateStack {\n public stack: StackFrame[] = [];\n private mode: \"serialize\" | \"deserialize\" = \"serialize\";\n public globals: Record<string, any> = {};\n public other: Record<string, any> = {};\n\n private deserializeStackLength = 0;\n\n constructor(stack: StackFrame[] = [], mode: \"serialize\" | \"deserialize\" = \"serialize\") {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState(): StackFrame | null {\n if (this.mode === \"deserialize\" && this.deserializeStackLength <= 0) {\n console.log(\"Forcing mode to serialize, nothing left to deserialize\");\n this.mode = \"serialize\";\n }\n if (this.mode === \"serialize\") {\n const newState: StackFrame = {\n args: {},\n locals: {},\n step: 0,\n };\n this.stack.push(newState);\n return newState;\n } else if (this.mode === \"deserialize\") {\n this.deserializeStackLength -= 1;\n const item = this.stack.shift();\n this.stack.push(item);\n return item;\n }\n return null;\n }\n\n deserializeMode() {\n this.mode = \"deserialize\";\n this.deserializeStackLength = this.stack.length;\n }\n\n pop(): StackFrame | undefined {\n return this.stack.pop();\n }\n\n toJSON() {\n return structuredClone({\n stack: this.stack,\n globals: this.globals,\n other: this.other,\n mode: this.mode,\n deserializeStackLength: this.deserializeStackLength,\n });\n }\n\n static fromJSON(json: any): StateStack {\n const stateStack = new StateStack([], \"serialize\");\n stateStack.stack = json.stack || [];\n stateStack.globals = json.globals || {};\n stateStack.other = json.other || {};\n stateStack.mode = json.mode || \"serialize\";\n stateStack.deserializeStackLength = json.deserializeStackLength || 0;\n return stateStack;\n }\n}\n\nlet __stateStack = new StateStack();";
|
|
2
2
|
export type TemplateType = {};
|
|
3
3
|
declare const render: (args: TemplateType) => string;
|
|
4
4
|
export default render;
|
|
@@ -72,7 +72,9 @@ const empty = <T>(arr: T[]): boolean => arr.length === 0;
|
|
|
72
72
|
export type Interrupt<T> = {
|
|
73
73
|
type: "interrupt";
|
|
74
74
|
data: T;
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
// JSONified StateStack, i.e. serialized execution state
|
|
77
|
+
__state?: Record<string, any>;
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
export function interrupt<T>(data: T): Interrupt<T> {
|
|
@@ -106,12 +108,16 @@ export type InterruptResponseModify = {
|
|
|
106
108
|
export async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {
|
|
107
109
|
const interrupt = structuredClone(_interrupt);
|
|
108
110
|
const interruptResponse = structuredClone(_interruptResponse);
|
|
111
|
+
|
|
109
112
|
__stateStack = StateStack.fromJSON(interrupt.__state || {});
|
|
110
|
-
__stateStack.
|
|
113
|
+
__stateStack.deserializeMode();
|
|
114
|
+
|
|
111
115
|
const messages = (__stateStack.other.messages || []).map((json: any) => {
|
|
116
|
+
// create message objects from JSON
|
|
112
117
|
return messageFromJSON(json);
|
|
113
118
|
});
|
|
114
119
|
|
|
120
|
+
// start at the last node we visited
|
|
115
121
|
const nodesTraversed = __stateStack.other.nodesTraversed || [];
|
|
116
122
|
const nodeName = nodesTraversed[nodesTraversed.length - 1];
|
|
117
123
|
const result = await graph.run(nodeName, {
|
|
@@ -123,69 +129,48 @@ export async function respondToInterrupt(_interrupt: Interrupt, _interruptRespon
|
|
|
123
129
|
state: interrupt.__state,
|
|
124
130
|
__stateStack: __stateStack,
|
|
125
131
|
},
|
|
132
|
+
|
|
133
|
+
// restore args from the state stack
|
|
126
134
|
data: "<from-stack>"
|
|
127
135
|
});
|
|
128
|
-
//console.log(\`Result of graph.run("\${nodeName}"):\`, JSON.stringify(result, null, 2));
|
|
129
136
|
return result.data;
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
export async function approveInterrupt(interrupt: Interrupt) {
|
|
140
|
+
return await respondToInterrupt(interrupt, { type: "approve" });
|
|
141
|
+
}
|
|
132
142
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
public nodesTraversed?: string[];
|
|
136
|
-
public toolCall?: Record<string, any>;
|
|
137
|
-
public step?: number;
|
|
138
|
-
public self?: Record<string, any>;
|
|
139
|
-
public global?: Record<string, any>;
|
|
140
|
-
public args?: any;
|
|
141
|
-
constructor(_state: Record<string, any>, args?: any) {
|
|
142
|
-
const state = structuredClone(_state);
|
|
143
|
-
this.messages = state.messages;
|
|
144
|
-
this.nodesTraversed = state.graph?.getNodesTraversed();
|
|
145
|
-
this.toolCall = state.toolCall;
|
|
146
|
-
this.step = state.part;
|
|
147
|
-
this.self = state.self;
|
|
148
|
-
this.global = state.global;
|
|
149
|
-
this.args = state.args;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
toJSON() {
|
|
153
|
-
return {
|
|
154
|
-
messages: this.messages,
|
|
155
|
-
nodesTraversed: this.nodesTraversed,
|
|
156
|
-
toolCall: this.toolCall,
|
|
157
|
-
step: this.step,
|
|
158
|
-
self: this.self,
|
|
159
|
-
global: this.global,
|
|
160
|
-
args: this.args,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
nextStep() {
|
|
165
|
-
this.step ||= 0;
|
|
166
|
-
this.step += 1;
|
|
167
|
-
}
|
|
143
|
+
export async function rejectInterrupt(interrupt: Interrupt) {
|
|
144
|
+
return await respondToInterrupt(interrupt, { type: "reject" });
|
|
168
145
|
}
|
|
169
146
|
|
|
147
|
+
type StackFrame = {
|
|
148
|
+
args: Record<string, any>;
|
|
149
|
+
locals: Record<string, any>;
|
|
150
|
+
step: number;
|
|
151
|
+
};
|
|
170
152
|
|
|
153
|
+
// See docs for notes on how this works.
|
|
171
154
|
class StateStack {
|
|
172
|
-
public stack:
|
|
173
|
-
|
|
155
|
+
public stack: StackFrame[] = [];
|
|
156
|
+
private mode: "serialize" | "deserialize" = "serialize";
|
|
174
157
|
public globals: Record<string, any> = {};
|
|
175
158
|
public other: Record<string, any> = {};
|
|
176
159
|
|
|
177
|
-
|
|
160
|
+
private deserializeStackLength = 0;
|
|
161
|
+
|
|
162
|
+
constructor(stack: StackFrame[] = [], mode: "serialize" | "deserialize" = "serialize") {
|
|
178
163
|
this.stack = stack;
|
|
179
164
|
this.mode = mode;
|
|
180
165
|
}
|
|
181
166
|
|
|
182
|
-
getNewState():
|
|
183
|
-
if (this.
|
|
167
|
+
getNewState(): StackFrame | null {
|
|
168
|
+
if (this.mode === "deserialize" && this.deserializeStackLength <= 0) {
|
|
184
169
|
console.log("Forcing mode to serialize, nothing left to deserialize");
|
|
185
170
|
this.mode = "serialize";
|
|
186
171
|
}
|
|
187
172
|
if (this.mode === "serialize") {
|
|
188
|
-
const newState:
|
|
173
|
+
const newState: StackFrame = {
|
|
189
174
|
args: {},
|
|
190
175
|
locals: {},
|
|
191
176
|
step: 0,
|
|
@@ -193,6 +178,7 @@ class StateStack {
|
|
|
193
178
|
this.stack.push(newState);
|
|
194
179
|
return newState;
|
|
195
180
|
} else if (this.mode === "deserialize") {
|
|
181
|
+
this.deserializeStackLength -= 1;
|
|
196
182
|
const item = this.stack.shift();
|
|
197
183
|
this.stack.push(item);
|
|
198
184
|
return item;
|
|
@@ -200,11 +186,12 @@ class StateStack {
|
|
|
200
186
|
return null;
|
|
201
187
|
}
|
|
202
188
|
|
|
203
|
-
|
|
204
|
-
this.mode =
|
|
189
|
+
deserializeMode() {
|
|
190
|
+
this.mode = "deserialize";
|
|
191
|
+
this.deserializeStackLength = this.stack.length;
|
|
205
192
|
}
|
|
206
193
|
|
|
207
|
-
pop():
|
|
194
|
+
pop(): StackFrame | undefined {
|
|
208
195
|
return this.stack.pop();
|
|
209
196
|
}
|
|
210
197
|
|
|
@@ -214,6 +201,7 @@ class StateStack {
|
|
|
214
201
|
globals: this.globals,
|
|
215
202
|
other: this.other,
|
|
216
203
|
mode: this.mode,
|
|
204
|
+
deserializeStackLength: this.deserializeStackLength,
|
|
217
205
|
});
|
|
218
206
|
}
|
|
219
207
|
|
|
@@ -223,6 +211,7 @@ class StateStack {
|
|
|
223
211
|
stateStack.globals = json.globals || {};
|
|
224
212
|
stateStack.other = json.other || {};
|
|
225
213
|
stateStack.mode = json.mode || "serialize";
|
|
214
|
+
stateStack.deserializeStackLength = json.deserializeStackLength || 0;
|
|
226
215
|
return stateStack;
|
|
227
216
|
}
|
|
228
217
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "\nexport async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {\n const __messages: Message[] = [];\n const __stack = __stateStack.getNewState();\n const __step = __stack.step
|
|
1
|
+
export declare const template = "\nexport async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {\n const __messages: Message[] = [];\n const __stack = __stateStack.getNewState();\n const __step = __stack.step;\n const __self: Record<string, any> = __stack.locals;\n\n // TODO: Note that we don't need to use the same kind of restoration\n // from state for arguments as we do for nodes,\n // because the args are serialized in the tool call.\n // But what about situations where it was a function call, not a tool call?\n // In that case, we would want to deserialize the argument.\n const __params = [{{{argsStr}}}];\n (args).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n\n\n {{{functionBody}}}\n}";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
functionName: string;
|
|
4
4
|
returnType: string | boolean | number;
|
|
@@ -6,13 +6,18 @@ export const template = `
|
|
|
6
6
|
export async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {
|
|
7
7
|
const __messages: Message[] = [];
|
|
8
8
|
const __stack = __stateStack.getNewState();
|
|
9
|
-
const __step = __stack.step;
|
|
9
|
+
const __step = __stack.step;
|
|
10
10
|
const __self: Record<string, any> = __stack.locals;
|
|
11
11
|
|
|
12
|
+
// TODO: Note that we don't need to use the same kind of restoration
|
|
13
|
+
// from state for arguments as we do for nodes,
|
|
14
|
+
// because the args are serialized in the tool call.
|
|
15
|
+
// But what about situations where it was a function call, not a tool call?
|
|
16
|
+
// In that case, we would want to deserialize the argument.
|
|
12
17
|
const __params = [{{{argsStr}}}];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
(args).forEach((item, index) => {
|
|
19
|
+
__stack.args[__params[index]] = item;
|
|
20
|
+
});
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
{{{functionBody}}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{typeString:string}}}> {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n const __messages: Message[] = __metadata?.messages || [];\n let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];\n const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;\n const __tools = {{{tools}}};\n\n {{#hasResponseFormat}}\n // Need to make sure this is always an object\n const __responseFormat = z.object({\n response: {{{zodSchema:string}}}\n });\n {{/hasResponseFormat}}\n {{^hasResponseFormat}}\n const __responseFormat = undefined;\n {{/hasResponseFormat}}\n \n const __client = getClientWithConfig({{{clientConfig:string}}});\n let responseMessage:any;\n\n if (__toolCalls.length === 0) {\n __messages.push(userMessage(__prompt));\n \n \n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n \n const endTime = performance.now();\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n });\n \n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n \n responseMessage = __completion.value;\n __toolCalls = responseMessage.toolCalls || [];\n\n if (__toolCalls.length > 0) {\n // Add assistant's response with tool calls to message history\n __messages.push(assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n }\n\n // Handle function calls\n if (__toolCalls.length > 0) {\n let toolCallStartTime, toolCallEndTime;\n let haltExecution = false;\n let haltToolCall = {}\n let haltInterrupt:any = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n await statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.other = {\n messages: __messages.map((msg) => msg.toJSON()),\n nodesTraversed: __graph.getNodesTraversed(),\n toolCall: haltToolCall,\n };\n haltInterrupt.__state = __stateStack.toJSON();\n return haltInterrupt;\n }\n \n const nextStartTime = performance.now();\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n\n const nextEndTime = performance.now();\n\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n });\n\n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n responseMessage = __completion.value;\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(assistantMessage(responseMessage.output));\n {{#hasResponseFormat}}\n try {\n const result = JSON.parse(responseMessage.output || \"\");\n return result.response;\n } catch (e) {\n return responseMessage.output;\n // console.error(\"Error parsing response for variable '{{{variableName:string}}}':\", e);\n // console.error(\"Full completion response:\", JSON.stringify(__completion, null, 2));\n // throw e;\n }\n {{/hasResponseFormat}}\n\n {{^hasResponseFormat}}\n return responseMessage.output;\n {{/hasResponseFormat}}\n}\n\n__self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});\n\nif (isInterrupt(__self.{{{variableName:string}}})) {\n return { ...state, data: __self.{{{variableName:string}}} };\n}";
|
|
1
|
+
export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{typeString:string}}}> {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n const __messages: Message[] = __metadata?.messages || [];\n\n // These are to restore state after interrupt.\n // TODO I think this could be implemented in a cleaner way.\n let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];\n const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;\n const __tools = {{{tools}}};\n\n {{#hasResponseFormat}}\n // Need to make sure this is always an object\n const __responseFormat = z.object({\n response: {{{zodSchema:string}}}\n });\n {{/hasResponseFormat}}\n {{^hasResponseFormat}}\n const __responseFormat = undefined;\n {{/hasResponseFormat}}\n \n const __client = getClientWithConfig({{{clientConfig:string}}});\n let responseMessage:any;\n\n if (__toolCalls.length === 0) {\n __messages.push(userMessage(__prompt));\n \n \n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n \n const endTime = performance.now();\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n });\n \n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n \n responseMessage = __completion.value;\n __toolCalls = responseMessage.toolCalls || [];\n\n if (__toolCalls.length > 0) {\n // Add assistant's response with tool calls to message history\n __messages.push(assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n }\n\n // Handle function calls\n if (__toolCalls.length > 0) {\n let toolCallStartTime, toolCallEndTime;\n let haltExecution = false;\n let haltToolCall = {}\n let haltInterrupt:any = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n await statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.other = {\n messages: __messages.map((msg) => msg.toJSON()),\n nodesTraversed: __graph.getNodesTraversed(),\n toolCall: haltToolCall,\n };\n haltInterrupt.__state = __stateStack.toJSON();\n return haltInterrupt;\n }\n \n const nextStartTime = performance.now();\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n\n const nextEndTime = performance.now();\n\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n });\n\n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n responseMessage = __completion.value;\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(assistantMessage(responseMessage.output));\n {{#hasResponseFormat}}\n try {\n const result = JSON.parse(responseMessage.output || \"\");\n return result.response;\n } catch (e) {\n return responseMessage.output;\n // console.error(\"Error parsing response for variable '{{{variableName:string}}}':\", e);\n // console.error(\"Full completion response:\", JSON.stringify(__completion, null, 2));\n // throw e;\n }\n {{/hasResponseFormat}}\n\n {{^hasResponseFormat}}\n return responseMessage.output;\n {{/hasResponseFormat}}\n}\n\n__self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});\n\n// return early from node if this is an interrupt\nif (isInterrupt(__self.{{{variableName:string}}})) {\n return { ...state, data: __self.{{{variableName:string}}} };\n}";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
variableName: string;
|
|
4
4
|
argsStr: string;
|
|
@@ -7,6 +7,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
|
|
|
7
7
|
const __prompt = {{{promptCode:string}}};
|
|
8
8
|
const startTime = performance.now();
|
|
9
9
|
const __messages: Message[] = __metadata?.messages || [];
|
|
10
|
+
|
|
11
|
+
// These are to restore state after interrupt.
|
|
12
|
+
// TODO I think this could be implemented in a cleaner way.
|
|
10
13
|
let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];
|
|
11
14
|
const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;
|
|
12
15
|
const __tools = {{{tools}}};
|
|
@@ -130,6 +133,7 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
|
|
|
130
133
|
|
|
131
134
|
__self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});
|
|
132
135
|
|
|
136
|
+
// return early from node if this is an interrupt
|
|
133
137
|
if (isInterrupt(__self.{{{variableName:string}}})) {
|
|
134
138
|
return { ...state, data: __self.{{{variableName:string}}} };
|
|
135
139
|
}`;
|
package/dist/lib/utils.js
CHANGED
|
@@ -19,32 +19,3 @@ export function zip(arr1, arr2) {
|
|
|
19
19
|
export function uniq(arr) {
|
|
20
20
|
return Array.from(new Set(arr));
|
|
21
21
|
}
|
|
22
|
-
class PackagedState {
|
|
23
|
-
messages;
|
|
24
|
-
nodesTraversed;
|
|
25
|
-
toolCall;
|
|
26
|
-
step;
|
|
27
|
-
selfState;
|
|
28
|
-
globalState;
|
|
29
|
-
args;
|
|
30
|
-
constructor(state, args) {
|
|
31
|
-
this.messages = state.messages;
|
|
32
|
-
this.nodesTraversed = state.graph?.getNodesTraversed();
|
|
33
|
-
this.toolCall = state.toolCall;
|
|
34
|
-
this.step = state.part;
|
|
35
|
-
this.selfState = JSON.parse(JSON.stringify(state.self));
|
|
36
|
-
this.globalState = JSON.parse(JSON.stringify(state.global));
|
|
37
|
-
this.args = args;
|
|
38
|
-
}
|
|
39
|
-
toJSON() {
|
|
40
|
-
return {
|
|
41
|
-
messages: this.messages,
|
|
42
|
-
nodesTraversed: this.nodesTraversed,
|
|
43
|
-
toolCall: this.toolCall,
|
|
44
|
-
step: this.step,
|
|
45
|
-
selfState: this.selfState,
|
|
46
|
-
globalState: this.globalState,
|
|
47
|
-
args: this.args,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
}
|