mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-02 05:04:23 +01:00
feat: add /plan command in CLI to invoke reasoner with plan system prompt (#1616)
This commit is contained in:
@@ -15,6 +15,8 @@ pub enum InputResult {
|
||||
ListPrompts(Option<String>),
|
||||
PromptCommand(PromptCommandOptions),
|
||||
GooseMode(String),
|
||||
Plan(PlanCommandOptions),
|
||||
EndPlan,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -24,6 +26,11 @@ pub struct PromptCommandOptions {
|
||||
pub arguments: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlanCommandOptions {
|
||||
pub message_text: String,
|
||||
}
|
||||
|
||||
pub fn get_input(
|
||||
editor: &mut Editor<GooseCompleter, rustyline::history::DefaultHistory>,
|
||||
) -> Result<InputResult> {
|
||||
@@ -72,6 +79,8 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
|
||||
const CMD_EXTENSION: &str = "/extension ";
|
||||
const CMD_BUILTIN: &str = "/builtin ";
|
||||
const CMD_MODE: &str = "/mode ";
|
||||
const CMD_PLAN: &str = "/plan";
|
||||
const CMD_ENDPLAN: &str = "/endplan";
|
||||
|
||||
match input {
|
||||
"/exit" | "/quit" => Some(InputResult::Exit),
|
||||
@@ -111,6 +120,8 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
|
||||
s if s.starts_with(CMD_MODE) => {
|
||||
Some(InputResult::GooseMode(s[CMD_MODE.len()..].to_string()))
|
||||
}
|
||||
s if s.starts_with(CMD_PLAN) => parse_plan_command(s[CMD_PLAN.len()..].trim().to_string()),
|
||||
s if s == CMD_ENDPLAN => Some(InputResult::EndPlan),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -168,6 +179,14 @@ fn parse_prompt_command(args: &str) -> Option<InputResult> {
|
||||
Some(InputResult::PromptCommand(options))
|
||||
}
|
||||
|
||||
fn parse_plan_command(input: String) -> Option<InputResult> {
|
||||
let options = PlanCommandOptions {
|
||||
message_text: input.trim().to_string(),
|
||||
};
|
||||
|
||||
Some(InputResult::Plan(options))
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"Available commands:
|
||||
@@ -178,6 +197,12 @@ fn print_help() {
|
||||
/prompts [--extension <name>] - List all available prompts, optionally filtered by extension
|
||||
/prompt <n> [--info] [key=value...] - Get prompt info or execute a prompt
|
||||
/mode <name> - Set the goose mode to use ('auto', 'approve', 'chat')
|
||||
/plan <message_text> - Enters 'plan' mode with optional message. Create a plan based on the current messages and asks user if they want to act on it.
|
||||
If user acts on the plan, goose mode is set to 'auto' and returns to 'normal' goose mode.
|
||||
To warm up goose before using '/plan', we recommend setting '/mode approve' & putting appropriate context into goose.
|
||||
The model is used based on $GOOSE_PLANNER_PROVIDER and $GOOSE_PLANNER_MODEL environment variables.
|
||||
If no model is set, the default model is used.
|
||||
/endplan - Exit plan mode and return to 'normal' goose mode.
|
||||
/? or /help - Display this help message
|
||||
|
||||
Navigation:
|
||||
@@ -370,4 +395,22 @@ mod tests {
|
||||
panic!("Expected PromptCommand");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plan_mode() {
|
||||
// Test plan mode with no text
|
||||
let result = handle_slash_command("/plan");
|
||||
assert!(result.is_some());
|
||||
|
||||
// Test plan mode with text
|
||||
let result = handle_slash_command("/plan hello world");
|
||||
assert!(result.is_some());
|
||||
let options = result.unwrap();
|
||||
match options {
|
||||
InputResult::Plan(options) => {
|
||||
assert_eq!(options.message_text, "hello world");
|
||||
}
|
||||
_ => panic!("Expected Plan"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ mod prompt;
|
||||
mod thinking;
|
||||
|
||||
pub use builder::build_session;
|
||||
use goose::providers::base::Provider;
|
||||
pub use goose::session::Identifier;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -28,6 +29,11 @@ use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio;
|
||||
|
||||
pub enum RunMode {
|
||||
Normal,
|
||||
Plan,
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
agent: Box<dyn Agent>,
|
||||
messages: Vec<Message>,
|
||||
@@ -35,6 +41,7 @@ pub struct Session {
|
||||
// Cache for completion data - using std::sync for thread safety without async
|
||||
completion_cache: Arc<std::sync::RwLock<CompletionCache>>,
|
||||
debug: bool, // New field for debug mode
|
||||
run_mode: RunMode,
|
||||
}
|
||||
|
||||
// Cache structure for completion data
|
||||
@@ -54,6 +61,42 @@ impl CompletionCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PlannerResponseType {
|
||||
Plan,
|
||||
ClarifyingQuestions,
|
||||
}
|
||||
|
||||
/// Decide if the planner's reponse is a plan or a clarifying question
|
||||
///
|
||||
/// This function is called after the planner has generated a response
|
||||
/// to the user's message. The response is either a plan or a clarifying
|
||||
/// question.
|
||||
pub async fn classify_planner_response(
|
||||
message_text: String,
|
||||
provider: Arc<Box<dyn Provider>>,
|
||||
) -> Result<PlannerResponseType> {
|
||||
let prompt = format!("The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}");
|
||||
|
||||
// Generate the description
|
||||
let message = Message::user().with_text(&prompt);
|
||||
let (result, _usage) = provider
|
||||
.complete(
|
||||
"Reply only with the classification label: \"plan\" or \"clarifying questions\"",
|
||||
&[message],
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// println!("classify_planner_response: {result:?}\n"); // TODO: remove
|
||||
|
||||
let predicted = result.as_concat_text();
|
||||
if predicted.to_lowercase().contains("plan") {
|
||||
Ok(PlannerResponseType::Plan)
|
||||
} else {
|
||||
Ok(PlannerResponseType::ClarifyingQuestions)
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(agent: Box<dyn Agent>, session_file: PathBuf, debug: bool) -> Self {
|
||||
let messages = match session::read_messages(&session_file) {
|
||||
@@ -70,6 +113,7 @@ impl Session {
|
||||
session_file,
|
||||
completion_cache: Arc::new(std::sync::RwLock::new(CompletionCache::new())),
|
||||
debug,
|
||||
run_mode: RunMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,20 +309,35 @@ impl Session {
|
||||
loop {
|
||||
match input::get_input(&mut editor)? {
|
||||
input::InputResult::Message(content) => {
|
||||
save_history(&mut editor);
|
||||
match self.run_mode {
|
||||
RunMode::Normal => {
|
||||
save_history(&mut editor);
|
||||
|
||||
self.messages.push(Message::user().with_text(&content));
|
||||
self.messages.push(Message::user().with_text(&content));
|
||||
|
||||
// Get the provider from the agent for description generation
|
||||
let provider = self.agent.provider().await;
|
||||
// Get the provider from the agent for description generation
|
||||
let provider = self.agent.provider().await;
|
||||
|
||||
// Persist messages with provider for automatic description generation
|
||||
session::persist_messages(&self.session_file, &self.messages, Some(provider))
|
||||
.await?;
|
||||
// Persist messages with provider for automatic description generation
|
||||
session::persist_messages(
|
||||
&self.session_file,
|
||||
&self.messages,
|
||||
Some(provider),
|
||||
)
|
||||
.await?;
|
||||
|
||||
output::show_thinking();
|
||||
self.process_agent_response(true).await?;
|
||||
output::hide_thinking();
|
||||
output::show_thinking();
|
||||
self.process_agent_response(true).await?;
|
||||
output::hide_thinking();
|
||||
}
|
||||
RunMode::Plan => {
|
||||
let mut plan_messages = self.messages.clone();
|
||||
plan_messages.push(Message::user().with_text(&content));
|
||||
let reasoner = get_reasoner()?;
|
||||
self.plan_with_reasoner_model(plan_messages, reasoner)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
input::InputResult::Exit => break,
|
||||
input::InputResult::AddExtension(cmd) => {
|
||||
@@ -345,7 +404,27 @@ impl Session {
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String(mode.to_string()))
|
||||
.unwrap();
|
||||
println!("Goose mode set to '{}'", mode);
|
||||
output::goose_mode_message(&format!("Goose mode set to '{}'", mode));
|
||||
continue;
|
||||
}
|
||||
input::InputResult::Plan(options) => {
|
||||
self.run_mode = RunMode::Plan;
|
||||
output::render_enter_plan_mode();
|
||||
|
||||
let message_text = options.message_text;
|
||||
if message_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut plan_messages = self.messages.clone();
|
||||
plan_messages.push(Message::user().with_text(&message_text));
|
||||
|
||||
let reasoner = get_reasoner()?;
|
||||
self.plan_with_reasoner_model(plan_messages, reasoner)
|
||||
.await?;
|
||||
}
|
||||
input::InputResult::EndPlan => {
|
||||
self.run_mode = RunMode::Normal;
|
||||
output::render_exit_plan_mode();
|
||||
continue;
|
||||
}
|
||||
input::InputResult::PromptCommand(opts) => {
|
||||
@@ -419,6 +498,72 @@ impl Session {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn plan_with_reasoner_model(
|
||||
&mut self,
|
||||
plan_messages: Vec<Message>,
|
||||
reasoner: Box<dyn Provider + Send + Sync>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let plan_prompt = self.agent.get_plan_prompt().await?;
|
||||
output::show_thinking();
|
||||
let (plan_response, _usage) = reasoner.complete(&plan_prompt, &plan_messages, &[]).await?;
|
||||
output::render_message(&plan_response, self.debug);
|
||||
output::hide_thinking();
|
||||
let planner_response_type =
|
||||
classify_planner_response(plan_response.as_concat_text(), self.agent.provider().await)
|
||||
.await?;
|
||||
|
||||
match planner_response_type {
|
||||
PlannerResponseType::Plan => {
|
||||
println!();
|
||||
let should_act =
|
||||
cliclack::confirm("Do you want to clear message history & act on this plan?")
|
||||
.initial_value(true)
|
||||
.interact()?;
|
||||
if should_act {
|
||||
output::render_act_on_plan();
|
||||
self.run_mode = RunMode::Normal;
|
||||
// set goose mode: auto if that isn't already the case
|
||||
let config = Config::global();
|
||||
let curr_goose_mode =
|
||||
config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
|
||||
if curr_goose_mode != "auto" {
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String("auto".to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// clear the messages before acting on the plan
|
||||
self.messages.clear();
|
||||
// add the plan response as a user message
|
||||
let plan_message = Message::user().with_text(plan_response.as_concat_text());
|
||||
self.messages.push(plan_message);
|
||||
// act on the plan
|
||||
output::show_thinking();
|
||||
self.process_agent_response(true).await?;
|
||||
output::hide_thinking();
|
||||
|
||||
// Reset run & goose mode
|
||||
if curr_goose_mode != "auto" {
|
||||
config
|
||||
.set_param("GOOSE_MODE", Value::String(curr_goose_mode.to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
// add the plan response (assistant message) & carry the conversation forward
|
||||
// in the next round, the user might wanna slightly modify the plan
|
||||
self.messages.push(plan_response);
|
||||
}
|
||||
}
|
||||
PlannerResponseType::ClarifyingQuestions => {
|
||||
// add the plan response (assistant message) & carry the conversation forward
|
||||
// in the next round, the user will answer the clarifying questions
|
||||
self.messages.push(plan_response);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a single message and exit
|
||||
pub async fn headless(&mut self, message: String) -> Result<()> {
|
||||
self.process_message(message).await
|
||||
@@ -650,3 +795,34 @@ impl Session {
|
||||
Ok(metadata.total_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reasoner() -> Result<Box<dyn Provider + Send + Sync>, anyhow::Error> {
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::create;
|
||||
|
||||
let (reasoner_provider, reasoner_model) = match (
|
||||
std::env::var("GOOSE_PLANNER_PROVIDER"),
|
||||
std::env::var("GOOSE_PLANNER_MODEL"),
|
||||
) {
|
||||
(Ok(provider), Ok(model)) => (provider, model),
|
||||
_ => {
|
||||
println!(
|
||||
"WARNING: GOOSE_PLANNER_PROVIDER or GOOSE_PLANNER_MODEL is not set. \
|
||||
Using default model from config..."
|
||||
);
|
||||
let config = Config::global();
|
||||
let provider = config
|
||||
.get_param("GOOSE_PROVIDER")
|
||||
.expect("No provider configured. Run 'goose configure' first");
|
||||
let model = config
|
||||
.get_param("GOOSE_MODEL")
|
||||
.expect("No model configured. Run 'goose configure' first");
|
||||
(provider, model)
|
||||
}
|
||||
};
|
||||
|
||||
let model_config = ModelConfig::new(reasoner_model);
|
||||
let reasoner = create(&reasoner_provider, model_config)?;
|
||||
|
||||
Ok(reasoner)
|
||||
}
|
||||
|
||||
@@ -126,6 +126,33 @@ pub fn render_message(message: &Message, debug: bool) {
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn render_enter_plan_mode() {
|
||||
println!(
|
||||
"\n{} {}\n",
|
||||
style("Entering plan mode.").green().bold(),
|
||||
style("You can provide instructions to create a plan and then act on it. To exit early, type /endplan")
|
||||
.green()
|
||||
.dim()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_act_on_plan() {
|
||||
println!(
|
||||
"\n{}\n",
|
||||
style("Exiting plan mode and acting on the above plan")
|
||||
.green()
|
||||
.bold(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_exit_plan_mode() {
|
||||
println!("\n{}\n", style("Exiting plan mode.").green().bold());
|
||||
}
|
||||
|
||||
pub fn goose_mode_message(text: &str) {
|
||||
println!("\n{}", style(text).yellow(),);
|
||||
}
|
||||
|
||||
fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
|
||||
match &req.tool_call {
|
||||
Ok(call) => match call.name.as_str() {
|
||||
|
||||
@@ -63,6 +63,9 @@ pub trait Agent: Send + Sync {
|
||||
/// Returns the prompt text that would be used as user input
|
||||
async fn get_prompt(&self, name: &str, arguments: Value) -> Result<GetPromptResult>;
|
||||
|
||||
/// Get the plan prompt, which will be used with the planner (reasoner) model
|
||||
async fn get_plan_prompt(&self) -> anyhow::Result<String>;
|
||||
|
||||
/// Get a reference to the provider used by this agent
|
||||
async fn provider(&self) -> Arc<Box<dyn Provider>>;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::extension::{ExtensionConfig, ExtensionError, ExtensionInfo, ExtensionResult};
|
||||
use super::extension::{ExtensionConfig, ExtensionError, ExtensionInfo, ExtensionResult, ToolInfo};
|
||||
use crate::config::Config;
|
||||
use crate::prompt_template;
|
||||
use crate::providers::base::Provider;
|
||||
@@ -83,6 +83,14 @@ fn normalize(input: String) -> String {
|
||||
result.to_lowercase()
|
||||
}
|
||||
|
||||
pub fn get_parameter_names(tool: &Tool) -> Vec<String> {
|
||||
tool.input_schema
|
||||
.get("properties")
|
||||
.and_then(|props| props.as_object())
|
||||
.map(|props| props.keys().cloned().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Create a new Capabilities with the specified provider
|
||||
pub fn new(provider: Box<dyn Provider>) -> Self {
|
||||
@@ -296,6 +304,14 @@ impl Capabilities {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Get the extension prompt including client instructions
|
||||
pub async fn get_planning_prompt(&self, tools_info: Vec<ToolInfo>) -> String {
|
||||
let mut context: HashMap<&str, Value> = HashMap::new();
|
||||
context.insert("tools", serde_json::to_value(tools_info).unwrap());
|
||||
|
||||
prompt_template::render_global_file("plan.md", &context).expect("Prompt should render")
|
||||
}
|
||||
|
||||
/// Get the extension prompt including client instructions
|
||||
pub async fn get_system_prompt(&self) -> String {
|
||||
let mut context: HashMap<&str, Value> = HashMap::new();
|
||||
|
||||
@@ -192,3 +192,21 @@ impl ExtensionInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the tool used for building prompts
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ToolInfo {
|
||||
name: String,
|
||||
description: String,
|
||||
parameters: Vec<String>,
|
||||
}
|
||||
|
||||
impl ToolInfo {
|
||||
pub fn new(name: &str, description: &str, parameters: Vec<String>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use tokio::sync::Mutex;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::agent::SessionConfig;
|
||||
use super::capabilities::get_parameter_names;
|
||||
use super::extension::ToolInfo;
|
||||
use super::Agent;
|
||||
use crate::agents::capabilities::Capabilities;
|
||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
||||
@@ -243,6 +245,19 @@ impl Agent for ReferenceAgent {
|
||||
Err(anyhow!("Prompt '{}' not found", name))
|
||||
}
|
||||
|
||||
async fn get_plan_prompt(&self) -> anyhow::Result<String> {
|
||||
let mut capabilities = self.capabilities.lock().await;
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
Ok(plan_prompt)
|
||||
}
|
||||
|
||||
async fn provider(&self) -> Arc<Box<dyn Provider>> {
|
||||
let capabilities = self.capabilities.lock().await;
|
||||
capabilities.provider()
|
||||
|
||||
@@ -10,7 +10,9 @@ use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, instrument, warn};
|
||||
|
||||
use super::agent::SessionConfig;
|
||||
use super::capabilities::get_parameter_names;
|
||||
use super::detect_read_only_tools;
|
||||
use super::extension::ToolInfo;
|
||||
use super::Agent;
|
||||
use crate::agents::capabilities::Capabilities;
|
||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
||||
@@ -457,6 +459,19 @@ impl Agent for SummarizeAgent {
|
||||
Err(anyhow!("Prompt '{}' not found", name))
|
||||
}
|
||||
|
||||
async fn get_plan_prompt(&self) -> anyhow::Result<String> {
|
||||
let mut capabilities = self.capabilities.lock().await;
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
Ok(plan_prompt)
|
||||
}
|
||||
|
||||
async fn provider(&self) -> Arc<Box<dyn Provider>> {
|
||||
let capabilities = self.capabilities.lock().await;
|
||||
capabilities.provider()
|
||||
|
||||
@@ -10,8 +10,9 @@ use tracing::{debug, error, instrument, warn};
|
||||
|
||||
use super::agent::SessionConfig;
|
||||
use super::detect_read_only_tools;
|
||||
use super::extension::ToolInfo;
|
||||
use super::Agent;
|
||||
use crate::agents::capabilities::Capabilities;
|
||||
use crate::agents::capabilities::{get_parameter_names, Capabilities};
|
||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
||||
use crate::agents::ToolPermissionStore;
|
||||
use crate::config::Config;
|
||||
@@ -511,6 +512,19 @@ impl Agent for TruncateAgent {
|
||||
Err(anyhow!("Prompt '{}' not found", name))
|
||||
}
|
||||
|
||||
async fn get_plan_prompt(&self) -> anyhow::Result<String> {
|
||||
let mut capabilities = self.capabilities.lock().await;
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
Ok(plan_prompt)
|
||||
}
|
||||
|
||||
async fn provider(&self) -> Arc<Box<dyn Provider>> {
|
||||
let capabilities = self.capabilities.lock().await;
|
||||
capabilities.provider()
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
You prepare plans for an agent system. You will receive the current system
|
||||
status as well as in an incoming request from the human. Your plan will be used by an AI agent,
|
||||
who is taking actions on behalf of the human.
|
||||
|
||||
The agent currently has access to the following tools
|
||||
You are a specialized "planner" AI. Your task is to analyze the user’s request from the chat messages and create either:
|
||||
1. A detailed step-by-step plan (if you have enough information) on behalf of user that another "executor" AI agent can follow, or
|
||||
2. A list of clarifying questions (if you do not have enough information) prompting the user to reply with the needed clarifications
|
||||
|
||||
{% if (tools is defined) and tools %} ## Available Tools
|
||||
{% for tool in tools %}
|
||||
{{tool.name}}: {{tool.description}}{% endfor %}
|
||||
**{{tool.name}}**
|
||||
Description: {{tool.description}}
|
||||
Parameters: {{tool.parameters}}
|
||||
|
||||
If the request is simple, such as a greeting or a request for information or advice, the plan can simply be:
|
||||
"reply to the user".
|
||||
|
||||
However for anything more complex, reflect on the available tools and describe a step by step
|
||||
solution that the agent can follow using their tools.
|
||||
|
||||
Your plan needs to use the following format, but can have any number of tasks.
|
||||
|
||||
```json
|
||||
[
|
||||
{"description": "the first task here"},
|
||||
{"description": "the second task here"},
|
||||
]
|
||||
```
|
||||
|
||||
# Examples
|
||||
|
||||
These examples show the format you should follow. *Do not reply with any other text, just the json plan*
|
||||
|
||||
```json
|
||||
[
|
||||
{"description": "reply to the user"},
|
||||
]
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
{"description": "create a directory 'demo'"},
|
||||
{"description": "write a file at 'demo/fibonacci.py' with a function fibonacci implementation"},
|
||||
{"description": "run python demo/fibonacci.py"},
|
||||
]
|
||||
```
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No tools are defined.
|
||||
{% endif %}
|
||||
## Guidelines
|
||||
1. Check for clarity and feasibility
|
||||
- If the user’s request is ambiguous, incomplete, or requires more information, respond only with all your clarifying questions in a concise list.
|
||||
- If available tools are inadequate to complete the request, outline the gaps and suggest next steps or ask for additional tools or guidance.
|
||||
2. Create a detailed plan
|
||||
- Once you have sufficient clarity, produce a step-by-step plan that covers all actions the executor AI must take.
|
||||
- Number the steps, and explicitly note any dependencies between steps (e.g., “Use the output from Step 3 as input for Step 4”).
|
||||
- Include any conditional or branching logic needed (e.g., “If X occurs, do Y; otherwise, do Z”).
|
||||
3. Provide essential context
|
||||
- The executor AI will see only your final plan (as a user message) or your questions (as an assistant message) and will not have access to this conversation’s full history.
|
||||
- Therefore, restate any relevant background, instructions, or prior conversation details needed to execute the plan successfully.
|
||||
4. One-time response
|
||||
- You can respond only once.
|
||||
- If you respond with a plan, it will appear as a user message in a fresh conversation for the executor AI, effectively clearing out the previous context.
|
||||
- If you respond with clarifying questions, it will appear as an assistant message in this same conversation, prompting the user to reply with the needed clarifications.
|
||||
5. Keep it action oriented and clear
|
||||
- In your final output (whether plan or questions), be concise yet thorough.
|
||||
- The goal is to enable the executor AI to proceed confidently, without further ambiguity.
|
||||
|
||||
Reference in New Issue
Block a user