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
10//! ├── model:       ResolvedModel     (Provider + model name)
11//! ├── executor:    ToolExecutor      (ToolCatalog + 执行引擎)
12//! ├── config:      ToolUseConfig     (纯参数, Clone + Send + Sync)
13//! └── deps:        ToolUseDeps       (策略服务, Arc 包裹)
14//! ```
15
16use lellm_core::{ChatResponse, LlmError, Message};
17use lellm_provider::ResolvedModel;
18use std::sync::Arc;
19
20use super::config::{ToolUseConfig, ToolUseDeps, build_request_messages_inner, empty_response};
21use super::context::LocalCompactor;
22use super::event::{AgentEvent, AgentStream, StopReason};
23use super::tools::{ToolExecutor, ToolSnapshot};
24
25// ─── 本轮解析数据 ────────────────────────────────────────────────
26
27/// 本轮对话锁定的快照 + 定义。
28///
29/// 一旦创建,内容不再变化。充当单轮的"真理之源"。
30#[derive(Clone)]
31pub struct ResolvedRound {
32    /// 本轮对话锁定的快照
33    pub snapshot: Arc<ToolSnapshot>,
34    /// 为当前 LLM 供给的工具定义(已在前置阶段从快照中提取并平铺)
35    pub definitions: Vec<lellm_core::ToolDefinition>,
36}
37
38impl ResolvedRound {
39    pub fn new(snapshot: Arc<ToolSnapshot>) -> Self {
40        Self {
41            definitions: snapshot.definitions().to_vec(),
42            snapshot,
43        }
44    }
45}
46
47// ─── 执行结果 ───────────────────────────────────────────────────
48
49/// ToolUseLoop 执行结果
50#[derive(Debug, Clone)]
51pub struct ToolUseResult {
52    pub stop_reason: StopReason,
53    pub response: ChatResponse,
54    pub messages: Vec<Message>,
55    pub iterations: usize,
56    pub tool_calls_executed: usize,
57}
58
59impl ToolUseResult {
60    pub fn is_success(&self) -> bool {
61        matches!(self.stop_reason, StopReason::Complete)
62    }
63}
64
65// ─── ToolUseLoop ────────────────────────────────────────────────
66
67/// 管理 LLM 与工具调用闭环。
68///
69/// 内部全为 Arc/Clone,clone 为 O(1),支持并发 execute。
70#[derive(Clone)]
71pub struct ToolUseLoop {
72    model: ResolvedModel,
73    executor: ToolExecutor,
74    config: ToolUseConfig,
75    deps: ToolUseDeps,
76}
77
78impl ToolUseLoop {
79    pub fn new(
80        model: ResolvedModel,
81        executor: ToolExecutor,
82        config: ToolUseConfig,
83        deps: ToolUseDeps,
84    ) -> Self {
85        if config.stream_thinking {
86            let caps = model.provider.capabilities_for(&model.model);
87            if !caps.supports_stream_thinking {
88                tracing::warn!(
89                    provider = %model.provider.provider_id(),
90                    model = %model.model,
91                    "stream_thinking=true but provider does not support thinking deltas; \
92                     reasoning content will only be available in the final response"
93                );
94            }
95        }
96
97        Self {
98            model,
99            executor,
100            config,
101            deps,
102        }
103    }
104
105    /// 便捷构造 — 使用默认配置和依赖。
106    pub fn simple(model: ResolvedModel, executor: ToolExecutor) -> Self {
107        Self::new(
108            model,
109            executor,
110            ToolUseConfig::default(),
111            ToolUseDeps::default(),
112        )
113    }
114
115    /// 获取模型引用。
116    pub fn model(&self) -> &ResolvedModel {
117        &self.model
118    }
119
120    /// 获取执行器引用。
121    pub fn executor(&self) -> &ToolExecutor {
122        &self.executor
123    }
124
125    /// 获取配置引用。
126    pub fn config(&self) -> &ToolUseConfig {
127        &self.config
128    }
129
130    /// 获取依赖引用。
131    pub fn deps(&self) -> &ToolUseDeps {
132        &self.deps
133    }
134
135    /// 构建 LlmInvoker(共享实例)。
136    fn build_invoker(&self) -> Arc<super::invoker::LlmInvoker> {
137        Arc::new(super::invoker::LlmInvoker::from_config(
138            self.model.clone(),
139            &self.config,
140            self.deps.fallback.clone(),
141        ))
142    }
143
144    /// 非流式执行
145    ///
146    /// v0.4+: AgentState 驱动 — 使用 `Graph<AgentState>.run_inline()` 执行 ReAct 循环。
147    /// 零序列化,编译期类型安全。
148    pub async fn execute(&self, messages: Vec<Message>) -> Result<ToolUseResult, LlmError> {
149        let initial_messages = build_request_messages_inner(&self.config, &messages)?;
150
151        // 构建 ReAct Graph (Graph<AgentState, AgentStateMerge>)
152        let invoker = self.build_invoker();
153        let llm_node =
154            super::react::LLMNode::new("llm", invoker, self.executor.clone(), self.config.clone());
155        let tool_node =
156            super::react::ToolNode::new("tool", self.executor.clone(), self.config.clone());
157        let compactor_node = super::react::CompactorNode::new(
158            "compactor",
159            Arc::new(LocalCompactor::new()),
160            self.config.context_budget.clone(),
161        );
162        let graph = super::react::build_react_graph(llm_node, tool_node, compactor_node);
163        // 每轮 ReAct 迭代最坏 4 steps: budget_check + llm + post_llm_check + tool
164        // N 轮最坏: 4*(N-1) + 3 = 4N-1 (最后一轮无 tool)
165        // +1 buffer 应对 edge cases
166        let max_steps = self.config.max_iterations * 4 + 1;
167
168        // 初始化 AgentState
169        let agent_state_init = super::typed_state::AgentState::from_messages(initial_messages);
170
171        // 创建 ExecutionContext<AgentState> 并调用 run_inline
172        let mut exec_ctx = lellm_graph::node_context::ExecutionContext::new(
173            agent_state_init,
174            None,
175            lellm_graph::CancellationToken::new(),
176        );
177
178        graph
179            .run_inline(&mut exec_ctx, max_steps)
180            .await
181            .map_err(|e| lellm_core::LlmError::Provider {
182                provider: "react_graph".into(),
183                status: None,
184                code: None,
185                message: e.to_string(),
186            })?;
187
188        let agent_state = exec_ctx.state();
189        let stop_reason = agent_state
190            .stop_reason
191            .clone()
192            .unwrap_or(StopReason::Complete);
193        let last_response = agent_state
194            .last_response
195            .clone()
196            .unwrap_or_else(empty_response);
197
198        Ok(ToolUseResult {
199            stop_reason,
200            response: last_response,
201            messages: agent_state.messages.clone(),
202            iterations: agent_state.iterations,
203            tool_calls_executed: agent_state.total_tool_calls,
204        })
205    }
206
207    /// 流式执行,返回事件接收器。
208    ///
209    /// v0.4+: AgentState 驱动 — 使用 `Graph<AgentState>.run_inline()` + `AgentEventSink`。
210    /// 与 `execute()` 共享 ReAct Graph 逻辑,消除手写 while 循环。
211    ///
212    /// # 数据流
213    ///
214    /// ```text
215    /// execute_stream()
216    ///   → 创建 channel + AgentEventSink
217    ///   → 构建 ReAct Graph
218    ///   → ExecutionContext(state, sink)
219    ///   → graph.run_inline()
220    ///   → AgentEventSink: StreamChunk → AgentEvent → channel
221    ///   → 完成后发送 LoopEnd / LoopError
222    /// ```
223    pub fn execute_stream(&self, messages: Vec<Message>) -> AgentStream {
224        let (tx, rx) = tokio::sync::mpsc::channel(32);
225        let invoker = self.build_invoker();
226        let executor = self.executor.clone();
227        let config = self.config.clone();
228
229        tokio::spawn(async move {
230            // 1. 构建初始消息
231            let initial_messages = match build_request_messages_inner(&config, &messages) {
232                Ok(m) => m,
233                Err(e) => {
234                    let _ = tx
235                        .send(AgentEvent::LoopError {
236                            error: e,
237                            iterations: 0,
238                        })
239                        .await;
240                    return;
241                }
242            };
243
244            // 2. 构建 ReAct Graph
245            let llm_node =
246                super::react::LLMNode::new("llm", invoker, executor.clone(), config.clone());
247            let tool_node = super::react::ToolNode::new("tool", executor.clone(), config.clone());
248            let compactor_node = super::react::CompactorNode::new(
249                "compactor",
250                Arc::new(LocalCompactor::new()),
251                config.context_budget.clone(),
252            );
253            let graph = super::react::build_react_graph(llm_node, tool_node, compactor_node);
254
255            // 每轮 ReAct 迭代最坏 4 steps: budget_check + llm + post_llm_check + tool
256            let max_steps = config.max_iterations * 4 + 1;
257
258            // 3. 初始化 AgentState
259            let agent_state = super::typed_state::AgentState::from_messages(initial_messages);
260
261            // 4. 创建 AgentEventSink (StreamChunk → AgentEvent 桥接)
262            let event_sink = super::event_bridge::AgentEventSink::new(tx.clone());
263            let sink: std::sync::Arc<dyn lellm_graph::StreamSink> = std::sync::Arc::new(event_sink);
264
265            // 5. 创建 ExecutionContext 并调用 run_inline
266            let mut exec_ctx = lellm_graph::node_context::ExecutionContext::new(
267                agent_state,
268                Some(sink),
269                lellm_graph::CancellationToken::new(),
270            );
271
272            match graph.run_inline(&mut exec_ctx, max_steps).await {
273                Ok(()) => {
274                    // 执行成功,从 AgentState 提取结果
275                    let state = exec_ctx.state();
276                    let stop_reason = state.stop_reason.clone().unwrap_or(StopReason::Complete);
277                    let last_response = state.last_response.clone().unwrap_or_else(empty_response);
278
279                    let _ = tx
280                        .send(AgentEvent::LoopEnd {
281                            result: ToolUseResult {
282                                stop_reason,
283                                response: last_response,
284                                messages: state.messages.clone(),
285                                iterations: state.iterations,
286                                tool_calls_executed: state.total_tool_calls,
287                            },
288                        })
289                        .await;
290                }
291                Err(e) => {
292                    // 执行失败,发送 LoopError
293                    let state = exec_ctx.state();
294                    let error = LlmError::Provider {
295                        provider: "react_graph".into(),
296                        status: None,
297                        code: None,
298                        message: e.to_string(),
299                    };
300                    let _ = tx
301                        .send(AgentEvent::LoopError {
302                            error,
303                            iterations: state.iterations,
304                        })
305                        .await;
306                }
307            }
308        });
309
310        rx
311    }
312}