Skip to main content

fiddlesticks/
runtime.rs

1//! Runtime wiring helpers for chat and harness usage.
2//!
3//! ```rust
4//! use std::sync::Arc;
5//!
6//! use fiddlesticks::{ModelProvider, build_runtime_with_memory, in_memory_backend};
7//!
8//! fn wire_runtime(provider: Arc<dyn ModelProvider>) {
9//!     let memory = in_memory_backend();
10//!     let runtime = build_runtime_with_memory(provider, memory);
11//!     assert!(runtime.is_ok());
12//! }
13//! ```
14
15use std::sync::Arc;
16
17use crate::{
18    ChatService, Harness, HarnessError, InMemoryMemoryBackend, MemoryBackend,
19    MemoryConversationStore, ModelProvider, ToolRuntime, create_default_memory_backend,
20};
21
22#[derive(Clone)]
23pub struct RuntimeBundle {
24    pub memory: Arc<dyn MemoryBackend>,
25    pub chat: ChatService,
26    pub harness: Harness,
27}
28
29pub fn in_memory_backend() -> Arc<dyn MemoryBackend> {
30    Arc::new(InMemoryMemoryBackend::new())
31}
32
33pub fn chat_service(provider: Arc<dyn ModelProvider>) -> ChatService {
34    ChatService::builder(provider).build()
35}
36
37pub fn chat_service_with_memory(
38    provider: Arc<dyn ModelProvider>,
39    memory: Arc<dyn MemoryBackend>,
40) -> ChatService {
41    let store = Arc::new(MemoryConversationStore::new(memory));
42    ChatService::builder(provider).store(store).build()
43}
44
45pub fn build_runtime(provider: Arc<dyn ModelProvider>) -> Result<RuntimeBundle, HarnessError> {
46    let memory = create_default_memory_backend()?;
47    build_runtime_with(provider, memory, None)
48}
49
50pub fn build_runtime_with_memory(
51    provider: Arc<dyn ModelProvider>,
52    memory: Arc<dyn MemoryBackend>,
53) -> Result<RuntimeBundle, HarnessError> {
54    build_runtime_with(provider, memory, None)
55}
56
57pub fn build_runtime_with_tooling(
58    provider: Arc<dyn ModelProvider>,
59    tool_runtime: Arc<dyn ToolRuntime>,
60) -> Result<RuntimeBundle, HarnessError> {
61    let memory = create_default_memory_backend()?;
62    build_runtime_with(provider, memory, Some(tool_runtime))
63}
64
65pub fn build_runtime_with(
66    provider: Arc<dyn ModelProvider>,
67    memory: Arc<dyn MemoryBackend>,
68    tool_runtime: Option<Arc<dyn ToolRuntime>>,
69) -> Result<RuntimeBundle, HarnessError> {
70    let store = Arc::new(MemoryConversationStore::new(Arc::clone(&memory)));
71
72    let mut chat_builder = ChatService::builder(Arc::clone(&provider)).store(store);
73    let mut harness_builder = Harness::builder(Arc::clone(&memory)).provider(provider);
74
75    if let Some(runtime) = tool_runtime {
76        chat_builder = chat_builder.tool_runtime(Arc::clone(&runtime));
77        harness_builder = harness_builder.tool_runtime(runtime);
78    }
79
80    let chat = chat_builder.build();
81    let harness = harness_builder.build()?;
82
83    Ok(RuntimeBundle {
84        memory,
85        chat,
86        harness,
87    })
88}
89
90#[cfg(test)]
91mod tests {
92    use std::sync::Arc;
93
94    use crate::{
95        DefaultToolRuntime, Message, ModelProvider, ModelRequest,
96        ModelResponse, OutputItem, ProviderError, ProviderFuture, ProviderId, Role, StopReason,
97        StreamEvent, TokenUsage, ToolRuntime, VecEventStream,
98    };
99
100    use super::{build_runtime_with_tooling};
101
102    #[derive(Debug)]
103    struct FakeProvider;
104
105    impl ModelProvider for FakeProvider {
106        fn id(&self) -> ProviderId {
107            ProviderId::OpenAi
108        }
109
110        fn complete<'a>(
111            &'a self,
112            request: ModelRequest,
113        ) -> ProviderFuture<'a, Result<ModelResponse, ProviderError>> {
114            Box::pin(async move {
115                request.validate()?;
116                Ok(ModelResponse {
117                    provider: ProviderId::OpenAi,
118                    model: request.model,
119                    output: vec![OutputItem::Message(Message::new(Role::Assistant, "done"))],
120                    stop_reason: StopReason::EndTurn,
121                    usage: TokenUsage::default(),
122                })
123            })
124        }
125
126        fn stream<'a>(
127            &'a self,
128            request: ModelRequest,
129        ) -> ProviderFuture<'a, Result<crate::BoxedEventStream<'a>, ProviderError>> {
130            Box::pin(async move {
131                request.validate()?;
132                let response = ModelResponse {
133                    provider: ProviderId::OpenAi,
134                    model: request.model,
135                    output: vec![OutputItem::Message(Message::new(Role::Assistant, "done"))],
136                    stop_reason: StopReason::EndTurn,
137                    usage: TokenUsage::default(),
138                };
139                let stream = VecEventStream::new(vec![Ok(StreamEvent::ResponseComplete(response))]);
140                Ok(Box::pin(stream) as crate::BoxedEventStream<'a>)
141            })
142        }
143    }
144
145    #[test]
146    fn build_runtime_with_tooling_builds_successfully() {
147        let provider: Arc<dyn ModelProvider> = Arc::new(FakeProvider);
148        let tool_runtime: Arc<dyn ToolRuntime> = Arc::new(DefaultToolRuntime::default());
149
150        let runtime =
151            build_runtime_with_tooling(provider, tool_runtime).expect("runtime should build");
152        let _chat = runtime.chat.clone();
153        let _harness = runtime.harness.clone();
154    }
155}