mod action;
mod dependency_graph;
mod learn_stats;
mod llm;
mod strategy_advice;
mod stream;
use serde::{Deserialize, Serialize};
use crate::events::{ActionEvent, LearningEvent};
pub use action::ActionRecord;
pub use dependency_graph::DependencyGraphRecord;
pub use learn_stats::LearnStatsRecord;
pub use llm::LlmCallRecord;
pub use strategy_advice::StrategyAdviceRecord;
pub use stream::RecordStream;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Record {
Action(ActionRecord),
Llm(LlmCallRecord),
DependencyGraph(DependencyGraphRecord),
StrategyAdvice(StrategyAdviceRecord),
LearnStats(LearnStatsRecord),
}
impl Record {
pub fn is_action(&self) -> bool {
matches!(self, Self::Action(_))
}
pub fn is_llm(&self) -> bool {
matches!(self, Self::Llm(_))
}
pub fn is_dependency_graph(&self) -> bool {
matches!(self, Self::DependencyGraph(_))
}
pub fn is_strategy_advice(&self) -> bool {
matches!(self, Self::StrategyAdvice(_))
}
pub fn is_learn_stats(&self) -> bool {
matches!(self, Self::LearnStats(_))
}
pub fn as_action(&self) -> Option<&ActionRecord> {
match self {
Self::Action(r) => Some(r),
_ => None,
}
}
pub fn as_llm(&self) -> Option<&LlmCallRecord> {
match self {
Self::Llm(r) => Some(r),
_ => None,
}
}
pub fn as_dependency_graph(&self) -> Option<&DependencyGraphRecord> {
match self {
Self::DependencyGraph(r) => Some(r),
_ => None,
}
}
pub fn as_strategy_advice(&self) -> Option<&StrategyAdviceRecord> {
match self {
Self::StrategyAdvice(r) => Some(r),
_ => None,
}
}
pub fn as_learn_stats(&self) -> Option<&LearnStatsRecord> {
match self {
Self::LearnStats(r) => Some(r),
_ => None,
}
}
pub fn worker_id(&self) -> Option<usize> {
match self {
Self::Action(r) => Some(r.worker_id),
Self::Llm(r) => r.worker_id,
Self::DependencyGraph(_) => None,
Self::StrategyAdvice(_) => None,
Self::LearnStats(_) => None,
}
}
pub fn timestamp_ms(&self) -> u64 {
match self {
Self::Action(r) => r.tick,
Self::Llm(r) => r.timestamp_ms,
Self::DependencyGraph(r) => r.timestamp_ms,
Self::StrategyAdvice(r) => r.timestamp_ms,
Self::LearnStats(r) => r.timestamp_ms,
}
}
}
impl From<ActionRecord> for Record {
fn from(record: ActionRecord) -> Self {
Self::Action(record)
}
}
impl From<LlmCallRecord> for Record {
fn from(record: LlmCallRecord) -> Self {
Self::Llm(record)
}
}
impl From<DependencyGraphRecord> for Record {
fn from(record: DependencyGraphRecord) -> Self {
Self::DependencyGraph(record)
}
}
impl From<StrategyAdviceRecord> for Record {
fn from(record: StrategyAdviceRecord) -> Self {
Self::StrategyAdvice(record)
}
}
impl From<LearnStatsRecord> for Record {
fn from(record: LearnStatsRecord) -> Self {
Self::LearnStats(record)
}
}
impl From<&ActionEvent> for Record {
fn from(event: &ActionEvent) -> Self {
Self::Action(ActionRecord::from(event))
}
}
impl From<&LearningEvent> for Record {
fn from(event: &LearningEvent) -> Self {
match event {
LearningEvent::StrategyAdvice { .. } => {
Self::StrategyAdvice(StrategyAdviceRecord::from(event))
}
LearningEvent::DependencyGraphInference { .. } => {
Self::DependencyGraph(DependencyGraphRecord::from(event))
}
LearningEvent::LearnStatsSnapshot { .. } => {
Self::LearnStats(LearnStatsRecord::from(event))
}
}
}
}
pub trait FromRecord: Sized {
fn from_record(record: &Record) -> Option<&Self>;
}
impl FromRecord for ActionRecord {
fn from_record(record: &Record) -> Option<&Self> {
record.as_action()
}
}
impl FromRecord for LlmCallRecord {
fn from_record(record: &Record) -> Option<&Self> {
record.as_llm()
}
}
impl FromRecord for DependencyGraphRecord {
fn from_record(record: &Record) -> Option<&Self> {
record.as_dependency_graph()
}
}
impl FromRecord for StrategyAdviceRecord {
fn from_record(record: &Record) -> Option<&Self> {
record.as_strategy_advice()
}
}
impl FromRecord for LearnStatsRecord {
fn from_record(record: &Record) -> Option<&Self> {
record.as_learn_stats()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_from_action_record() {
let action = ActionRecord::new(1, 0, "CheckStatus").success(true);
let record = Record::from(action);
assert!(record.is_action());
assert!(!record.is_llm());
assert_eq!(record.worker_id(), Some(0));
}
#[test]
fn test_record_from_llm_call_record() {
let llm = LlmCallRecord::new("decide", "qwen2.5")
.worker_id(1)
.prompt("test")
.response("ok");
let record = Record::from(llm);
assert!(!record.is_action());
assert!(record.is_llm());
assert_eq!(record.worker_id(), Some(1));
}
#[test]
fn test_record_stream_filtering() {
let records = vec![
Record::from(ActionRecord::new(1, 0, "A").success(true)),
Record::from(LlmCallRecord::new("decide", "model").worker_id(0)),
Record::from(ActionRecord::new(2, 1, "B").success(true)),
];
let stream = RecordStream::new(&records);
assert_eq!(stream.actions().count(), 2);
assert_eq!(stream.llm_calls().count(), 1);
assert_eq!(stream.by_worker(0).count(), 2);
assert_eq!(stream.by_worker(1).count(), 1);
}
}