Skip to content

Commit bf27daa

Browse files
authored
plugin-flow-builder: use an ai agent from flow builder #BLT-1644 (#3022)
## Description - Add node of type AiAgent - Add Ai Agents flow - Infer getAiAgentResponse by plugin constructor from bot plugins - Use getAiAgentResponse to create text messages from ai agent ## Context In bot you should pass the getAiAgentResponse to flow builder, the same way as getKnowledgeBaseResponse: src/server/config.ts ```typescript ... function getFlowBuilderConfig( env: ENVIRONMENT ): BotonicPluginFlowBuilderOptions<BotPlugins, UserData> { return { getAccessToken: () => '82ff9243e97ad293705424e358866b', // Used locally, trackEvent: async (request: BotRequest, eventName, args) => { await trackEvent(request, eventName, args) }, getAiAgentResponse: async ( request: BotRequest, aiAgentArgs: { name: string instructions: string } ) => { const aiAgentPlugin = request.plugins.aiAgent return await aiAgentPlugin.getInference(request, aiAgentArgs) }, getKnowledgeBaseResponse: async ( ... } } ``` ## Testing Add a test to check basic usage of AiAgent node
1 parent e36cf88 commit bf27daa

File tree

19 files changed

+417
-10
lines changed

19 files changed

+417
-10
lines changed

package-lock.json

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/botonic-plugin-ai-agents/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "botonic-plugin-ai-agents",
3-
"version": "0.36.0",
2+
"name": "@botonic/plugin-ai-agents",
3+
"version": "0.36.0-alpha.0",
44
"main": "./lib/cjs/index.js",
55
"module": "./lib/esm/index.js",
66
"description": "Use AI Agents to generate your contents",

packages/botonic-plugin-flow-builder/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ All notable changes to Botonic will be documented in this file.
1010
Click to see more.
1111
</summary>
1212

13-
## [0.35.x] - 2025-mm-dd
13+
## [0.36.x] - 2025-mm-dd
1414

1515
### Added
1616

17+
- [PR-3022](https://github.com/hubtype/botonic/pull/3022): Add an ai agent from flow builder.
18+
1719
### Changed
1820

1921
### Fixed

packages/botonic-plugin-flow-builder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@botonic/plugin-flow-builder",
3-
"version": "0.35.2",
3+
"version": "0.36.0-alpha.0",
44
"main": "./lib/cjs/index.js",
55
"module": "./lib/esm/index.js",
66
"description": "Use Flow Builder to show your contents",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { FlowContent } from '../content-fields'
2+
import { FlowAiAgent } from '../content-fields/flow-ai-agent'
3+
import { AiAgentResponse } from '../types'
4+
import { FlowBuilderContext } from './index'
5+
6+
export async function getContentsByAiAgent({
7+
cmsApi,
8+
flowBuilderPlugin,
9+
request,
10+
}: FlowBuilderContext): Promise<FlowContent[]> {
11+
const startNodeAiAgentFlow = cmsApi.getStartNodeAiAgentFlow()
12+
if (!startNodeAiAgentFlow) {
13+
return []
14+
}
15+
16+
const contents =
17+
await flowBuilderPlugin.getContentsByNode(startNodeAiAgentFlow)
18+
const aiAgentContent = contents.find(
19+
content => content instanceof FlowAiAgent
20+
) as FlowAiAgent
21+
22+
if (!aiAgentContent) {
23+
return []
24+
}
25+
26+
const aiAgentResponse = await flowBuilderPlugin.getAiAgentResponse?.(
27+
request,
28+
{
29+
name: aiAgentContent.name,
30+
instructions: aiAgentContent.instructions,
31+
}
32+
)
33+
34+
if (!aiAgentResponse) {
35+
return []
36+
}
37+
38+
return updateContentsWithAiAgentResponse(contents, aiAgentResponse)
39+
}
40+
41+
function updateContentsWithAiAgentResponse(
42+
contents: FlowContent[],
43+
aiAgentResponse: AiAgentResponse
44+
): FlowContent[] {
45+
return contents.map(content => {
46+
if (content instanceof FlowAiAgent) {
47+
content.text = aiAgentResponse.message.content
48+
}
49+
50+
return content
51+
})
52+
}

packages/botonic-plugin-flow-builder/src/action/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getFlowBuilderPlugin } from '../helpers'
99
import BotonicPluginFlowBuilder from '../index'
1010
import { trackFlowContent } from '../tracking'
1111
import { inputHasTextData } from '../utils'
12+
import { getContentsByAiAgent } from './ai-agent'
1213
import { getContentsByFallback } from './fallback'
1314
import { getContentsByFirstInteraction } from './first-interaction'
1415
import { getContentsByKnowledgeBase } from './knowledge-bases'
@@ -106,6 +107,10 @@ async function getContents(
106107
}
107108

