@botonic/plugin-ai-agents 0.49.0-alpha.3 → 0.49.0-alpha.5

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.
Files changed (50) hide show
  1. package/lib/cjs/agents/specialist-agent.js +2 -1
  2. package/lib/cjs/agents/specialist-agent.js.map +1 -1
  3. package/lib/cjs/constants.d.ts +10 -12
  4. package/lib/cjs/constants.js +11 -14
  5. package/lib/cjs/constants.js.map +1 -1
  6. package/lib/cjs/debug-logger.js +2 -8
  7. package/lib/cjs/debug-logger.js.map +1 -1
  8. package/lib/cjs/guardrails/input.js.map +1 -1
  9. package/lib/cjs/index.js +14 -2
  10. package/lib/cjs/index.js.map +1 -1
  11. package/lib/cjs/llm-config.d.ts +11 -8
  12. package/lib/cjs/llm-config.js +27 -43
  13. package/lib/cjs/llm-config.js.map +1 -1
  14. package/lib/cjs/runners/base-runner.d.ts +1 -0
  15. package/lib/cjs/runners/base-runner.js +22 -16
  16. package/lib/cjs/runners/base-runner.js.map +1 -1
  17. package/lib/cjs/runners/router-runner.d.ts +2 -0
  18. package/lib/cjs/runners/router-runner.js +23 -8
  19. package/lib/cjs/runners/router-runner.js.map +1 -1
  20. package/lib/cjs/services/types.d.ts +1 -1
  21. package/lib/esm/agents/specialist-agent.js +2 -1
  22. package/lib/esm/agents/specialist-agent.js.map +1 -1
  23. package/lib/esm/constants.d.ts +10 -12
  24. package/lib/esm/constants.js +11 -14
  25. package/lib/esm/constants.js.map +1 -1
  26. package/lib/esm/debug-logger.js +2 -8
  27. package/lib/esm/debug-logger.js.map +1 -1
  28. package/lib/esm/guardrails/input.js.map +1 -1
  29. package/lib/esm/index.js +14 -2
  30. package/lib/esm/index.js.map +1 -1
  31. package/lib/esm/llm-config.d.ts +11 -8
  32. package/lib/esm/llm-config.js +27 -43
  33. package/lib/esm/llm-config.js.map +1 -1
  34. package/lib/esm/runners/base-runner.d.ts +1 -0
  35. package/lib/esm/runners/base-runner.js +22 -16
  36. package/lib/esm/runners/base-runner.js.map +1 -1
  37. package/lib/esm/runners/router-runner.d.ts +2 -0
  38. package/lib/esm/runners/router-runner.js +23 -8
  39. package/lib/esm/runners/router-runner.js.map +1 -1
  40. package/lib/esm/services/types.d.ts +1 -1
  41. package/package.json +2 -2
  42. package/src/agents/specialist-agent.ts +2 -1
  43. package/src/constants.ts +15 -16
  44. package/src/debug-logger.ts +2 -8
  45. package/src/guardrails/input.ts +0 -1
  46. package/src/index.ts +14 -14
  47. package/src/llm-config.ts +49 -58
  48. package/src/runners/base-runner.ts +31 -19
  49. package/src/runners/router-runner.ts +35 -12
  50. package/src/services/types.ts +1 -1
package/src/constants.ts CHANGED
@@ -1,11 +1,21 @@
1
1
  export const HUBTYPE_API_URL =
2
2
  process.env.HUBTYPE_API_URL || 'https://api.hubtype.com'
3
3
 
4
- // Azure OpenAI Configuration (used as local-dev fallback; production values come from BotSettings/BotSecrets)
5
- export const AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY // pragma: allowlist secret
6
- export const AZURE_OPENAI_API_BASE = process.env.AZURE_OPENAI_API_BASE
7
- export const AZURE_OPENAI_API_VERSION =
8
- process.env.AZURE_OPENAI_API_VERSION || '2025-01-01-preview'
4
+ // LLM Configuration (provider-agnostic; set LLM_PROVIDER=azure|openai)
5
+ export const LLM_PROVIDERS = {
6
+ AZURE: 'azure',
7
+ OPENAI: 'openai',
8
+ } as const
9
+
10
+ export type LLMProviderType = (typeof LLM_PROVIDERS)[keyof typeof LLM_PROVIDERS]
11
+
12
+ export const LLM_PROVIDER: LLMProviderType =
13
+ (process.env.LLM_PROVIDER as LLMProviderType) || LLM_PROVIDERS.AZURE
14
+ export const LLM_API_KEY = process.env.LLM_API_KEY // pragma: allowlist secret
15
+ export const LLM_API_URL = process.env.LLM_API_URL
16
+ export const LLM_AZURE_API_VERSION =
17
+ process.env.LLM_AZURE_API_VERSION || '2025-01-01-preview'
18
+ export const LLM_OPENAI_MODEL = process.env.LLM_OPENAI_MODEL || 'gpt-4.1-mini'
9
19
 
