swarm_engine_core/learn/record/
mod.rs1mod action;
15mod dependency_graph;
16mod llm;
17mod stream;
18
19use serde::{Deserialize, Serialize};
20
21use crate::events::ActionEvent;
22
23pub use action::ActionRecord;
24pub use dependency_graph::DependencyGraphRecord;
25pub use llm::LlmCallRecord;
26pub use stream::RecordStream;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
37pub enum Record {
38 Action(ActionRecord),
40 Llm(LlmCallRecord),
42 DependencyGraph(DependencyGraphRecord),
44}
45
46impl Record {
47 pub fn is_action(&self) -> bool {
49 matches!(self, Self::Action(_))
50 }
51
52 pub fn is_llm(&self) -> bool {
54 matches!(self, Self::Llm(_))
55 }
56
57 pub fn is_dependency_graph(&self) -> bool {
59 matches!(self, Self::DependencyGraph(_))
60 }
61
62 pub fn as_action(&self) -> Option<&ActionRecord> {
64 match self {
65 Self::Action(r) => Some(r),
66 _ => None,
67 }
68 }
69
70 pub fn as_llm(&self) -> Option<&LlmCallRecord> {
72 match self {
73 Self::Llm(r) => Some(r),
74 _ => None,
75 }
76 }
77
78 pub fn as_dependency_graph(&self) -> Option<&DependencyGraphRecord> {
80 match self {
81 Self::DependencyGraph(r) => Some(r),
82 _ => None,
83 }
84 }
85
86 pub fn worker_id(&self) -> Option<usize> {
88 match self {
89 Self::Action(r) => Some(r.worker_id),
90 Self::Llm(r) => r.worker_id,
91 Self::DependencyGraph(_) => None, }
93 }
94
95 pub fn timestamp_ms(&self) -> u64 {
97 match self {
98 Self::Action(r) => r.tick,
99 Self::Llm(r) => r.timestamp_ms,
100 Self::DependencyGraph(r) => r.timestamp_ms,
101 }
102 }
103}
104
105impl From<ActionRecord> for Record {
106 fn from(record: ActionRecord) -> Self {
107 Self::Action(record)
108 }
109}
110
111impl From<LlmCallRecord> for Record {
112 fn from(record: LlmCallRecord) -> Self {
113 Self::Llm(record)
114 }
115}
116
117impl From<DependencyGraphRecord> for Record {
118 fn from(record: DependencyGraphRecord) -> Self {
119 Self::DependencyGraph(record)
120 }
121}
122
123impl From<&ActionEvent> for Record {
124 fn from(event: &ActionEvent) -> Self {
125 Self::Action(ActionRecord::from(event))
126 }
127}
128
129pub trait FromRecord: Sized {
138 fn from_record(record: &Record) -> Option<&Self>;
139}
140
141impl FromRecord for ActionRecord {
142 fn from_record(record: &Record) -> Option<&Self> {
143 record.as_action()
144 }
145}
146
147impl FromRecord for LlmCallRecord {
148 fn from_record(record: &Record) -> Option<&Self> {
149 record.as_llm()
150 }
151}
152
153impl FromRecord for DependencyGraphRecord {
154 fn from_record(record: &Record) -> Option<&Self> {
155 record.as_dependency_graph()
156 }
157}
158
159#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_record_from_action_record() {
169 let action = ActionRecord::new(1, 0, "CheckStatus").success(true);
170 let record = Record::from(action);
171
172 assert!(record.is_action());
173 assert!(!record.is_llm());
174 assert_eq!(record.worker_id(), Some(0));
175 }
176
177 #[test]
178 fn test_record_from_llm_call_record() {
179 let llm = LlmCallRecord::new("decide", "qwen2.5")
180 .worker_id(1)
181 .prompt("test")
182 .response("ok");
183 let record = Record::from(llm);
184
185 assert!(!record.is_action());
186 assert!(record.is_llm());
187 assert_eq!(record.worker_id(), Some(1));
188 }
189
190 #[test]
191 fn test_record_stream_filtering() {
192 let records = vec![
193 Record::from(ActionRecord::new(1, 0, "A").success(true)),
194 Record::from(LlmCallRecord::new("decide", "model").worker_id(0)),
195 Record::from(ActionRecord::new(2, 1, "B").success(true)),
196 ];
197
198 let stream = RecordStream::new(&records);
199
200 assert_eq!(stream.actions().count(), 2);
201 assert_eq!(stream.llm_calls().count(), 1);
202 assert_eq!(stream.by_worker(0).count(), 2);
203 assert_eq!(stream.by_worker(1).count(), 1);
204 }
205}