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}