Skip to main content

lellm_agent/runtime/
runtime.rs

1//! Agent Loop — LLM ↔ 工具调用闭环。
2//!
3//! 负责 LLM 返回 tool_calls → 执行工具 → 结果注入 → 再次调用 LLM 的循环,
4//! 直到 LLM 返回纯文本或达到最大轮次。
5//!
6//! # 架构分层
7//!
8//! ```text
9//! ToolUseLoop (薄 Facade)
10//! ├── graph:  Graph<AgentState>  (预构建的 ReAct Graph)
11//! └── config: ToolUseConfig      (构建 ExecutionContext 的默认参数)
12//!
13//! AgentBuilder::build()   → Arc<Graph<AgentState>>   (DSL 层)
14//! AgentBuilder::compile() → ToolUseLoop              (Facade 层)
15//! ```
16
17use lellm_core::{ChatResponse, LlmError, Message};
18use lellm_graph::{Graph, NoopStepCallback};
19use std::sync::Arc;
20
21use super::config::{ToolUseConfig, build_request_messages_inner, empty_response};
22use super::event::{AgentEvent, AgentStream, StopReason};
23use super::tools::ToolSnapshot;
24use super::typed_state::{AgentState, AgentStateMerge};
25
26// ─── 本轮解析数据 ────────────────────────────────────────────────
27
28/// 本轮对话锁定的快照 + 定义。
29///
30/// 一旦创建,内容不再变化。充当单轮的"真理之源"。
31#[derive(Clone)]
32pub struct ResolvedRound {
33    /// 本轮对话锁定的快照
34    pub snapshot: Arc<ToolSnapshot>,
35    /// 为当前 LLM 供给的工具定义(已在前置阶段从快照中提取并平铺)
36    pub definitions: Vec<lellm_core::ToolDefinition>,
37}
38
39impl ResolvedRound {
40    pub fn new(snapshot: Arc<ToolSnapshot>) -> Self {
41        Self {
42            definitions: snapshot.definitions().to_vec(),
43            snapshot,
44        }
45    }
46}
47
48// ─── 执行结果 ───────────────────────────────────────────────────
49
50/// ToolUseLoop 执行结果
51#[derive(Debug, Clone)]
52pub struct ToolUseResult {
53    pub stop_reason: StopReason,
54    pub response: ChatResponse,
55    pub messages: Vec<Message>,
56    pub iterations: usize,
57    pub tool_calls_executed: usize,
58}
59
60impl ToolUseResult {
61    pub fn is_success(&self) -> bool {
62        matches!(self.stop_reason, StopReason::Complete)
63    }
64}
65
66// ─── ToolUseLoop ────────────────────────────────────────────────
67
68/// 薄 Facade — 持有预构建的 Graph,提供便捷执行 API。
69///
70/// 不是独立的运行时,只是 Graph 的便捷包装。
71/// 内部调用 `Graph::run_inline()` / `Graph::run_stream()`,不创建独立的 ExecutionEngine。
72///
73/// # 架构
74///
75/// ```text
76/// AgentBuilder::build()   → Arc<Graph<AgentState>>   (DSL 层)
77/// AgentBuilder::compile() → ToolUseLoop              (Facade 层)
78///
79/// ToolUseLoop {
80///     graph: Arc<Graph<AgentState>>,  // 共享的 ReAct Graph
81///     config: ToolUseConfig,          // 构建 ExecutionContext 的默认参数
82/// }
83/// ```
84///
85/// # 示例
86///
87/// ```ignore
88/// let agent = AgentBuilder::new(model).tools([...]).compile();
89/// let result = agent.invoke(messages).await?;
90/// println!("Answer: {}", result.response.text());
91/// ```
92#[derive(Clone)]
93pub struct ToolUseLoop {
94    graph: Arc<Graph<AgentState, AgentStateMerge>>,
95    config: ToolUseConfig,
96}
97
98impl ToolUseLoop {
99    /// 从预构建的 Graph 创建 Facade。
100    pub fn new(graph: Arc<Graph<AgentState, AgentStateMerge>>, config: ToolUseConfig) -> Self {
101        Self { graph, config }
102    }
103
104    /// 获取 Graph 引用。
105    pub fn graph(&self) -> &Graph<AgentState, AgentStateMerge> {
106        &self.graph
107    }
108
109    /// 获取配置引用。
110    pub fn config(&self) -> &ToolUseConfig {
111        &self.config
112    }
113
114    /// 非流式执行 — 执行 Agent 循环并返回结果。
115    ///
116    /// 内部使用预构建的 Graph,调用 `run_inline()` 执行 ReAct 循环。
117    ///
118    /// # 示例
119    /// ```ignore
120    /// let agent = AgentBuilder::new(model).tools([...]).compile();
121    /// let result = agent.invoke(messages).await?;
122    /// println!("Answer: {}", result.response.text());
123    /// ```
124    pub async fn invoke(&self, messages: Vec<Message>) -> Result<ToolUseResult, LlmError> {
125        let initial_messages = build_request_messages_inner(&self.config, &messages)?;
126
127        // 每轮 ReAct 迭代最坏 4 steps: budget_check + llm + post_llm_check + tool
128        // N 轮最坏: 4*(N-1) + 3 = 4N-1 (最后一轮无 tool)
129        // +1 buffer 应对 edge cases
130        let max_steps = self.config.max_iterations * 4 + 1;
131
132        // 初始化 AgentState
133        let mut agent_state_init = super::typed_state::AgentState::from_messages(initial_messages);
134
135        // 创建 ExecutionContext<AgentState> 并调用 run_inline
136        // ToolUseLoop 不需要自动 checkpoint/barrier,传 None
137        let mut exec_ctx = lellm_graph::ExecutionContext::new(
138            &mut agent_state_init,
139            None,
140            lellm_graph::CancellationToken::new(),
141            None,
142            None,
143        );
144
145        let mut cb = NoopStepCallback;
146        self.graph
147            .run_inline(&mut exec_ctx, max_steps, &mut cb)
148            .await
149            .map_err(|e| lellm_core::LlmError::Provider {
150                provider: "react_graph".into(),
151                status: None,
152                code: None,
153                message: e.to_string(),
154            })?;
155
156        let agent_state = exec_ctx.state();
157        let stop_reason = agent_state
158            .stop_reason
159            .clone()
160            .unwrap_or(StopReason::Complete);
161        let last_response = agent_state
162            .last_response
163            .clone()
164            .unwrap_or_else(empty_response);
165
166        Ok(ToolUseResult {
167            stop_reason,
168            response: last_response,
169            messages: agent_state.messages.clone(),
170            iterations: agent_state.iterations,
171            tool_calls_executed: agent_state.total_tool_calls,
172        })
173    }
174
175    /// 流式执行,返回事件接收器。
176    ///
177    /// 内部使用预构建的 Graph,调用 `run_inline()` + `AgentEventSink`。
178    ///
179    /// # 示例
180    /// ```ignore
181    /// let agent = AgentBuilder::new(model).tools([...]).compile();
182    /// let mut stream = agent.invoke_stream(messages);
183    /// while let Some(event) = stream.recv().await {
184    ///     match event {
185    ///         AgentEvent::LoopEnd { result } => println!("Done: {}", result.response.text()),
186    ///         AgentEvent::Provider(e) => print!("{}", e.delta()),
187    ///         _ => {}
188    ///     }
189    /// }
190    /// ```
191    pub fn invoke_stream(&self, messages: Vec<Message>) -> AgentStream {
192        let (tx, rx) = tokio::sync::mpsc::channel(32);
193        let graph = self.graph.clone();
194        let config = self.config.clone();
195
196        tokio::spawn(async move {
197            // 1. 构建初始消息
198            let initial_messages = match build_request_messages_inner(&config, &messages) {
199                Ok(m) => m,
200                Err(e) => {
201                    let _ = tx
202                        .send(AgentEvent::LoopError {
203                            error: e,
204                            iterations: 0,
205                        })
206                        .await;
207                    return;
208                }
209            };
210
211            // 每轮 ReAct 迭代最坏 4 steps: budget_check + llm + post_llm_check + tool
212            let max_steps = config.max_iterations * 4 + 1;
213
214            // 2. 初始化 AgentState
215            let mut agent_state = super::typed_state::AgentState::from_messages(initial_messages);
216
217            // 3. 创建 AgentEventSink (StreamChunk → AgentEvent 桥接)
218            let event_sink = super::event_bridge::AgentEventSink::new(tx.clone());
219            let sink: std::sync::Arc<dyn lellm_graph::StreamSink> = std::sync::Arc::new(event_sink);
220
221            // 4. 创建 ExecutionContext 并调用 run_inline
222            // ToolUseLoop 不需要自动 checkpoint/barrier,传 None
223            let mut exec_ctx = lellm_graph::ExecutionContext::new(
224                &mut agent_state,
225                Some(sink),
226                lellm_graph::CancellationToken::new(),
227                None,
228                None,
229            );
230
231            let mut cb = NoopStepCallback;
232            match graph.run_inline(&mut exec_ctx, max_steps, &mut cb).await {
233                Ok(()) => {
234                    // 执行成功,从 AgentState 提取结果
235                    let state = exec_ctx.state();
236                    let stop_reason = state.stop_reason.clone().unwrap_or(StopReason::Complete);
237                    let last_response = state.last_response.clone().unwrap_or_else(empty_response);
238
239                    let _ = tx
240                        .send(AgentEvent::LoopEnd {
241                            result: ToolUseResult {
242                                stop_reason,
243                                response: last_response,
244                                messages: state.messages.clone(),
245                                iterations: state.iterations,
246                                tool_calls_executed: state.total_tool_calls,
247                            },
248                        })
249                        .await;
250                }
251                Err(e) => {
252                    // 执行失败,发送 LoopError
253                    let state = exec_ctx.state();
254                    let error = LlmError::Provider {
255                        provider: "react_graph".into(),
256                        status: None,
257                        code: None,
258                        message: e.to_string(),
259                    };
260                    let _ = tx
261                        .send(AgentEvent::LoopError {
262                            error,
263                            iterations: state.iterations,
264                        })
265                        .await;
266                }
267            }
268        });
269
270        rx
271    }
272}