lc_agents/orchestrator/mod.rs
1//! Common trait for high-level orchestrators (P1-1)
2//!
3//! `PlanExecuteAgent` / `DeepResearchAgent` / `CorrectiveRAGAgent` / `AdaptiveRAG`
4//! each used to define its own `run()`, with incompatible signatures that could
5//! not be composed or plugged into LCEL. This module unifies them:
6//!
7//! - [`Orchestrator`] defines `run_with_context(input, ctx)`, with errors
8//! unified to [`AgentError`].
9//! - [`RunContext`] carries `trace_id` (P1-4 observability) and a cross-step
10//! shared workspace.
11//! - [`crate::adapter::OrchestratorRunnable`] lets orchestrators enter LCEL pipelines.
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use lc_agents::orchestration::{Orchestrator, RunContext};
17//!
18//! let plan_agent = PlanExecuteAgent::new(llm, tools);
19//! let ctx = RunContext::new("trace-abc");
20//! let output = plan_agent.run_with_context("目标".to_string(), &ctx).await?;
21//! ```
22
23use std::sync::{Arc, Mutex};
24
25use async_trait::async_trait;
26use lc_core::runnables::RunnableConfig;
27use serde_json::Value;
28
29use crate::AgentError;
30
31mod fan_out_fan_in;
32mod impls;
33mod review;
34mod sequential;
35mod task_adapter;
36#[cfg(test)]
37mod tests;
38
39pub use fan_out_fan_in::FanOutFanIn;
40pub use review::{parse_review_verdict, review_envelope, ReviewOrchestrator, ReviewVerdict};
41pub use sequential::SequentialPipeline;
42pub use task_adapter::{task_adapter, TaskAdapter};
43
44/// Common trait for high-level orchestrators.
45///
46/// Associated types express each orchestrator's different input/output
47/// (PlanExecute→String, AdaptiveRAG→AdaptiveRAGResult, etc.); `run_with_context`
48/// unifies the signature + [`AgentError`], so orchestrators are composable and
49/// can enter LCEL.
50#[async_trait]
51pub trait Orchestrator: Send + Sync {
52 /// Input type (usually a `String` objective/question).
53 type Input;
54 /// Output type.
55 type Output;
56
57 /// Execution entry point carrying the run context.
58 async fn run_with_context(
59 &self,
60 input: Self::Input,
61 ctx: &RunContext,
62 ) -> Result<Self::Output, AgentError>;
63}
64
65/// Orchestrator run context.
66///
67/// `trace_id` propagates across multi-agent / cross-step call chains (P1-4);
68/// `shared_state` provides a JSON workspace shared across steps.
69#[derive(Debug, Clone)]
70pub struct RunContext {
71 /// Trace ID: shared across the whole call chain, for log/audit/metric correlation.
72 pub trace_id: String,
73 /// Workspace shared across steps (optional).
74 pub shared_state: Option<Arc<Mutex<Value>>>,
75}
76
77/// Generates a lightweight trace_id (hex timestamp).
78pub fn generate_trace_id() -> String {
79 use std::time::{SystemTime, UNIX_EPOCH};
80 let nanos = SystemTime::now()
81 .duration_since(UNIX_EPOCH)
82 .map(|d| d.as_nanos())
83 .unwrap_or(0);
84 format!("trace-{:x}", nanos)
85}
86
87impl RunContext {
88 /// Creates a context with the given `trace_id`.
89 pub fn new(trace_id: impl Into<String>) -> Self {
90 Self {
91 trace_id: trace_id.into(),
92 shared_state: None,
93 }
94 }
95
96 /// Creates a context, auto-generating `trace_id`.
97 pub fn new_random() -> Self {
98 Self::new(generate_trace_id())
99 }
100
101 /// Carries the shared workspace.
102 pub fn with_shared_state(mut self, shared_state: Arc<Mutex<Value>>) -> Self {
103 self.shared_state = Some(shared_state);
104 self
105 }
106
107 /// Extracts `trace_id` from the LCEL [`RunnableConfig`] (reads
108 /// `metadata["trace_id"]`), generating one if missing. Used to thread the
109 /// LCEL pipeline's trace through to the orchestrator.
110 pub fn from_config(config: &RunnableConfig) -> Self {
111 let trace_id = config
112 .metadata
113 .get("trace_id")
114 .and_then(|v| v.as_str())
115 .map(|s| s.to_string())
116 .unwrap_or_else(generate_trace_id);
117 Self::new(trace_id)
118 }
119}