10
20
  export const isProd = process.env.NODE_ENV === 'production'
11
21
 
@@ -13,14 +23,3 @@ export const MAX_MEMORY_LENGTH = 25
13
23
 
14
24
  export const DEFAULT_TIMEOUT_16_SECONDS = 16000
15
25
  export const DEFAULT_MAX_RETRIES = 2
16
-
17
- export const LLM_PROVIDERS = {
18
- LITELLM: 'litellm',
19
- AZURE: 'azure',
20
- } as const
21
-
22
- export const LITELLM_TAG_KEYS = {
23
- BOT_ID: 'bot_id',
24
- ORG_ID: 'org_id',
25
- SEPARATOR: ',',
26
- } as const
@@ -4,6 +4,7 @@ import {
4
4
  type ToolExecution,
5
5
  } from '@botonic/core'
6
6
  import type { ModelSettings } from '@openai/agents'
7
+ import { LLM_PROVIDER } from './constants'
7
8
  import type { AgenticInputMessage, MemoryOptions, RunResult } from './types'
8
9
 
9
10
  const PREFIX = '[BotonicPluginAiAgents]'
@@ -43,6 +44,7 @@ export interface DebugLogger {
43
44
  class EnabledDebugLogger implements DebugLogger {
44
45
  logInitialConfig(config: DebugLoggerConfig): void {
45
46
  console.log(`${PREFIX} === Plugin Initialization ===`)
47
+ console.log(`${PREFIX} Provider: ${LLM_PROVIDER}`)
46
48
  console.log(
47
49
  `${PREFIX} Message History API Version: ${config.messageHistoryApiVersion}`
48
50
  )
@@ -155,21 +157,13 @@ class EnabledDebugLogger implements DebugLogger {
155
157
  }
156
158
 
157
159
  class DisabledDebugLogger implements DebugLogger {
158
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
159
160
  logInitialConfig(): void {}
160
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
161
161
  logAgentDebugInfo(): void {}
162
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
163
162
  logModelSettings(): void {}
164
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
165
163
  logRunnerStart(_model: string, _modelSettings: ModelSettings): void {}
166
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
167
164
  logRunResult(): void {}
168
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
169
165
  logGuardrailTriggered(): void {}
170
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
171
166
  logRunnerError(): void {}
172
- // biome-ignore lint/suspicious/noEmptyBlockStatements: intentional no-op implementation
173
167
  logToolExecution(): void {}
174
168
  }
175
169
 
@@ -123,7 +123,6 @@ async function sendGuardrailLlmRunTracking(
123
123
  const durationPerCall = Math.round(totalDuration / rawResponses.length)
124
124
  const temperature =
125
125
  (llmConfig.modelSettings.temperature as number | undefined) ?? 0
126
-
127
126
  const llmRuns = rawResponses.map(response => ({
128
127
  inference_id: trackingContext.inferenceId,
129
128
  is_test: trackingContext.isTest,
package/src/index.ts CHANGED
@@ -132,13 +132,13 @@ export default class BotonicPluginAiAgents<
132
132
  authToken: string,
133
133
  inferenceId: string
134
134
  ) {
135
- const llmConfig = new LLMConfig(
136
- this.maxRetries,
137
- this.timeout,
138
- aiAgentArgs.model,
139
- aiAgentArgs.verbosity,
140
- botContext
141
- )
135
+ const llmConfig = new LLMConfig({
136
+ maxRetries: this.maxRetries,
137
+ timeout: this.timeout,
138
+ modelName: aiAgentArgs.model,
139
+ verbosity: aiAgentArgs.verbosity,
140
+ botContext,
141
+ })
142
142
 
143
143
  // Get LLM config, tools and agent
144
144
  const { tools, agent } = await this.getSpecialistAgentAndTools(
@@ -194,13 +194,13 @@ export default class BotonicPluginAiAgents<
194
194
  ) {
195
195
  const { specialists, name, instructions } = aiAgentArgs
196
196
 
197
- const llmConfig = new LLMConfig(
198
- this.maxRetries,
199
- this.timeout,
200
- aiAgentArgs.model,
201
- aiAgentArgs.verbosity,
202
- botContext
203
- )
197
+ const llmConfig = new LLMConfig({
198
+ maxRetries: this.maxRetries,
199
+ timeout: this.timeout,
200
+ modelName: aiAgentArgs.model,
201
+ verbosity: aiAgentArgs.verbosity,
202
+ botContext,
203
+ })
204
204
 
205
205
  const specialistsAgents = await Promise.all(
206
206
  specialists.map(async specialistData => {
package/src/llm-config.ts CHANGED
@@ -7,15 +7,22 @@ import {
7
7
  } from '@openai/agents'
8
8
  import OpenAI, { AzureOpenAI } from 'openai'
9
9
  import {
10
- AZURE_OPENAI_API_BASE,
11
- AZURE_OPENAI_API_KEY,
12
- AZURE_OPENAI_API_VERSION,
13
10
  isProd,
14
- LITELLM_TAG_KEYS,
11
+ LLM_API_KEY,
12
+ LLM_API_URL,
13
+ LLM_AZURE_API_VERSION,
14
+ LLM_OPENAI_MODEL,
15
+ LLM_PROVIDER,
15
16
  LLM_PROVIDERS,
16
17
  } from './constants'
17
18
 
18
- export type LLMProvider = (typeof LLM_PROVIDERS)[keyof typeof LLM_PROVIDERS]
19
+ export interface LLMConfigParams {
20
+ maxRetries: number
21
+ timeout: number
22
+ modelName: string
23
+ verbosity: VerbosityLevel
24
+ botContext: BotContext
25
+ }
19
26
 
20
27
  export class LLMConfig {
21
28
  private readonly maxRetries: number
@@ -25,17 +32,18 @@ export class LLMConfig {
25
32
  public readonly modelSettings: ModelSettings
26
33
  public readonly modelProvider: ModelProvider
27
34
 
28
- constructor(
29
- maxRetries: number,
30
- timeout: number,
31
- modelName: string,
32
- verbosity: VerbosityLevel,
33
- botContext: BotContext
34
- ) {
35
+ constructor({
36
+ maxRetries,
37
+ timeout,
38
+ modelName,
39
+ verbosity,
40
+ botContext,
41
+ }: LLMConfigParams) {
35
42
  this.maxRetries = maxRetries
36
43
  this.timeout = timeout
37
44
  this.botContext = botContext
38
- this.modelName = modelName
45
+ this.modelName =
46
+ LLM_PROVIDER === LLM_PROVIDERS.OPENAI ? LLM_OPENAI_MODEL : modelName
39
47
  this.modelProvider = this.getModelProvider()
40
48
  this.modelSettings = this.getModelSettings(modelName, verbosity)
41
49
  }
@@ -44,19 +52,6 @@ export class LLMConfig {
44
52
  return await this.modelProvider.getModel(this.modelName)
45
53
  }
46
54
 
47
- getProviderName(): LLMProvider {
48
- return this.botContext.settings.LITELLM_API_URL
49
- ? LLM_PROVIDERS.LITELLM
50
- : LLM_PROVIDERS.AZURE
51
- }
52
-
53
- getApiVersion(): string | undefined {
54
- return this.getProviderName() === LLM_PROVIDERS.AZURE
55
- ? this.botContext.settings.AZURE_OPENAI_API_VERSION ||
56
- AZURE_OPENAI_API_VERSION
57
- : undefined
58
- }
59
-
60
55
  private getModelProvider(): ModelProvider {
61
56
  const client = this.getClient()
62
57
  return new OpenAIProvider({
@@ -66,49 +61,32 @@ export class LLMConfig {
66
61
  }
67
62
 
68
63
  private getClient(): OpenAI | AzureOpenAI {
69
- if (this.botContext.settings.LITELLM_API_URL) {
70
- return this.getLiteLLMClient()
64
+ if (LLM_PROVIDER === LLM_PROVIDERS.OPENAI) {
65
+ return this.getOpenAIClient()
71
66
  }
72
67
 
73
- return this.getAzureClient()
74
- }
75
-
76
- private buildLiteLLMTags(): { 'x-litellm-tags': string } | undefined {
77
- const botId = this.botContext.session.bot.id
78
- const orgId = this.botContext.session.organization_id
79
- const parts: string[] = []
80
- if (botId) {
81
- parts.push(`${LITELLM_TAG_KEYS.BOT_ID}:${botId}`)
82
- }
83
- if (orgId) {
84
- parts.push(`${LITELLM_TAG_KEYS.ORG_ID}:${orgId}`)
85
- }
86
- return parts.length > 0
87
- ? { 'x-litellm-tags': parts.join(LITELLM_TAG_KEYS.SEPARATOR) }
88
- : undefined
68
+ return this.getAzureOpenAiClient()
89
69
  }
90
70
 
91
- private getLiteLLMClient(): OpenAI {
71
+ private getOpenAIClient(): OpenAI {
92
72
  return new OpenAI({
93
- apiKey: this.botContext.secrets.LITELLM_API_KEY,
94
- baseURL: this.botContext.settings.LITELLM_API_URL,
73
+ apiKey: this.botContext.secrets.AZURE_OPENAI_API_KEY || LLM_API_KEY,
95
74
  timeout: this.timeout,
96
75
  maxRetries: this.maxRetries,
97
76
  dangerouslyAllowBrowser: !isProd,
98
- defaultHeaders: this.buildLiteLLMTags(),
99
77
  })
100
78
  }
101
79
 
102
- private getAzureClient(): AzureOpenAI {
80
+ private getAzureOpenAiClient(): AzureOpenAI {
81
+ const baseURL = `${this.botContext.settings.AZURE_OPENAI_API_BASE || LLM_API_URL}openai/`
82
+
103
83
  return new AzureOpenAI({
104
- apiKey:
105
- this.botContext.secrets.AZURE_OPENAI_API_KEY || AZURE_OPENAI_API_KEY,
84
+ apiKey: this.botContext.secrets.AZURE_OPENAI_API_KEY || LLM_API_KEY,
106
85
  apiVersion:
107
86
  this.botContext.settings.AZURE_OPENAI_API_VERSION ||
108
- AZURE_OPENAI_API_VERSION,
87
+ LLM_AZURE_API_VERSION,
109
88
  deployment: this.modelName,
110
- baseURL:
111
- this.botContext.settings.AZURE_OPENAI_API_BASE || AZURE_OPENAI_API_BASE,
89
+ baseURL,
112
90
  timeout: this.timeout,
113
91
  maxRetries: this.maxRetries,
114
92
  dangerouslyAllowBrowser: !isProd,
@@ -119,6 +97,14 @@ export class LLMConfig {
119
97
  model: string,
120
98
  verbosity: VerbosityLevel
121
99
  ): ModelSettings {
100
+ if (model.includes('gpt-5')) {
101
+ return {
102
+ reasoning: { effort: 'none' },
103
+ temperature: 1,
104
+ text: { verbosity },
105
+ }
106
+ }
107
+
122
108
  if (model.includes('gpt-4')) {
123
109
  return {
124
110
  temperature: 0,
@@ -126,10 +112,15 @@ export class LLMConfig {
126
112
  }
127
113
  }
128
114
 
129
- return {
130
- reasoning: { effort: 'none' },
131
- temperature: 1,
132
- text: { verbosity },
115
+ throw new Error(`Unsupported model: ${model}`)
116
+ }
117
+
118
+ getApiVersion(): string {
119
+ if (LLM_PROVIDER !== LLM_PROVIDERS.AZURE) {
120
+ return 'NOT_API_VERSION_FOR_OPENAI_PROVIDER'
133
121
  }
122
+ return (
123
+ this.botContext.settings.AZURE_OPENAI_API_VERSION || LLM_AZURE_API_VERSION
124
+ )
134
125
  }
135
126
  }
@@ -86,24 +86,11 @@ export abstract class BaseRunner<
86
86
 
87
87
  return runResult
88
88
  } catch (error) {
89
- if (error instanceof InputGuardrailTripwireTriggered) {
90
- const runResult: RunResult = {
91
- startingAgentName: '',
92
- lastAgentName: '',
93
- availableSpecialists: [],
94
- isTransferredToSpecialist: false,
95
- messages: [],
96
- memoryLength: 0,
97
- toolsExecuted: [],
98
- exit: true,
99
- error: false,
100
- inputGuardrailsTriggered: error.result.output.outputInfo,
101
- outputGuardrailsTriggered: [],
102
- }
103
-
104
- this.logger.logGuardrailTriggered()
105
- this.logger.logRunResult(runResult, startTime)
106
-
89
+ const runResult = this.handleInputGuardrailTripwireTriggered(
90
+ error,
91
+ startTime
92
+ )
93
+ if (runResult) {
107
94
  return runResult
108
95
  }
109
96
 
@@ -263,7 +250,6 @@ export abstract class BaseRunner<
263
250
  const durationPerCall = Math.round(totalDuration / rawResponses.length)
264
251
  const temperature =
265
252
  (this.llmConfig.modelSettings.temperature as number | undefined) ?? 0
266
-
267
253
  const llmRuns = rawResponses.map(response => ({
268
254
  inference_id: this.inferenceId,
269
255
  is_test: isTest,
@@ -286,4 +272,30 @@ export abstract class BaseRunner<
286
272
  llm_runs: llmRuns,
287
273
  })
288
274
  }
275
+
276
+ protected handleInputGuardrailTripwireTriggered(
277
+ error: unknown,
278
+ startTime: number
279
+ ): RunResult | undefined {
280
+ if (error instanceof InputGuardrailTripwireTriggered) {
281
+ const runResult: RunResult = {
282
+ startingAgentName: '',
283
+ lastAgentName: '',
284
+ availableSpecialists: [],
285
+ isTransferredToSpecialist: false,
286
+ messages: [],
287
+ memoryLength: 0,
288
+ toolsExecuted: [],
289
+ exit: true,
290
+ error: false,
291
+ inputGuardrailsTriggered: error.result.output.outputInfo,
292
+ outputGuardrailsTriggered: [],
293
+ }
294
+
295
+ this.logger.logGuardrailTriggered()
296
+ this.logger.logRunResult(runResult, startTime)
297
+
298
+ return runResult
299
+ }
300
+ }
289
301
  }
@@ -1,4 +1,4 @@
1
- import type { ResolvedPlugins } from '@botonic/core'
1
+ import type { AvailableSpecialist, ResolvedPlugins } from '@botonic/core'
2
2
  import { Handoff } from '@openai/agents'
3
3
  import type { Agent } from '@openai/agents-core'
4
4
 
@@ -16,17 +16,7 @@ export class RouterRunner<
16
16
  ): RunResult {
17
17
  const base = super.buildRunResult(result, context, memoryLength)
18
18
 
19
- const availableSpecialists = (this.agent.handoffs ?? []).map(
20
- (entry: Agent<any, any> | Handoff<any, any>) => {
21
- const isHandoff = entry instanceof Handoff
22
- const agent = isHandoff ? entry.agent : (entry as Agent<any, any>)
23
- const description = isHandoff
24
- ? entry.toolDescription
25
- : agent.handoffDescription
26
- return { name: agent.name, description }
27
- }
28
- )
29
-
19
+ const availableSpecialists = this.getAvailableSpecialists()
30
20
  const startingAgentName = this.agent.name ?? ''
31
21
  const lastAgentName = result.lastAgent?.name ?? ''
32
22
 
@@ -38,4 +28,37 @@ export class RouterRunner<
38
28
  isTransferredToSpecialist: startingAgentName !== lastAgentName,
39
29
  }
40
30
  }
31
+
32
+ protected override handleInputGuardrailTripwireTriggered(
33
+ error: unknown,
34
+ startTime: number
35
+ ): RunResult | undefined {
36
+ const runResult = super.handleInputGuardrailTripwireTriggered(
37
+ error,
38
+ startTime
39
+ )
40
+ if (runResult) {
41
+ // Override attributes to match router agent
42
+ runResult.startingAgentName = this.agent.name
43
+ runResult.lastAgentName = this.agent.name
44
+ runResult.availableSpecialists = this.getAvailableSpecialists()
45
+ runResult.isTransferredToSpecialist = false
46
+
47
+ return runResult
48
+ }
49
+ return undefined
50
+ }
51
+
52
+ private getAvailableSpecialists(): AvailableSpecialist[] {
53
+ return (this.agent.handoffs ?? []).map(
54
+ (entry: Agent<any, any> | Handoff<any, any>) => {
55
+ const isHandoff = entry instanceof Handoff
56
+ const agent = isHandoff ? entry.agent : (entry as Agent<any, any>)
57
+ const description = isHandoff
58
+ ? entry.toolDescription
59
+ : agent.handoffDescription
60
+ return { name: agent.name, description }
61
+ }
62
+ )
63
+ }
41
64
  }
@@ -17,7 +17,7 @@ export interface LlmRunData {
17
17
  deployment_name: string
18
18
  model_name: string
19
19
  feature: string
20
- api_version?: string
20
+ api_version: string
21
21
  num_prompt_tokens: number
22
22
  num_completion_tokens: number
23
23
  duration_in_milliseconds: number