agentnet 0.1.20 → 0.1.30

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/README.md CHANGED
@@ -13,6 +13,282 @@
13
13
  - [Direct Access to Agent Fluent Interface](#direct-access-to-agent-fluent-interface)
14
14
  - [Examples](#examples)
15
15
 
16
+ ## Basic usage
17
+
18
+ ```javascript
19
+ import { Agent, LLMRuntime } from '/path/to/agentnet/src/index.js'
20
+ ```
21
+
22
+ ### Core Agent Building Pattern
23
+
24
+ AgentNet uses a fluent/builder pattern for creating agents:
25
+
26
+ ```javascript
27
+ const agent = await Agent()
28
+ .setMetadata(metadata)
29
+ .withStore(store, initialState)
30
+ .withLLM(runtime, config)
31
+ .withRunner(runnerConfig)
32
+ .prompt(promptFunction)
33
+ .addTool(toolDefinition, handlerFunction) // optional, can add multiple
34
+ .compile()
35
+ ```
36
+
37
+ ### Agent Configuration Methods
38
+
39
+ ### `.setMetadata(metadata)`
40
+ Sets agent metadata for identification and description.
41
+
42
+ ```javascript
43
+ .setMetadata({
44
+ name: "agent_name",
45
+ description: "Agent description"
46
+ })
47
+ ```
48
+
49
+ ### `.withStore(store, initialState)`
50
+ Configures the agent's state storage.
51
+
52
+ ```javascript
53
+ .withStore(store, {}) // Empty initial state
54
+ ```
55
+
56
+ ### `.withLLM(runtime, config)`
57
+ Configures the Language Model runtime and settings.
58
+
59
+ **For GPT:**
60
+ ```javascript
61
+ .withLLM(LLMRuntime.GPT, {
62
+ model: "gpt-4-turbo-preview",
63
+ instructions: "System instructions here",
64
+ temperature: 0,
65
+ tool_choice: "auto" | "required" | "none",
66
+ text: {
67
+ format: {
68
+ type: "json_schema",
69
+ name: "schema_name",
70
+ schema: { /* JSON Schema object */ }
71
+ }
72
+ }
73
+ })
74
+ ```
75
+
76
+ **For Gemini:**
77
+ ```javascript
78
+ .withLLM(LLMRuntime.GEMINI, {
79
+ model: "gemini-2.0-flash-exp",
80
+ systemInstruction: "System instructions here",
81
+ config: {
82
+ temperature: 0.0,
83
+ responseMimeType: "application/json", // For JSON responses
84
+ responseSchema: { /* JSON Schema */ },
85
+ toolConfig: {
86
+ functionCallingConfig: {
87
+ mode: "AUTO" | "ANY" | "NONE"
88
+ }
89
+ }
90
+ }
91
+ })
92
+ ```
93
+
94
+ ### `.withRunner(config)`
95
+ Configures execution parameters.
96
+
97
+ ```javascript
98
+ .withRunner({
99
+ maxRuns: 5, // Maximum number of execution runs
100
+ maxConversationLength: 200 // Maximum conversation length
101
+ })
102
+ ```
103
+
104
+ ### `.prompt(function)`
105
+ Defines the prompt generation function.
106
+
107
+ ```javascript
108
+ .prompt(async (state, input) => {
109
+ // state: Current agent state
110
+ // input: Input message/data
111
+ return `Formatted prompt with ${input} and ${state.someValue}`
112
+ })
113
+ ```
114
+
115
+ ### `.addTool(definition, handler)`
116
+ Adds tools/functions the agent can call.
117
+
118
+ ```javascript
119
+ .addTool(
120
+ {
121
+ name: "tool_name",
122
+ description: "Tool description",
123
+ mode: "stopAfterOneExecution", // Optional mode
124
+ parameters: {
125
+ type: "object",
126
+ properties: {
127
+ param1: { type: "string", description: "Parameter description" }
128
+ },
129
+ required: ["param1"]
130
+ }
131
+ },
132
+ async (state, args) => {
133
+ // Handler function
134
+ // args contains the parameters passed by the LLM
135
+ return "Tool execution result"
136
+ }
137
+ )
138
+ ```
139
+
140
+ ### `.compile()`
141
+ Finalizes and compiles the agent configuration.
142
+
143
+ ```javascript
144
+ const compiledAgent = await agent.compile()
145
+ ```
146
+
147
+ ## Using Compiled Agents
148
+
149
+ ### Query Execution
150
+ ```javascript
151
+ const response = await compiledAgent.query(inputMessage)
152
+ const content = response.getContent()
153
+ ```
154
+
155
+ ### LLM Runtime Enums
156
+
157
+ ```javascript
158
+ LLMRuntime.GPT // For OpenAI GPT models
159
+ LLMRuntime.GEMINI // For Google Gemini models
160
+ ```
161
+
162
+ ### JSON Schema Response Format
163
+
164
+ ### Structured Output Configuration
165
+
166
+ For enforcing JSON responses with specific schemas:
167
+
168
+ **GPT Configuration:**
169
+ ```javascript
170
+ text: {
171
+ format: {
172
+ type: "json_schema",
173
+ name: "response_name",
174
+ schema: {
175
+ type: "object",
176
+ properties: {
177
+ field1: { type: "string", description: "..." },
178
+ field2: { type: "number", description: "..." }
179
+ },
180
+ required: ["field1", "field2"],
181
+ additionalProperties: false
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ **Gemini Configuration:**
188
+ ```javascript
189
+ config: {
190
+ responseMimeType: "application/json",
191
+ responseSchema: {
192
+ type: "object",
193
+ properties: {
194
+ field1: { type: "string", description: "..." },
195
+ field2: { type: "number", description: "..." }
196
+ },
197
+ required: ["field1", "field2"]
198
+ // Note: Gemini doesn't support additionalProperties
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Tool Choice Configuration
204
+
205
+ ### GPT Tool Choice Options
206
+ - `"auto"` - Model decides whether to call tools
207
+ - `"required"` - Model must call at least one tool
208
+ - `"none"` - Model cannot call tools
209
+
210
+ ### Gemini Tool Choice (via functionCallingConfig)
211
+ - `"AUTO"` - Model decides whether to call tools
212
+ - `"ANY"` - Model must call at least one tool
213
+ - `"NONE"` - Model cannot call tools
214
+
215
+ ### Special Tool Mode
216
+
217
+ ```javascript
218
+ {
219
+ name: "tool_name",
220
+ mode: "stopAfterOneExecution", // Stops after single execution
221
+ // ... rest of tool definition
222
+ }
223
+ ```
224
+
225
+ ### State Management
226
+
227
+ The state object passed to prompt functions and tool handlers maintains conversation context and can store custom data between agent interactions.
228
+
229
+ ```javascript
230
+ .prompt(async (state, input) => {
231
+ // Access state properties
232
+ console.log(state.chatHistory)
233
+ console.log(state.customProperty)
234
+ return promptString
235
+ })
236
+ ```
237
+
238
+ ### Error Handling & Response Access
239
+
240
+ ```javascript
241
+ const response = await agent.query(message)
242
+ const content = response.getContent() // Extract response content
243
+ ```
244
+
245
+ ### Complete Example
246
+
247
+ ```javascript
248
+ import { Agent, LLMRuntime } from '/path/to/agentnet/index.js'
249
+
250
+ const agent = await Agent()
251
+ .setMetadata({
252
+ name: "example_agent",
253
+ description: "An example agent"
254
+ })
255
+ .withStore(store, {})
256
+ .withLLM(LLMRuntime.GPT, {
257
+ model: "gpt-4-turbo-preview",
258
+ instructions: "You are a helpful assistant",
259
+ temperature: 0,
260
+ tool_choice: "auto"
261
+ })
262
+ .withRunner({
263
+ maxRuns: 3,
264
+ maxConversationLength: 100
265
+ })
266
+ .prompt(async (state, input) => {
267
+ return `Process this request: ${input}`
268
+ })
269
+ .addTool(
270
+ {
271
+ name: "search",
272
+ description: "Search for information",
273
+ parameters: {
274
+ type: "object",
275
+ properties: {
276
+ query: { type: "string" }
277
+ },
278
+ required: ["query"]
279
+ }
280
+ },
281
+ async (state, args) => {
282
+ return `Search results for: ${args.query}`
283
+ }
284
+ )
285
+ .compile()
286
+
287
+ // Use the agent
288
+ const response = await agent.query("Find information about hotels")
289
+ console.log(response.getContent())
290
+ ```
291
+
16
292
 
17
293
  ## Introduction
18
294
 
@@ -0,0 +1,200 @@
1
+ # Direct Agent Usage
2
+
3
+ This document describes how to create agents directly using the Agent interface without using the AgentLoader.
4
+
5
+ ## Overview
6
+
7
+ The Agent interface now provides all the necessary methods to create and configure agents programmatically without requiring YAML definitions or the AgentLoader. This gives you more flexibility and control when creating agents dynamically.
8
+
9
+ ## New Methods Added
10
+
11
+ The following methods have been added to the Agent interface to enable direct agent creation:
12
+
13
+ ### Tool Management
14
+
15
+ - **`addTool(toolDefinition, handlerFunction)`**: Adds a tool with its schema and optional handler function
16
+ - `toolDefinition`: Object containing name, description, type, and parameters
17
+ - `handlerFunction`: Optional function to handle tool execution
18
+
19
+ - **`bindTool(toolName, handlerFunction)`**: Binds a handler function to an existing tool
20
+ - `toolName`: Name of the previously added tool
21
+ - `handlerFunction`: Function to handle tool execution
22
+
23
+ ### Event Handlers
24
+
25
+ - **`prompt(handler)`**: Sets the prompt preprocessing handler
26
+ - `handler`: Function that receives (state, formattedInput) and returns modified input
27
+
28
+ - **`response(handler)`**: Sets the response postprocessing handler
29
+ - `handler`: Function that receives (state, conversation, result) and returns modified result
30
+
31
+ ### Configuration
32
+
33
+ - **`withRunner(runnerConfig)`**: Configures runner settings
34
+ - `runnerConfig.maxRuns`: Maximum number of runs (default: 10)
35
+ - `runnerConfig.maxConversationLength`: Maximum conversation length (default: 10)
36
+
37
+ ## Example Usage
38
+
39
+ ```javascript
40
+ import { Agent, GPT, Message, MemoryStore } from "agentnet"
41
+
42
+ // Create an agent directly without AgentLoader
43
+ const agent = Agent()
44
+ .setMetadata({
45
+ name: "myAgent",
46
+ namespace: "myNamespace",
47
+ description: "My custom agent"
48
+ })
49
+ .withLLM(GPT, {
50
+ model: "gpt-4o-mini",
51
+ instructions: "You are a helpful assistant."
52
+ })
53
+ .withStore(MemoryStore(), {
54
+ type: "Memory"
55
+ })
56
+ .withRunner({
57
+ maxRuns: 10,
58
+ maxConversationLength: 10
59
+ })
60
+ .addTool({
61
+ name: "my_tool",
62
+ description: "Does something useful",
63
+ type: "function",
64
+ parameters: {
65
+ type: "object",
66
+ properties: {
67
+ input: {
68
+ type: "string",
69
+ description: "The input parameter"
70
+ }
71
+ },
72
+ required: ["input"]
73
+ }
74
+ })
75
+ .bindTool("my_tool", async (state, input) => {
76
+ // Tool implementation
77
+ return { result: `Processed: ${input.input}` }
78
+ })
79
+ .prompt(async (state, formattedInput) => {
80
+ // Optional: Preprocess the prompt
81
+ console.log("Prompt:", formattedInput)
82
+ return formattedInput
83
+ })
84
+ .response(async (state, conversation, result) => {
85
+ // Optional: Postprocess the response
86
+ console.log("Response:", result)
87
+ return result
88
+ })
89
+
90
+ // Compile the agent
91
+ const agentInstance = await agent.compile()
92
+
93
+ // Use the agent
94
+ const message = new Message("Hello, can you help me?")
95
+ const result = await agentInstance.query(message)
96
+ console.log(result.getContent())
97
+ ```
98
+
99
+ ## Method Chaining
100
+
101
+ All builder methods return the agent instance, allowing for fluent method chaining:
102
+
103
+ ```javascript
104
+ const agent = Agent()
105
+ .setMetadata({ name: "agent1", namespace: "ns1" })
106
+ .withLLM(GPT, { model: "gpt-4" })
107
+ .addTool(tool1)
108
+ .addTool(tool2)
109
+ .bindTool("tool1", handler1)
110
+ .bindTool("tool2", handler2)
111
+ .compile()
112
+ ```
113
+
114
+ ## Adding Tools with Handlers
115
+
116
+ You can add tools in two ways:
117
+
118
+ ### 1. Add tool with handler in one call
119
+ ```javascript
120
+ .addTool({
121
+ name: "get_weather",
122
+ description: "Gets weather information",
123
+ type: "function",
124
+ parameters: { /* ... */ }
125
+ }, async (state, input) => {
126
+ // Handler implementation
127
+ return { weather: "sunny" }
128
+ })
129
+ ```
130
+
131
+ ### 2. Add tool first, bind handler later
132
+ ```javascript
133
+ .addTool({
134
+ name: "get_weather",
135
+ description: "Gets weather information",
136
+ type: "function",
137
+ parameters: { /* ... */ }
138
+ })
139
+ .bindTool("get_weather", async (state, input) => {
140
+ // Handler implementation
141
+ return { weather: "sunny" }
142
+ })
143
+ ```
144
+
145
+ ## Comparison with AgentLoader
146
+
147
+ ### Using AgentLoader (YAML-based)
148
+ ```javascript
149
+ const agents = await AgentLoaderFile("agents.yaml", { bindings })
150
+ agents.myAgent.tools.my_tool.bind(handler)
151
+ const instance = await agents.myAgent.compile()
152
+ ```
153
+
154
+ ### Using Direct Agent Interface
155
+ ```javascript
156
+ const agent = Agent()
157
+ .setMetadata({ name: "myAgent" })
158
+ .withLLM(GPT, config)
159
+ .addTool(toolDef, handler)
160
+ const instance = await agent.compile()
161
+ ```
162
+
163
+ ## Benefits of Direct Usage
164
+
165
+ 1. **No YAML files required**: Create agents entirely in code
166
+ 2. **Dynamic configuration**: Build agents based on runtime conditions
167
+ 3. **Type safety**: Better IDE support and type checking
168
+ 4. **Simpler testing**: Easier to unit test agent creation
169
+ 5. **Flexibility**: Mix and match configurations programmatically
170
+
171
+ ## Migration from AgentLoader
172
+
173
+ If you have existing YAML-based agents, you can migrate them to direct usage:
174
+
175
+ 1. Extract the agent configuration from YAML
176
+ 2. Convert each spec section to the corresponding method call
177
+ 3. Add tools using `addTool()` instead of YAML tool definitions
178
+ 4. Bind tool handlers using `bindTool()` or pass them directly to `addTool()`
179
+
180
+ ## Error Handling
181
+
182
+ All methods include validation and will throw `ConfigurationError` if:
183
+ - Required parameters are missing
184
+ - Invalid types are provided
185
+ - Tools are bound before being added
186
+ - Invalid handler functions are provided
187
+
188
+ Example:
189
+ ```javascript
190
+ try {
191
+ const agent = Agent()
192
+ .bindTool("nonexistent", handler) // Error: Tool not found
193
+ } catch (error) {
194
+ console.error(error.message)
195
+ }
196
+ ```
197
+
198
+ ## Complete Example
199
+
200
+ See `examples/direct-agent-usage.js` for a complete working example of creating an accommodation booking agent using the direct Agent interface.
@@ -0,0 +1,114 @@
1
+ import { Agent, GPT, Message, MemoryStore } from "../src/index.js"
2
+
3
+ // Example: Creating an agent directly without using AgentLoader
4
+ async function createAgentDirectly() {
5
+ // Create a new agent using the Agent builder interface
6
+ const agent = Agent()
7
+ .setMetadata({
8
+ name: "accomodationAgent",
9
+ namespace: "smartchat",
10
+ description: "A highly advanced accommodation manager agent"
11
+ })
12
+ .withLLM(GPT, {
13
+ model: "gpt-4o-mini",
14
+ instructions: "You are a highly advanced accommodation manager agent. \nPrioritize clarity and helpfulness.\nUse tools effectively to gather information."
15
+ })
16
+ .withStore(MemoryStore(), {
17
+ type: "Memory"
18
+ })
19
+ .withRunner({
20
+ maxRuns: 10, // Maximum number of LLM calls per query
21
+ maxConversationLength: 10 // Maximum conversation history length
22
+ })
23
+ .addTool({
24
+ name: "get_rooms_list_tool",
25
+ description: "Retrieves a list of available rooms based on criteria.",
26
+ type: "function",
27
+ parameters: {
28
+ type: "object",
29
+ properties: {
30
+ checkinDate: {
31
+ type: "string",
32
+ description: "The check-in date."
33
+ },
34
+ checkoutDate: {
35
+ type: "string",
36
+ description: "The check-out date."
37
+ },
38
+ guests: {
39
+ type: "integer",
40
+ description: "Number of guests."
41
+ }
42
+ },
43
+ required: ["checkinDate", "checkoutDate"]
44
+ }
45
+ })
46
+ .addTool({
47
+ name: "get_room_detail_tool",
48
+ description: "Retrieves detailed information about a specific room.",
49
+ type: "function",
50
+ parameters: {
51
+ type: "object",
52
+ properties: {
53
+ roomName: {
54
+ type: "string",
55
+ description: "The name of the room."
56
+ }
57
+ },
58
+ required: ["roomName"]
59
+ }
60
+ })
61
+ .bindTool("get_rooms_list_tool", async (state, input) => {
62
+ return {
63
+ answer: "We have Double room with a view of the sea and a single room with a view of the pool, and a suite with a view of the city."
64
+ }
65
+ })
66
+ .bindTool("get_room_detail_tool", async (state, input) => {
67
+ return {
68
+ answer: "The Double room with a view of the sea has a king size bed, a private balcony, and a view of the sea."
69
+ }
70
+ })
71
+ // Optional: Add prompt handler
72
+ .prompt(async (state, formattedInput) => {
73
+ console.log("Processing prompt:", formattedInput)
74
+ return formattedInput
75
+ })
76
+ // Optional: Add response handler
77
+ .response(async (state, conversation, result) => {
78
+ console.log("Generated response:", result)
79
+ return result
80
+ })
81
+
82
+ // Compile the agent
83
+ const agentInstance = await agent.compile()
84
+
85
+ return agentInstance
86
+ }
87
+
88
+ // Usage example
89
+ async function main() {
90
+ try {
91
+ // Create the agent directly
92
+ const agentInstance = await createAgentDirectly()
93
+
94
+ // Create a message
95
+ const input = new Message({
96
+ content: "What rooms do you have from 2025-05-10 to 2025-05-15 for 2 guests?",
97
+ session: {
98
+ id: "67a71e42-a7d8-1db2-ad17-64e1c8546b21",
99
+ propertySetId: "123"
100
+ }
101
+ })
102
+
103
+ // Query the agent
104
+ const result = await agentInstance.query(input)
105
+
106
+ console.log("Agent response:", result.getContent())
107
+
108
+ } catch (error) {
109
+ console.error("Error:", error)
110
+ }
111
+ }
112
+
113
+ // Run the example
114
+ main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentnet",
3
- "version": "0.1.20",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "description": "Agent library used by Smartness",
6
6
  "main": "src/index.js",
@@ -360,6 +360,84 @@ export function Agent() {
360
360
  return this;
361
361
  }
362
362
 
363
+ /**
364
+ * Adds a tool with its schema and optional handler function
365
+ * @param {Object} toolDefinition - Tool definition including name, description, parameters
366
+ * @param {Function} handlerFunction - Optional handler function for the tool
367
+ * @returns {Object} Agent builder for chaining
368
+ */
369
+ function addTool(toolDefinition, handlerFunction = null) {
370
+ if (!toolDefinition || !toolDefinition.name) {
371
+ throw new ConfigurationError("Tool definition must have a name", {
372
+ toolDefinition: toolDefinition
373
+ });
374
+ }
375
+
376
+ // Store the tool schema in the format expected by executor
377
+ config.toolsSchemas[toolDefinition.name] = {
378
+ name: toolDefinition.name,
379
+ schema: toolDefinition,
380
+ function: handlerFunction
381
+ };
382
+
383
+ return this;
384
+ }
385
+
386
+ /**
387
+ * Binds a handler function to an existing tool
388
+ * @param {String} toolName - Name of the tool
389
+ * @param {Function} handlerFunction - Handler function for the tool
390
+ * @returns {Object} Agent builder for chaining
391
+ */
392
+ function bindTool(toolName, handlerFunction) {
393
+ if (!config.toolsSchemas[toolName]) {
394
+ throw new ConfigurationError(`Tool ${toolName} not found. Add the tool first using addTool()`, {
395
+ availableTools: Object.keys(config.toolsSchemas)
396
+ });
397
+ }
398
+
399
+ if (typeof handlerFunction !== 'function') {
400
+ throw new ConfigurationError(`Handler for tool ${toolName} must be a function`, {
401
+ provided: typeof handlerFunction
402
+ });
403
+ }
404
+
405
+ config.toolsSchemas[toolName].function = handlerFunction;
406
+ return this;
407
+ }
408
+
409
+ /**
410
+ * Sets the prompt handler
411
+ * @param {Function} handler - Prompt handler function
412
+ * @returns {Object} Agent builder for chaining
413
+ */
414
+ function prompt(handler) {
415
+ if (typeof handler !== 'function') {
416
+ throw new ConfigurationError("Prompt handler must be a function", {
417
+ provided: typeof handler
418
+ });
419
+ }
420
+
421
+ config.on.prompt = handler;
422
+ return this;
423
+ }
424
+
425
+ /**
426
+ * Sets the response handler
427
+ * @param {Function} handler - Response handler function
428
+ * @returns {Object} Agent builder for chaining
429
+ */
430
+ function response(handler) {
431
+ if (typeof handler !== 'function') {
432
+ throw new ConfigurationError("Response handler must be a function", {
433
+ provided: typeof handler
434
+ });
435
+ }
436
+
437
+ config.on.response = handler;
438
+ return this;
439
+ }
440
+
363
441
  /**
364
442
  * Sets agent metadata
365
443
  * @param {Object} metadata - Agent metadata
@@ -380,6 +458,39 @@ export function Agent() {
380
458
  return this;
381
459
  }
382
460
 
461
+ /**
462
+ * Configures runner settings
463
+ * @param {Object} runnerConfig - Runner configuration
464
+ * @returns {Object} Agent builder for chaining
465
+ */
466
+ function withRunner(runnerConfig) {
467
+ if (!runnerConfig) {
468
+ throw new ConfigurationError("Runner configuration is required", {
469
+ provided: runnerConfig
470
+ });
471
+ }
472
+
473
+ if (runnerConfig.maxRuns !== undefined) {
474
+ if (typeof runnerConfig.maxRuns !== 'number' || runnerConfig.maxRuns <= 0) {
475
+ throw new ConfigurationError("maxRuns must be a positive number", {
476
+ provided: runnerConfig.maxRuns
477
+ });
478
+ }
479
+ config.runner.maxRuns = runnerConfig.maxRuns;
480
+ }
481
+
482
+ if (runnerConfig.maxConversationLength !== undefined) {
483
+ if (typeof runnerConfig.maxConversationLength !== 'number' || runnerConfig.maxConversationLength <= 0) {
484
+ throw new ConfigurationError("maxConversationLength must be a positive number", {
485
+ provided: runnerConfig.maxConversationLength
486
+ });
487
+ }
488
+ config.runner.maxConversationLength = runnerConfig.maxConversationLength;
489
+ }
490
+
491
+ return this;
492
+ }
493
+
383
494
  /**
384
495
  * Gets all registered tool schemas
385
496
  * @returns {Object} Map of tool schemas
@@ -396,6 +507,7 @@ export function Agent() {
396
507
  // Validate configuration before compiling
397
508
  validateConfiguration();
398
509
 
510
+
399
511
  try {
400
512
  logger.info(`Compiling agent ${config.metadata.name}`);
401
513
  const runtime = await AgentRuntime(config);
@@ -422,9 +534,14 @@ export function Agent() {
422
534
  addIO,
423
535
  withLLM,
424
536
  withStore,
537
+ withRunner,
425
538
  on,
426
539
  addDiscoverySchema,
427
540
  addToolSchema,
541
+ addTool,
542
+ bindTool,
543
+ prompt,
544
+ response,
428
545
  compile,
429
546
  setMetadata,
430
547
  getToolsSchemas,
@@ -215,24 +215,38 @@ export async function build(
215
215
  state: state,
216
216
  contents: contents
217
217
  });
218
-
218
+
219
+ const calledTools = api.getCalledTools();
220
+ const modeStopTools = toolsAndHandoffsMap.tools.filter(tool => tool.mode === 'stopAfterOneExecution').map(tool => tool.name);
221
+
222
+ if (modeStopTools.some(tool => calledTools.includes(tool))) {
223
+ api.resetCalledTools();
224
+ llmConfig = await api.resetToolConfig(llmConfig);
225
+ }
226
+
219
227
  // Check for max runs exceeded
220
- if (run >= maxRuns) {
221
- logger.warn(`Agent ${agentName} max runs reached: ${run}/${maxRuns}`);
222
-
223
- await emit(hooks, 'executorMaxRuns', {
224
- agentName: agentName,
225
- run: run,
226
- state: state,
227
- contents: contents
228
- });
228
+ if (run == maxRuns - 1) {
229
+ logger.info(`Agent ${agentName} almost max runs reached: ${run}/${maxRuns}`);
229
230
 
230
- // Return the last message as the result
231
+ //return rawConversation[rawConversation.length - 1];
232
+ // toolsAndHandoffsMap = { tools: [] }
233
+ try {
234
+ llmConfig = await api.resetToolConfig(llmConfig);
235
+ api.resetCalledTools();
236
+ } catch (error) {
237
+ logger.error(`Error resetting tool config for agent ${agentName}`, { error });
238
+ }
239
+ }
240
+
241
+ if (run >= maxRuns) {
242
+ logger.warn(`Agent ${agentName} max over runs reached: ${run}/${maxRuns}`);
243
+ api.resetCalledTools();
231
244
  const rawConversation = contents.getRawConversation();
232
- return rawConversation[rawConversation.length - 1];
245
+ return rawConversation.join('\n');
233
246
  }
234
247
 
235
248
  try {
249
+
236
250
  // Prepare input for LLM
237
251
  const input = {
238
252
  client: client,
package/src/llm/base.js CHANGED
@@ -12,6 +12,7 @@ export class BaseLLM {
12
12
  */
13
13
  constructor(providerType) {
14
14
  this.type = providerType;
15
+ this.calledTools = [];
15
16
  }
16
17
 
17
18
  /**
@@ -23,6 +24,18 @@ export class BaseLLM {
23
24
  throw new Error('getClient() must be implemented by subclasses');
24
25
  }
25
26
 
27
+ async resetToolConfig(config) {
28
+ return config;
29
+ }
30
+
31
+ getCalledTools() {
32
+ return this.calledTools;
33
+ }
34
+
35
+ resetCalledTools() {
36
+ this.calledTools = [];
37
+ }
38
+
26
39
  /**
27
40
  * Call the LLM model with the provided configuration and context
28
41
  * @param {Object} config - LLM-specific configuration
@@ -106,6 +119,7 @@ export class BaseLLM {
106
119
  } else {
107
120
  result = await toolsAndHandoffsMap[name].function(state, args);
108
121
  }
122
+ this.calledTools.push(name);
109
123
 
110
124
  logger.debug('Tool execution successful', { toolName: name });
111
125
  return result;
package/src/llm/gemini.js CHANGED
@@ -50,7 +50,11 @@ class GeminiLLM extends BaseLLM {
50
50
  input.config.tools = toolsAndHandoffsMap.tools;
51
51
  } else if (toolsAndHandoffsMap.tools.length > 0) {
52
52
  input.config = input.config || {};
53
- input.config.tools = [{ functionDeclarations: toolsAndHandoffsMap.tools }];
53
+ // remove the mode key from the tools, for gpt is not necessary beacuase it doesn't bother if is present or not
54
+ input.config.tools = [{ functionDeclarations: toolsAndHandoffsMap.tools.map(tool => {
55
+ const { mode, ...toolWithoutMode } = tool;
56
+ return toolWithoutMode;
57
+ }) }];
54
58
  }
55
59
 
56
60
  logger.debug('Calling Gemini model', {
@@ -189,7 +193,10 @@ const geminiLLM = new GeminiLLM();
189
193
  export default {
190
194
  type: geminiLLM.type,
191
195
  getClient: geminiLLM.getClient.bind(geminiLLM),
196
+ resetToolConfig: geminiLLM.resetToolConfig.bind(geminiLLM),
192
197
  prompt: geminiLLM.prompt.bind(geminiLLM),
193
198
  callModel: geminiLLM.callModel.bind(geminiLLM),
194
- onResponse: geminiLLM.onResponse.bind(geminiLLM)
199
+ onResponse: geminiLLM.onResponse.bind(geminiLLM),
200
+ getCalledTools: geminiLLM.getCalledTools.bind(geminiLLM),
201
+ resetCalledTools: geminiLLM.resetCalledTools.bind(geminiLLM)
195
202
  }
package/src/llm/gpt.js CHANGED
@@ -10,7 +10,7 @@ import { Conversation } from '../utils/conversation.js'
10
10
  class OpenAILLM extends BaseLLM {
11
11
  constructor() {
12
12
  super('openai');
13
- }
13
+ }
14
14
 
15
15
  /**
16
16
  * Initializes and returns an OpenAI client
@@ -33,6 +33,11 @@ class OpenAILLM extends BaseLLM {
33
33
  }
34
34
  }
35
35
 
36
+ async resetToolConfig (llmClientConfig) {
37
+ llmClientConfig.tool_choice = 'auto';
38
+ return llmClientConfig;
39
+ }
40
+
36
41
  /**
37
42
  * Calls the OpenAI model with the provided configuration and context
38
43
  * @param {Object} llmClientConfig - Configuration for the OpenAI model
@@ -188,7 +193,10 @@ const openaiLLM = new OpenAILLM();
188
193
  export default {
189
194
  type: openaiLLM.type,
190
195
  getClient: openaiLLM.getClient.bind(openaiLLM),
196
+ resetToolConfig: openaiLLM.resetToolConfig.bind(openaiLLM),
191
197
  prompt: openaiLLM.prompt.bind(openaiLLM),
192
198
  callModel: openaiLLM.callModel.bind(openaiLLM),
193
- onResponse: openaiLLM.onResponse.bind(openaiLLM)
199
+ onResponse: openaiLLM.onResponse.bind(openaiLLM),
200
+ getCalledTools: openaiLLM.getCalledTools.bind(openaiLLM),
201
+ resetCalledTools: openaiLLM.resetCalledTools.bind(openaiLLM)
194
202
  }
@@ -202,6 +202,9 @@ export function memoryStore () {
202
202
  },
203
203
  get: async function (key) {
204
204
  return state[key] || null
205
+ },
206
+ getAll: async function () {
207
+ return state
205
208
  }
206
209
  }
207
210
  }
@@ -0,0 +1,78 @@
1
+ import { Agent, GPT, Message, MemoryStore } from "./src/index.js"
2
+
3
+ // Test to debug the tools array issue
4
+ async function testToolsFormat() {
5
+ console.log("=== Testing Tools Format ===\n");
6
+
7
+ // Create a simple agent with one tool
8
+ const agent = Agent()
9
+ .setMetadata({
10
+ name: "testAgent",
11
+ namespace: "test"
12
+ })
13
+ .withLLM(GPT, {
14
+ model: "gpt-4o-mini",
15
+ instructions: "You are a test agent."
16
+ })
17
+ .withStore(MemoryStore(), {})
18
+ .addTool({
19
+ name: "test_tool",
20
+ description: "A test tool",
21
+ type: "function",
22
+ parameters: {
23
+ type: "object",
24
+ properties: {
25
+ input: {
26
+ type: "string",
27
+ description: "Test input"
28
+ }
29
+ },
30
+ required: ["input"]
31
+ }
32
+ }, async (state, input) => {
33
+ return { result: "test" }
34
+ });
35
+
36
+ // Check the internal structure
37
+ console.log("1. Agent config toolsSchemas:");
38
+ console.log(JSON.stringify(agent._config.toolsSchemas, null, 2));
39
+
40
+ console.log("\n2. Agent config toolsAndHandoffsMap:");
41
+ console.log(JSON.stringify(agent._config.toolsAndHandoffsMap, null, 2));
42
+
43
+ // Now let's see what Object.values gives us
44
+ console.log("\n3. Object.values(toolsSchemas):");
45
+ const toolsArray = Object.values(agent._config.toolsSchemas);
46
+ console.log(JSON.stringify(toolsArray, null, 2));
47
+
48
+ // Check what each tool looks like
49
+ console.log("\n4. First tool structure:");
50
+ if (toolsArray.length > 0) {
51
+ const firstTool = toolsArray[0];
52
+ console.log("Type of first tool:", typeof firstTool);
53
+ console.log("First tool keys:", Object.keys(firstTool));
54
+ console.log("First tool:", JSON.stringify(firstTool, (key, value) => {
55
+ if (typeof value === 'function') return '[Function]';
56
+ return value;
57
+ }, 2));
58
+ }
59
+
60
+ // Simulate what makeToolsAndHandoffsMap would do
61
+ console.log("\n5. Simulating makeToolsAndHandoffsMap:");
62
+ const simulatedMap = { tools: [] };
63
+ for (const tool of toolsArray) {
64
+ if (!tool.schema) {
65
+ console.log(" - Tool has no schema, pushing tool itself");
66
+ simulatedMap.tools.push(tool);
67
+ } else {
68
+ console.log(" - Tool has schema, pushing tool.schema");
69
+ simulatedMap.tools.push(tool.schema);
70
+ }
71
+ }
72
+ console.log("Simulated tools array:", JSON.stringify(simulatedMap.tools, null, 2));
73
+
74
+ console.log("\n=== End Test ===");
75
+ }
76
+
77
+ // Run the test
78
+ testToolsFormat().catch(console.error);