pub mod act;
pub mod analyze;
pub mod answer;
pub mod compose;
pub mod custom;
pub mod decompose;
pub mod diagnose;
pub mod evaluate;
pub mod explain;
pub mod generate;
pub mod hypothesize;
pub mod observe;
pub mod optimize;
pub mod plan;
pub mod prioritize;
pub mod reason;
pub mod reduce;
pub mod reflect;
pub mod revise;
pub mod search;
pub mod select;
pub mod simulate;
pub mod solve;
pub mod synthesize;
pub mod test_op;
pub mod verify;
pub use self::act::ActOperator;
pub use self::analyze::AnalyzeOperator;
pub use self::answer::AnswerOperator;
pub use self::compose::ComposeOperator;
pub use self::custom::CustomOperator;
pub use self::decompose::DecomposeOperator;
pub use self::diagnose::DiagnoseOperator;
pub use self::evaluate::EvaluateOperator;
pub use self::explain::ExplainOperator;
pub use self::generate::GenerateOperator;
pub use self::hypothesize::HypothesizeOperator;
pub use self::observe::ObserveOperator;
pub use self::optimize::OptimizeOperator;
pub use self::plan::PlanOperator;
pub use self::prioritize::PrioritizeOperator;
pub use self::reason::ReasonOperator;
pub use self::reduce::ReduceOperator;
pub use self::reflect::ReflectOperator;
pub use self::revise::ReviseOperator;
pub use self::search::SearchOperator;
pub use self::select::SelectOperator;
pub use self::simulate::SimulateOperator;
pub use self::solve::SolveOperator;
pub use self::synthesize::SynthesizeOperator;
pub use self::test_op::TestOperator;
pub use self::verify::VerifyOperator;
use std::fmt::Write;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::reasoning::error::ReasoningError;
use crate::reasoning::state::{Step, StepStatus, TaskState};
use crate::runtime::invocation::{Control, RuntimeInvocation};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum OperatorKind {
Analyze,
Decompose,
Plan,
Reason,
Act,
Observe,
Solve,
Verify,
Reflect,
Revise,
Select,
Compose,
Reduce,
Explain,
Evaluate,
Generate,
Hypothesize,
Test,
Search,
Synthesize,
Prioritize,
Optimize,
Simulate,
Diagnose,
Answer,
}
impl OperatorKind {
#[must_use]
pub const fn is_terminal(&self) -> bool {
matches!(self, Self::Answer)
}
#[must_use]
pub const fn requires_tools(&self) -> bool {
matches!(
self,
Self::Act | Self::Observe | Self::Search | Self::Test | Self::Simulate
)
}
}
#[async_trait]
pub trait LlmProvider: Send + Sync {
async fn complete(&self, prompt: &str, system: Option<&str>) -> Result<String, ReasoningError>;
}
pub struct OperatorContext<'a> {
pub invocation: Option<&'a RuntimeInvocation>,
pub control: Option<&'a Control>,
pub memory: Option<&'a dyn MemoryStore>,
pub llm: Option<&'a dyn LlmProvider>,
}
#[async_trait]
pub trait MemoryStore: Send + Sync {
async fn get(&self, key: &str) -> Option<String>;
async fn keys(&self, prefix: &str) -> Vec<String>;
}
#[async_trait]
pub trait ReasoningOperator: Send + Sync {
fn kind(&self) -> OperatorKind;
fn name(&self) -> &'static str;
fn prompt(&self) -> &str;
fn set_prompt(&mut self, prompt: String);
async fn apply(
&self,
ctx: &OperatorContext<'_>,
state: TaskState,
) -> Result<TaskState, ReasoningError>;
}
pub struct BaseOperator {
pub kind: OperatorKind,
pub name: &'static str,
pub prompt: String,
}
impl BaseOperator {
#[must_use]
pub fn new(kind: OperatorKind, name: &'static str) -> Self {
Self {
kind,
name,
prompt: String::new(),
}
}
#[must_use]
pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
self.prompt = prompt.into();
self
}
}
pub(crate) fn format_state(prompt: &str, state: &TaskState) -> String {
let mut buf = String::new();
let _ = writeln!(buf, "Goal: {}", state.goal.description);
if !state.constraints.is_empty() {
let _ = writeln!(buf);
let _ = writeln!(buf, "Constraints:");
for c in &state.constraints {
let _ = writeln!(buf, "- {} ({:?})", c.description, c.kind);
}
}
let _ = writeln!(buf);
let _ = writeln!(buf, "Context: {}", state.context.input);
if !state.context.facts.is_empty() {
let _ = writeln!(buf, "Facts:");
for f in &state.context.facts {
let _ = writeln!(buf, "- {f}");
}
}
if let Some(ref plan) = state.plan {
let _ = writeln!(buf);
let _ = writeln!(buf, "Plan: {}", plan.rationale);
for step in &plan.steps {
let _ = writeln!(
buf,
"- Step {}: {} ({:?})",
step.id, step.description, step.status
);
}
}
if !state.observations.is_empty() {
let _ = writeln!(buf);
let _ = writeln!(buf, "Observations:");
for o in &state.observations {
let _ = writeln!(
buf,
"- [{}] {} (from {:?})",
o.sequence, o.content, o.source
);
}
}
if !state.actions.is_empty() {
let _ = writeln!(buf);
let _ = writeln!(buf, "Actions:");
for a in &state.actions {
let tool_str = a.tool.as_deref().unwrap_or("reasoning");
let _ = writeln!(buf, "- [{}] {} -> {:?}", tool_str, a.input, a.output);
}
}
if !state.reflections.is_empty() {
let _ = writeln!(buf);
let _ = writeln!(buf, "Reflections:");
for r in &state.reflections {
let _ = writeln!(buf, "- {}", r.content);
}
}
if !state.artifacts.is_empty() {
let _ = writeln!(buf);
let _ = writeln!(buf, "Artifacts:");
for a in &state.artifacts {
let _ = writeln!(buf, "- [{:?}] {}", a.kind, a.content);
}
}
let _ = writeln!(buf);
let _ = write!(buf, "---\n{prompt}");
buf
}
pub(crate) async fn call_llm_and_record_step(
ctx: &OperatorContext<'_>,
kind: OperatorKind,
name: &str,
prompt: &str,
state: TaskState,
system: &str,
) -> Result<(String, TaskState), ReasoningError> {
let llm = ctx.llm.ok_or_else(|| ReasoningError::OperatorFailed {
operator: name.into(),
message: "no LLM provider configured in operator context".into(),
})?;
let user = format_state(prompt, &state);
let response =
llm.complete(&user, Some(system))
.await
.map_err(|e| ReasoningError::OperatorFailed {
operator: name.into(),
message: e.to_string(),
})?;
let new_state = state.record_step(Step {
operator: kind,
input: user,
output: Some(response.clone()),
status: StepStatus::Completed,
});
Ok((response, new_state))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn operator_kind_is_terminal() {
assert!(OperatorKind::Answer.is_terminal());
assert!(!OperatorKind::Reason.is_terminal());
assert!(!OperatorKind::Act.is_terminal());
assert!(!OperatorKind::Explain.is_terminal());
assert!(!OperatorKind::Evaluate.is_terminal());
assert!(!OperatorKind::Generate.is_terminal());
}
#[test]
fn operator_kind_requires_tools() {
assert!(OperatorKind::Act.requires_tools());
assert!(OperatorKind::Observe.requires_tools());
assert!(OperatorKind::Search.requires_tools());
assert!(OperatorKind::Test.requires_tools());
assert!(OperatorKind::Simulate.requires_tools());
assert!(!OperatorKind::Reason.requires_tools());
assert!(!OperatorKind::Plan.requires_tools());
assert!(!OperatorKind::Explain.requires_tools());
assert!(!OperatorKind::Evaluate.requires_tools());
}
#[test]
fn operator_kind_should_serialize() {
let kind = OperatorKind::Analyze;
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, "\"Analyze\"");
let deserialized: OperatorKind = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, OperatorKind::Analyze);
}
#[test]
fn all_operator_kinds_should_serialize_roundtrip() {
let kinds = [
OperatorKind::Analyze,
OperatorKind::Decompose,
OperatorKind::Plan,
OperatorKind::Reason,
OperatorKind::Act,
OperatorKind::Observe,
OperatorKind::Solve,
OperatorKind::Verify,
OperatorKind::Reflect,
OperatorKind::Revise,
OperatorKind::Select,
OperatorKind::Compose,
OperatorKind::Reduce,
OperatorKind::Explain,
OperatorKind::Evaluate,
OperatorKind::Generate,
OperatorKind::Hypothesize,
OperatorKind::Test,
OperatorKind::Search,
OperatorKind::Synthesize,
OperatorKind::Prioritize,
OperatorKind::Optimize,
OperatorKind::Simulate,
OperatorKind::Diagnose,
OperatorKind::Answer,
];
for kind in &kinds {
let json = serde_json::to_string(kind).unwrap();
let deserialized: OperatorKind = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, *kind);
}
}
#[test]
fn base_operator_new() {
let base = BaseOperator::new(OperatorKind::Analyze, "Analyze");
assert_eq!(base.kind, OperatorKind::Analyze);
assert_eq!(base.name, "Analyze");
assert!(base.prompt.is_empty());
}
}