248 lines
7.9 KiB
C++
248 lines
7.9 KiB
C++
#include "planning.h"
|
|
#include <iomanip>
|
|
|
|
namespace humanus {
|
|
|
|
// Initialize the agent with a default plan ID and validate required tools.
|
|
void PlanningAgent::initialize_plan_and_verify_tools() {
|
|
active_plan_id = "plan_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
|
|
|
if (available_tools.tools_map.find("planning") == available_tools.tools_map.end()) {
|
|
available_tools.add_tool(std::make_shared<PlanningTool>());
|
|
}
|
|
}
|
|
|
|
// Decide the next action based on plan status.
|
|
bool PlanningAgent::think() {
|
|
std::string prompt;
|
|
if (!active_plan_id.empty()) {
|
|
prompt = "CURRENT PLAN STATUS:\n" + get_plan();
|
|
}
|
|
memory->add_message(Message::user_message(prompt));
|
|
|
|
// Get the current step index before thinking
|
|
current_step_index = _get_current_step_index();
|
|
|
|
bool result = ToolCallAgent::think(); // Will set tool_calls
|
|
|
|
// After thinking, if we decided to execute a tool and it's not a planning tool or special tool,
|
|
// associate it with the current step for tracking
|
|
if (result && !tool_calls.empty()) {
|
|
auto latest_tool_call = tool_calls.back(); // Get the most recent tool call
|
|
if (latest_tool_call.function.name != "planning"
|
|
&& !_is_special_tool(latest_tool_call.function.name)
|
|
&& current_step_index >= 0) {
|
|
step_execution_tracker[latest_tool_call.id] = {
|
|
{"step_index", current_step_index},
|
|
{"tool_name", latest_tool_call.function.name},
|
|
{"step_status", "pending"} // Will be updated after execution
|
|
};
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Execute a step and track its completion status.
|
|
std::string PlanningAgent::act() {
|
|
std::string result = ToolCallAgent::act();
|
|
|
|
// After executing the tool, update the plan status
|
|
if (!tool_calls.empty()) {
|
|
auto latest_tool_call = tool_calls.back();
|
|
|
|
// Update the execution status to completed
|
|
if (step_execution_tracker.find(latest_tool_call.id) != step_execution_tracker.end()) {
|
|
step_execution_tracker[latest_tool_call.id]["status"] = "completed";
|
|
step_execution_tracker[latest_tool_call.id]["result"] = result;
|
|
|
|
// Update the plan status if this was a non-planning, non-special tool
|
|
if (
|
|
latest_tool_call.function.name != "planning"
|
|
&& !_is_special_tool(latest_tool_call.function.name)
|
|
) {
|
|
update_plan_status(latest_tool_call.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Retrieve the current plan status.
|
|
std::string PlanningAgent::get_plan() {
|
|
if (active_plan_id.empty()) {
|
|
return "No active plan. Please create a plan first.";
|
|
}
|
|
|
|
ToolResult result = available_tools.execute(
|
|
"planning",
|
|
{{"command", "get"}, {"plan_id", active_plan_id}}
|
|
);
|
|
|
|
return result.to_string();
|
|
}
|
|
|
|
// Run the agent with an optional initial request.
|
|
std::string PlanningAgent::run(const std::string& request) {
|
|
if (!request.empty()) {
|
|
create_initial_plan(request);
|
|
}
|
|
|
|
return BaseAgent::run();
|
|
}
|
|
|
|
// Update the current plan progress based on completed tool execution.
|
|
// Only marks a step as completed if the associated tool has been successfully executed.
|
|
void PlanningAgent::update_plan_status(const std::string& tool_call_id) {
|
|
if (active_plan_id.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (step_execution_tracker.find(tool_call_id) == step_execution_tracker.end()) {
|
|
logger->warn("No step tracking found for tool call " + tool_call_id);
|
|
return;
|
|
}
|
|
|
|
auto tracker = step_execution_tracker[tool_call_id];
|
|
if (tracker["status"] != "completed") {
|
|
logger->warn("Tool call " + tool_call_id + " has not completed successfully");
|
|
return;
|
|
}
|
|
|
|
int step_index = tracker["step_index"];
|
|
|
|
try {
|
|
// Mark the step as completed
|
|
ToolResult result = available_tools.execute(
|
|
"planning",
|
|
{
|
|
{"command", "mark_step"},
|
|
{"plan_id", active_plan_id},
|
|
{"step_index", step_index},
|
|
{"step_status", "completed"}
|
|
}
|
|
);
|
|
logger->info(
|
|
"Marked step " + std::to_string(step_index) + " as completed in plan " + active_plan_id
|
|
+ "\n\n" + result.to_string() + "\n\n"
|
|
);
|
|
} catch (const std::exception& e) {
|
|
logger->warn("Failed to update plan status: " + std::string(e.what()));
|
|
}
|
|
}
|
|
|
|
// Parse the current plan to identify the first non-completed step's index.
|
|
// Returns -1 if no active step is found.
|
|
int PlanningAgent::_get_current_step_index() {
|
|
if (active_plan_id.empty()) {
|
|
return -1;
|
|
}
|
|
|
|
auto plan = get_plan();
|
|
|
|
auto splitlines = [](const std::string& s) {
|
|
std::vector<std::string> lines;
|
|
std::istringstream iss(s);
|
|
std::string line;
|
|
while (std::getline(iss, line)) {
|
|
lines.push_back(line);
|
|
}
|
|
return lines;
|
|
};
|
|
|
|
try {
|
|
std::vector<std::string> plan_lines = splitlines(plan);
|
|
int steps_index = -1;
|
|
|
|
// Find the index of the "Steps:" line
|
|
for (size_t i = 0; i < plan_lines.size(); ++i) {
|
|
if (plan_lines[i].find("Steps:") == 0) {
|
|
steps_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (steps_index == -1) {
|
|
return -1;
|
|
}
|
|
|
|
// Find the first non-completed step
|
|
for (size_t i = steps_index + 1; i < plan_lines.size(); ++i) {
|
|
std::string line = plan_lines[i];
|
|
if (line.find("[ ]") != std::string::npos || line.find("[→]") != std::string::npos) { // not_started or in_progress
|
|
// Mark current step as in_progress
|
|
available_tools.execute(
|
|
"planning",
|
|
{
|
|
{"command", "mark_step"},
|
|
{"plan_id", active_plan_id},
|
|
{"step_index", i},
|
|
{"step_status", "in_progress"}
|
|
}
|
|
);
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1; // # No active step found
|
|
} catch (const std::exception& e) {
|
|
logger->warn("Error finding current step index: " + std::string(e.what()));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Create an initial plan based on the request.
|
|
void PlanningAgent::create_initial_plan(const std::string& request) {
|
|
logger->info("Creating initial plan with ID: " + active_plan_id);
|
|
|
|
std::vector<Message> messages = {
|
|
Message::user_message(
|
|
"Analyze the request and create a plan with ID " + active_plan_id + ": " + request
|
|
)
|
|
};
|
|
memory->add_messages(messages);
|
|
json response = llm->ask_tool(
|
|
messages,
|
|
system_prompt,
|
|
next_step_prompt,
|
|
available_tools.to_params(),
|
|
tool_choice
|
|
);
|
|
|
|
tool_calls = ToolCall::from_json_list(response["tool_calls"]);
|
|
|
|
Message assistant_msg = Message::assistant_message(
|
|
response["content"], tool_calls
|
|
);
|
|
|
|
memory->add_message(assistant_msg);
|
|
|
|
bool plan_created = false;
|
|
for (const ToolCall& tool_call : tool_calls) {
|
|
if (tool_call.function.name == "planning") {
|
|
std::string result = execute_tool(tool_call);
|
|
logger->info("Executed tool " + tool_call.function.name + " with result: " + result);
|
|
|
|
// Add tool response to memory
|
|
Message tool_msg = Message::tool_message(
|
|
result,
|
|
tool_call.id,
|
|
tool_call.function.name
|
|
);
|
|
memory->add_message(tool_msg);
|
|
plan_created = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!plan_created) {
|
|
logger->warn("No plan created from initial request");
|
|
Message tool_msg = Message::assistant_message(
|
|
"Error: Parameter `plan_id` is required for command: create"
|
|
);
|
|
memory->add_message(tool_msg);
|
|
}
|
|
}
|
|
|
|
} // namespace humanus
|