Code Nodes
Code Nodes
Code Nodes in BotDojo provide the flexibility and power of running JavaScript (with Python support coming soon) within your AI applications. While visual designers excel at handling functions that follow similar patterns, such as building prompts and making calls to Language Models (LLMs), there are cases where writing code is more efficient and effective.
Code Nodes come in handy when you need to perform specific actions like making calculations or calling out to an API. They also simplify the process of handling complex logic involving loops, branching (if/then/else), and other programming constructs.
Input and Execution
Each Code Node accepts an input defined by a JSON Schema, similar to LLM Nodes and AI Agent Nodes. The entry function for a Code Node is called execute
, and it takes three parameters:
input
: An object that represents the input data, structured according to the defined input JSON Schema.node
: The node object itself, which allows you to inspect properties of the Code Node to alter its behavior.context
: An object that provides access to the session state and allows you to log events that will be available in the execution trace.
Here's an example of the execute
function signature:
async function execute(
input: InputSchema, // Input data structured according to the defined JSON Schema
node: INode, // Node object containing properties that can alter behavior
context: IFlowContext // Provides access to session state and execution environment
): Promise<OutputSchema> // Returns output matching the defined output JSON Schema
{
// Code logic goes here
}
Interacting with Other Nodes
Code Nodes can take special nodes as inputs, such as LLMs, Prompts, and Tools. This enables the Code Node to call these interfaces and access other nodes within the flow. By leveraging this capability, you can seamlessly integrate third-party libraries with BotDojo, getting the best of both worlds—the ability to mix and match functionalities that best suits your senario.
async function execute(input,node, context)
{
let spanishResults = []
context.log('starting function')
for(let sentence of input.sentences)
{
let spanish = await input.llm.completion("Please translate "+sentence+" to spanish. Only respond with the translation.\n")
spanishResults.push(spanish.outputText)
}
context.log('return result')
return {
spanish_sentences:spanishResults
}
}
Below is an example of using Code Nodes. Click the image below to Clone it to your project.
Writing Code
The context
parameter provides access to several important features and capabilities:
Core Context Object (IFlowContext)
The IFlowContext
interface represents the execution environment for a flow. Think of it as the central hub that provides access to various services and state management. Here are its key components:
interface IFlowContext {
sessionState: ISessionState;
onIntermediateStepUpdate: (step: FlowRequestIntermediateStep) => Promise<void>;
flowId: string;
flowRequestId: string;
sessionId: string;
callSubFlow: (subFlowId: string, input: any) => Promise<any>;
env: (name: string) => Promise<any>;
}
Session State Management
The session state system provides a way to store and retrieve data during flow execution. It's organized in a hierarchical structure:
ISessionState
- The top-level session state managerISessionStateStore
- Individual state stores that contain messages and variables
Here's how you would work with session state:
// Getting a state store
const stateStore = await context.sessionState.get("myStateKey");
// Adding a message
await stateStore.addMessage({
role: "user",
content: "Hello, how can I help you today?"
});
// Getting all messages
const messages = await stateStore.getMessages();
// Setting a variable
await stateStore.setVariable("userName", "John");
// Getting a variable
const userName = await stateStore.getVariable("userName");
Message Management
The system uses a structured approach to handling messages through the ChatMessage
class:
// Example of creating and storing a message
const message = new ChatMessage();
message.role = "assistant";
message.content = "I can help you with that!";
message.date = new Date();
await stateStore.addMessage(message);
Progress Tracking
The context provides a way to report progress through intermediate steps:
// Reporting progress
await context.onIntermediateStepUpdate({
stepId: "step1",
stepLabel: "Processing User Input",
stepStatus: "processing",
stepPercentComplete: 50,
content: "Analyzing user request...",
startTime: new Date()
});
Sub-Flow Execution
The context allows for calling sub-flows, which is useful for modular flow design:
// Calling a sub-flow
const result = await context.callSub_MySubFlow(
{ userId: "123" }
);
Environment Variables
You can access environment variables through the context:
// Getting an environment variable
const apiKey = await context.env("API_KEY");
For more information on Environment Variables see Environment Variables.
Connecting interface Nodes
You can connect and interact with other primative interfaces in a Code Node. To do so add an Interface Node to your flow and connect it to the Code Node. You can access and call methods on these interfaces by calling await input.inputName.method().
The system includes several other important interfaces that work with the context:
ILanguageModel
- For interacting with language modelsIRetriever
- For searching and retrieving informationIFlowStorage
- For managing file storageIPrompt
- For handling prompt generationIMemory
- For managing conversational memory
These interfaces work together to provide a complete environment for flow execution. For example, you might use them together like this:
// Example of a complex interaction
async function handleUserQuery(context: IFlowContext) {
// Get the state store
const store = await context.sessionState.get("mainConversation");
// Get previous messages
const messages = await store.getMessages();
// Add new user message
await store.addMessage({
role: "user",
content: "What's the weather like?"
});
// Report progress
await context.onIntermediateStepUpdate({
stepId: "weatherQuery",
stepLabel: "Checking Weather",
stepStatus: "processing",
content: "Retrieving weather information...",
startTime: new Date()
});
// Use environment variables
const weatherApiKey = await context.env("WEATHER_API_KEY");
// Store the result
await store.setVariable("lastWeatherCheck", new Date());
}
This system is designed to provide a robust environment for building complex flows while maintaining state, handling messages, and managing resources effectively. The context object serves as the central point of access to all these capabilities.