From 27b9d3e4bd4e8c81a638113a8d2025bbb9b809cc Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Thu, 30 Apr 2026 19:15:55 +0800 Subject: [PATCH] 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 --- libs/agent/modes/cot/mod.rs | 3 + libs/agent/modes/cot/types.rs | 58 +++++++++++++++ libs/agent/modes/mod.rs | 13 ++++ libs/agent/modes/reflexion/mod.rs | 3 + libs/agent/modes/reflexion/types.rs | 70 ++++++++++++++++++ libs/agent/modes/rewoo/mod.rs | 3 + libs/agent/modes/rewoo/types.rs | 108 ++++++++++++++++++++++++++++ 7 files changed, 258 insertions(+) create mode 100644 libs/agent/modes/cot/mod.rs create mode 100644 libs/agent/modes/cot/types.rs create mode 100644 libs/agent/modes/mod.rs create mode 100644 libs/agent/modes/reflexion/mod.rs create mode 100644 libs/agent/modes/reflexion/types.rs create mode 100644 libs/agent/modes/rewoo/mod.rs create mode 100644 libs/agent/modes/rewoo/types.rs diff --git a/libs/agent/modes/cot/mod.rs b/libs/agent/modes/cot/mod.rs new file mode 100644 index 0000000..a47f198 --- /dev/null +++ b/libs/agent/modes/cot/mod.rs @@ -0,0 +1,3 @@ +pub mod types; + +pub use types::{CotStep, COT_SYSTEM_PROMPT}; \ No newline at end of file diff --git a/libs/agent/modes/cot/types.rs b/libs/agent/modes/cot/types.rs new file mode 100644 index 0000000..783dddb --- /dev/null +++ b/libs/agent/modes/cot/types.rs @@ -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(_)) + } +} diff --git a/libs/agent/modes/mod.rs b/libs/agent/modes/mod.rs new file mode 100644 index 0000000..b51b75c --- /dev/null +++ b/libs/agent/modes/mod.rs @@ -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; +} diff --git a/libs/agent/modes/reflexion/mod.rs b/libs/agent/modes/reflexion/mod.rs new file mode 100644 index 0000000..4ceda48 --- /dev/null +++ b/libs/agent/modes/reflexion/mod.rs @@ -0,0 +1,3 @@ +pub mod types; + +pub use types::{ReflexionCycle, ReflexionStep, REFLEXION_CRITIQUE_PROMPT, REFLEXION_REVISE_PROMPT, REFLEXION_SYSTEM_PROMPT}; \ No newline at end of file diff --git a/libs/agent/modes/reflexion/types.rs b/libs/agent/modes/reflexion/types.rs new file mode 100644 index 0000000..06ad8fb --- /dev/null +++ b/libs/agent/modes/reflexion/types.rs @@ -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, +} diff --git a/libs/agent/modes/rewoo/mod.rs b/libs/agent/modes/rewoo/mod.rs new file mode 100644 index 0000000..3caf142 --- /dev/null +++ b/libs/agent/modes/rewoo/mod.rs @@ -0,0 +1,3 @@ +pub mod types; + +pub use types::{ReWooPlan, ReWooStep, ReWooToolCall, REWOO_SYSTEM_PROMPT, extract_plan}; \ No newline at end of file diff --git a/libs/agent/modes/rewoo/types.rs b/libs/agent/modes/rewoo/types.rs new file mode 100644 index 0000000..0266172 --- /dev/null +++ b/libs/agent/modes/rewoo/types.rs @@ -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, + pub raw_text: String, +} + +pub enum ReWooStep { + Plan { calls: Vec, 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 { + 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::>(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::>(inner) { + return Some(ReWooPlan { calls, raw_text: text.to_string() }); + } + } + } + + None +} + +fn find_matching_brace(s: &str, open: char, close: char) -> Option { + 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 +}