agency-lang 0.0.66 → 0.0.67
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/dist/lib/templates/backends/graphGenerator/goToNode.d.ts +1 -1
- package/dist/lib/templates/backends/graphGenerator/goToNode.js +5 -0
- package/dist/lib/templates/backends/graphGenerator/imports.d.ts +1 -1
- package/dist/lib/templates/backends/graphGenerator/imports.js +7 -4
- package/dist/lib/templates/backends/typescriptGenerator/promptFunction.d.ts +1 -1
- package/dist/lib/templates/backends/typescriptGenerator/promptFunction.js +6 -2
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "goToNode(\"{{{nodeName:string}}}\",\n {\n messages: __self.messages,\n __metadata: {\n graph: __graph,\n statelogClient,\n callbacks: __callbacks,\n },\n {{#hasData}}\n data: {{{data:string}}}\n {{/hasData}}\n {{^hasData}}\n data: null\n {{/hasData}}\n }\n);";
|
|
1
|
+
export declare const template = "goToNode(\"{{{nodeName:string}}}\",\n {\n messages: __self.messages,\n __metadata: {\n graph: __graph,\n // we need to pass in the state log client here because\n // if we rely on the local state log client\n // each client in each file has a different trace id.\n // So we pass in the client to make sure they all use the same trace id\n\n statelogClient,\n callbacks: __callbacks,\n },\n {{#hasData}}\n data: {{{data:string}}}\n {{/hasData}}\n {{^hasData}}\n data: null\n {{/hasData}}\n }\n);";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
nodeName: string;
|
|
4
4
|
hasData: boolean;
|
|
@@ -7,6 +7,11 @@ export const template = `goToNode("{{{nodeName:string}}}",
|
|
|
7
7
|
messages: __self.messages,
|
|
8
8
|
__metadata: {
|
|
9
9
|
graph: __graph,
|
|
10
|
+
// we need to pass in the state log client here because
|
|
11
|
+
// if we rely on the local state log client
|
|
12
|
+
// each client in each file has a different trace id.
|
|
13
|
+
// So we pass in the client to make sure they all use the same trace id
|
|
14
|
+
|
|
10
15
|
statelogClient,
|
|
11
16
|
callbacks: __callbacks,
|
|
12
17
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "import { fileURLToPath } from \"url\";\nimport process from \"process\";\nimport { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { StatelogClient, SimpleMachine, goToNode, nanoid } from \"agency-lang\";\nimport * as smoltalk from \"agency-lang\";\n\n/* Code to log to statelog */\nconst statelogHost = \"{{{logHost:string}}}\";\nconst __traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: __traceId,\n {{#hasApiKey}}\n apiKey: \"{{{logApiKey?:string}}}\",\n {{/hasApiKey}}\n {{^hasApiKey}}\n apiKey: process.env.STATELOG_API_KEY || \"\",\n {{/hasApiKey}}\n projectId: \"{{{logProjectId:string}}}\",\n debugMode: {{{logDebugMode:boolean}}},\n};\nconst __statelogClient = new StatelogClient(statelogConfig);\n\n/* Code for Smoltalk client */\nconst __model = \"{{{clientDefaultModel:string}}}\";\n\nconst __getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n {{#hasOpenAiApiKey}}\n openAiApiKey: \"{{{clientOpenAiApiKey?:string}}}\",\n {{/hasOpenAiApiKey}}\n {{^hasOpenAiApiKey}}\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n {{/hasOpenAiApiKey}}\n {{#hasGoogleApiKey}}\n googleApiKey: \"{{{clientGoogleApiKey?:string}}}\",\n {{/hasGoogleApiKey}}\n {{^hasGoogleApiKey}}\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n {{/hasGoogleApiKey}}\n model: __model,\n logLevel: \"{{{clientLogLevel:string}}}\",\n };\n\n return smoltalk.getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = __getClientWithConfig();\n\n/* Code for SimpleMachine graph */\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new SimpleMachine(graphConfig);\n\n/******** builtins ********/\n\nconst not = (val) => !val;\nconst eq = (a, b) => a === b;\nconst neq = (a, b) => a !== b;\nconst lt = (a, b) => a < b;\nconst lte = (a, b) => a <= b;\nconst gt = (a, b) => a > b;\nconst gte = (a, b) => a >= b;\nconst and = (a, b) => a && b;\nconst or = (a, b) => a || b;\nconst head = (arr) => arr[0];\nconst tail = (arr) => arr.slice(1);\nconst empty = (arr) => arr.length === 0;\n\nasync function _builtinFetch(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const text = await result.text();\n return text;\n } catch (e) {\n throw new Error(`Failed to get text from ${url}: ${e}`);\n }\n}\n\nasync function _builtinFetchJSON(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const json = await result.json();\n return json;\n } catch (e) {\n throw new Error(`Failed to parse JSON from ${url}: ${e}`);\n }\n}\n\nfunction _builtinInput(prompt) {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\nfunction _builtinRead(filename) {\n const data = fs.readFileSync(filename);\n const contents = data.toString(\"utf8\");\n return contents;\n}\n\n/*\n * @param filePath The absolute or relative path to the image file.\n * @returns The Base64 string, or null if an error occurs.\n */\nfunction _builtinReadImage(filePath) {\n const data = fs.readFileSync(filePath); // Synchronous file reading\n const base64String = data.toString(\"base64\");\n return base64String;\n}\n\nfunction _builtinSleep(seconds) {\n return new Promise((resolve) => {\n setTimeout(resolve, seconds * 1000);\n });\n}\n\nfunction printJSON(obj) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport const __readSkillTool = {\n name: \"readSkill\",\n description: `Skills provide specialized knowledge and instructions for particular scenarios.\nUse this tool when you need enhanced guidance for a specific type of task.\n\nArgs:\n filepath: The name of the skill to read.\n\nReturns:\n The skill content with specialized instructions, or an error message\n if the skill is not found.\n`,\n schema: z.object({\"filepath\": z.string(), })\n};\n\nexport function readSkill({filepath}) {\n return _builtinRead(filepath);\n}\n\n/******** for internal agency use only ********/\n\nfunction __createReturnObject(result) {\n // Note: we're *not* using structuredClone here because structuredClone\n // doesn't call `toJSON`, so it's not cloning our message objects correctly.\n return JSON.parse(JSON.stringify({\n messages: result.messages,\n data: result.data,\n tokens: __stateStack.globals.__tokenStats\n }));\n}\n\n\n\n/******** interrupts ********/\n\nexport function interrupt(data) {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt(obj) {\n return obj && obj.type === \"interrupt\";\n}\n\nexport async function respondToInterrupt(\n _interrupt,\n _interruptResponse,\n metadata = {},\n) {\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.interruptData.messages || []).map(\n (json) => {\n // create message objects from JSON\n return smoltalk.messageFromJSON(json);\n },\n );\n __stateStack.interruptData.messages = messages;\n __stateStack.interruptData.interruptResponse = interruptResponse;\n\n if (interruptResponse.type === \"approve\" && interruptResponse.newArguments) {\n __stateStack.interruptData.toolCall = {\n ...__stateStack.interruptData.toolCall,\n arguments: {\n ...__stateStack.interruptData.toolCall.arguments,\n ...interruptResponse.newArguments,\n },\n };\n // Error:\n // TypeError: Cannot set property arguments of #<ToolCall> which has only a getter\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n //\n // const lastMessage = __stateStack.interruptData.messages[__stateStack.interruptData.messages.length - 1];\n // if (lastMessage && lastMessage.role === \"assistant\") {\n // const toolCall = lastMessage.toolCalls?.[lastMessage.toolCalls.length - 1];\n // if (toolCall) {\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n // }\n // }\n }\n\n // start at the last node we visited\n const nodesTraversed = __stateStack.interruptData.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 __stateStack: __stateStack,\n __callbacks: metadata.callbacks,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\",\n });\n return __createReturnObject(__result);\n}\n\nexport async function approveInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"approve\" }, metadata);\n}\n\nexport async function modifyInterrupt(\n interrupt,\n newArguments,\n metadata = {},\n) {\n return await respondToInterrupt(\n interrupt,\n { type: \"approve\", newArguments },\n metadata,\n );\n}\n\nexport async function rejectInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"reject\" }, metadata);\n}\n\n/****** StateStack and related functions for serializing/deserializing execution state during interrupts ********/\n\n// See docs for notes on how this works.\nclass StateStack {\n stack = [];\n mode = \"serialize\";\n globals = {};\n other = {};\n interruptData = {};\n\n deserializeStackLength = 0;\n\n constructor(\n stack = [],\n mode = \"serialize\",\n ) {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState() {\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 = {\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() {\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 interruptData: this.interruptData,\n mode: this.mode,\n deserializeStackLength: this.deserializeStackLength,\n });\n }\n\n static fromJSON(json) {\n const stateStack = new StateStack([], \"serialize\");\n stateStack.stack = json.stack || [];\n stateStack.globals = json.globals || {};\n stateStack.other = json.other || {};\n stateStack.interruptData = json.interruptData || {};\n stateStack.mode = json.mode || \"serialize\";\n stateStack.deserializeStackLength = json.deserializeStackLength || 0;\n return stateStack;\n }\n}\n\nlet __stateStack = new StateStack();\n\n__stateStack.globals.__tokenStats = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cachedInputTokens: 0,\n totalTokens: 0,\n },\n cost: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n currency: \"USD\",\n },\n};\n\nfunction __updateTokenStats(\n usage,\n cost,\n) {\n if (!usage || !cost) return;\n const tokenStats = __stateStack.globals.__tokenStats;\n tokenStats.usage.inputTokens += usage.inputTokens || 0;\n tokenStats.usage.outputTokens += usage.outputTokens || 0;\n tokenStats.usage.cachedInputTokens += usage.cachedInputTokens || 0;\n tokenStats.usage.totalTokens += usage.totalTokens || 0;\n\n tokenStats.cost.inputCost += cost.inputCost || 0;\n tokenStats.cost.outputCost += cost.outputCost || 0;\n tokenStats.cost.totalCost += cost.totalCost || 0;\n}\n\n/**** Streaming callback and lock ****/\nfunction isGenerator(variable) {\n const toString = Object.prototype.toString.call(variable);\n return (\n toString === \"[object Generator]\" || toString === \"[object AsyncGenerator]\"\n );\n}\n\nlet __callbacks = {};\n\nasync function __callHook(name, data) {\n if (__callbacks[name]) {\n await __callbacks[name](data);\n }\n}\n\nlet onStreamLock = false;\n\nfunction __cloneArray(arr) {\n if (arr == undefined) return [];\n return [...arr];\n}\n\nconst handleStreamingResponse = async (__completion) => {\n if (isGenerator(__completion)) {\n if (!__callbacks.onStream) {\n console.log(\n \"No onStream callback provided for streaming response, returning response synchronously\",\n );\n statelogClient.debug(\n \"Got streaming response but no onStream callback provided, returning response synchronously\",\n {\n prompt: __prompt,\n callbacks: Object.keys(__callbacks),\n },\n );\n let syncResult = \"\";\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n break;\n case \"done\":\n syncResult = chunk.result;\n break;\n case \"error\":\n console.error(`Error in LLM response stream: ${chunk.error}`);\n break;\n default:\n break;\n }\n }\n __completion = { success: true, value: syncResult };\n } else {\n // try to acquire lock\n let count = 0;\n // wait 60 seconds to acquire lock\n while (onStreamLock && count < 10 * 60) {\n await _builtinSleep(0.1);\n count++;\n }\n if (onStreamLock) {\n console.log(`Couldn't acquire lock, ${count}`);\n }\n onStreamLock = true;\n\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"text\":\n __callbacks.onStream({ type: \"text\", text: chunk.text });\n break;\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n __callbacks.onStream({\n type: \"tool_call\",\n toolCall: chunk.toolCall,\n });\n break;\n case \"done\":\n __callbacks.onStream({ type: \"done\", result: chunk.result });\n __completion = { success: true, value: chunk.result };\n break;\n case \"error\":\n __callbacks.onStream({ type: \"error\", error: chunk.error });\n break;\n }\n }\n\n onStreamLock = false;\n }\n }\n};\n\n\n/**** Message thread handling ****/\n\nclass MessageThread {\n messages = [];\n children = [];\n\n constructor(messages = []) {\n this.messages = messages;\n this.children = [];\n }\n\n addMessage(message) {\n this.messages.push(message);\n }\n\n cloneMessages() {\n return this.messages.map(m => m.toJSON()).map(m => smoltalk.messageFromJSON(m));\n }\n\n getMessages() {\n return this.messages;\n }\n\n setMessages(messages) {\n this.messages = messages;\n }\n\n newChild() {\n const child = new MessageThread();\n this.children.push(child);\n return child;\n }\n\n newSubthreadChild() {\n const child = new MessageThread(this.cloneMessages());\n this.children.push(child);\n return child;\n }\n\n toJSON() {\n return {\n messages: this.messages.map(m => m.toJSON()),\n children: this.children.map((child) => child.toJSON()),\n };\n }\n}";
|
|
1
|
+
export declare const template = "import { fileURLToPath } from \"url\";\nimport process from \"process\";\nimport { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { StatelogClient, SimpleMachine, goToNode, nanoid } from \"agency-lang\";\nimport * as smoltalk from \"agency-lang\";\n\n/* Code to log to statelog */\nconst statelogHost = \"{{{logHost:string}}}\";\nconst __traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: __traceId,\n {{#hasApiKey}}\n apiKey: \"{{{logApiKey?:string}}}\",\n {{/hasApiKey}}\n {{^hasApiKey}}\n apiKey: process.env.STATELOG_API_KEY || \"\",\n {{/hasApiKey}}\n projectId: \"{{{logProjectId:string}}}\",\n debugMode: {{{logDebugMode:boolean}}},\n};\nconst __statelogClient = new StatelogClient(statelogConfig);\n\n/* Code for Smoltalk client */\nconst __model = \"{{{clientDefaultModel:string}}}\";\n\nconst __getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n {{#hasOpenAiApiKey}}\n openAiApiKey: \"{{{clientOpenAiApiKey?:string}}}\",\n {{/hasOpenAiApiKey}}\n {{^hasOpenAiApiKey}}\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n {{/hasOpenAiApiKey}}\n {{#hasGoogleApiKey}}\n googleApiKey: \"{{{clientGoogleApiKey?:string}}}\",\n {{/hasGoogleApiKey}}\n {{^hasGoogleApiKey}}\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n {{/hasGoogleApiKey}}\n model: __model,\n logLevel: \"{{{clientLogLevel:string}}}\",\n };\n\n return smoltalk.getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = __getClientWithConfig();\n\n/* Code for SimpleMachine graph */\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new SimpleMachine(graphConfig);\n\n/******** builtins ********/\n\nconst not = (val) => !val;\nconst eq = (a, b) => a === b;\nconst neq = (a, b) => a !== b;\nconst lt = (a, b) => a < b;\nconst lte = (a, b) => a <= b;\nconst gt = (a, b) => a > b;\nconst gte = (a, b) => a >= b;\nconst and = (a, b) => a && b;\nconst or = (a, b) => a || b;\nconst head = (arr) => arr[0];\nconst tail = (arr) => arr.slice(1);\nconst empty = (arr) => arr.length === 0;\n\nasync function _builtinFetch(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const text = await result.text();\n return text;\n } catch (e) {\n throw new Error(`Failed to get text from ${url}: ${e}`);\n }\n}\n\nasync function _builtinFetchJSON(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const json = await result.json();\n return json;\n } catch (e) {\n throw new Error(`Failed to parse JSON from ${url}: ${e}`);\n }\n}\n\nfunction _builtinInput(prompt) {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\nfunction _builtinRead(filename) {\n const data = fs.readFileSync(filename);\n const contents = data.toString(\"utf8\");\n return contents;\n}\n\n/*\n * @param filePath The absolute or relative path to the image file.\n * @returns The Base64 string, or null if an error occurs.\n */\nfunction _builtinReadImage(filePath) {\n const data = fs.readFileSync(filePath); // Synchronous file reading\n const base64String = data.toString(\"base64\");\n return base64String;\n}\n\nfunction _builtinSleep(seconds) {\n return new Promise((resolve) => {\n setTimeout(resolve, seconds * 1000);\n });\n}\n\nfunction printJSON(obj) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport const __readSkillTool = {\n name: \"readSkill\",\n description: `Skills provide specialized knowledge and instructions for particular scenarios.\nUse this tool when you need enhanced guidance for a specific type of task.\n\nArgs:\n filepath: The name of the skill to read.\n\nReturns:\n The skill content with specialized instructions, or an error message\n if the skill is not found.\n`,\n schema: z.object({\"filepath\": z.string(), })\n};\n\nexport function readSkill({filepath}) {\n return _builtinRead(filepath);\n}\n\n/******** for internal agency use only ********/\n\nfunction __createReturnObject(result) {\n // Note: we're *not* using structuredClone here because structuredClone\n // doesn't call `toJSON`, so it's not cloning our message objects correctly.\n return JSON.parse(JSON.stringify({\n messages: result.messages,\n data: result.data,\n tokens: __stateStack.globals.__tokenStats\n }));\n}\n\n\n\n/******** interrupts ********/\n\nexport function interrupt(data) {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt(obj) {\n return obj && obj.type === \"interrupt\";\n}\n\nexport async function respondToInterrupt(\n _interrupt,\n _interruptResponse,\n metadata = {},\n) {\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.interruptData.messages || []).map(\n (json) => {\n // create message objects from JSON\n return smoltalk.messageFromJSON(json);\n },\n );\n __stateStack.interruptData.messages = messages;\n __stateStack.interruptData.interruptResponse = interruptResponse;\n\n if (interruptResponse.type === \"approve\" && interruptResponse.newArguments) {\n __stateStack.interruptData.toolCall = {\n ...__stateStack.interruptData.toolCall,\n arguments: {\n ...__stateStack.interruptData.toolCall.arguments,\n ...interruptResponse.newArguments,\n },\n };\n // Error:\n // TypeError: Cannot set property arguments of #<ToolCall> which has only a getter\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n //\n // const lastMessage = __stateStack.interruptData.messages[__stateStack.interruptData.messages.length - 1];\n // if (lastMessage && lastMessage.role === \"assistant\") {\n // const toolCall = lastMessage.toolCalls?.[lastMessage.toolCalls.length - 1];\n // if (toolCall) {\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n // }\n // }\n }\n\n // start at the last node we visited\n const nodesTraversed = __stateStack.interruptData.nodesTraversed || [];\n const nodeName = nodesTraversed[nodesTraversed.length - 1];\n const __result = await graph.run(nodeName, {\n messages: messages,\n __metadata: {\n graph: graph,\n // we need to pass in the state log client here because\n // if we rely on the local state log client\n // each client in each file has a different trace id.\n // So we pass in the client to make sure they all use the same trace id\n statelogClient: __statelogClient,\n __stateStack: __stateStack,\n __callbacks: metadata.callbacks,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\",\n });\n return __createReturnObject(__result);\n}\n\nexport async function approveInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"approve\" }, metadata);\n}\n\nexport async function modifyInterrupt(\n interrupt,\n newArguments,\n metadata = {},\n) {\n return await respondToInterrupt(\n interrupt,\n { type: \"approve\", newArguments },\n metadata,\n );\n}\n\nexport async function rejectInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"reject\" }, metadata);\n}\n\n/****** StateStack and related functions for serializing/deserializing execution state during interrupts ********/\n\n// See docs for notes on how this works.\nclass StateStack {\n stack = [];\n mode = \"serialize\";\n globals = {};\n other = {};\n interruptData = {};\n\n deserializeStackLength = 0;\n\n constructor(\n stack = [],\n mode = \"serialize\",\n ) {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState() {\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 = {\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() {\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 interruptData: this.interruptData,\n mode: this.mode,\n deserializeStackLength: this.deserializeStackLength,\n });\n }\n\n static fromJSON(json) {\n const stateStack = new StateStack([], \"serialize\");\n stateStack.stack = json.stack || [];\n stateStack.globals = json.globals || {};\n stateStack.other = json.other || {};\n stateStack.interruptData = json.interruptData || {};\n stateStack.mode = json.mode || \"serialize\";\n stateStack.deserializeStackLength = json.deserializeStackLength || 0;\n return stateStack;\n }\n}\n\nlet __stateStack = new StateStack();\n\n__stateStack.globals.__tokenStats = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cachedInputTokens: 0,\n totalTokens: 0,\n },\n cost: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n currency: \"USD\",\n },\n};\n\nfunction __updateTokenStats(\n usage,\n cost,\n) {\n if (!usage || !cost) return;\n const tokenStats = __stateStack.globals.__tokenStats;\n tokenStats.usage.inputTokens += usage.inputTokens || 0;\n tokenStats.usage.outputTokens += usage.outputTokens || 0;\n tokenStats.usage.cachedInputTokens += usage.cachedInputTokens || 0;\n tokenStats.usage.totalTokens += usage.totalTokens || 0;\n\n tokenStats.cost.inputCost += cost.inputCost || 0;\n tokenStats.cost.outputCost += cost.outputCost || 0;\n tokenStats.cost.totalCost += cost.totalCost || 0;\n}\n\n/**** Streaming callback and lock ****/\nfunction isGenerator(variable) {\n const toString = Object.prototype.toString.call(variable);\n return (\n toString === \"[object Generator]\" || toString === \"[object AsyncGenerator]\"\n );\n}\n\nlet __callbacks = {};\n\nasync function __callHook(name, data) {\n if (__callbacks[name]) {\n await __callbacks[name](data);\n }\n}\n\nlet onStreamLock = false;\n\nfunction __cloneArray(arr) {\n if (arr == undefined) return [];\n return [...arr];\n}\n\nconst handleStreamingResponse = async (__completion, statelogClient, __prompt, __toolCalls) => {\n if (isGenerator(__completion)) {\n if (!__callbacks.onStream) {\n console.log(\n \"No onStream callback provided for streaming response, returning response synchronously\",\n );\n statelogClient.debug(\n \"Got streaming response but no onStream callback provided, returning response synchronously\",\n {\n prompt: __prompt,\n callbacks: Object.keys(__callbacks),\n },\n );\n let syncResult = \"\";\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n break;\n case \"done\":\n syncResult = chunk.result;\n break;\n case \"error\":\n console.error(`Error in LLM response stream: ${chunk.error}`);\n break;\n default:\n break;\n }\n }\n return { success: true, value: syncResult };\n } else {\n // try to acquire lock\n let count = 0;\n // wait 60 seconds to acquire lock\n while (onStreamLock && count < 10 * 60) {\n await _builtinSleep(0.1);\n count++;\n }\n if (onStreamLock) {\n console.log(`Couldn't acquire lock, ${count}`);\n }\n onStreamLock = true;\n\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"text\":\n __callbacks.onStream({ type: \"text\", text: chunk.text });\n break;\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n __callbacks.onStream({\n type: \"tool_call\",\n toolCall: chunk.toolCall,\n });\n break;\n case \"done\":\n __callbacks.onStream({ type: \"done\", result: chunk.result });\n return { success: true, value: chunk.result };\n case \"error\":\n __callbacks.onStream({ type: \"error\", error: chunk.error });\n break;\n }\n }\n\n onStreamLock = false;\n }\n }\n};\n\n\n/**** Message thread handling ****/\n\nclass MessageThread {\n messages = [];\n children = [];\n\n constructor(messages = []) {\n this.messages = messages;\n this.children = [];\n }\n\n addMessage(message) {\n this.messages.push(message);\n }\n\n cloneMessages() {\n return this.messages.map(m => m.toJSON()).map(m => smoltalk.messageFromJSON(m));\n }\n\n getMessages() {\n return this.messages;\n }\n\n setMessages(messages) {\n this.messages = messages;\n }\n\n newChild() {\n const child = new MessageThread();\n this.children.push(child);\n return child;\n }\n\n newSubthreadChild() {\n const child = new MessageThread(this.cloneMessages());\n this.children.push(child);\n return child;\n }\n\n toJSON() {\n return {\n messages: this.messages.map(m => m.toJSON()),\n children: this.children.map((child) => child.toJSON()),\n };\n }\n}";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
logHost: string;
|
|
4
4
|
hasApiKey: boolean;
|
|
@@ -235,6 +235,10 @@ export async function respondToInterrupt(
|
|
|
235
235
|
messages: messages,
|
|
236
236
|
__metadata: {
|
|
237
237
|
graph: graph,
|
|
238
|
+
// we need to pass in the state log client here because
|
|
239
|
+
// if we rely on the local state log client
|
|
240
|
+
// each client in each file has a different trace id.
|
|
241
|
+
// So we pass in the client to make sure they all use the same trace id
|
|
238
242
|
statelogClient: __statelogClient,
|
|
239
243
|
__stateStack: __stateStack,
|
|
240
244
|
__callbacks: metadata.callbacks,
|
|
@@ -402,7 +406,7 @@ function __cloneArray(arr) {
|
|
|
402
406
|
return [...arr];
|
|
403
407
|
}
|
|
404
408
|
|
|
405
|
-
const handleStreamingResponse = async (__completion) => {
|
|
409
|
+
const handleStreamingResponse = async (__completion, statelogClient, __prompt, __toolCalls) => {
|
|
406
410
|
if (isGenerator(__completion)) {
|
|
407
411
|
if (!__callbacks.onStream) {
|
|
408
412
|
console.log(
|
|
@@ -431,7 +435,7 @@ const handleStreamingResponse = async (__completion) => {
|
|
|
431
435
|
break;
|
|
432
436
|
}
|
|
433
437
|
}
|
|
434
|
-
|
|
438
|
+
return { success: true, value: syncResult };
|
|
435
439
|
} else {
|
|
436
440
|
// try to acquire lock
|
|
437
441
|
let count = 0;
|
|
@@ -459,8 +463,7 @@ const handleStreamingResponse = async (__completion) => {
|
|
|
459
463
|
break;
|
|
460
464
|
case "done":
|
|
461
465
|
__callbacks.onStream({ type: "done", result: chunk.result });
|
|
462
|
-
|
|
463
|
-
break;
|
|
466
|
+
return { success: true, value: chunk.result };
|
|
464
467
|
case "error":
|
|
465
468
|
__callbacks.onStream({ type: "error", error: chunk.error });
|
|
466
469
|
break;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}) {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n let __messages = __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 = __stateStack.interruptData?.toolCall ? [__stateStack.interruptData.toolCall] : [];\n const __interruptResponse = __stateStack.interruptData?.interruptResponse || null;\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;\n\n if (__toolCalls.length === 0) {\n __messages.push(smoltalk.userMessage(__prompt));\n \n \n await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const endTime = performance.now();\n\n await handleStreamingResponse(__completion);\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n tools: __tools,\n responseFormat: __responseFormat\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(smoltalk.assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n\n __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: endTime - startTime });\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 = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.interruptData = {\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 await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const nextEndTime = performance.now();\n\n await handleStreamingResponse(__completion);\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n tools: __tools,\n responseFormat: __responseFormat,\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 __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: nextEndTime - nextStartTime });\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(smoltalk.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{{#isAsync}}\n__self.{{{variableName:string}}} = _{{{variableName:string}}}({{{funcCallParams:string}}});\n{{/isAsync}}\n\n{{^isAsync}}\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 {{#nodeContext}}\n return { messages: {{{messagesVar:string}}} , data: __self.{{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return __self.{{{variableName:string}}};\n {{/nodeContext}}\n}\n{{/isAsync}}";
|
|
1
|
+
export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}) {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n let __messages = __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 = __stateStack.interruptData?.toolCall ? [__stateStack.interruptData.toolCall] : [];\n const __interruptResponse = __stateStack.interruptData?.interruptResponse || null;\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;\n\n if (__toolCalls.length === 0) {\n __messages.push(smoltalk.userMessage(__prompt));\n \n \n await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const endTime = performance.now();\n\n {{#isStreaming}}\n __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);\n {{/isStreaming}}\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n tools: __tools,\n responseFormat: __responseFormat\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(smoltalk.assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n\n __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: endTime - startTime });\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 = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.interruptData = {\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 await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const nextEndTime = performance.now();\n\n {{#isStreaming}}\n __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);\n {{/isStreaming}}\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n tools: __tools,\n responseFormat: __responseFormat,\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 __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: nextEndTime - nextStartTime });\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(smoltalk.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{{#isAsync}}\n__self.{{{variableName:string}}} = _{{{variableName:string}}}({{{funcCallParams:string}}});\n{{/isAsync}}\n\n{{^isAsync}}\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 {{#nodeContext}}\n return { messages: {{{messagesVar:string}}} , data: __self.{{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return __self.{{{variableName:string}}};\n {{/nodeContext}}\n}\n{{/isAsync}}";
|
|
2
2
|
export type TemplateType = {
|
|
3
3
|
variableName: string;
|
|
4
4
|
argsStr: string;
|
|
@@ -41,7 +41,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}) {
|
|
|
41
41
|
|
|
42
42
|
const endTime = performance.now();
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
{{#isStreaming}}
|
|
45
|
+
__completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);
|
|
46
|
+
{{/isStreaming}}
|
|
45
47
|
|
|
46
48
|
statelogClient.promptCompletion({
|
|
47
49
|
messages: __messages,
|
|
@@ -109,7 +111,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}) {
|
|
|
109
111
|
|
|
110
112
|
const nextEndTime = performance.now();
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
{{#isStreaming}}
|
|
115
|
+
__completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);
|
|
116
|
+
{{/isStreaming}}
|
|
113
117
|
|
|
114
118
|
statelogClient.promptCompletion({
|
|
115
119
|
messages: __messages,
|
package/package.json
CHANGED