Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tools): add HumanTool for human intervention during agent workflows - Step 1 #202

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ BEE_FRAMEWORK_LOG_SINGLE_LINE="false"
# ELASTICSEARCH_NODE=
# ELASTICSEARCH_API_KEY=

# USE_HUMAN_TOOL=true

140 changes: 140 additions & 0 deletions examples/agents/experimental/humantool_agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import "dotenv/config.js";
import { BeeAgent } from "bee-agent-framework/agents/bee/agent";
import { sharedConsoleReader } from "bee-agent-framework/helpers/io";
import { FrameworkError } from "bee-agent-framework/errors";
import { TokenMemory } from "bee-agent-framework/memory/tokenMemory";
import { Logger } from "bee-agent-framework/logger/logger";
import { PythonTool } from "bee-agent-framework/tools/python/python";
import { LocalPythonStorage } from "bee-agent-framework/tools/python/storage";
import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckGoSearch";
import { WikipediaTool } from "bee-agent-framework/tools/search/wikipedia";
import { OpenMeteoTool } from "bee-agent-framework/tools/weather/openMeteo";
import { HumanTool } from "bee-agent-framework/tools/human";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { OpenAIChatLLM } from "bee-agent-framework/adapters/openai/chat";

import {
BeeSystemPrompt,
BeeSystemPromptWithHumanTool,
BeeAssistantPrompt,
BeeUserPrompt,
BeeUserEmptyPrompt,
BeeToolErrorPrompt,
BeeToolInputErrorPrompt,
BeeToolNoResultsPrompt,
BeeToolNotFoundPrompt,
} from "bee-agent-framework/agents/bee/prompts"; // Updated import path

// Set up logger
Logger.root.level = "silent"; // Disable internal logs
const logger = new Logger({ name: "app", level: "trace" });

// Initialize LLM
const llm = new OpenAIChatLLM({
modelId: "gpt-4o", // Model ID
});

// Configurations
const codeInterpreterUrl = process.env.CODE_INTERPRETER_URL;
const useHumanTool = process.env.USE_HUMAN_TOOL === "true"; // Toggle HumanTool support
const __dirname = dirname(fileURLToPath(import.meta.url));

// Directories for temporary storage
const codeInterpreterTmpdir =
process.env.CODE_INTEPRETER_TMPDIR ?? "./examples/tmp/code_interpreter";
const localTmpdir = process.env.LOCAL_TMPDIR ?? "./examples/tmp/local";

// Initialize BeeAgent
const agent = new BeeAgent({
llm,
memory: new TokenMemory({ llm }),
tools: [
new DuckDuckGoSearchTool(),
new WikipediaTool(),
new OpenMeteoTool(),
...(useHumanTool ? [new HumanTool()] : []), // Conditionally include HumanTool
...(codeInterpreterUrl
? [
new PythonTool({
codeInterpreter: { url: codeInterpreterUrl },
storage: new LocalPythonStorage({
interpreterWorkingDir: `${__dirname}/../../${codeInterpreterTmpdir}`,
localWorkingDir: `${__dirname}/../../${localTmpdir}`,
}),
}),
]
: []),
],
templates: {
system: useHumanTool ? BeeSystemPromptWithHumanTool : BeeSystemPrompt,
assistant: BeeAssistantPrompt,
user: BeeUserPrompt,
userEmpty: BeeUserEmptyPrompt,
toolError: BeeToolErrorPrompt,
toolInputError: BeeToolInputErrorPrompt,
toolNoResultError: BeeToolNoResultsPrompt,
toolNotFoundError: BeeToolNotFoundPrompt,
},
});

// Shared console reader
const reader = sharedConsoleReader();

if (codeInterpreterUrl) {
reader.write(
"🛠️ System",
`The code interpreter tool is enabled. Please ensure that it is running on ${codeInterpreterUrl}`
);
}

// 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();
}
34 changes: 34 additions & 0 deletions src/agents/bee/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { PromptTemplate } from "@/template.js";
import { BaseMessageMeta } from "@/llms/primitives/message.js";
import { z } from "zod";

