diff --git a/adk/concepts/actions.mdx b/adk/concepts/actions.mdx index a6860acb..762f7e61 100644 --- a/adk/concepts/actions.mdx +++ b/adk/concepts/actions.mdx @@ -81,15 +81,18 @@ export default new Trigger({ }); ``` -```ts From a tool highlight={1,6} -import { Autonomous, actions } from "@botpress/runtime" +```ts From a tool highlight={1,8} +import { Autonomous, actions, z } from "@botpress/runtime" export default new Autonomous.Tool({ name: "someTool", + description: "A tool that does something", + input: z.object({}), + output: z.object({ result: z.string() }), handler: async () => { const result = await actions.doSomething() - } - // Other logic + return { result } + }, }) ``` diff --git a/adk/concepts/conversations.mdx b/adk/concepts/conversations.mdx index 35b8aa9e..5a7b59b4 100644 --- a/adk/concepts/conversations.mdx +++ b/adk/concepts/conversations.mdx @@ -13,7 +13,7 @@ import { Conversation } from "@botpress/runtime"; export default new Conversation({ channel: "*", - handler: async ({ execute, message }) => { + handler: async ({ execute }) => { await execute({ instructions: "You are a helpful assistant.", }); @@ -71,7 +71,7 @@ The handler function receives context about the incoming message and provides AP export default new Conversation({ channel: "*", handler: async ({ execute, message }) => { - const userMessage = message.text; + const userMessage = message.payload.text; await execute({ instructions: `You are a helpful assistant. The user said: ${userMessage}`, }); @@ -122,7 +122,18 @@ export default new Conversation({ ## Conversation instance -The `handler` receives a `conversation` object that provides methods to interact with the current conversation: +The `handler` receives a `conversation` object that provides methods to interact with the current conversation. It exposes `conversation.id` (the conversation ID) and `conversation.channel` (the channel name) as read-only properties. + +### Loading a conversation by ID + +You can load any conversation by its ID using the static `Conversation.get()` method: + +```typescript +import { Conversation } from "@botpress/runtime"; + +const otherConversation = await Conversation.get("conversation-id"); +await otherConversation.send({ type: "text", payload: { text: "Hello from another conversation!" } }); +``` ### Sending messages @@ -157,21 +168,6 @@ const priority = conversation.tags.priority; conversation.tags.priority = "high"; ``` -### Trigger subscriptions - -Subscribe a conversation to triggers: - -```typescript -// Subscribe to a trigger -await conversation.subscribeToTrigger("myTrigger"); - -// Subscribe with a key for filtered triggers -await conversation.subscribeToTrigger("myTrigger", "specific-key"); - -// Unsubscribe -await conversation.unsubscribeFromTrigger("myTrigger"); -``` - ## Multiple conversations You can create multiple conversation handlers for different channels or use cases: @@ -209,36 +205,26 @@ src/conversations/ Optional Zod schema defining the conversation state structure. Defaults to an empty object if not provided. + Optional array of event names this conversation should listen to. Supports integration events with a `conversationId` (e.g., `"webchat:conversationStarted"`) and custom events defined in `agent.config.ts`. By default, conversations only receive messages, not events. + + - Optional function to determine if a conversation should start from a trigger event. Returns a conversation ID or false. + Optional factory function to provide a custom `Chat` instance for the conversation. ### Handler parameters -The handler function receives different parameters depending on the incoming request type. The handler uses a discriminated union based on the `type` field: +The handler function receives different parameters depending on the incoming request type. The handler uses a discriminated union based on the `type` field. -#### Message handler - -When `type` is `"message"`: +All handler types share these common parameters: - - - Indicates this is a message request. - - - Message object containing the user's message data. Typed by `ConversationInstance` at runtime based on the channel. - + Function to execute autonomous AI logic. Used to run the agent with instructions, tools, knowledge bases, etc. - - - Instructions for the AI agent. Can be a string or a function that returns a string. - - - Optional array of tools the agent can use. - - - Optional array of objects the agent can interact with. - - - Optional record of exit handlers. - - - Optional abort signal to cancel execution. - - - Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, etc.). - - - Optional temperature for the AI model. Defaults to 0.7. - - - Optional model name(s) to use. Can be a string, array of strings, or a function that returns either. - - - Optional array of knowledge bases to use. - - - Optional maximum number of iterations (loops). Defaults to 10. - - - - -#### Event handler - -When `type` is `"event"`: - - - Indicates this is an event request. + Chat instance for sending messages and managing the conversation transcript. - Event object containing event data from integrations or system events. Typed by `ConversationInstance` at runtime based on the channel. + The channel this message or event came from. + + +#### Message handler + +When `type` is `"message"`: + + - Conversation instance providing methods to interact with the conversation (send messages, manage state, etc.). + Indicates this is a message request. - Mutable conversation state object. Changes to this object are automatically persisted. Typed based on your state schema. + Message object containing `type` and `payload` properties. Typed based on the channel at runtime. + + +#### Event handler + +When `type` is `"event"`: + + - Botpress client for making API calls (tables, events, etc.). + Indicates this is an event request. - Function to execute autonomous AI logic. Used to run the agent with instructions, tools, knowledge bases, etc. - - - Instructions for the AI agent. Can be a string or a function that returns a string. - - - Optional array of tools the agent can use. - - - Optional array of objects the agent can interact with. - - - Optional record of exit handlers. - - - Optional abort signal to cancel execution. - - - Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, etc.). - - - Optional temperature for the AI model. Defaults to 0.7. - - - Optional model name(s) to use. Can be a string, array of strings, or a function that returns either. - - - Optional array of knowledge bases to use. - - - Optional maximum number of iterations (loops). Defaults to 10. - - + Event object containing `type` and `payload` properties from integrations or custom events. Typed based on the channel at runtime. + + To receive events in a conversation, you must specify the `events` prop on the Conversation definition. By default, conversations only receive messages. + + #### Workflow request handler When `type` is `"workflow_request"`: @@ -486,96 +357,100 @@ When `type` is `"workflow_request"`: + + +#### Workflow callback handler + +When `type` is `"workflow_callback"`: + + + Indicates this is a workflow callback event. + + - Conversation instance providing methods to interact with the conversation (send messages, manage state, etc.). + Workflow callback object containing information about the completed workflow. + + +### Execute props + +The `execute` function accepts the following props: + + - Mutable conversation state object. Changes to this object are automatically persisted. Typed based on your state schema. + Instructions for the AI agent. Can be a string or a function that returns a string. + Optional array of tools the agent can use. + + + Optional array of objects the agent can interact with. + + + Optional record of exit handlers. + + + Optional abort signal to cancel execution. + + - Botpress client for making API calls (tables, events, etc.). + Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, `onBeforeExecution`, `onExit`, `onIterationStart`, `onIterationEnd`). - Function to execute autonomous AI logic. Used to run the agent with instructions, tools, knowledge bases, etc. - - - Instructions for the AI agent. Can be a string or a function that returns a string. - - - Optional array of tools the agent can use. - - - Optional array of objects the agent can interact with. - - - Optional record of exit handlers. - - - Optional abort signal to cancel execution. - - - Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, etc.). - - - Optional temperature for the AI model. Defaults to 0.7. - - - Optional model name(s) to use. Can be a string, array of strings, or a function that returns either. - - - Optional array of knowledge bases to use. - - - Optional maximum number of iterations (loops). Defaults to 10. - - + Optional temperature for the AI model. + + + Optional model name(s) to use. Can be a string, array of strings, or a function that returns either. + + + Optional reasoning effort for models that support reasoning. `"none"` disables reasoning, `"dynamic"` lets the provider decide. + + + Optional array of knowledge bases to use. + + + Optional maximum number of iterations (loops). Defaults to 10. diff --git a/adk/concepts/knowledge.mdx b/adk/concepts/knowledge.mdx index 8faafca7..23a60015 100644 --- a/adk/concepts/knowledge.mdx +++ b/adk/concepts/knowledge.mdx @@ -184,6 +184,34 @@ export default new Conversation({ }); ``` +## Searching knowledge + +You can search a knowledge base directly using the `search()` method: + +```typescript +import { DocsKB } from "../knowledge/files"; + +const results = await DocsKB.search("How do I deploy my agent?"); + +for (const passage of results.passages) { + console.log(passage.content); + console.log(passage.metadata.url); +} +``` + +The `search()` method accepts optional options: + +```typescript +const results = await DocsKB.search("deployment", { + contextDepth: 4, // Surrounding passages for context (1–20, default: 4) + limit: 10, // Max passages to return (1–50, default: 20) +}); +``` + + + In most cases you don't need to call `search()` directly. When you pass a knowledge base to `execute()` via the `knowledge` prop, the AI model automatically searches it as needed. + + ## Refreshing knowledge Refresh knowledge bases to update their content: @@ -240,6 +268,20 @@ await WebsiteKB.refreshSource("my-source-id", { force: true }); type="DataSource[]" required > - Array of data sources to index. Can include `Website`, `Directory`, or `Table` sources. + Array of data sources to index. Can include `Website` or `Directory` sources. + + + +### Knowledge methods + + + + Search the knowledge base for relevant passages. Options: `contextDepth` (1–20, default 4), `limit` (1–50, default 20). Returns passages with `content` and `metadata` (url, title, file, description, etc.). + + + Refresh the knowledge base by re-indexing all sources. Pass `{ force: true }` to re-index even if unchanged. + + + Refresh a specific data source by its ID. Pass `{ force: true }` to force re-indexing. diff --git a/adk/concepts/tables.mdx b/adk/concepts/tables.mdx index d82e036a..5b8525e3 100644 --- a/adk/concepts/tables.mdx +++ b/adk/concepts/tables.mdx @@ -24,8 +24,8 @@ export default new Table({ To properly deploy your agent, make sure your table name follows the correct convention: -- Cannot start with a number -- Must be 30 characters or less +- Must start with a letter or underscore +- Must be 35 characters or less - Can contain only letters, numbers, and underscores - Must end with `Table` @@ -75,45 +75,171 @@ Check out the [column options](#table-props) in the Table props reference for a ## Using tables -Tables are automatically created and synced when you deploy. You can import them and interact with them directly using the table instance methods, which provide type-safe operations: +Tables are automatically created and synced when you deploy. You can import them and interact with them directly using the table instance methods, which provide type-safe operations. -```ts src/workflows/create-order.ts highlight={8-10, 13-18, 21-23} -import { Workflow } from "@botpress/runtime"; +### Creating rows + +```typescript import OrderTable from "../tables/order-table"; -export default new Workflow({ - name: "createOrder", - handler: async ({ step }) => { - // Create rows - const { rows } = await OrderTable.createRows({ - rows: [{ userId: "user123", total: 99.99, status: "pending" }], - }); - - // Find rows with filters - const { rows: orders } = await OrderTable.findRows({ - filter: { status: "pending" }, - orderBy: "createdAt", - orderDirection: "desc", - limit: 10, - }); - - // Update rows - await OrderTable.updateRows({ - rows: [{ id: orders[0].id, status: "completed" }], - }); +const { rows } = await OrderTable.createRows({ + rows: [{ userId: "user123", total: 99.99, status: "pending" }], +}); +``` + +### Finding rows + +```typescript +const { rows: orders } = await OrderTable.findRows({ + filter: { status: "pending" }, + orderBy: "createdAt", + orderDirection: "desc", + limit: 10, +}); +``` + +### Updating rows + +```typescript +await OrderTable.updateRows({ + rows: [{ id: orders[0].id, status: "completed" }], +}); +``` + +### Getting a single row + +```typescript +const row = await OrderTable.getRow({ id: 42 }); +``` + +### Deleting rows + + +```ts By filter +await OrderTable.deleteRows({ status: "cancelled" }); +``` + +```ts By IDs +await OrderTable.deleteRowIds([1, 2, 3]); +``` + +```ts All rows +await OrderTable.deleteAllRows(); +``` + + +### Upserting rows + +Insert rows or update them if they match on a key column: + +```typescript +const { inserted, updated } = await OrderTable.upsertRows({ + keyColumn: "userId", + rows: [{ userId: "user123", total: 149.99, status: "pending" }], +}); +``` + +## Filtering + +Use filter operators for advanced queries: + +```typescript +const { rows } = await OrderTable.findRows({ + filter: { + total: { $gt: 100 }, + status: { $in: ["pending", "processing"] }, + }, +}); +``` + +### Available operators + +| Operator | Description | +|----------|-------------| +| `$eq` | Equal to | +| `$ne` | Not equal to | +| `$gt` | Greater than | +| `$gte` | Greater than or equal | +| `$lt` | Less than | +| `$lte` | Less than or equal | +| `$in` | In array | +| `$nin` | Not in array | +| `$exists` | Field exists | +| `$regex` | Regex match | + +### Logical operators + +Combine filters with `$and`, `$or`, and `$not`: + +```typescript +const { rows } = await OrderTable.findRows({ + filter: { + $or: [ + { status: "pending" }, + { total: { $gt: 500 } }, + ], + }, +}); +``` + +## Semantic search + +Search across columns marked as `searchable` using natural language: + +```typescript +const { rows } = await TicketsTable.findRows({ + search: "VPN connection issues", + limit: 10, +}); +``` + +You can combine `search` with `filter` for hybrid queries: + +```typescript +const { rows } = await TicketsTable.findRows({ + search: "network problems", + filter: { status: "open" }, + limit: 20, +}); +``` + +## Aggregation + +Use the `group` parameter to perform aggregation operations: + +```typescript +const { rows } = await OrderTable.findRows({ + group: { + status: "key", + total: ["sum", "avg", "count"], }, }); +// rows: [{ statusKey: "pending", totalSum: 500, totalAvg: 100, totalCount: 5 }, ...] ``` +### Available operations + +| Operation | Applies to | Description | +|-----------|-----------|-------------| +| `key` | All types | Group by this value | +| `count` | All types | Count of rows | +| `sum` | Numbers | Sum of values | +| `avg` | Numbers | Average of values | +| `max` | Numbers, strings, dates | Maximum value | +| `min` | Numbers, strings, dates | Minimum value | +| `unique` | All types | Array of unique values | + ## Reference +### Table props + - Unique name for the table. Must be 30 characters or less, cannot start with a number, can contain only letters, numbers, and underscores, and must end with 'Table'. + Unique name for the table. Must start with a letter or underscore, be 35 characters or less, contain only letters, numbers, and underscores, and end with `Table`. - Optional factor for table operations. Defaults to 1. + Search weighting factor (1–30). Higher values increase the table's relevance in semantic search results. Defaults to 1. + + + Name of a column to use as the key for upsert operations. Must reference an existing column. + + + Optional metadata tags for the table. Keys must be 3–50 characters, values 1–255 characters. Object mapping column names to their definitions. Each key is a column name. Values can be either a simple Zod schema or an object with advanced column configuration options. - + - + - Whether this column is computed from other columns. Defaults to false. + Whether this column should be searchable via the `search` parameter in `findRows()`. Defaults to false. - Whether this column should be searchable. Defaults to false. + Whether this column is computed from other columns. Defaults to false. - Array of column names this computed column depends on. Required for computed columns. + Array of column names this computed column depends on. Required when `computed` is true. - Async function that computes the column value from the row data. Required for computed columns. + Async function that computes the column value from the row data. Required when `computed` is true. +### Table methods + + + + Fetch a single row by its ID. + + + Find rows with optional `filter`, `search`, `group`, `orderBy`, `orderDirection`, `limit`, and `offset`. + + + Insert one or more rows. Set `waitComputed` to true to wait for computed columns to be calculated before returning. + + + Update one or more rows. Each row must include an `id` field. Only changed fields need to be provided. + + + Insert or update rows based on a `keyColumn`. Defaults to `id` if not specified. + + + Delete rows matching a filter. + + + Delete rows by their IDs. + + + Delete all rows in the table. + + + Get table metadata including row count and indexing status. + + + Tables are automatically synced with Botpress Cloud. When you deploy your agent, table schemas are created or updated to match your definitions. diff --git a/adk/concepts/tools.mdx b/adk/concepts/tools.mdx index d0ba53d0..d6e051ae 100644 --- a/adk/concepts/tools.mdx +++ b/adk/concepts/tools.mdx @@ -6,7 +6,7 @@ Tools are functions that can be called by the AI model during conversations. The ## Creating a tool -Create a tool in `src/tools/`: +Create a tool anywhere in your agent's source code: ```typescript import { Autonomous, z } from "@botpress/runtime"; diff --git a/adk/concepts/workflows/overview.mdx b/adk/concepts/workflows.mdx similarity index 56% rename from adk/concepts/workflows/overview.mdx rename to adk/concepts/workflows.mdx index 58f1cfee..84a75bb7 100644 --- a/adk/concepts/workflows/overview.mdx +++ b/adk/concepts/workflows.mdx @@ -1,6 +1,5 @@ --- title: Workflows -sidebarTitle: Overview --- Workflows are long-running processes that handle complex, multi-step operations or scheduled tasks. Unlike [conversations](/adk/concepts/conversations), workflows can run independently on a schedule or be triggered by events. @@ -19,7 +18,7 @@ import { Workflow } from "@botpress/runtime"; export default new Workflow({ name: "my-workflow", description: "A workflow that processes data", - handler: async ({}) => { + handler: async ({ step }) => { // Workflow logic }, }); @@ -44,7 +43,7 @@ export default new Workflow({ ## Steps -By default, workflows time out after 2 minutes—if you need to run a workflow for longer, you should use the `step` function. This lets you break the workflow up into a series of steps that each take only a few seconds to run individually: +By default, workflows time out after 5 minutes. For longer workflows, use the `step` function to break the workflow into a series of persisted steps: ```typescript highlight={4-17} export default new Workflow({ @@ -68,23 +67,44 @@ export default new Workflow({ }); ``` -Steps are *persisted*—if a workflow is interrupted, it can resume from the last completed step. This provides better handling when errors occur in complex, long-running workflows. +Steps are *persisted* — if a workflow is interrupted, it can resume from the last completed step. This provides better handling when errors occur in complex, long-running workflows. - You can nest steps inside other steps—each step will complete when each of its sub-steps complete. + You can nest steps inside other steps — each step will complete when each of its sub-steps complete. -### Reference +### Step parameters + +```typescript +const data = await step("fetch-data", async ({ attempt }) => { + return await fetchDataFromAPI(); +}, { maxAttempts: 5 }); +``` -The `step` object also provides many additional methods for workflow control: + + + Unique identifier for this step within the workflow. - - Learn about all available step methods - + + Make sure you set a unique identifier for each step. Otherwise, the second step won't execute and will reuse the output of the first step. + + + + Function to execute. Receives the current attempt number. + + + Optional configuration object. + + + Maximum number of retry attempts if the step fails. Defaults to 5. + + + + ### Handling failing steps -If a step (or a [step method](/adk/concepts/workflows/steps#methods)) fails, it'll throw a rejected promise. This will fail not only the current step, but the *entire workflow*. +If a step fails, it throws a rejected promise. This will fail not only the current step, but the *entire workflow*. To avoid this, make sure you catch errors gracefully: @@ -96,7 +116,7 @@ try { } ``` -If the method you're using has an `options.maxAttempts` field, it'll throw an error after the maximum number of retries has been exceeded. You can track the retries using the `attempt` parameter and catch any errors: +If the method you're using has an `options.maxAttempts` field, it throws an error after the maximum number of retries has been exceeded: ```ts highlight={5, 8, 10-12} try { @@ -113,6 +133,146 @@ try { } ``` +## Step methods + +The `step` object provides methods for workflow control: + +### `step.listen` + +Put the workflow into listening mode, waiting for external events to resume: + +```typescript +await step.listen("wait-for-approval"); +// Workflow pauses here until triggered +``` + +### `step.sleep` + +Pause workflow execution for a specified duration: + +```typescript +await step.sleep("wait-5-min", 5 * 60 * 1000); +``` + +### `step.sleepUntil` + +Sleep until a specific date: + +```typescript +await step.sleepUntil("wait-until-noon", new Date("2025-01-15T12:00:00Z")); +``` + +### `step.fail` + +Mark the workflow as failed and stop execution: + +```typescript +if (!user.isVerified) { + await step.fail("User verification required"); +} +``` + +### `step.abort` + +Immediately abort the workflow execution without marking it as failed: + +```typescript +if (shouldPause) { + step.abort(); +} +``` + +### `step.progress` + +Record a progress checkpoint without performing any action: + +```typescript +await step.progress("Started processing"); +// ... do work ... +await step.progress("Finished processing"); +``` + +### `step.waitForWorkflow` + +Wait for another workflow to complete before continuing: + +```typescript +const childWorkflow = await childWorkflowInstance.start({}); +const result = await step.waitForWorkflow("wait-for-child", childWorkflow.id); +``` + +### `step.executeWorkflow` + +Start another workflow and wait for it to complete: + +```typescript +import ProcessingWorkflow from "../workflows/processing"; + +const result = await step.executeWorkflow( + "process-data", + ProcessingWorkflow, + { data: inputData } +); +``` + +### `step.map` + +Process an array of items in parallel with controlled concurrency: + +```typescript +const results = await step.map( + "process-users", + users, + async (user, { i }) => await processUser(user), + { concurrency: 5, maxAttempts: 3 } +); +``` + +### `step.forEach` + +Process an array of items without collecting results: + +```typescript +await step.forEach( + "notify-users", + users, + async (user) => await sendNotification(user), + { concurrency: 10 } +); +``` + +### `step.batch` + +Process items in sequential batches: + +```typescript +await step.batch( + "bulk-insert", + records, + async (batch) => await database.bulkInsert(batch), + { batchSize: 100 } +); +``` + +### `step.request` + +Request data from a conversation and wait for a response. Requires defining `requests` in the workflow: + +```typescript +export default new Workflow({ + name: "order-workflow", + requests: { + orderId: z.object({ + orderId: z.string(), + }), + }, + handler: async ({ step }) => { + const data = await step.request("orderId", "Please provide the order ID"); + // data is typed based on the request schema + }, +}); +``` + ## Output Workflows can return an output that matches the output schema: @@ -132,6 +292,145 @@ export default new Workflow({ }); ``` +## Workflow methods + +Workflows can be started and managed programmatically. + +### `workflow.start()` + +Start a new workflow instance: + +```typescript +import ProcessingWorkflow from "../workflows/processing"; + +const instance = await ProcessingWorkflow.start({ orderId: "12345" }); +console.log("Started workflow:", instance.id); +``` + +### `workflow.getOrCreate()` + +Get an existing workflow or create a new one with deduplication: + +```typescript +const instance = await ProcessingWorkflow.getOrCreate({ + key: "order-12345", // Unique key for deduplication + input: { orderId: "12345" }, + statuses: ["pending", "in_progress"], // Only match workflows with these statuses +}); +``` + +### `Workflow.get()` + +Load any workflow instance by its ID: + +```typescript +import { Workflow } from "@botpress/runtime"; + +const instance = await Workflow.get("workflow-id"); +``` + +### `workflow.asTool()` + +Convert a workflow into a tool for use with `execute()`: + +```typescript highlight={2, 9} +import { Conversation } from "@botpress/runtime"; +import ProcessingWorkflow from "../workflows/processing"; + +export default new Conversation({ + channel: "*", + handler: async ({ execute }) => { + await execute({ + instructions: "You are a helpful assistant.", + tools: [ProcessingWorkflow.asTool()], + }); + }, +}); +``` + +### `workflow.provide()` + +Provide data in response to a workflow data request (used with `step.request()`): + +```typescript highlight={1, 6-9} +import { isWorkflowDataRequest } from "@botpress/runtime"; +import OrderWorkflow from "../workflows/order"; + +export default new Conversation({ + channel: "*", + handler: async ({ type, request }) => { + if (type === "workflow_request") { + await OrderWorkflow.provide(request, { orderId: "12345" }); + } + }, +}); +``` + +## Workflow instance + +When you start a workflow, you get a workflow instance with additional methods: + +### `instance.cancel()` + +Cancel a running workflow: + +```typescript +const instance = await ProcessingWorkflow.start({ orderId: "12345" }); +await instance.cancel(); +``` + +### `instance.setTimeout()` + +Extend the timeout of a running workflow: + +```typescript +// Relative duration +await instance.setTimeout({ in: "30m" }); + +// Absolute timestamp +await instance.setTimeout({ at: "2025-01-15T12:00:00Z" }); +``` + +### `instance.complete()` + +Complete a workflow early with output (only available inside the handler): + +```typescript +export default new Workflow({ + name: "my-workflow", + output: z.object({ result: z.string() }), + handler: async ({ workflow }) => { + workflow.complete({ result: "done early" }); + }, +}); +``` + +## Helper functions + +### `isWorkflowDataRequest()` + +Check if an event is a workflow data request: + +```typescript +import { isWorkflowDataRequest } from "@botpress/runtime"; + +if (isWorkflowDataRequest(event)) { + // Handle the workflow data request +} +``` + +### `isWorkflowCallback()` + +Check if an event is a workflow callback: + +```typescript +import { isWorkflowCallback } from "@botpress/runtime"; + +if (isWorkflowCallback(event)) { + // Handle the workflow callback +} +``` + ## Reference ### Workflow props @@ -197,22 +496,20 @@ export default new Workflow({ ### Handler parameters -The handler function receives workflow context and provides step management: - - The validated input object matching the workflow's input schema. Typed based on your input schema. + The validated input object matching the workflow's input schema. - Workflow state object that persists across workflow executions and steps. Typed based on your state schema. + Workflow state object that persists across workflow executions and steps. - Botpress client for making API calls (tables, events, etc.). + Botpress client for making API calls. + + + Function to execute autonomous AI logic. See [Execute props](/adk/concepts/conversations#execute-props) for the full reference. - Function to execute autonomous AI logic. Used to run the agent with instructions, tools, knowledge bases, etc. - - - Instructions for the AI agent. Can be a string or a function that returns a string. - - - Optional array of tools the agent can use. - - - Optional array of objects the agent can interact with. - - - Optional record of exit handlers. - - - Optional abort signal to cancel execution. - - - Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, etc.). - - - Optional temperature for the AI model. Defaults to 0.7. - - - Optional model name(s) to use. Can be a string, array of strings, or a function that returns either. - - - Optional array of knowledge bases to use. - - - Optional maximum number of iterations (loops). Defaults to 10. - - + The current workflow instance. Provides access to `id`, `name`, `tags`, and methods like `cancel()`, `complete()`, and `setTimeout()`. - -## Methods - -Workflows can be started and managed programmatically: - -### `workflow.start()` - -Start a new workflow instance: - -```typescript -import ProcessingWorkflow from "../workflows/processing"; - -const instance = await ProcessingWorkflow.start({ orderId: "12345" }); -console.log("Started workflow:", instance.id); -``` - -### `workflow.getOrCreate()` - -Get an existing workflow or create a new one with deduplication: - -```typescript -const instance = await ProcessingWorkflow.getOrCreate({ - key: "order-12345", // Unique key for deduplication - input: { orderId: "12345" }, - statuses: ["pending", "in_progress"], // Only match workflows with these statuses -}); -``` - -### `workflow.asTool()` - -Convert a workflow into a tool for use with `execute()`: - -```typescript highlight={2, 9} -import { Conversation } from "@botpress/runtime"; -import ProcessingWorkflow from "../workflows/processing"; - -export default new Conversation({ - channel: "*", - handler: async ({ execute }) => { - await execute({ - instructions: "You are a helpful assistant.", - tools: [ProcessingWorkflow.asTool()], - }); - }, -}); -``` - -### `workflow.provide()` - -Provide data in response to a workflow data request (used with `step.request()`): - -```typescript highlight={1, 6-9} -import { isWorkflowDataRequest } from "@botpress/runtime"; -import OrderWorkflow from "../workflows/order"; - -export default new Conversation({ - channel: "*", - handler: async ({ type, request }) => { - if (type === "workflow_request") { - await OrderWorkflow.provide(request, { orderId: "12345" }); - } - }, -}); -``` - -## Helper functions - -### `isWorkflowDataRequest()` - -Check if an event is a workflow data request: - -```typescript -import { isWorkflowDataRequest } from "@botpress/runtime"; - -if (isWorkflowDataRequest(event)) { - // Handle the workflow data request -} -``` - -### `isWorkflowCallback()` - -Check if an event is a workflow callback: - -```typescript -import { isWorkflowCallback } from "@botpress/runtime"; - -if (isWorkflowCallback(event)) { - // Handle the workflow callback -} -``` - diff --git a/adk/concepts/workflows/steps.mdx b/adk/concepts/workflows/steps.mdx deleted file mode 100644 index f6486a6d..00000000 --- a/adk/concepts/workflows/steps.mdx +++ /dev/null @@ -1,341 +0,0 @@ ---- -title: Steps ---- - - -This page contains a full reference for the [`step` function](/adk/concepts/workflows/overview#steps). - -```typescript -const data = await step("fetch-data", async () => { - return await fetchDataFromAPI(); -}); -``` - -## Parameters - - - Unique identifier for this step within the workflow. - - - Make sure you set a unique identifier for each step. Otherwise, the second step won't execute and will reuse the output of the first step. - - - - - Function to execute. Receives the current attempt number. - - - Optional configuration object. - - - Maximum number of retry attempts if the step fails. Defaults to 5. - - - - ---- - -## Methods - -The following methods are available on each step: - -### `listen` - -Put the workflow into listening mode, waiting for external events to resume: - -```typescript -await step.listen("wait-for-approval"); -// Workflow pauses here until triggered -``` - -**Parameters**: - - - The name of the step. - - ---- - -### `sleep` - -Pause workflow execution for a specified duration: - -```typescript -// Sleep for 5 minutes -await step.sleep("wait-5-min", 5 * 60 * 1000); -``` - -**Parameters**: - - - The name of the step. - - - Duration to sleep in milliseconds. - - ---- - -### `sleepUntil` - -Sleep until a specific date: - -```typescript -await step.sleepUntil("wait-until-noon", new Date("2025-01-15T12:00:00Z")); -``` - -**Parameters**: - - - The name of the step. - - - The date to sleep until. - - ---- - -### `fail` - -Mark the workflow as failed and stop execution: - -```typescript -if (!user.isVerified) { - await step.fail("User verification required"); -} -``` - -**Parameters**: - - - Description of why the workflow failed. - - ---- - -### `abort` - -Immediately abort the workflow execution without marking it as failed: - -```typescript -if (shouldPause) { - step.abort(); -} -``` - -**Parameters**: - -No parameters. - ---- - -### `progress` - -Record a progress checkpoint without performing any action: - -```typescript -await step.progress("Started processing"); -// ... do work ... -await step.progress("Finished processing"); -``` - -**Parameters**: - - - The name of the progress checkpoint. - - ---- - -### `waitForWorkflow` - -Wait for another workflow to complete before continuing: - -```typescript -const childWorkflow = await childWorkflowInstance.start({}); -const result = await step.waitForWorkflow("wait-for-child", childWorkflow.id); -``` - -**Parameters**: - - - The name of the step. - - - ID of the workflow to wait for. - - ---- - -### `executeWorkflow` - -Start another workflow and wait for it to complete: - -```typescript -import ProcessingWorkflow from "../workflows/processing"; - -const result = await step.executeWorkflow( - "process-data", - ProcessingWorkflow, - { data: inputData } -); -``` - -**Parameters**: - - - The name of the step. - - - The workflow instance to execute. - - - Input data for the workflow (typed based on workflow's input schema). - - ---- - -### `map` - -Process an array of items in parallel with controlled concurrency: - -```typescript -const results = await step.map( - "process-users", - users, - async (user, { i }) => await processUser(user), - { concurrency: 5, maxAttempts: 3 } -); -``` - -**Parameters**: - - - The name of the map operation. - - - Array of items to process. - - - Function to process each item. Receives the item and its index. - - - Optional configuration object. - - - Maximum retry attempts per item. Defaults to 5. - - - Maximum number of concurrent operations. Defaults to 1. - - - - ---- - -### `forEach` - -Process an array of items without collecting results: - -```typescript -await step.forEach( - "notify-users", - users, - async (user) => await sendNotification(user), - { concurrency: 10 } -); -``` - -**Parameters**: - - - The name of the forEach operation. - - - Array of items to process. - - - Function to process each item. Receives the item and its index. - - - Optional configuration object. - - - Maximum retry attempts per item. Defaults to 5. - - - Maximum number of concurrent operations. Defaults to 1. - - - - ---- - -### `batch` - -Process items in sequential batches: - -```typescript -await step.batch( - "bulk-insert", - records, - async (batch) => await database.bulkInsert(batch), - { batchSize: 100 } -); -``` - -**Parameters**: - - - The name of the batch operation. - - - Array of items to process. - - - Function to process each batch. Receives the batch array and starting index. - - - Optional configuration object. - - - Number of items per batch. Defaults to 20. - - - Maximum retry attempts per batch. Defaults to 5. - - - - ---- - -### `request` - -Request data from a conversation and wait for a response. Requires defining `requests` in the workflow: - -```typescript -export default new Workflow({ - name: "order-workflow", - requests: { - orderId: z.object({ - orderId: z.string(), - }), - }, - handler: async ({ step }) => { - const data = await step.request("orderId", "Please provide the order ID"); - // data is typed based on the request schema - }, -}); -``` - -**Parameters**: - - - The name of the request (must be defined in workflow's `requests` field). - - - Message to display to the user describing what data is needed. - - - Optional custom name for the step. Defaults to the request name. - diff --git a/adk/introduction.mdx b/adk/introduction.mdx index 7fd6ceee..3e771410 100644 --- a/adk/introduction.mdx +++ b/adk/introduction.mdx @@ -10,7 +10,7 @@ sidebarTitle: Introduction export default new Conversation({ channel: "*", - handler: async ({ execute, message }) => { + handler: async ({ execute }) => { await execute({ instructions: "You are a helpful assistant.", }); @@ -24,7 +24,7 @@ sidebarTitle: Introduction export default new Conversation({ channel: "*", - handler: async ({ execute, message }) => { + handler: async ({ execute }) => { await execute({ instructions: "You are a helpful assistant.", knowledge: [WebsiteKB] diff --git a/adk/project-structure.mdx b/adk/project-structure.mdx index d64e2db1..5f9eb64d 100644 --- a/adk/project-structure.mdx +++ b/adk/project-structure.mdx @@ -8,16 +8,13 @@ ADK projects follow a consistent structure that organizes your agent's code, con - + - - - - + - + @@ -26,13 +23,18 @@ ADK projects follow a consistent structure that organizes your agent's code, con - + + + + + + @@ -120,7 +122,7 @@ Links the agent to a bot in your Workspace: { "botId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "workspaceId": "wkspace_xxxxxxxxxxxxxxxxxxxxxxxxxx", - "devId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + "apiUrl": "https://api.botpress.cloud" } ``` @@ -233,24 +235,6 @@ export default new Action({ }); ``` -### `src/tools/` - -Tools that the AI model can call during conversations. Tools are provided to the `execute()` function. - -```typescript -import { Autonomous, z } from "@botpress/runtime"; - -export default new Autonomous.Tool({ - name: "myTool", - description: "A tool that does something useful", - input: z.object({ query: z.string() }), - output: z.object({ result: z.string() }), - handler: async ({ query }) => { - return { result: "Tool output" }; - }, -}); -``` - ### `src/tables/` Table schemas that define data storage structures. Tables are automatically synced with Botpress Cloud. @@ -356,13 +340,15 @@ const maxRetries = configuration.maxRetries; ## Generated files -The ADK generates TypeScript types in `.adk/bot/.botpress`: +When you run `adk dev` or `adk build`, the ADK generates files in the `.adk/` directory: -- `.botpress/types/` - Type definitions for integrations and interfaces -- `.botpress/dist/` - Compiled output (created during build) +- `.adk/*.d.ts` — Type definitions for actions, conversations, workflows, tables, triggers, and more +- `.adk/client.ts` — Generated client wrapper +- `.adk/integrations/` — Integration type definitions +- `.adk/bot/` — Generated Botpress bot project and compiled output -Don't edit files in the `.botpress` directory manually. They are automatically generated and will be overwritten. +Don't edit files in the `.adk/` directory manually. They are automatically generated and will be overwritten. ## Next steps diff --git a/adk/quickstart.mdx b/adk/quickstart.mdx index 22842d83..81272e0d 100644 --- a/adk/quickstart.mdx +++ b/adk/quickstart.mdx @@ -46,54 +46,61 @@ adk init my-agent ▄▀█ █▀▄ █▄▀ Botpress ADK █▀█ █▄▀ █░█ Initialize New Agent + Step 1 of 2 + Select a template: -› Blank - Start with an empty agent project +› blank + Empty project with just the folder structure - Hello World - Start with a conversation agent project + hello-world + A working chatbot with chat and webchat integrations ``` -Select the `Hello World` template, then follow the prompts to set up the agent. This creates a new `my-agent` directory with: +Select the `hello-world` template. Next, choose your preferred package manager: -- `agent.config.ts` - Agent configuration and integration dependencies -- `agent.json` - Configuration for your linked bot -- `package.json` - Project configuration -- `tsconfig.json` - TypeScript configuration -- `src/` - Source code directory structure +```txt + ▄▀█ █▀▄ █▄▀ Botpress ADK + █▀█ █▄▀ █░█ Initialize New Agent -## Install dependencies + Step 2 of 2 -Once you've finished the setup, navigate into your project: -```bash -cd my-agent +Select a package manager: + +● Bun + Install with bun +○ pnpm + Install with pnpm +○ Yarn + Install with yarn +○ npm + Install with npm ``` -Install the dependencies: +If you are logged in, the CLI will also prompt you to select a workspace to link your agent to. - +The CLI automatically installs dependencies and creates a `my-agent` directory with: -```bash bun -bun install -``` +- `agent.config.ts` — Agent configuration, state schemas, and integration dependencies +- `agent.json` — Workspace and bot IDs for your linked agent +- `package.json` — Project configuration with `dev`, `build`, and `deploy` scripts +- `tsconfig.json` — TypeScript configuration +- `src/` — Source code with subdirectories for `conversations`, `actions`, `tables`, `triggers`, `workflows`, and `knowledge` +- `.mcp.json` — MCP configuration for AI coding assistants (Claude Code, Cursor, etc.) +- `CLAUDE.md` / `AGENTS.md` — AI assistant instructions for your project -```bash pnpm -pnpm install -``` + + The CLI generates `.mcp.json` for Claude Code by default. To add MCP configuration for other supported editors, run `adk mcp:init --all` inside your project. + -```bash yarn -yarn install -``` +Navigate into your project: -```bash npm -npm install +```bash +cd my-agent ``` - - ## Test your agent Now, you're ready to test your agent. @@ -129,9 +136,9 @@ Type "exit" or press ESC key to quit >> ``` -### View the ADK console +### View the Control Panel -Visit [`http://localhost:3001/`](http://localhost:3001/) to access the interactive ADK console. This gives you an intuitive, visual breakdown of your agent project and lets you [configure its integration's settings](/adk/managing-integrations#integration-configuration). +While `adk dev` is running, visit [`http://localhost:3001/`](http://localhost:3001/) to access the Control Panel. This gives you a visual breakdown of your agent project — browse actions, tools, workflows, tables, and [configure integration settings](/adk/managing-integrations#integration-configuration). ## Build your agent @@ -141,37 +148,20 @@ Compile your agent for production: adk build ``` -```txt -📦 Loaded project: my-agent -📝 Generating assets types... -✅ Assets types generated -🔨 Generating Botpress bot project... -✅ Bot project generated -🏗️ Building agent... - -✅ Build completed successfully! -📦 Output directory: /.adk/bot/.botpress/dist -``` - This creates an optimized build in the `.adk/bot/.botpress/dist` directory. ## Deploy your agent -When you're ready, you can deploy your agent to Botpress Cloud: + + If you skipped workspace selection during `adk init`, run `adk login` then `adk link` to connect your agent before deploying. + + +When you're ready, deploy your agent to Botpress Cloud: ```bash adk deploy ``` -```txt -📤 Deploying bot to Botpress... - -✅ Agent deployed successfully! -🌐 Your agent is now live on Botpress -🤖 Bot ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -🏢 Workspace ID: wkspace_xxxxxxxxxxxxxxxxxxxxxxxxxx -``` - This uploads your agent to your Botpress workspace and makes it available for use. diff --git a/docs.json b/docs.json index 079060fc..caf9e275 100644 --- a/docs.json +++ b/docs.json @@ -96,10 +96,7 @@ "group": "Concepts", "pages": [ "/adk/concepts/conversations", - { - "group": "Workflows", - "pages": ["/adk/concepts/workflows/overview", "/adk/concepts/workflows/steps"] - }, + "/adk/concepts/workflows", "/adk/concepts/actions", "/adk/concepts/tables", "/adk/concepts/triggers",