feat: add /plan command in CLI to invoke reasoner with plan system prompt (#1616)
This commit is contained in:
@@ -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