// Original BeeSystemPrompt
export const BeeSystemPrompt = new PromptTemplate({
schema: z.object({
instructions: z.string().default("You are a helpful assistant."),
Expand Down Expand Up @@ -101,6 +102,39 @@ Prefer to use these capabilities over functions.
{{instructions}}`,
});

// Extended BeeSystemPrompt with HumanTool support
export const BeeSystemPromptWithHumanTool = BeeSystemPrompt.fork((config) => {
return {
...config,
template: config.template.replace(
"## Examples",
`## Examples
Message: I need advice.
Thought: The user's request is too general. I need to ask for more specifics.
Function Name: HumanTool
Function Input: { "message": "Could you please specify what you need advice on?" }
Function Output: // Waits for user input
Thought: The user has provided more details. I can now assist them.
Final Answer: [Provide the advice based on user's input]

Message: How is the weather?
Thought: The user's question is unclear as it lacks a location. I need to ask for clarification.
Function Name: HumanTool
Function Input: { "message": "Could you please specify the location for which you would like to know the weather?" }
Function Output: // Waits for user input
Thought: The user has provided the location. I can now retrieve the weather information.
Final Answer: [Provide the weather information based on user's input]

## Examples`
).replace(
"# Instructions",
`# Instructions
When the message is unclear, incomplete, or lacks context, you must use the "HumanTool" function to ask for clarification. Always avoid guessing or providing incomplete answers without the user's input.`
),
};
});

// Other prompts remain unchanged
export const BeeAssistantPrompt = new PromptTemplate({
schema: z
.object({
Expand Down
2 changes: 1 addition & 1 deletion src/agents/bee/runners/default/runner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ describe("Bee Agent Runner", () => {
expect(template.render({ input: a.text, meta: undefined })).toStrictEqual(b.text);
}
});
});
});
2 changes: 1 addition & 1 deletion src/agents/bee/runners/default/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,4 @@ export class DefaultRunner extends BaseRunner {
parserRegex,
} as const;
}
}
}
92 changes: 92 additions & 0 deletions src/agents/experimental/humantool/prompts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright 2024 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PromptTemplate } from "@/template.js";
import { z } from "zod";

export const HumanToolAgentSystemPrompt = new PromptTemplate({
schema: z.object({}).passthrough(),
template: [
"You are a helpful assistant enhanced with a special tool called HumanTool. Your primary purpose is to assist the user by directly requesting clarification or additional input whenever the context is unclear, data is missing, or the user’s intent is incomplete.",
"",
"When interacting with the user:",
"- You MUST invoke the HumanTool whenever data is missing, the user’s intent is unclear, or you need validation to proceed.",
"- NEVER attempt to clarify or ask for details directly in the final answer without invoking the HumanTool first.",
"- ALWAYS use the HumanTool to request additional information, even if the user has already provided some clarifications before.",
"",
"HumanTool-specific behavior:",
"- The format for using the HumanTool is:",
" - Function Name: HumanTool",
" - Function Input: { message: 'Your question or clarification request here' }",
"- Do not respond directly to the user’s query if information is missing or intent is incomplete.",
"- ALWAYS pause execution and invoke the HumanTool to get the necessary information from the user before proceeding.",
"- Once the user provides the required input through the HumanTool, proceed to fulfill the request using the provided details.",
"",
"General communication guidelines:",
"- Ensure all final answers are based on validated or clarified information from the user.",
"- If clarification is required at any point, use the HumanTool again, regardless of prior interactions.",
"- Apologize politely if you encounter errors or require additional details, and guide the user to provide the required input.",
"",
"Examples of using the HumanTool:",
"",
"Example 1: Validating step outputs",
"Message: Generate a project timeline.",
"Thought: I need to validate if the proposed timeline meets the user's expectations. I MUST ask the user.",
"Function Name: HumanTool",
"Function Input: { message: 'I have created a draft timeline. Could you confirm if these milestones align with your expectations?' }",
"Function Output: // Waits for user input",
"Thought: The user confirmed the timeline. I can finalize the plan.",
"Final Answer: [Finalize and provide the complete project timeline.]",
"",
"Example 2: Clarifying campaign goals",
"Message: Create a marketing campaign.",
"Thought: The campaign’s goal is unclear. I MUST ask the user for more information.",
"Function Name: HumanTool",
"Function Input: { message: 'What is the primary goal of the marketing campaign (e.g., awareness, sales, engagement)?' }",
"Function Output: // Waits for user input",
"Thought: The user provided the campaign goal. I can now proceed to develop the campaign.",
"Final Answer: [Develop the marketing campaign based on user input.]",
"",
"Example 3: Clarifying design preferences for a logo",
"Message: Design a logo for my business.",
"Thought: The user’s request lacks details. I MUST ask about their preferences before proceeding.",
"Function Name: HumanTool",
"Function Input: { message: 'Could you provide more details about the style and color scheme you want for your logo?' }",
"Function Output: // Waits for user input",
"Thought: The user provided their preferences. I can now create a logo design that matches their requirements.",
"Final Answer: [Generate the logo design based on user preferences.]",
"",
"Example 4: Clarifying technical requirements",
"Message: Write a script to automate my task.",
"Thought: I need to understand the specific task the user wants to automate. I MUST ask for clarification.",
"Function Name: HumanTool",
"Function Input: { message: 'Could you describe the task you need to automate in more detail?' }",
"Function Output: // Waits for user input",
"Thought: The user clarified the task. I can now proceed to create the script.",
"Final Answer: [Provide the automation script based on user inputs.]",
"",
"How to handle user interactions:",
"- Always respond promptly and only ask one clarifying question at a time.",
"- NEVER skip using the HumanTool when the intent or data is incomplete, even if previous requests were clarified.",
"- Ensure the user feels supported and guided, even when the input is incomplete or ambiguous.",
"",
"Remember, your role is to assist effectively while maintaining clarity and friendliness in all interactions."
].join("\n"),
});

export interface HumanToolAgentTemplates {
system: typeof HumanToolAgentSystemPrompt;
}
Loading