2025-03-16 22:56:03 +08:00
# include "planning.h"
2025-03-16 17:17:01 +08:00
namespace humanus {
// Get an appropriate executor agent for the current step.
// Can be extended to select agents based on step type/requirements.
2025-03-17 01:58:37 +08:00
std : : shared_ptr < BaseAgent > PlanningFlow : : get_executor ( const std : : string & step_type ) const {
2025-03-16 17:17:01 +08:00
// If step type is provided and matches an agent key, use that agent
if ( ! step_type . empty ( ) & & agents . find ( step_type ) ! = agents . end ( ) ) {
2025-03-16 22:56:03 +08:00
return agents . at ( step_type ) ;
2025-03-16 17:17:01 +08:00
}
// Fallback to primary agent
return primary_agent ( ) ;
}
// Execute the planning flow with agents.
std : : string PlanningFlow : : execute ( const std : : string & input ) {
try {
if ( agents . find ( primary_agent_key ) = = agents . end ( ) ) {
throw std : : runtime_error ( " No primary agent available " ) ;
}
// Create initial plan if input provided
if ( ! input . empty ( ) ) {
_create_initial_plan ( input ) ;
// Verify plan was created successfully
2025-03-16 22:56:03 +08:00
if ( planning_tool - > plans . find ( active_plan_id ) = = planning_tool - > plans . end ( ) ) {
2025-03-16 17:17:01 +08:00
logger - > error ( " Plan creation failed. Plan ID " + active_plan_id + " not found in planning tool. " ) ;
return " Failed to create plan for: " + input ;
}
}
std : : string result = " " ;
while ( true ) {
// Get current step to execute
2025-03-16 22:56:03 +08:00
json step_info ;
2025-03-16 17:17:01 +08:00
_get_current_step_info ( current_step_index , step_info ) ;
// Exit if no more steps or plan completed
if ( current_step_index < 0 ) {
break ;
}
// Execute current step with appropriate agent
std : : string step_type = step_info . value ( " type " , " " ) ;
auto executor = get_executor ( step_type ) ;
std : : string step_result = _execute_step ( executor , step_info ) ;
2025-03-20 01:12:15 +08:00
// result += step_result + "\n";
2025-03-16 17:17:01 +08:00
// Check if agent wants to terminate
2025-03-18 16:40:16 +08:00
if ( executor - > state = = AgentState : : FINISHED | | executor - > state = = AgentState : : ERR ) {
2025-03-16 17:17:01 +08:00
break ;
}
2025-03-19 18:44:54 +08:00
// Refactor memory
2025-03-26 19:28:02 +08:00
std : : string prefix_sum = _summarize_plan ( executor - > memory - > get_messages ( step_result ) ) ;
2025-04-06 16:32:51 +08:00
executor - > reset ( false ) ;
2025-03-20 01:12:15 +08:00
executor - > update_memory ( " assistant " , prefix_sum ) ;
if ( ! input . empty ( ) ) {
executor - > update_memory ( " user " , " Continue to accomplish the task: " + input ) ;
}
2025-04-06 16:32:51 +08:00
result + = " ## " + step_info . value ( " type " , " Step " + std : : to_string ( current_step_index ) ) + " : \n " + prefix_sum + " \n \n " ;
2025-03-16 17:17:01 +08:00
}
2025-04-13 00:02:18 +08:00
reset ( true ) ; // Clear short-term memory and state for next plan
2025-03-20 01:12:15 +08:00
2025-03-16 17:17:01 +08:00
return result ;
} catch ( const std : : exception & e ) {
LOG_ERROR ( " Error executing planning flow: " + std : : string ( e . what ( ) ) ) ;
return " Execution failed: " + std : : string ( e . what ( ) ) ;
}
}
// Create an initial plan based on the request using the flow's LLM and PlanningTool.
void PlanningFlow : : _create_initial_plan ( const std : : string & request ) {
logger - > info ( " Creating initial plan with ID: " + active_plan_id ) ;
// Create a system message for plan creation
2025-03-19 18:44:54 +08:00
std : : string system_prompt = " You are a planning assistant. Your task is to create a detailed plan with clear steps. " ;
2025-03-16 17:17:01 +08:00
// Create a user message with the request
2025-04-06 16:32:51 +08:00
std : : string user_prompt = " Please provide a detailed plan to accomplish this task: " + request + " \n \n " ;
user_prompt + = " **Note**: The following executors will be used to accomplish the plan. \n \n " ;
for ( const auto & [ key , agent ] : agents ) {
auto tool_call_agent = std : : dynamic_pointer_cast < ToolCallAgent > ( agent ) ;
if ( tool_call_agent ) {
user_prompt + = " Available tools for executor ` " + key + " `: \n " ;
user_prompt + = tool_call_agent - > available_tools . to_params ( ) . dump ( 2 ) + " \n \n " ;
}
}
2025-03-16 17:17:01 +08:00
// Call LLM with PlanningTool
auto response = llm - > ask_tool (
2025-04-06 16:32:51 +08:00
{ Message : : user_message ( user_prompt ) } ,
2025-03-19 18:44:54 +08:00
system_prompt ,
" " , // No next_step_prompt for initial plan creation
json : : array ( { planning_tool - > to_param ( ) } ) ,
2025-03-16 17:17:01 +08:00
" required "
) ;
// Process tool calls if present
if ( response . contains ( " tool_calls " ) & & ! response [ " tool_calls " ] . empty ( ) ) {
2025-03-16 22:56:03 +08:00
auto tool_calls = ToolCall : : from_json_list ( response [ " tool_calls " ] ) ;
2025-03-16 17:17:01 +08:00
for ( const auto & tool_call : tool_calls ) {
// Parse the arguments
auto args = tool_call . function . arguments ;
if ( args . is_string ( ) ) {
try {
2025-03-17 01:58:37 +08:00
std : : string args_str = args . get < std : : string > ( ) ;
args = json : : parse ( args_str ) ;
2025-03-16 17:17:01 +08:00
} catch ( . . . ) {
logger - > error ( " Failed to parse tool arguments: " + args . dump ( ) ) ;
continue ;
}
}
// Ensure plan_id is set correctly and execute the tool
args [ " plan_id " ] = active_plan_id ;
// Execute the tool via ToolCollection instead of directly
2025-03-16 22:56:03 +08:00
auto result = planning_tool - > execute ( args ) ;
2025-03-16 17:17:01 +08:00
logger - > info ( " Plan creation result: " + result . to_string ( ) ) ;
return ;
}
}
// If execution reached here, create a default plan
logger - > warn ( " Creating default plan " ) ;
// Create default plan using the ToolCollection
2025-04-13 00:02:18 +08:00
auto title = request ;
if ( title . size ( ) > 50 ) {
title = title . substr ( 0 , validate_utf8 ( title . substr ( 0 , 50 ) ) ) + " ... " ;
}
2025-03-16 22:56:03 +08:00
planning_tool - > execute ( {
2025-03-16 17:17:01 +08:00
{ " command " , " create " } ,
{ " plan_id " , active_plan_id } ,
2025-04-13 00:02:18 +08:00
{ " title " , title } ,
2025-03-16 17:17:01 +08:00
{ " steps " , { " Analyze request " , " Execute task " , " Verify results " } }
} ) ;
}
// Parse the current plan to identify the first non-completed step's index and info.
2025-03-20 01:12:15 +08:00
// Returns (-1, None) if no active step is found.
2025-03-16 22:56:03 +08:00
void PlanningFlow : : _get_current_step_info ( int & current_step_index , json & step_info ) {
if ( active_plan_id . empty ( ) | | planning_tool - > plans . find ( active_plan_id ) = = planning_tool - > plans . end ( ) ) {
2025-03-16 17:17:01 +08:00
logger - > error ( " Plan with ID " + active_plan_id + " not found " ) ;
current_step_index = - 1 ;
2025-03-16 22:56:03 +08:00
step_info = json : : object ( ) ;
2025-03-16 17:17:01 +08:00
return ;
}
try {
// Direct access to plan data from planning tool storage
2025-03-20 01:12:15 +08:00
const json & plan_data = planning_tool - > plans [ active_plan_id ] ;
2025-03-16 22:56:03 +08:00
json steps = plan_data . value ( " steps " , json : : array ( ) ) ;
json step_statuses = plan_data . value ( " step_statuses " , json : : array ( ) ) ;
2025-03-16 17:17:01 +08:00
// Find first non-completed step
for ( size_t i = 0 ; i < steps . size ( ) ; + + i ) {
2025-03-16 22:56:03 +08:00
const auto & step = steps [ i ] . get < std : : string > ( ) ;
2025-03-16 17:17:01 +08:00
std : : string step_status ;
2025-03-20 01:12:15 +08:00
if ( i > = step_statuses . size ( ) ) {
2025-03-16 17:17:01 +08:00
step_status = " not_started " ;
} else {
2025-03-20 01:12:15 +08:00
step_status = step_statuses [ i ] . get < std : : string > ( ) ;
2025-03-16 17:17:01 +08:00
}
if ( step_status = = " not_started " | | step_status = = " in_progress " ) {
// Extract step type/category if available
step_info = {
2025-03-16 22:56:03 +08:00
{ " type " , step }
} ;
2025-03-20 01:12:15 +08:00
} else { // completed or skipped
continue ;
2025-03-16 17:17:01 +08:00
}
// Try to extract step type from the text (e.g., [SEARCH] or [CODE])
std : : regex step_regex ( " \\ [([A-Z_]+) \\ ] " ) ;
std : : smatch match ;
if ( std : : regex_search ( step , match , step_regex ) ) {
step_info [ " type " ] = match [ 1 ] . str ( ) ; // to_lower?
}
// Mark current step as in_progress
try {
2025-03-20 01:12:15 +08:00
ToolResult result = planning_tool - > execute ( {
2025-03-16 17:17:01 +08:00
{ " command " , " mark_step " } ,
{ " plan_id " , active_plan_id } ,
{ " step_index " , i } ,
2025-03-20 01:12:15 +08:00
{ " step_status " , " in_progress " }
2025-03-16 17:17:01 +08:00
} ) ;
2025-03-20 01:12:15 +08:00
logger - > info (
" Started executing step " + std : : to_string ( i ) + " in plan " + active_plan_id
+ " \n \n " + result . to_string ( ) + " \n \n "
) ;
2025-03-16 17:17:01 +08:00
} catch ( const std : : exception & e ) {
logger - > error ( " Error marking step as in_progress: " + std : : string ( e . what ( ) ) ) ;
// Update step status directly if needed
2025-03-16 22:56:03 +08:00
if ( i < step_statuses . size ( ) ) {
step_statuses [ i ] = " in_progress " ;
2025-03-16 17:17:01 +08:00
} else {
2025-03-16 22:56:03 +08:00
while ( i > step_statuses . size ( ) ) {
step_statuses . push_back ( " not_started " ) ;
2025-03-16 17:17:01 +08:00
}
2025-03-16 22:56:03 +08:00
step_statuses . push_back ( " in_progress " ) ;
2025-03-16 17:17:01 +08:00
}
2025-03-20 01:12:15 +08:00
planning_tool - > plans [ active_plan_id ] [ " step_statuses " ] = step_statuses ;
2025-03-16 17:17:01 +08:00
}
current_step_index = i ;
return ;
}
current_step_index = - 1 ;
2025-03-16 22:56:03 +08:00
step_info = json : : object ( ) ; // No active step found
2025-03-16 17:17:01 +08:00
} catch ( const std : : exception & e ) {
logger - > error ( " Error finding current step index: " + std : : string ( e . what ( ) ) ) ;
current_step_index = - 1 ;
2025-03-16 22:56:03 +08:00
step_info = json : : object ( ) ;
2025-03-16 17:17:01 +08:00
}
}
// Execute the current step with the specified agent using agent.run().
2025-03-16 22:56:03 +08:00
std : : string PlanningFlow : : _execute_step ( const std : : shared_ptr < BaseAgent > & executor , const json & step_info ) {
2025-03-16 17:17:01 +08:00
// Prepare context for the agent with current plan status
2025-03-16 22:56:03 +08:00
json plan_status = _get_plan_text ( ) ;
2025-03-16 17:17:01 +08:00
std : : string step_text = step_info . value ( " text " , " Step " + std : : to_string ( current_step_index ) ) ;
// Create a prompt for the agent to execute the current step
std : : string step_prompt ;
2025-03-16 22:56:03 +08:00
step_prompt + = " \n CURRENT PLAN STATUS: \n " ;
step_prompt + = plan_status . dump ( 2 ) ;
2025-03-16 17:17:01 +08:00
step_prompt + = " \n \n YOUR CURRENT TASK: \n " ;
step_prompt + = " You are now working on step " + std : : to_string ( current_step_index ) + " : \" " + step_text + " \" \n " ;
2025-04-06 16:32:51 +08:00
step_prompt + = " Please execute this step using the appropriate tools. When you're done, provide a summary of what you accomplished and call `terminate` to trigger the next step. " ;
2025-03-16 17:17:01 +08:00
// Use agent.run() to execute the step
try {
2025-03-16 22:56:03 +08:00
std : : string step_result = executor - > run ( step_prompt ) ;
2025-03-16 17:17:01 +08:00
// Mark the step as completed after successful execution
2025-03-19 18:44:54 +08:00
if ( executor - > state ! = AgentState : : ERR ) {
_mark_step_completed ( ) ;
}
2025-03-16 17:17:01 +08:00
return step_result ;
} catch ( const std : : exception & e ) {
LOG_ERROR ( " Error executing step " + std : : to_string ( current_step_index ) + " : " + std : : string ( e . what ( ) ) ) ;
return " Error executing step " + std : : to_string ( current_step_index ) + " : " + std : : string ( e . what ( ) ) ;
}
}
// Mark the current step as completed.
void PlanningFlow : : _mark_step_completed ( ) {
if ( current_step_index < 0 ) {
return ;
}
try {
// Mark the step as completed
2025-03-17 14:07:41 +08:00
ToolResult result = planning_tool - > execute ( {
2025-03-16 17:17:01 +08:00
{ " command " , " mark_step " } ,
{ " plan_id " , active_plan_id } ,
{ " step_index " , current_step_index } ,
2025-03-17 14:24:03 +08:00
{ " step_status " , " completed " }
2025-03-16 17:17:01 +08:00
} ) ;
logger - > info (
" Marked step " + std : : to_string ( current_step_index ) + " as completed in plan " + active_plan_id
2025-03-17 14:07:41 +08:00
+ " \n \n " + result . to_string ( ) + " \n \n "
2025-03-16 17:17:01 +08:00
) ;
} catch ( const std : : exception & e ) {
2025-03-16 22:56:03 +08:00
logger - > warn ( " Failed to update plan status: " + std : : string ( e . what ( ) ) ) ;
2025-03-16 17:17:01 +08:00
// Update step status directly in planning tool storage
2025-03-16 22:56:03 +08:00
if ( planning_tool - > plans . find ( active_plan_id ) ! = planning_tool - > plans . end ( ) ) {
2025-03-20 01:12:15 +08:00
const json & plan_data = planning_tool - > plans [ active_plan_id ] ;
2025-03-16 22:56:03 +08:00
json step_statuses = plan_data . value ( " step_statuses " , json : : array ( ) ) ;
2025-03-16 17:17:01 +08:00
// Ensure the step_statuses list is long enough
while ( current_step_index > = step_statuses . size ( ) ) {
step_statuses . push_back ( " not_started " ) ;
}
// Update the status
step_statuses [ current_step_index ] = " completed " ;
2025-03-20 01:12:15 +08:00
planning_tool - > plans [ active_plan_id ] [ " step_statuses " ] = step_statuses ;
2025-03-16 17:17:01 +08:00
}
}
}
// Get the current plan as formatted text.
std : : string PlanningFlow : : _get_plan_text ( ) {
try {
2025-03-16 22:56:03 +08:00
auto result = planning_tool - > execute ( {
2025-03-16 17:17:01 +08:00
{ " command " , " get " } ,
{ " plan_id " , active_plan_id }
} ) ;
2025-04-10 00:10:05 +08:00
return result . to_string ( ) ;
2025-03-16 17:17:01 +08:00
} catch ( const std : : exception & e ) {
LOG_ERROR ( " Error getting plan: " + std : : string ( e . what ( ) ) ) ;
return _generate_plan_text_from_storage ( ) ;
}
}
// Generate plan text directly from storage if the planning tool fails.
std : : string PlanningFlow : : _generate_plan_text_from_storage ( ) {
try {
2025-03-16 22:56:03 +08:00
if ( planning_tool - > plans . find ( active_plan_id ) = = planning_tool - > plans . end ( ) ) {
2025-03-16 17:17:01 +08:00
return " Error: Plan with ID " + active_plan_id + " not found " ;
}
2025-03-20 01:12:15 +08:00
const json & plan_data = planning_tool - > plans [ active_plan_id ] ;
2025-03-16 22:56:03 +08:00
auto title = plan_data . value ( " title " , " Untitled Plan " ) ;
auto steps = plan_data . value ( " steps " , json : : array ( ) ) ;
auto step_statuses = plan_data . value ( " step_statuses " , json : : array ( ) ) ;
auto step_notes = plan_data . value ( " step_notes " , json : : array ( ) ) ;
2025-03-16 17:17:01 +08:00
// Ensure step_statuses and step_notes match the number of steps
while ( step_statuses . size ( ) < steps . size ( ) ) {
step_statuses . push_back ( " not_started " ) ;
}
while ( step_notes . size ( ) < steps . size ( ) ) {
step_notes . push_back ( " " ) ;
}
// Count steps by status
2025-03-16 22:56:03 +08:00
std : : map < std : : string , int > status_counts = {
2025-03-16 17:17:01 +08:00
{ " completed " , 0 } ,
{ " in_progress " , 0 } ,
{ " blocked " , 0 } ,
{ " not_started " , 0 }
} ;
for ( const auto & status : step_statuses ) {
2025-03-16 22:56:03 +08:00
if ( status_counts . find ( status ) ! = status_counts . end ( ) ) {
status_counts [ status ] = status_counts [ status ] + 1 ;
2025-03-16 17:17:01 +08:00
}
}
int completed = status_counts [ " completed " ] ;
int total = steps . size ( ) ;
double progress = total > 0 ? ( static_cast < double > ( completed ) / total ) * 100.0 : 0.0 ;
std : : stringstream plan_text_ss ;
plan_text_ss < < " Plan: " < < title < < " (ID: " < < active_plan_id < < " ) \n " ;
plan_text_ss < < std : : string ( plan_text_ss . str ( ) . size ( ) , ' = ' ) < < " \n \n " ;
plan_text_ss < < " Total steps: " < < completed < < " / " < < total < < " steps completed ( " < < std : : fixed < < std : : setprecision ( 1 ) < < progress < < " %) \n " ;
2025-03-16 22:56:03 +08:00
plan_text_ss < < " Status: " < < status_counts [ " completed " ] < < " completed, " < < status_counts [ " in_progress " ] < < " in progress, "
< < status_counts [ " blocked " ] < < " blocked, " < < status_counts [ " not_started " ] < < " not started \n \n " ;
2025-03-16 17:17:01 +08:00
plan_text_ss < < " Steps: \n " ;
for ( size_t i = 0 ; i < steps . size ( ) ; + + i ) {
auto step = steps [ i ] ;
auto status = step_statuses [ i ] ;
auto notes = step_notes [ i ] ;
std : : string status_mark ;
if ( status = = " completed " ) {
status_mark = " [✓] " ;
} else if ( status = = " in_progress " ) {
status_mark = " [→] " ;
} else if ( status = = " blocked " ) {
status_mark = " [!] " ;
} else if ( status = = " not_started " ) {
status_mark = " [ ] " ;
2025-03-16 22:56:03 +08:00
} else { // unknown status
status_mark = " [?] " ;
2025-03-16 17:17:01 +08:00
}
plan_text_ss < < i < < " . " < < status_mark < < " " < < step < < " \n " ;
if ( ! notes . empty ( ) ) {
plan_text_ss < < " Notes: " < < notes < < " \n " ;
}
}
return plan_text_ss . str ( ) ;
} catch ( const std : : exception & e ) {
logger - > error ( " Error generating plan text from storage: " + std : : string ( e . what ( ) ) ) ;
return " Error: Unable to retrieve plan with ID " + active_plan_id ;
}
}
2025-03-20 01:12:15 +08:00
// Summarize the plan using the flow's LLM directly
std : : string PlanningFlow : : _summarize_plan ( const std : : vector < Message > messages ) {
2025-03-16 17:17:01 +08:00
std : : string plan_text = _get_plan_text ( ) ;
2025-03-20 01:12:15 +08:00
std : : string system_prompt = " You are a planning assistant. Your task is to summarize the current plan. " ;
std : : string next_step_prompt = " Above is the nearest finished step in the plan. Here is the current plan status: \n \n " + plan_text + " \n \n "
+ " Please provide a summary of what was accomplished and any thoughts for next steps (when the plan is not fully finished). " ;
2025-03-16 17:17:01 +08:00
// Create a summary using the flow's LLM directly
try {
auto response = llm - > ask (
2025-03-20 01:12:15 +08:00
messages ,
system_prompt ,
next_step_prompt
2025-03-16 17:17:01 +08:00
) ;
2025-03-16 22:56:03 +08:00
return response ;
2025-03-16 17:17:01 +08:00
} catch ( const std : : exception & e ) {
2025-03-20 01:12:15 +08:00
LOG_ERROR ( " Error summarizing plan with LLM: " + std : : string ( e . what ( ) ) ) ;
2025-03-16 17:17:01 +08:00
// Fallback to using an agent for the summary
try {
auto agent = primary_agent ( ) ;
2025-03-20 01:12:15 +08:00
std : : string summary = agent - > run ( system_prompt + next_step_prompt ) ;
return summary ;
2025-03-16 17:17:01 +08:00
} catch ( const std : : exception & e2 ) {
2025-03-20 01:12:15 +08:00
LOG_ERROR ( " Error summarizing plan with agent: " + std : : string ( e2 . what ( ) ) ) ;
return " Error generating summary. " ;
2025-03-16 17:17:01 +08:00
}
}
}
}