runifold_model/
invocation.rs1use std::{
2 future::Future,
3 pin::Pin,
4 time::{Duration, Instant},
5};
6
7use futures_core::Stream;
8use futures_util::{
9 StreamExt,
10 future::{Either, select},
11};
12use runifold_core::{CancellationToken, InvocationId, RunContext, RunId};
13
14use crate::{
15 ModelCapabilities, ModelError, ModelErrorKind, ModelRef, ModelRequest, ModelResponse,
16 ModelStreamAccumulator, ModelStreamEvent,
17};
18
19pub type ModelFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
21
22pub type ModelEventStream =
24 Pin<Box<dyn Stream<Item = Result<ModelStreamEvent, ModelError>> + Send + 'static>>;
25
26#[derive(Clone, Debug)]
32pub struct ModelCallContext {
33 invocation_id: InvocationId,
34 run_id: Option<RunId>,
35 deadline: Option<Instant>,
36 cancellation: CancellationToken,
37}
38
39impl ModelCallContext {
40 pub fn new() -> Self {
42 Self {
43 invocation_id: InvocationId::new(),
44 run_id: None,
45 deadline: None,
46 cancellation: CancellationToken::new(),
47 }
48 }
49
50 pub fn for_run(run: &RunContext) -> Self {
52 Self {
53 invocation_id: InvocationId::new(),
54 run_id: Some(run.run_id()),
55 deadline: run.deadline(),
56 cancellation: run.cancellation().child_token(),
57 }
58 }
59
60 pub const fn invocation_id(&self) -> InvocationId {
62 self.invocation_id
63 }
64
65 pub const fn run_id(&self) -> Option<RunId> {
67 self.run_id
68 }
69
70 pub const fn deadline(&self) -> Option<Instant> {
72 self.deadline
73 }
74
75 pub fn remaining(&self) -> Option<Duration> {
77 self.deadline
78 .map(|deadline| deadline.saturating_duration_since(Instant::now()))
79 }
80
81 pub const fn cancellation(&self) -> &CancellationToken {
83 &self.cancellation
84 }
85
86 #[must_use]
88 pub fn with_deadline(mut self, deadline: Instant) -> Self {
89 self.deadline = Some(
90 self.deadline
91 .map_or(deadline, |current| current.min(deadline)),
92 );
93 self
94 }
95
96 #[must_use]
101 pub fn with_cancellation(mut self, cancellation: &CancellationToken) -> Self {
102 self.cancellation = cancellation.child_token();
103 self
104 }
105
106 #[must_use]
112 pub fn child_attempt(&self) -> Self {
113 Self {
114 invocation_id: InvocationId::new(),
115 run_id: self.run_id,
116 deadline: self.deadline,
117 cancellation: self.cancellation.child_token(),
118 }
119 }
120}
121
122impl Default for ModelCallContext {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128pub trait Model: Send + Sync {
134 fn capabilities<'a>(
136 &'a self,
137 model: &'a ModelRef,
138 ) -> ModelFuture<'a, Result<ModelCapabilities, ModelError>>;
139
140 fn stream(
142 &self,
143 request: ModelRequest,
144 context: ModelCallContext,
145 ) -> ModelFuture<'_, Result<ModelEventStream, ModelError>>;
146
147 fn invoke(
149 &self,
150 request: ModelRequest,
151 context: ModelCallContext,
152 ) -> ModelFuture<'_, Result<ModelResponse, ModelError>> {
153 Box::pin(async move {
154 let cancellation = context.cancellation().clone();
155 let stream_future = self.stream(request, context);
156 let mut stream =
157 match select(Box::pin(cancellation.cancelled()), Box::pin(stream_future)).await {
158 Either::Left(_) => return Err(cancelled_error()),
159 Either::Right((result, _)) => result?,
160 };
161
162 let mut accumulator = ModelStreamAccumulator::new();
163 loop {
164 let next = stream.next();
165 match select(Box::pin(cancellation.cancelled()), Box::pin(next)).await {
166 Either::Left(_) => return Err(cancelled_error()),
167 Either::Right((Some(event), _)) => {
168 if let Some(response) = accumulator.push(event?)? {
169 return Ok(response);
170 }
171 }
172 Either::Right((None, _)) => {
173 return Err(ModelError::local(
174 ModelErrorKind::Protocol,
175 "model stream ended before a terminal response event",
176 ));
177 }
178 }
179 }
180 })
181 }
182}
183
184fn cancelled_error() -> ModelError {
185 ModelError::local(ModelErrorKind::Cancelled, "model invocation was cancelled")
186}