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:
ZhenYi 2026-04-30 19:15:55 +08:00
parent e9d5407c66
commit 27b9d3e4bd
7 changed files with 258 additions and 0 deletions

View File

@ -0,0 +1,3 @@
pub mod types;
pub use types::{CotStep, COT_SYSTEM_PROMPT};

View 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
View 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;
}

View File

@ -0,0 +1,3 @@
pub mod types;
pub use types::{ReflexionCycle, ReflexionStep, REFLEXION_CRITIQUE_PROMPT, REFLEXION_REVISE_PROMPT, REFLEXION_SYSTEM_PROMPT};

View 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,
}

View File

@ -0,0 +1,3 @@
pub mod types;
pub use types::{ReWooPlan, ReWooStep, ReWooToolCall, REWOO_SYSTEM_PROMPT, extract_plan};

View 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
}