feat(agent): add CoT, Reflexion, and ReWOO reasoning modes
Implement three alternative reasoning strategies: - Chain-of-Thought (cot): explicit step-by-step reasoning - Reflexion: self-critique with revise cycle - ReWOO: reasoning with external observation tokens
This commit is contained in:
parent
e9d5407c66
commit
27b9d3e4bd
3
libs/agent/modes/cot/mod.rs
Normal file
3
libs/agent/modes/cot/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod types;
|
||||
|
||||
pub use types::{CotStep, COT_SYSTEM_PROMPT};
|
||||
58
libs/agent/modes/cot/types.rs
Normal file
58
libs/agent/modes/cot/types.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use crate::modes::ModeStep;
|
||||
|
||||
pub const COT_SYSTEM_PROMPT: &str = r#"You are an AI assistant embedded in a development collaboration platform.
|
||||
|
||||
## Core Rule: Think Step by Step
|
||||
|
||||
When answering questions, you MUST reason step by step before providing a final answer. Break down the problem into clear intermediate steps. Show your reasoning process explicitly.
|
||||
|
||||
## Tool Use
|
||||
|
||||
- Use available tools to gather information when needed — verify claims against actual data.
|
||||
- After receiving tool results, incorporate them into your reasoning chain.
|
||||
- Do NOT guess when tools can provide concrete answers.
|
||||
|
||||
## Output Format
|
||||
|
||||
1. Reason through the problem step by step, using tools as needed.
|
||||
2. Then provide a clear, actionable final answer.
|
||||
3. Label your final answer with "**Answer:**" or similar.
|
||||
"#;
|
||||
|
||||
/// Events emitted during a CoT reasoning cycle (step-by-step with optional tools).
|
||||
pub enum CotStep {
|
||||
/// Intermediate reasoning thought.
|
||||
Thought(String),
|
||||
/// A tool call requested by the model.
|
||||
Action { name: String, args: serde_json::Value },
|
||||
/// Result returned by a tool execution.
|
||||
Observation(String),
|
||||
/// Final answer after reasoning.
|
||||
Answer(String),
|
||||
}
|
||||
|
||||
impl ModeStep for CotStep {
|
||||
fn chunk_type(&self) -> &'static str {
|
||||
match self {
|
||||
CotStep::Thought(_) => "thinking",
|
||||
CotStep::Action { .. } => "tool_call",
|
||||
CotStep::Observation(_) => "tool_result",
|
||||
CotStep::Answer(_) => "answer",
|
||||
}
|
||||
}
|
||||
|
||||
fn content(&self) -> String {
|
||||
match self {
|
||||
CotStep::Thought(t) => t.clone(),
|
||||
CotStep::Action { name, args } => {
|
||||
serde_json::json!({"name": name, "arguments": args}).to_string()
|
||||
}
|
||||
CotStep::Observation(o) => o.clone(),
|
||||
CotStep::Answer(a) => a.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_final(&self) -> bool {
|
||||
matches!(self, CotStep::Answer(_))
|
||||
}
|
||||
}
|
||||
13
libs/agent/modes/mod.rs
Normal file
13
libs/agent/modes/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub mod cot;
|
||||
pub mod reflexion;
|
||||
pub mod rewoo;
|
||||
|
||||
pub use cot::{CotStep, COT_SYSTEM_PROMPT};
|
||||
pub use reflexion::{ReflexionCycle, ReflexionStep, REFLEXION_CRITIQUE_PROMPT, REFLEXION_REVISE_PROMPT, REFLEXION_SYSTEM_PROMPT};
|
||||
pub use rewoo::{ReWooPlan, ReWooStep, ReWooToolCall, REWOO_SYSTEM_PROMPT, extract_plan};
|
||||
|
||||
pub trait ModeStep: Send + 'static {
|
||||
fn chunk_type(&self) -> &'static str;
|
||||
fn content(&self) -> String;
|
||||
fn is_final(&self) -> bool;
|
||||
}
|
||||
3
libs/agent/modes/reflexion/mod.rs
Normal file
3
libs/agent/modes/reflexion/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod types;
|
||||
|
||||
pub use types::{ReflexionCycle, ReflexionStep, REFLEXION_CRITIQUE_PROMPT, REFLEXION_REVISE_PROMPT, REFLEXION_SYSTEM_PROMPT};
|
||||
70
libs/agent/modes/reflexion/types.rs
Normal file
70
libs/agent/modes/reflexion/types.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use crate::modes::ModeStep;
|
||||
|
||||
pub const REFLEXION_SYSTEM_PROMPT: &str = r#"You are an AI assistant embedded in a development collaboration platform.
|
||||
|
||||
## Reflexion Protocol: Generate → Critique → Improve
|
||||
|
||||
Your responses follow a self-reflection loop to maximize answer quality.
|
||||
|
||||
### Round 1 — Generate
|
||||
Produce your best initial answer to the user's question. Use available tools if needed.
|
||||
|
||||
After your answer, the system will prompt you for a **self-critique**. When you see the critique prompt, evaluate your own answer honestly:
|
||||
- Did you verify all claims with available data?
|
||||
- Are there gaps or assumptions in your reasoning?
|
||||
- Could any part be clearer or more precise?
|
||||
|
||||
### Round 2 — Revise
|
||||
Based on your self-critique, produce a revised, improved answer. The system may repeat the critique-revise cycle up to 3 times.
|
||||
|
||||
### Guidelines
|
||||
- Be honest in self-assessment.
|
||||
- Each revision should be measurably better than the previous one.
|
||||
- If your first answer was already correct and complete, say so in your critique and confirm it in the revision.
|
||||
"#;
|
||||
|
||||
pub const REFLEXION_CRITIQUE_PROMPT: &str =
|
||||
"Now critique your own answer above. Identify any gaps, inaccuracies, \
|
||||
or areas for improvement. Be specific and honest.";
|
||||
|
||||
pub const REFLEXION_REVISE_PROMPT: &str =
|
||||
"Based on your self-critique, produce a revised and improved answer.";
|
||||
|
||||
pub enum ReflexionStep {
|
||||
Generate(String),
|
||||
Critique(String),
|
||||
Revise(String),
|
||||
Final(String),
|
||||
}
|
||||
|
||||
impl ModeStep for ReflexionStep {
|
||||
fn chunk_type(&self) -> &'static str {
|
||||
match self {
|
||||
ReflexionStep::Generate(_) => "thinking",
|
||||
ReflexionStep::Critique(_) => "tool_call",
|
||||
ReflexionStep::Revise(_) => "thinking",
|
||||
ReflexionStep::Final(_) => "answer",
|
||||
}
|
||||
}
|
||||
|
||||
fn content(&self) -> String {
|
||||
match self {
|
||||
ReflexionStep::Generate(s) => s.clone(),
|
||||
ReflexionStep::Critique(s) => s.clone(),
|
||||
ReflexionStep::Revise(s) => s.clone(),
|
||||
ReflexionStep::Final(s) => s.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_final(&self) -> bool {
|
||||
matches!(self, ReflexionStep::Final(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ReflexionCycle {
|
||||
pub round: usize,
|
||||
pub generated: String,
|
||||
pub critique: String,
|
||||
pub revised: String,
|
||||
}
|
||||
3
libs/agent/modes/rewoo/mod.rs
Normal file
3
libs/agent/modes/rewoo/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod types;
|
||||
|
||||
pub use types::{ReWooPlan, ReWooStep, ReWooToolCall, REWOO_SYSTEM_PROMPT, extract_plan};
|
||||
108
libs/agent/modes/rewoo/types.rs
Normal file
108
libs/agent/modes/rewoo/types.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::modes::ModeStep;
|
||||
|
||||
pub const REWOO_SYSTEM_PROMPT: &str = r#"You are an AI assistant embedded in a development collaboration platform.
|
||||
|
||||
## ReWOO Protocol: Reason Without Observation
|
||||
|
||||
Your responses MUST follow a strict **Plan → Execute → Synthesize** protocol.
|
||||
|
||||
### Phase 1 — Plan
|
||||
Analyze the user's question and produce a structured plan listing every tool call needed. Output your plan as a JSON array:
|
||||
|
||||
```json
|
||||
[
|
||||
{"step": 1, "tool": "tool_name", "args": {"param": "value"}},
|
||||
{"step": 2, "tool": "tool_name", "args": {"param": "value"}}
|
||||
]
|
||||
```
|
||||
|
||||
The plan must be wrapped in a `[PLAN]` ... `[/PLAN]` block. Only output the plan — no other text in this block.
|
||||
|
||||
### Phase 2 — Execute
|
||||
All tool calls in the plan will be executed automatically in parallel. You will receive the results as context.
|
||||
|
||||
### Phase 3 — Synthesize
|
||||
Based on the tool results, synthesize a comprehensive final answer. Cite specific data from the results.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ReWooToolCall {
|
||||
pub step: usize,
|
||||
pub tool: String,
|
||||
pub args: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ReWooPlan {
|
||||
pub calls: Vec<ReWooToolCall>,
|
||||
pub raw_text: String,
|
||||
}
|
||||
|
||||
pub enum ReWooStep {
|
||||
Plan { calls: Vec<ReWooToolCall>, raw: String },
|
||||
Execution { tool_name: String, result: String },
|
||||
Synthesis(String),
|
||||
}
|
||||
|
||||
impl ModeStep for ReWooStep {
|
||||
fn chunk_type(&self) -> &'static str {
|
||||
match self {
|
||||
ReWooStep::Plan { .. } => "tool_call",
|
||||
ReWooStep::Execution { .. } => "tool_result",
|
||||
ReWooStep::Synthesis(_) => "answer",
|
||||
}
|
||||
}
|
||||
|
||||
fn content(&self) -> String {
|
||||
match self {
|
||||
ReWooStep::Plan { calls, raw } => {
|
||||
if !raw.is_empty() {
|
||||
raw.clone()
|
||||
} else {
|
||||
serde_json::to_string(calls).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
ReWooStep::Execution { tool_name, result } => {
|
||||
format!("{}: {}", tool_name, result)
|
||||
}
|
||||
ReWooStep::Synthesis(s) => s.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_final(&self) -> bool {
|
||||
matches!(self, ReWooStep::Synthesis(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_plan(text: &str) -> Option<ReWooPlan> {
|
||||
if let (Some(start), Some(end)) = (text.find("[PLAN]"), text.find("[/PLAN]")) {
|
||||
let inner = &text[start + 6..end].trim();
|
||||
if inner.starts_with('[') || inner.starts_with('{') {
|
||||
if let Ok(calls) = serde_json::from_str::<Vec<ReWooToolCall>>(inner) {
|
||||
return Some(ReWooPlan { calls, raw_text: text.to_string() });
|
||||
}
|
||||
}
|
||||
return Some(ReWooPlan { calls: Vec::new(), raw_text: text.to_string() });
|
||||
}
|
||||
|
||||
if let Some(array_start) = text.find('[') {
|
||||
let candidate = &text[array_start..];
|
||||
if let Some(array_end) = find_matching_brace(candidate, '[', ']') {
|
||||
let inner = &candidate[..=array_end];
|
||||
if let Ok(calls) = serde_json::from_str::<Vec<ReWooToolCall>>(inner) {
|
||||
return Some(ReWooPlan { calls, raw_text: text.to_string() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn find_matching_brace(s: &str, open: char, close: char) -> Option<usize> {
|
||||
let mut depth = 0i32;
|
||||
for (i, c) in s.char_indices() {
|
||||
if c == open { depth += 1; }
|
||||
else if c == close { depth -= 1; if depth == 0 { return Some(i); } }
|
||||
}
|
||||
None
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user