From 7be475e0a1679da0e23ca0a1e7e8a8924c376d78 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Fri, 13 Dec 2024 20:28:45 -0300 Subject: [PATCH 01/14] feat(tools): add experimental human tool Added human.ts to experimental tools and human.ts to agents. Updated io.ts in helpers to support the new tool. Ref: #121 Signed-off-by: Matias Molinas --- examples/agents/experimental/human.ts | 102 ++++++++++++++++++++++++++ examples/helpers/io.ts | 16 +++- examples/tools/experimental/human.ts | 84 +++++++++++++++++++++ 3 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 examples/agents/experimental/human.ts create mode 100644 examples/tools/experimental/human.ts diff --git a/examples/agents/experimental/human.ts b/examples/agents/experimental/human.ts new file mode 100644 index 00000000..2fc478e2 --- /dev/null +++ b/examples/agents/experimental/human.ts @@ -0,0 +1,102 @@ +import "dotenv/config.js"; +import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; +import { createConsoleReader } from "../../helpers/io.js"; // Use the examples console reader +import { FrameworkError } from "bee-agent-framework/errors"; +import { TokenMemory } from "bee-agent-framework/memory/tokenMemory"; +import { Logger } from "bee-agent-framework/logger/logger"; +import { OpenMeteoTool } from "bee-agent-framework/tools/weather/openMeteo"; + +// Import the HumanTool from the updated file +import { HumanTool } from "../../tools/experimental/human.js"; + +import { + BeeSystemPrompt, + BeeAssistantPrompt, + BeeUserPrompt, + BeeUserEmptyPrompt, + BeeToolErrorPrompt, + BeeToolInputErrorPrompt, + BeeToolNoResultsPrompt, + BeeToolNotFoundPrompt, +} from "bee-agent-framework/agents/bee/prompts"; + +// Set up logger +Logger.root.level = "silent"; // Disable internal logs +const logger = new Logger({ name: "app", level: "trace" }); + +// Initialize LLM (test against llama as requested) +import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat"; +const llm = new OllamaChatLLM({ + modelId: "llama3.1", +}); + +// Create the console reader once, share it with HumanTool +const reader = createConsoleReader(); + +// Initialize BeeAgent with shared reader for HumanTool +const agent = new BeeAgent({ + llm, + memory: new TokenMemory({ llm }), + tools: [new OpenMeteoTool(), new HumanTool(reader)], + templates: { + system: BeeSystemPrompt, + assistant: BeeAssistantPrompt, + user: BeeUserPrompt, + userEmpty: BeeUserEmptyPrompt, + toolError: BeeToolErrorPrompt, + toolInputError: BeeToolInputErrorPrompt, + toolNoResultError: BeeToolNoResultsPrompt, + toolNotFoundError: BeeToolNotFoundPrompt, + }, +}); + +// Main loop +try { + for await (const { prompt } of reader) { + // Run the agent and observe events + const response = await agent + .run( + { prompt }, + { + execution: { + maxRetriesPerStep: 3, + totalMaxRetries: 10, + maxIterations: 20, + }, + }, + ) + .observe((emitter) => { + // Show only final answers + emitter.on("update", async ({ update }) => { + if (update.key === "final_answer") { + reader.write("Agent 🤖 : ", update.value); + } + }); + + // Log errors + emitter.on("error", ({ error }) => { + reader.write("Agent 🤖 : ", FrameworkError.ensure(error).dump()); + }); + + // Retry notifications + emitter.on("retry", () => { + reader.write("Agent 🤖 : ", "Retrying the action..."); + }); + }); + + // Print the final response + if (response.result?.text) { + reader.write("Agent 🤖 : ", response.result.text); + } else { + reader.write( + "Agent 🤖 : ", + "No result was returned. Ensure your input is valid or check tool configurations.", + ); + } + } +} catch (error) { + logger.error(FrameworkError.ensure(error).dump()); +} finally { + // Gracefully close the reader when exiting the app + reader.close(); +} \ No newline at end of file diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index 06f1085a..3806694a 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -27,16 +27,26 @@ export function createConsoleReader({ .concat("\n"), ); }, + async prompt(): Promise { + // This uses the async iterator below. If it's exhausted, return empty string. for await (const { prompt } of this) { return prompt; } - process.exit(0); + return ""; + }, + + // New method: Asks a single question without consuming the async iterator. + async askSingleQuestion(queryMessage: string): Promise { + const answer = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(queryMessage)); + return stripAnsi(answer.trim()); }, + close() { stdin.pause(); rl.close(); }, + async *[Symbol.asyncIterator]() { if (!isActive) { return; @@ -64,7 +74,7 @@ export function createConsoleReader({ } yield { prompt, iteration }; } - } catch (e) { + } catch (e: any) { if (e.code === "ERR_USE_AFTER_CLOSE") { return; } @@ -74,4 +84,4 @@ export function createConsoleReader({ } }, }; -} +} \ No newline at end of file diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts new file mode 100644 index 00000000..b5adeec6 --- /dev/null +++ b/examples/tools/experimental/human.ts @@ -0,0 +1,84 @@ +import { Emitter } from "bee-agent-framework/emitter/emitter"; +import { + Tool, + BaseToolRunOptions, + StringToolOutput, + ToolInput, + ToolEvents, +} from "bee-agent-framework/tools/base"; +import { z } from "zod"; + +export class HumanTool extends Tool { + name = "HumanTool"; + description = ` + This tool is used whenever the user's input is unclear, ambiguous, or incomplete. + The agent MUST invoke this tool when additional clarification is required to proceed. + The output must adhere strictly to the following structure: + - Thought: A single-line description of the need for clarification. + - Function Name: HumanTool + - Function Input: { "message": "Your question to the user for clarification." } + - Function Output: The user's response in JSON format. + Examples: + - Example 1: + Input: "What is the weather?" + Thought: "The user's request lacks a location. I need to ask for clarification." + Function Name: HumanTool + Function Input: { "message": "Could you provide the location for which you would like to know the weather?" } + Function Output: { "clarification": "Santa Fe, Argentina" } + Final Answer: The current weather in Santa Fe, Argentina is 17.3°C with a relative humidity of 48% and a wind speed of 10.1 km/h. + + - Example 2: + Input: "Can you help me?" + Thought: "The user's request is too vague. I need to ask for more details." + Function Name: HumanTool + Function Input: { "message": "Could you clarify what kind of help you need?" } + Function Output: { "clarification": "I need help understanding how to use the project management tool." } + Final Answer: Sure, I can help you with the project management tool. Let me know which feature you'd like to learn about or if you'd like a general overview. + + - Example 3: + Input: "Translate this sentence." + Thought: "The user's request is incomplete. I need to ask for the sentence they want translated." + Function Name: HumanTool + Function Input: { "message": "Could you specify the sentence you would like me to translate?" } + Function Output: { "clarification": "Translate 'Hello, how are you?' to French." } + Final Answer: The French translation of 'Hello, how are you?' is 'Bonjour, comment vas-tu?' + + Note: Do NOT attempt to guess or provide incomplete responses. Always use this tool when in doubt to ensure accurate and meaningful interactions. +`; + + public readonly emitter: Emitter, StringToolOutput>> = + Emitter.root.child({ + namespace: ["tool", "human"], + creator: this, + }); + + private reader: ReturnType; + + constructor(reader: ReturnType) { + super(); + this.reader = reader; + } + + inputSchema = () => + z.object({ + message: z.string().min(1, "Message cannot be empty"), + }); + + async _run( + input: z.infer>, + _options: BaseToolRunOptions, + ): Promise { + // Use the shared reader instance provided to the constructor + this.reader.write("HumanTool", input.message); + + // Use askSingleQuestion instead of prompt to avoid interfering with main loop iterator + const userInput = await this.reader.askSingleQuestion("User 👤 : "); + + // Format the output as required + const formattedOutput = `{ + "clarification": "${userInput.trim()}" + }`; + + return new StringToolOutput(formattedOutput); + } +} \ No newline at end of file From 6688e398e7dd38ed22f1d515edb4a566d0ffacca Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Wed, 18 Dec 2024 11:33:40 -0300 Subject: [PATCH 02/14] style(format): apply formatting fixes to experimental and helper files Ref: #121 Signed-off-by: Matias Molinas --- examples/agents/experimental/human.ts | 2 +- examples/helpers/io.ts | 2 +- examples/tools/experimental/human.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/agents/experimental/human.ts b/examples/agents/experimental/human.ts index 2fc478e2..5ef8fd38 100644 --- a/examples/agents/experimental/human.ts +++ b/examples/agents/experimental/human.ts @@ -99,4 +99,4 @@ try { } finally { // Gracefully close the reader when exiting the app reader.close(); -} \ No newline at end of file +} diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index 3806694a..296ce20f 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -84,4 +84,4 @@ export function createConsoleReader({ } }, }; -} \ No newline at end of file +} diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index b5adeec6..42437e6c 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -81,4 +81,4 @@ export class HumanTool extends Tool { return new StringToolOutput(formattedOutput); } -} \ No newline at end of file +} From 75255dbc03b6990fd89e4393808cbaedb636b6a0 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Tue, 31 Dec 2024 08:13:45 -0300 Subject: [PATCH 03/14] refactor(agents): remove unused prompt templates. The templates object in human.ts is not required for the current functionality. Removing it helps simplify the code and reduce unnecessary complexity. Ref: #121 Signed-off-by: Matias Molinas --- examples/agents/experimental/human.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/agents/experimental/human.ts b/examples/agents/experimental/human.ts index 5ef8fd38..69ea17e1 100644 --- a/examples/agents/experimental/human.ts +++ b/examples/agents/experimental/human.ts @@ -38,16 +38,6 @@ const agent = new BeeAgent({ llm, memory: new TokenMemory({ llm }), tools: [new OpenMeteoTool(), new HumanTool(reader)], - templates: { - system: BeeSystemPrompt, - assistant: BeeAssistantPrompt, - user: BeeUserPrompt, - userEmpty: BeeUserEmptyPrompt, - toolError: BeeToolErrorPrompt, - toolInputError: BeeToolInputErrorPrompt, - toolNoResultError: BeeToolNoResultsPrompt, - toolNotFoundError: BeeToolNotFoundPrompt, - }, }); // Main loop From cc1627ef1f2d5a2af21ef96e56a001ea8f093eac Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sat, 4 Jan 2025 15:17:01 -0300 Subject: [PATCH 04/14] fix(io): remove unnecessary comments from prompt method This commit removes all comments from the \prompt\ method in \examples/helpers/io.ts\ as requested in the PR review. The comments were deemed unnecessary and have been removed to improve code clarity and maintainability. Ref: #121 Signed-off-by: Matias Molinas --- examples/helpers/io.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index 296ce20f..c826e1ba 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -29,14 +29,12 @@ export function createConsoleReader({ }, async prompt(): Promise { - // This uses the async iterator below. If it's exhausted, return empty string. for await (const { prompt } of this) { return prompt; } return ""; }, - // New method: Asks a single question without consuming the async iterator. async askSingleQuestion(queryMessage: string): Promise { const answer = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(queryMessage)); return stripAnsi(answer.trim()); From ae19ae2b4e4832313bbbf31c1a91ed2d490c9e13 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sat, 4 Jan 2025 15:44:25 -0300 Subject: [PATCH 05/14] fix(io): revert change to ensure process.exit(0) is retained Reverted the previous modification. The original approach is reinstated to prevent breaking other examples, as highlighted in the PR review. Ref: #121 Signed-off-by: Matias Molinas --- examples/helpers/io.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index c826e1ba..75d834f6 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -32,7 +32,7 @@ export function createConsoleReader({ for await (const { prompt } of this) { return prompt; } - return ""; + process.exit(0); }, async askSingleQuestion(queryMessage: string): Promise { From 0670e9c186f7a4e0fbd71e86e0cdc38d4bbe62c7 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sat, 4 Jan 2025 16:00:43 -0300 Subject: [PATCH 06/14] fix(io): improve error handling in catch clause Ref: #121 Signed-off-by: Matias Molinas --- examples/helpers/io.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index 75d834f6..d227006c 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -72,8 +72,8 @@ export function createConsoleReader({ } yield { prompt, iteration }; } - } catch (e: any) { - if (e.code === "ERR_USE_AFTER_CLOSE") { + } catch (e: unknown) { + if (e instanceof Error && 'code' in e && e.code === "ERR_USE_AFTER_CLOSE") { return; } } finally { From 51888a9fd14723ed7ca1afea1a861da4010f52b5 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sat, 4 Jan 2025 16:33:04 -0300 Subject: [PATCH 07/14] refactor(tools): simplify type definition in human.ts Ref: #121 Signed-off-by: Matias Molinas --- examples/tools/experimental/human.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 42437e6c..3f0eaa63 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -4,7 +4,7 @@ import { BaseToolRunOptions, StringToolOutput, ToolInput, - ToolEvents, + ToolEmitter, } from "bee-agent-framework/tools/base"; import { z } from "zod"; @@ -46,7 +46,7 @@ export class HumanTool extends Tool { Note: Do NOT attempt to guess or provide incomplete responses. Always use this tool when in doubt to ensure accurate and meaningful interactions. `; - public readonly emitter: Emitter, StringToolOutput>> = +public readonly emitter: ToolEmitter, StringToolOutput> = Emitter.root.child({ namespace: ["tool", "human"], creator: this, From f5e74f15611e3c5a2999b566a47b3160cd4d1cf6 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sun, 5 Jan 2025 08:46:03 -0300 Subject: [PATCH 08/14] fix(tools): update inputSchema to use a named function in human.ts Ref: #121 Signed-off-by: Matias Molinas --- examples/tools/experimental/human.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 3f0eaa63..2e61f285 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -59,10 +59,11 @@ public readonly emitter: ToolEmitter, StringToolOutput> = this.reader = reader; } - inputSchema = () => - z.object({ + inputSchema() { + return z.object({ message: z.string().min(1, "Message cannot be empty"), }); + } async _run( input: z.infer>, From 18ce28bda6031978fe8d7993d4b7e2a152e6aa1c Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Sun, 5 Jan 2025 09:27:46 -0300 Subject: [PATCH 09/14] refactor: improve HumanTool and ConsoleReader implementation These changes improve the tool's ability to handle interruptions and follow the framework's standard implementation pattern. Ref: #121 Signed-off-by: Matias Molinas --- examples/helpers/io.ts | 8 ++++++-- examples/tools/experimental/human.ts | 16 +++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index d227006c..0011d573 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -3,6 +3,7 @@ import { stdin, stdout } from "node:process"; import picocolors from "picocolors"; import * as R from "remeda"; import stripAnsi from "strip-ansi"; +import type { Abortable } from 'node:events'; interface ReadFromConsoleInput { fallback?: string; @@ -35,8 +36,11 @@ export function createConsoleReader({ process.exit(0); }, - async askSingleQuestion(queryMessage: string): Promise { - const answer = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(queryMessage)); + async askSingleQuestion(queryMessage: string, options?: Abortable): Promise { + const answer = await rl.question( + R.piped(picocolors.cyan, picocolors.bold)(queryMessage), + options ?? { signal: undefined } + ); return stripAnsi(answer.trim()); }, diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 2e61f285..664c0cee 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -6,6 +6,7 @@ import { ToolInput, ToolEmitter, } from "bee-agent-framework/tools/base"; +import { RunContext } from "bee-agent-framework/context"; import { z } from "zod"; export class HumanTool extends Tool { @@ -66,20 +67,21 @@ public readonly emitter: ToolEmitter, StringToolOutput> = } async _run( - input: z.infer>, - _options: BaseToolRunOptions, + input: ToolInput, + _options: Partial, + run: RunContext ): Promise { // Use the shared reader instance provided to the constructor this.reader.write("HumanTool", input.message); - - // Use askSingleQuestion instead of prompt to avoid interfering with main loop iterator - const userInput = await this.reader.askSingleQuestion("User 👤 : "); - + + // Use askSingleQuestion with the signal + const userInput = await this.reader.askSingleQuestion("User 👤 : ", { signal: run.signal }); + // Format the output as required const formattedOutput = `{ "clarification": "${userInput.trim()}" }`; - + return new StringToolOutput(formattedOutput); } } From 144b76c566387b3bd77f6f3e952bb5f4b5fd8635 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Mon, 6 Jan 2025 10:10:46 -0300 Subject: [PATCH 10/14] refactor(tools): enhance HumanTool with JSONToolOutput and PromptTemplate Ref: #121 Signed-off-by: Matias Molinas --- examples/tools/experimental/human.ts | 39 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 664c0cee..11ddd5f7 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -2,14 +2,19 @@ import { Emitter } from "bee-agent-framework/emitter/emitter"; import { Tool, BaseToolRunOptions, - StringToolOutput, + JSONToolOutput, ToolInput, ToolEmitter, } from "bee-agent-framework/tools/base"; import { RunContext } from "bee-agent-framework/context"; import { z } from "zod"; +import { PromptTemplate } from "bee-agent-framework/template"; -export class HumanTool extends Tool { +interface HumanToolOutput { + clarification: string; +} + +export class HumanTool extends Tool> { name = "HumanTool"; description = ` This tool is used whenever the user's input is unclear, ambiguous, or incomplete. @@ -47,7 +52,16 @@ export class HumanTool extends Tool { Note: Do NOT attempt to guess or provide incomplete responses. Always use this tool when in doubt to ensure accurate and meaningful interactions. `; -public readonly emitter: ToolEmitter, StringToolOutput> = + static readonly template = new PromptTemplate({ + schema: z.object({ + clarification: z.string(), + }), + template: `{ + "clarification": "{clarification}" + }`, + }); + + public readonly emitter: ToolEmitter, JSONToolOutput> = Emitter.root.child({ namespace: ["tool", "human"], creator: this, @@ -55,7 +69,10 @@ public readonly emitter: ToolEmitter, StringToolOutput> = private reader: ReturnType; - constructor(reader: ReturnType) { + constructor( + reader: ReturnType, + private readonly template: typeof HumanTool.template = HumanTool.template + ) { super(); this.reader = reader; } @@ -70,18 +87,16 @@ public readonly emitter: ToolEmitter, StringToolOutput> = input: ToolInput, _options: Partial, run: RunContext - ): Promise { + ): Promise> { // Use the shared reader instance provided to the constructor this.reader.write("HumanTool", input.message); // Use askSingleQuestion with the signal const userInput = await this.reader.askSingleQuestion("User 👤 : ", { signal: run.signal }); - // Format the output as required - const formattedOutput = `{ - "clarification": "${userInput.trim()}" - }`; - - return new StringToolOutput(formattedOutput); + // Return JSONToolOutput with the clarification + return new JSONToolOutput({ + clarification: userInput.trim() + }); } -} +} \ No newline at end of file From bc124ce3d5c38a40335d9898c018e340025d3654 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Mon, 6 Jan 2025 11:00:08 -0300 Subject: [PATCH 11/14] refactor(tools): introduce Reader interface and HumanToolInput abstraction These changes improve the modularity and usability of HumanTool by adhering to framework best practices. Ref: #121 Signed-off-by: Matias Molinas --- examples/agents/experimental/human.ts | 18 +++++---------- examples/tools/experimental/human.ts | 33 +++++++++++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/examples/agents/experimental/human.ts b/examples/agents/experimental/human.ts index 69ea17e1..7c0f5c44 100644 --- a/examples/agents/experimental/human.ts +++ b/examples/agents/experimental/human.ts @@ -9,17 +9,6 @@ import { OpenMeteoTool } from "bee-agent-framework/tools/weather/openMeteo"; // Import the HumanTool from the updated file import { HumanTool } from "../../tools/experimental/human.js"; -import { - BeeSystemPrompt, - BeeAssistantPrompt, - BeeUserPrompt, - BeeUserEmptyPrompt, - BeeToolErrorPrompt, - BeeToolInputErrorPrompt, - BeeToolNoResultsPrompt, - BeeToolNotFoundPrompt, -} from "bee-agent-framework/agents/bee/prompts"; - // Set up logger Logger.root.level = "silent"; // Disable internal logs const logger = new Logger({ name: "app", level: "trace" }); @@ -37,7 +26,12 @@ const reader = createConsoleReader(); const agent = new BeeAgent({ llm, memory: new TokenMemory({ llm }), - tools: [new OpenMeteoTool(), new HumanTool(reader)], + tools: [ + new OpenMeteoTool(), + new HumanTool({ + reader: reader, + }) + ], }); // Main loop diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 11ddd5f7..22b38f66 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -1,6 +1,7 @@ import { Emitter } from "bee-agent-framework/emitter/emitter"; import { Tool, + BaseToolOptions, BaseToolRunOptions, JSONToolOutput, ToolInput, @@ -14,7 +15,19 @@ interface HumanToolOutput { clarification: string; } -export class HumanTool extends Tool> { +export interface Reader { + write(prefix: string, message: string): void; + askSingleQuestion(prompt: string, options?: { signal?: AbortSignal }): Promise; +} + +export interface HumanToolInput extends BaseToolOptions { + reader: Reader; + name?: string; + description?: string; + template?: typeof HumanTool.template; +} + +export class HumanTool extends Tool, HumanToolInput> { name = "HumanTool"; description = ` This tool is used whenever the user's input is unclear, ambiguous, or incomplete. @@ -67,14 +80,10 @@ export class HumanTool extends Tool> { creator: this, }); - private reader: ReturnType; - - constructor( - reader: ReturnType, - private readonly template: typeof HumanTool.template = HumanTool.template - ) { - super(); - this.reader = reader; + constructor(protected readonly input: HumanToolInput) { + super(input); + this.name = input?.name || this.name; + this.description = input?.description || this.description; } inputSchema() { @@ -88,11 +97,11 @@ export class HumanTool extends Tool> { _options: Partial, run: RunContext ): Promise> { - // Use the shared reader instance provided to the constructor - this.reader.write("HumanTool", input.message); + // Use the reader from input + this.input.reader.write("HumanTool", input.message); // Use askSingleQuestion with the signal - const userInput = await this.reader.askSingleQuestion("User 👤 : ", { signal: run.signal }); + const userInput = await this.input.reader.askSingleQuestion("User 👤 : ", { signal: run.signal }); // Return JSONToolOutput with the clarification return new JSONToolOutput({ From 87cabf4b627972d105a74ce1ddfbd0a6d0fc8b77 Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Mon, 6 Jan 2025 12:54:49 -0300 Subject: [PATCH 12/14] refactor(io): simplify error handling in createConsoleReader Ref: #121 Signed-off-by: Matias Molinas --- examples/helpers/io.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index 0011d573..fadd10ea 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -58,11 +58,11 @@ export function createConsoleReader({ rl.write( `${picocolors.dim(`Interactive session has started. To escape, input 'q' and submit.\n`)}`, ); - + for (let iteration = 1, prompt = ""; isActive; iteration++) { prompt = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(input)); prompt = stripAnsi(prompt); - + if (prompt === "q") { break; } @@ -76,10 +76,6 @@ export function createConsoleReader({ } yield { prompt, iteration }; } - } catch (e: unknown) { - if (e instanceof Error && 'code' in e && e.code === "ERR_USE_AFTER_CLOSE") { - return; - } } finally { isActive = false; rl.close(); From b4b6b684efdec83847f5aabbcb97b7d91fb30cdc Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Mon, 6 Jan 2025 13:16:32 -0300 Subject: [PATCH 13/14] refactor(tools): remove unused template property from HumanTool Ref: #121 Signed-off-by: Matias Molinas --- examples/tools/experimental/human.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 22b38f66..620b15dc 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -9,7 +9,6 @@ import { } from "bee-agent-framework/tools/base"; import { RunContext } from "bee-agent-framework/context"; import { z } from "zod"; -import { PromptTemplate } from "bee-agent-framework/template"; interface HumanToolOutput { clarification: string; @@ -24,7 +23,6 @@ export interface HumanToolInput extends BaseToolOptions { reader: Reader; name?: string; description?: string; - template?: typeof HumanTool.template; } export class HumanTool extends Tool, HumanToolInput> { @@ -65,15 +63,6 @@ export class HumanTool extends Tool, HumanToolIn Note: Do NOT attempt to guess or provide incomplete responses. Always use this tool when in doubt to ensure accurate and meaningful interactions. `; - static readonly template = new PromptTemplate({ - schema: z.object({ - clarification: z.string(), - }), - template: `{ - "clarification": "{clarification}" - }`, - }); - public readonly emitter: ToolEmitter, JSONToolOutput> = Emitter.root.child({ namespace: ["tool", "human"], From fe5c7233ebe5380b6c92233195af797c062889ce Mon Sep 17 00:00:00 2001 From: Matias Molinas Date: Tue, 7 Jan 2025 10:20:06 -0300 Subject: [PATCH 14/14] style(tools): fix code formatting in human and IO examples Ref: #121 Signed-off-by: Matias Molinas --- examples/agents/experimental/human.ts | 4 ++-- examples/helpers/io.ts | 8 ++++---- examples/tools/experimental/human.ts | 14 ++++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/agents/experimental/human.ts b/examples/agents/experimental/human.ts index 7c0f5c44..98d8c7ef 100644 --- a/examples/agents/experimental/human.ts +++ b/examples/agents/experimental/human.ts @@ -27,10 +27,10 @@ const agent = new BeeAgent({ llm, memory: new TokenMemory({ llm }), tools: [ - new OpenMeteoTool(), + new OpenMeteoTool(), new HumanTool({ reader: reader, - }) + }), ], }); diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts index fadd10ea..dac68a87 100644 --- a/examples/helpers/io.ts +++ b/examples/helpers/io.ts @@ -3,7 +3,7 @@ import { stdin, stdout } from "node:process"; import picocolors from "picocolors"; import * as R from "remeda"; import stripAnsi from "strip-ansi"; -import type { Abortable } from 'node:events'; +import type { Abortable } from "node:events"; interface ReadFromConsoleInput { fallback?: string; @@ -39,7 +39,7 @@ export function createConsoleReader({ async askSingleQuestion(queryMessage: string, options?: Abortable): Promise { const answer = await rl.question( R.piped(picocolors.cyan, picocolors.bold)(queryMessage), - options ?? { signal: undefined } + options ?? { signal: undefined }, ); return stripAnsi(answer.trim()); }, @@ -58,11 +58,11 @@ export function createConsoleReader({ rl.write( `${picocolors.dim(`Interactive session has started. To escape, input 'q' and submit.\n`)}`, ); - + for (let iteration = 1, prompt = ""; isActive; iteration++) { prompt = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(input)); prompt = stripAnsi(prompt); - + if (prompt === "q") { break; } diff --git a/examples/tools/experimental/human.ts b/examples/tools/experimental/human.ts index 620b15dc..f8171723 100644 --- a/examples/tools/experimental/human.ts +++ b/examples/tools/experimental/human.ts @@ -84,17 +84,19 @@ export class HumanTool extends Tool, HumanToolIn async _run( input: ToolInput, _options: Partial, - run: RunContext + run: RunContext, ): Promise> { // Use the reader from input this.input.reader.write("HumanTool", input.message); - + // Use askSingleQuestion with the signal - const userInput = await this.input.reader.askSingleQuestion("User 👤 : ", { signal: run.signal }); - + const userInput = await this.input.reader.askSingleQuestion("User 👤 : ", { + signal: run.signal, + }); + // Return JSONToolOutput with the clarification return new JSONToolOutput({ - clarification: userInput.trim() + clarification: userInput.trim(), }); } -} \ No newline at end of file +}