108109
if (inputHasTextData(request.input)) {
110+
const aiAgentContents = await getContentsByAiAgent(context)
111+
if (aiAgentContents.length > 0) {
112+
return aiAgentContents
113+
}
109114
const knowledgeBaseContents = await getContentsByKnowledgeBase(context)
110115
if (knowledgeBaseContents.length > 0) {
111116
return knowledgeBaseContents

packages/botonic-plugin-flow-builder/src/api.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Input, PluginPreRequest } from '@botonic/core'
22
import axios from 'axios'
33

4-
import { KNOWLEDGE_BASE_FLOW_NAME, SEPARATOR, UUID_REGEXP } from './constants'
4+
import {
5+
AI_AGENTS_FLOW_NAME,
6+
KNOWLEDGE_BASE_FLOW_NAME,
7+
SEPARATOR,
8+
UUID_REGEXP,
9+
} from './constants'
510
import {
611
HtBotActionNode,
712
HtFallbackNode,
@@ -202,6 +207,18 @@ export class FlowBuilderApi {
202207
return this.getNodeById<HtNodeWithContent>(knowledgeBaseFlow.start_node_id)
203208
}
204209

210+
getStartNodeAiAgentFlow(): HtNodeWithContent | undefined {
211+
const aiAgentFlow = this.flow.flows.find(
212+
flow => flow.name === AI_AGENTS_FLOW_NAME
213+
)
214+
215+
if (!aiAgentFlow) {
216+
return undefined
217+
}
218+
219+
return this.getNodeById<HtNodeWithContent>(aiAgentFlow.start_node_id)
220+
}
221+
205222
isKnowledgeBaseEnabled(): boolean {
206223
return this.flow.is_knowledge_base_active || false
207224
}

packages/botonic-plugin-flow-builder/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export const UUID_REGEXP =
1010

1111
export const MAIN_FLOW_NAME = 'Main'
1212
export const KNOWLEDGE_BASE_FLOW_NAME = 'Knowledge base'
13+
export const AI_AGENTS_FLOW_NAME = 'AI agents'
1314
export const FALLBACK_FLOW_NAME = 'Fallback'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Text } from '@botonic/react'
2+
3+
import { ContentFieldsBase } from './content-fields-base'
4+
import { HtAiAgentNode } from './hubtype-fields/ai-agent'
5+
6+
export class FlowAiAgent extends ContentFieldsBase {
7+
public code: string = ''
8+
public name: string = ''
9+
public instructions: string = ''
10+
public text: string = ''
11+
12+
static fromHubtypeCMS(component: HtAiAgentNode): FlowAiAgent {
13+
const newAiAgent = new FlowAiAgent(component.id)
14+
newAiAgent.name = component.content.name
15+
newAiAgent.instructions = component.content.instructions
16+
17+
return newAiAgent
18+
}
19+
20+
toBotonic(id: string): JSX.Element {
21+
return <Text key={id}>{this.text}</Text>
22+
}
23+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { HtBaseNode } from './common'
2+
import { HtNodeWithContentType } from './node-types'
3+
4+
export interface HtAiAgentNode extends HtBaseNode {
5+
type: HtNodeWithContentType.AI_AGENT
6+
content: {
7+
name: string
8+
instructions: string
9+
}
10+
}

packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/node-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum HtNodeWithContentType {
1212
WHATSAPP_CTA_URL_BUTTON = 'whatsapp-cta-url-button',
1313
KNOWLEDGE_BASE = 'knowledge-base',
1414
BOT_ACTION = 'bot-action',
15+
AI_AGENT = 'ai-agent',
1516
}
1617

1718
export enum HtNodeWithoutContentType {

packages/botonic-plugin-flow-builder/src/content-fields/hubtype-fields/nodes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HtAiAgentNode } from './ai-agent'
12
import { HtBotActionNode } from './bot-action'
23
import { HtCarouselNode } from './carousel'
34
import { HtFallbackNode } from './fallback'
@@ -29,6 +30,7 @@ export type HtNodeWithContent =
2930
| HtSmartIntentNode
3031
| HtKnowledgeBaseNode
3132
| HtBotActionNode
33+
| HtAiAgentNode
3234

3335
export type HtNodeWithoutContent = HtUrlNode | HtPayloadNode | HtGoToFlow
3436

packages/botonic-plugin-flow-builder/src/content-fields/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FlowAiAgent } from './flow-ai-agent'
12
import { FlowBotAction } from './flow-bot-action'
23
import { FlowCarousel } from './flow-carousel'
34
import { FlowHandoff } from './flow-handoff'
@@ -10,21 +11,21 @@ import { FlowText } from './flow-text'
1011
import { FlowVideo } from './flow-video'
1112
import { FlowWhatsappCtaUrlButtonNode } from './flow-whatsapp-cta-url-button'
1213
import { FlowWhatsappButtonList } from './whatsapp-button-list/flow-whatsapp-button-list'
13-
1414
export { ContentFieldsBase } from './content-fields-base'
1515
export { FlowButton } from './flow-button'
1616
export { FlowElement } from './flow-element'
1717
export {
18+
FlowAiAgent,
1819
FlowBotAction,
1920
FlowCarousel,
21+
FlowHandoff,
2022
FlowImage,
2123
FlowKnowledgeBase,
2224
FlowText,
2325
FlowVideo,
2426
FlowWhatsappButtonList,
27+
FlowWhatsappCtaUrlButtonNode,
2528
}
26-
export { FlowHandoff } from './flow-handoff'
27-
export { FlowWhatsappCtaUrlButtonNode } from './flow-whatsapp-cta-url-button'
2829

2930
export type FlowContent =
3031
| FlowCarousel
@@ -36,5 +37,6 @@ export type FlowContent =
3637
| FlowHandoff
3738
| FlowKnowledgeBase
3839
| FlowBotAction
40+
| FlowAiAgent
3941

4042
export { DISABLED_MEMORY_LENGTH }

packages/botonic-plugin-flow-builder/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
SOURCE_INFO_SEPARATOR,
1515
} from './constants'
1616
import {
17+
FlowAiAgent,
1718
FlowBotAction,
1819
FlowCarousel,
1920
FlowContent,
@@ -37,6 +38,7 @@ import {
3738
} from './content-fields/hubtype-fields'
3839
import { DEFAULT_FUNCTIONS } from './functions'
3940
import {
41+
AiAgentFunction,
4042
BotonicPluginFlowBuilderOptions,
4143
FlowBuilderJSONVersion,
4244
InShadowingConfig,
@@ -58,6 +60,7 @@ export default class BotonicPluginFlowBuilder implements Plugin {
5860
public getAccessToken: (session: Session) => string
5961
public trackEvent?: TrackEventFunction
6062
public getKnowledgeBaseResponse?: KnowledgeBaseFunction
63+
public getAiAgentResponse?: AiAgentFunction
6164
public smartIntentsConfig: SmartIntentsInferenceConfig
6265
public inShadowing: InShadowingConfig
6366

@@ -72,6 +75,7 @@ export default class BotonicPluginFlowBuilder implements Plugin {
7275
this.getAccessToken = resolveGetAccessToken(options.getAccessToken)
7376
this.trackEvent = options.trackEvent
7477
this.getKnowledgeBaseResponse = options.getKnowledgeBaseResponse
78+
this.getAiAgentResponse = options.getAiAgentResponse
7579
this.smartIntentsConfig = {
7680
...options?.smartIntentsConfig,
7781
useLatest: this.jsonVersion === FlowBuilderJSONVersion.LATEST,
@@ -230,6 +234,9 @@ export default class BotonicPluginFlowBuilder implements Plugin {
230234
case HtNodeWithContentType.KNOWLEDGE_BASE:
231235
return FlowKnowledgeBase.fromHubtypeCMS(hubtypeContent)
232236

237+
case HtNodeWithContentType.AI_AGENT:
238+
return FlowAiAgent.fromHubtypeCMS(hubtypeContent)
239+
233240
case HtNodeWithContentType.BOT_ACTION:
234241
return FlowBotAction.fromHubtypeCMS(hubtypeContent, locale, this.cmsApi)
235242

packages/botonic-plugin-flow-builder/src/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface BotonicPluginFlowBuilderOptions<
2424
getAccessToken: () => string
2525
trackEvent?: TrackEventFunction<TPlugins, TExtraData>
2626
getKnowledgeBaseResponse?: KnowledgeBaseFunction<TPlugins, TExtraData>
27+
getAiAgentResponse?: AiAgentFunction<TPlugins, TExtraData>
2728
smartIntentsConfig?: { numSmartIntentsToUse: number }
2829
inShadowing?: Partial<InShadowingConfig>
2930
}
@@ -48,6 +49,19 @@ export type KnowledgeBaseFunction<
4849
memoryLength: number
4950
) => Promise<KnowledgeBaseResponse>
5051

52+
export type AiAgentFunction<
53+
TPlugins extends ResolvedPlugins = ResolvedPlugins,
54+
TExtraData = any,
55+
> = (
56+
request: BotContext<TPlugins, TExtraData>,
57+
aiAgentArgs: AiAgentArgs
58+
) => Promise<AiAgentResponse>
59+
60+
export interface AiAgentArgs {
61+
name: string
62+
instructions: string
63+
}
64+
5165
export interface FlowBuilderApiOptions {
5266
url: string
5367
flowUrl: string
@@ -74,6 +88,10 @@ export interface KnowledgeBaseResponse {
7488
answer: string
7589
}
7690

91+
export interface AiAgentResponse {
92+
message: { role: string; content: string }
93+
}
94+
7795
export interface SmartIntentResponse {
7896
data: {
7997
smart_intent_title: string
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
interface MockAiAgentOptions {
2+
message: { role: string; content: string }
3+
}
4+
5+
export function mockAiAgentResponse({ message }: MockAiAgentOptions) {
6+
return jest.fn(() => {
7+
return Promise.resolve({ message })
8+
})
9+
}

0 commit comments

Comments
 (0)