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 AgentMutation {
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::workflow_state::StateMutation<AgentState> for AgentMutation {
147 fn apply(self, state: &mut AgentState) {
148 match self {
149 AgentMutation::AppendMessage(msg) => {
150 state.messages.push(msg);
151 }
152 AgentMutation::AppendMessages(msgs) => {
153 state.messages.extend(msgs);
154 }
155 AgentMutation::IncrementIteration => {
156 state.iterations += 1;
157 }
158 AgentMutation::AddToolCalls(n) => {
159 state.total_tool_calls += n;
160 }
161 AgentMutation::AddOutputTokens(n) => {
162 state.output_tokens += n;
163 }
164 AgentMutation::AddReasoningTokens(n) => {
165 state.reasoning_tokens += n;
166 }
167 AgentMutation::IncrementCompactCount => {
168 state.compact_count += 1;
169 }
170 AgentMutation::ReplaceMessages(msgs) => {
171 state.messages = msgs;
172 }
173 AgentMutation::SetStopReason(reason) => {
174 state.stop_reason = Some(reason);
175 }
176 AgentMutation::SetLastResponse(response) => {
177 state.last_response = Some(response);
178 }
179 }
180 }
181}
182
183impl WorkflowState for AgentState {
186 type Mutation = AgentMutation;
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