lellm_agent/runtime/
typed_state.rs1use lellm_core::{ChatResponse, Message};
14use lellm_graph::WorkflowState;
15
16use super::context::estimate_tokens;
17use super::event::StopReason;
18
19#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
26pub struct AgentState {
27 pub messages: Vec<Message>,
29 pub iterations: usize,
31 pub total_tool_calls: usize,
33 pub output_tokens: usize,
35 pub reasoning_tokens: usize,
37 pub compact_count: usize,
39 pub stop_reason: Option<StopReason>,
41 pub last_response: Option<ChatResponse>,
43}
44
45impl AgentState {
46 pub fn from_messages(messages: Vec<Message>) -> Self {
48 Self {
49 messages,
50 iterations: 0,
51 total_tool_calls: 0,
52 output_tokens: 0,
53 reasoning_tokens: 0,
54 compact_count: 0,
55 stop_reason: None,
56 last_response: None,
57 }
58 }
59
60 pub fn estimated_context_tokens(&self) -> usize {
63 estimate_tokens(&self.messages)
64 }
65
66 pub fn reached_max(&self, max_iterations: usize) -> bool {
68 self.iterations >= max_iterations
69 }
70
71 pub fn exceeded_output(&self, max: Option<u32>) -> bool {
73 match max {
74 Some(limit) => self.output_tokens >= limit as usize,
75 None => false,
76 }
77 }
78
79 pub fn exceeded_reasoning(&self, max: Option<u32>) -> bool {
81 match max {
82 Some(limit) => self.reasoning_tokens >= limit as usize,
83 None => false,
84 }
85 }
86
87 pub fn exceeded_output_with_extra(&self, max: Option<u32>, extra: usize) -> bool {
91 match max {
92 Some(limit) => self.output_tokens + extra >= limit as usize,
93 None => false,
94 }
95 }
96
97 pub fn exceeded_reasoning_with_extra(&self, max: Option<u32>, extra: usize) -> bool {
99 match max {
100 Some(limit) => self.reasoning_tokens + extra >= limit as usize,
101 None => false,
102 }
103 }
104
105 pub fn is_terminal(&self) -> bool {
107 self.stop_reason.is_some()
108 }
109
110 pub fn messages_ref(&self) -> &[Message] {
112 &self.messages
113 }
114}
115
116#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
123pub enum AgentEffect {
124 AppendMessage(Message),
126 AppendMessages(Vec<Message>),
128 IncrementIteration,
130 AddToolCalls(usize),
132 AddOutputTokens(usize),
134 AddReasoningTokens(usize),
136 IncrementCompactCount,
138 ReplaceMessages(Vec<Message>),
140 SetStopReason(StopReason),
142 SetLastResponse(ChatResponse),
144}
145
146impl lellm_graph::Effect for AgentEffect {}
147
148impl WorkflowState for AgentState {
151 type Effect = AgentEffect;
152
153 fn apply(&mut self, effect: Self::Effect) {
154 match effect {
155 AgentEffect::AppendMessage(msg) => {
156 self.messages.push(msg);
157 }
158 AgentEffect::AppendMessages(msgs) => {
159 self.messages.extend(msgs);
160 }
161 AgentEffect::IncrementIteration => {
162 self.iterations += 1;
163 }
164 AgentEffect::AddToolCalls(n) => {
165 self.total_tool_calls += n;
166 }
167 AgentEffect::AddOutputTokens(n) => {
168 self.output_tokens += n;
169 }
170 AgentEffect::AddReasoningTokens(n) => {
171 self.reasoning_tokens += n;
172 }
173 AgentEffect::IncrementCompactCount => {
174 self.compact_count += 1;
175 }
176 AgentEffect::ReplaceMessages(msgs) => {
177 self.messages = msgs;
178 }
179 AgentEffect::SetStopReason(reason) => {
180 self.stop_reason = Some(reason);
181 }
182 AgentEffect::SetLastResponse(response) => {
183 self.last_response = Some(response);
184 }
185 }
186 }
187}
188
189pub struct AgentStateMerge;
200
201impl lellm_graph::MergeStrategy<AgentState> for AgentStateMerge {
202 fn merge(branches: Vec<AgentState>) -> Result<AgentState, lellm_graph::WorkflowError> {
203 let mut iter = branches.into_iter();
204 let mut merged = iter.next().ok_or_else(|| {
205 lellm_graph::WorkflowError::MergeConflict("no branches to merge".into())
206 })?;
207
208 for branch in iter {
209 merged.messages.extend(branch.messages);
210 merged.iterations = merged.iterations.max(branch.iterations);
211 merged.total_tool_calls = merged.total_tool_calls.max(branch.total_tool_calls);
212 merged.output_tokens += branch.output_tokens;
213 merged.reasoning_tokens += branch.reasoning_tokens;
214 merged.compact_count += branch.compact_count;
215 if merged.stop_reason.is_none() {
216 merged.stop_reason = branch.stop_reason;
217 }
218 if merged.last_response.is_none() {
219 merged.last_response = branch.last_response;
220 }
221 }
222
223 Ok(merged)
224 }
225
226 fn default_instance() -> Self {
227 AgentStateMerge
228 }
229}
230
231