praxis_graph/streaming.rs
1/// Adapter Pattern for Event Conversion
2///
3/// Converts between provider-specific event formats and the graph's internal event format.
4/// This abstraction allows the graph to work with different LLM providers without
5/// coupling to their specific event structures.
6
7/// Stream adapter trait for converting between event formats
8///
9/// # Type Parameters
10/// * `ProviderEvent` - The event type from the LLM provider
11/// * `GraphEvent` - The internal graph event type
12pub trait StreamAdapter {
13 type ProviderEvent;
14 type GraphEvent;
15
16 /// Convert a provider event to a graph event
17 ///
18 /// Returns None if the event should be filtered/ignored
19 fn adapt(&self, event: Self::ProviderEvent) -> Option<Self::GraphEvent>;
20}
21
22/// OpenAI stream adapter
23///
24/// Converts OpenAI `StreamEvent` to graph `StreamEvent`.
25/// Currently uses the From trait for direct conversion, but this adapter
26/// provides a clear extension point for custom logic.
27pub struct OpenAIStreamAdapter;
28
29impl StreamAdapter for OpenAIStreamAdapter {
30 type ProviderEvent = praxis_llm::StreamEvent;
31 type GraphEvent = crate::types::StreamEvent;
32
33 fn adapt(&self, event: Self::ProviderEvent) -> Option<Self::GraphEvent> {
34 // Use the From trait implementation for conversion
35 // In the future, we could add filtering, transformation, or enrichment logic here
36 Some(event.into())
37 }
38}
39
40/// Future: Azure OpenAI adapter
41#[allow(dead_code)]
42pub struct AzureStreamAdapter;
43
44// impl StreamAdapter for AzureStreamAdapter {
45// type ProviderEvent = AzureStreamEvent;
46// type GraphEvent = crate::types::StreamEvent;
47//
48// fn adapt(&self, event: Self::ProviderEvent) -> Option<Self::GraphEvent> {
49// // Convert Azure-specific events to graph events
50// todo!("Azure adapter not yet implemented")
51// }
52// }
53
54/// Future: Anthropic adapter
55#[allow(dead_code)]
56pub struct AnthropicStreamAdapter;
57
58// impl StreamAdapter for AnthropicStreamAdapter {
59// type ProviderEvent = AnthropicStreamEvent;
60// type GraphEvent = crate::types::StreamEvent;
61//
62// fn adapt(&self, event: Self::ProviderEvent) -> Option<Self::GraphEvent> {
63// // Convert Anthropic-specific events to graph events
64// todo!("Anthropic adapter not yet implemented")
65// }
66// }
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use praxis_llm::StreamEvent as LLMEvent;
72 use crate::types::StreamEvent as GraphEvent;
73
74 #[test]
75 fn test_openai_adapter_message() {
76 let adapter = OpenAIStreamAdapter;
77 let llm_event = LLMEvent::Message {
78 content: "Hello".to_string(),
79 };
80
81 let graph_event = adapter.adapt(llm_event);
82 assert!(graph_event.is_some());
83
84 match graph_event.unwrap() {
85 GraphEvent::Message { content } => {
86 assert_eq!(content, "Hello");
87 }
88 _ => panic!("Expected Message event"),
89 }
90 }
91
92 #[test]
93 fn test_openai_adapter_reasoning() {
94 let adapter = OpenAIStreamAdapter;
95 let llm_event = LLMEvent::Reasoning {
96 content: "Thinking...".to_string(),
97 };
98
99 let graph_event = adapter.adapt(llm_event);
100 assert!(graph_event.is_some());
101
102 match graph_event.unwrap() {
103 GraphEvent::Reasoning { content } => {
104 assert_eq!(content, "Thinking...");
105 }
106 _ => panic!("Expected Reasoning event"),
107 }
108 }
109}
110