feat: add /plan command in CLI to invoke reasoner with plan system prompt (#1616)

This commit is contained in:
Salman Mohammed
2025-03-20 10:10:01 -04:00
committed by GitHub
parent 3a4866cb7d
commit e273f8ebce
10 changed files with 369 additions and 51 deletions
+3
View File
@@ -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>>;
}
+17 -1
View File
@@ -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();
+18
View File
@@ -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,
}
}
}
+15
View File
@@ -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()
+15
View File
@@ -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()
+15 -1
View File
@@ -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()
+29 -38
View File
@@ -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 users 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 users 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 conversations 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.