1use std::sync::{Arc, Mutex};
25
26use crate::{
27 completion::{CompletionRequest, ModelRef, UnsupportedOption},
28 driver::{DynModel, Sending, without_messages},
29 effect::{
30 Answered, EffectFamily, EffectId, EffectKind, EmbedInputs, EmbedModality, EmbedOutputs,
31 FamilyDescriptor, HandlerDescriptor, HandlerKey, RetrieveQuery, RetrievedDocuments,
32 ToolEmbeddingDescriptor,
33 },
34 error::{ErrorKind, ErrorReport},
35 operation::{Completion, Embedding, Rerank},
36 providers::registry::ProviderId,
37 tool::{Tool, ToolEmbedding, ToolExecutionError},
38 vector_store::{VectorStoreError, VectorStoreIndex, request::DynamicSearchFilter},
39 wasm_compat::{WasmBoxedFuture, WasmCompatSend, WasmCompatSync},
40 wire::Operation,
41};
42
43use super::{Dispatch, Reply, Serve};
44use crate::effect::family;
45
46fn wrong_family(handler: EffectFamily, kind: &EffectKind) -> ErrorReport {
47 ErrorReport::new(
48 ErrorKind::HandlerUnavailable,
49 format!(
50 "a {handler} handler cannot serve a `{}` effect",
51 kind.name()
52 ),
53 )
54}
55
56pub struct ModelAdapter<Op: Operation> {
60 label: ModelRef,
61 model: DynModel<Op>,
62 checked: Mutex<Option<(CompletionRequest, Vec<UnsupportedOption>)>>,
65}
66
67impl<Op: Operation> ModelAdapter<Op> {
68 pub fn new(label: impl Into<ModelRef>, model: impl Into<DynModel<Op>>) -> Self {
70 Self {
71 label: label.into(),
72 model: model.into(),
73 checked: Mutex::new(None),
74 }
75 }
76
77 fn sending(
80 &self,
81 handler: HandlerKey,
82 dispatch: &Dispatch,
83 observation: Option<crate::observe::AdapterContext>,
84 ) -> Sending {
85 let mut extensions = http::Extensions::new();
86 extensions.insert(Serving {
87 effect: dispatch.id(),
88 handler,
89 provider: self.model.spec().map(|spec| spec.provider),
90 });
91 Sending {
92 observation,
93 extensions,
94 }
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
119#[non_exhaustive]
120pub struct Serving {
121 pub effect: EffectId,
123 pub handler: HandlerKey,
125 pub provider: Option<ProviderId>,
128}
129
130impl Serving {
131 pub fn new(effect: EffectId, handler: HandlerKey) -> Self {
134 Self {
135 effect,
136 handler,
137 provider: None,
138 }
139 }
140}
141
142impl Serve for ModelAdapter<Completion> {
145 type Family = family::Completion;
146
147 fn descriptor(&self) -> HandlerDescriptor {
148 HandlerDescriptor {
149 key: crate::effect::model_key(self.label.as_str()),
150 family: FamilyDescriptor::Completion {
151 model: self.label.clone(),
152 capabilities: self.model.capabilities().completion,
153 },
154 layers: Vec::new(),
155 }
156 }
157
158 fn check(&self, kind: &EffectKind) -> Vec<UnsupportedOption> {
166 let EffectKind::Completion { request, .. } = kind else {
167 return Vec::new();
168 };
169 let memo = request.provider_options.is_empty();
170 let checked = without_messages(request);
171 if memo
172 && let Ok(last) = self.checked.lock()
173 && let Some((last, refused)) = last.as_ref()
174 && *last == checked
175 {
176 return refused.clone();
177 }
178 let refused = match self.model.check(request) {
179 Err(crate::completion::CheckError::Unsupported(refused)) => refused,
180 Ok(()) | Err(crate::completion::CheckError::Invalid(_)) => Vec::new(),
181 };
182 if memo && let Ok(mut last) = self.checked.lock() {
183 *last = Some((checked, refused.clone()));
184 }
185 refused
186 }
187
188 async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
189 let model = &self.model;
190 let sending = self.sending(
191 crate::effect::model_key(self.label.as_str()),
192 &dispatch,
193 dispatch.adapter_context(),
194 );
195 match kind {
196 EffectKind::Completion {
197 request,
198 stream: false,
199 } => {
200 let result = model.finished(Arc::unwrap_or_clone(request), sending).await;
201 let result = result.map(Arc::new);
202 Reply::Outcome(result.map(Answered::Completion).map_err(ErrorReport::from))
203 }
204 EffectKind::Completion {
205 request,
206 stream: true,
207 } => match model.opened(Arc::unwrap_or_clone(request), sending) {
208 Ok(stream) => Reply::Stream(stream.into_relay()),
209 Err(error) => Reply::Outcome(Err(ErrorReport::from(error))),
210 },
211 other @ (EffectKind::ToolCall { .. }
212 | EffectKind::Embed { .. }
213 | EffectKind::Retrieve { .. }
214 | EffectKind::Rerank { .. }
215 | EffectKind::Custom { .. }) => {
216 Reply::Outcome(Err(wrong_family(EffectFamily::Completion, &other)))
217 }
218 }
219 }
220}
221
222impl Serve for ModelAdapter<Embedding> {
224 type Family = family::Embed;
225
226 fn descriptor(&self) -> HandlerDescriptor {
227 let capabilities = self.model.capabilities();
228 HandlerDescriptor {
229 key: crate::effect::embed_key(self.label.as_str()),
230 family: FamilyDescriptor::Embed {
231 model: self.label.to_string(),
232 dims: Some(capabilities.ndims),
233 max_documents: capabilities.max_documents,
234 modality: EmbedModality::Text,
235 },
236 layers: Vec::new(),
237 }
238 }
239
240 async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
241 let model = &self.model;
242 let sending = self.sending(
243 crate::effect::embed_key(self.label.as_str()),
244 &dispatch,
245 None,
246 );
247 match kind {
248 EffectKind::Embed {
249 inputs: EmbedInputs::Texts(texts),
250 } => Reply::Outcome(
251 model
252 .finished(texts, sending)
253 .await
254 .map(|response| Answered::Embeddings(EmbedOutputs::Texts(response)))
255 .map_err(ErrorReport::from),
256 ),
257 EffectKind::Embed {
258 inputs: EmbedInputs::Images(_),
259 } => Reply::Outcome(Err(ErrorReport::new(
260 ErrorKind::HandlerUnavailable,
261 "a text embedding handler cannot embed images",
262 ))),
263 other @ (EffectKind::Completion { .. }
264 | EffectKind::ToolCall { .. }
265 | EffectKind::Retrieve { .. }
266 | EffectKind::Rerank { .. }
267 | EffectKind::Custom { .. }) => {
268 Reply::Outcome(Err(wrong_family(EffectFamily::Embed, &other)))
269 }
270 }
271 }
272}
273
274impl Serve for ModelAdapter<Rerank> {
276 type Family = family::Rerank;
277
278 fn descriptor(&self) -> HandlerDescriptor {
279 HandlerDescriptor {
280 key: crate::effect::rerank_key(self.label.as_str()),
281 family: FamilyDescriptor::Rerank {
282 model: self.label.to_string(),
283 max_documents: self.model.capabilities().max_documents,
284 },
285 layers: Vec::new(),
286 }
287 }
288
289 async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
290 let model = &self.model;
291 let sending = self.sending(
292 crate::effect::rerank_key(self.label.as_str()),
293 &dispatch,
294 None,
295 );
296 match kind {
297 EffectKind::Rerank { request } => Reply::Outcome(
298 model
299 .finished(request, sending)
300 .await
301 .map(Answered::Reranked)
302 .map_err(ErrorReport::from),
303 ),
304 other @ (EffectKind::Completion { .. }
305 | EffectKind::ToolCall { .. }
306 | EffectKind::Embed { .. }
307 | EffectKind::Retrieve { .. }
308 | EffectKind::Custom { .. }) => {
309 Reply::Outcome(Err(wrong_family(EffectFamily::Rerank, &other)))
310 }
311 }
312 }
313}
314
315pub(crate) async fn call_tool<A, O>(
325 kind: EffectKind,
326 dispatch: Dispatch,
327 bound: Option<&str>,
328 call: impl AsyncFnOnce(&mut crate::tool::ToolContext, A) -> Result<O, ToolExecutionError>,
329) -> Reply
330where
331 A: serde::de::DeserializeOwned,
332 O: crate::tool::IntoToolOutput,
333{
334 let args = match (kind, bound) {
335 (EffectKind::ToolCall { name, .. }, Some(bound)) if bound != name => {
336 return Reply::Outcome(Err(ErrorReport::new(
337 ErrorKind::Internal,
338 format!("tool handler `{bound}` asked to run `{name}`"),
339 )));
340 }
341 (EffectKind::ToolCall { args, .. }, _) => args,
342 (
343 other @ (EffectKind::Completion { .. }
344 | EffectKind::Embed { .. }
345 | EffectKind::Retrieve { .. }
346 | EffectKind::Rerank { .. }
347 | EffectKind::Custom { .. }),
348 _,
349 ) => return Reply::Outcome(Err(wrong_family(EffectFamily::Tool, &other))),
350 };
351 let result = run_tool(&args, &dispatch, call).await;
352 Reply::Outcome(Ok(Answered::ToolResult { result }))
353}
354
355pub(crate) async fn run_tool<A, O>(
359 args: &str,
360 dispatch: &Dispatch,
361 call: impl AsyncFnOnce(&mut crate::tool::ToolContext, A) -> Result<O, ToolExecutionError>,
362) -> crate::tool::ToolResult
363where
364 A: serde::de::DeserializeOwned,
365 O: crate::tool::IntoToolOutput,
366{
367 let mut context = dispatch
368 .scope::<crate::tool::ToolContext>()
369 .map(|inbound| inbound.for_dispatch())
370 .unwrap_or_default()
371 .with_scopes(dispatch.scopes());
372 let result = run_in(args, &mut context, call).await;
373 if let Some(published) = dispatch.scope::<crate::tool::PublishedContext>() {
374 published.publish(context);
375 }
376 result
377}
378
379pub(crate) async fn run_direct<A, O>(
383 context: &mut crate::tool::ToolContext,
384 args: serde_json::Value,
385 call: impl AsyncFnOnce(&mut crate::tool::ToolContext, A) -> Result<O, ToolExecutionError>,
386) -> Result<crate::tool::ToolOutput, ToolExecutionError>
387where
388 A: serde::de::DeserializeOwned,
389 O: crate::tool::IntoToolOutput,
390{
391 let mut dispatched = context.for_dispatch();
392 let result = run_in(&args.to_string(), &mut dispatched, call).await;
393 context.accept_dispatch_result(dispatched);
394 result.into_result()
395}
396
397async fn run_in<A, O>(
400 args: &str,
401 context: &mut crate::tool::ToolContext,
402 call: impl AsyncFnOnce(&mut crate::tool::ToolContext, A) -> Result<O, ToolExecutionError>,
403) -> crate::tool::ToolResult
404where
405 A: serde::de::DeserializeOwned,
406 O: crate::tool::IntoToolOutput,
407{
408 match crate::tool::contextual::parse_tool_args::<A>(args) {
409 Ok(args) => crate::tool::contextual::tool_result_from(call(context, args).await),
410 Err(error) => crate::tool::ToolResult::failed(error),
411 }
412}
413
414pub struct ToolAdapter<T> {
416 tool: T,
417 embedding: Option<ToolEmbeddingDescriptor>,
418}
419
420impl<T: Tool> ToolAdapter<T> {
421 pub fn new(tool: T) -> Self {
423 Self {
424 tool,
425 embedding: None,
426 }
427 }
428
429 pub fn retrievable(tool: T) -> Result<Self, serde_json::Error>
432 where
433 T: ToolEmbedding,
434 {
435 let embedding = ToolEmbeddingDescriptor {
436 context: serde_json::to_value(tool.context())?,
437 embedding_docs: tool.embedding_docs(),
438 };
439 Ok(Self {
440 tool,
441 embedding: Some(embedding),
442 })
443 }
444
445 pub fn tool(&self) -> &T {
447 &self.tool
448 }
449
450 pub fn embedding(&self) -> Option<&ToolEmbeddingDescriptor> {
452 self.embedding.as_ref()
453 }
454
455 pub async fn run(
460 &self,
461 args: serde_json::Value,
462 ) -> Result<crate::tool::ToolOutput, ToolExecutionError> {
463 self.run_with(&mut crate::tool::ToolContext::new(), args)
464 .await
465 }
466
467 pub async fn run_with(
512 &self,
513 context: &mut crate::tool::ToolContext,
514 args: serde_json::Value,
515 ) -> Result<crate::tool::ToolOutput, ToolExecutionError> {
516 run_direct(context, args, async |context, args| {
517 self.call(context, args).await
518 })
519 .await
520 }
521
522 async fn call(
524 &self,
525 context: &mut crate::tool::ToolContext,
526 args: T::Args,
527 ) -> Result<T::Output, ToolExecutionError> {
528 self.tool
529 .call(context, args)
530 .await
531 .map_err(|error| self.tool.map_error(error))
532 }
533}
534
535impl<T> Serve for ToolAdapter<T>
536where
537 T: Tool + 'static,
538{
539 type Family = family::Tool;
540
541 fn descriptor(&self) -> HandlerDescriptor {
542 HandlerDescriptor {
543 key: crate::effect::tool_key(T::NAME),
544 family: FamilyDescriptor::Tool {
545 name: T::NAME.to_owned(),
546 description: self.tool.description(),
547 parameters: self.tool.parameters(),
548 embedding: self.embedding.clone(),
549 },
550 layers: Vec::new(),
551 }
552 }
553
554 fn key(&self) -> HandlerKey {
555 crate::effect::tool_key(T::NAME)
556 }
557
558 fn footprint(&self, kind: &EffectKind) -> Option<crate::tool::Touches> {
559 match kind {
560 EffectKind::ToolCall { name, args } if name == T::NAME => {
561 let args = crate::tool::contextual::parse_tool_args::<T::Args>(args).ok()?;
562 Some(self.tool.footprint(&args))
563 }
564 _ => None,
565 }
566 }
567
568 async fn serve(&self, kind: EffectKind, dispatch: Dispatch) -> Reply {
569 call_tool(kind, dispatch, Some(T::NAME), async |context, args| {
570 self.call(context, args).await
571 })
572 .await
573 }
574}
575
576pub trait ToolCallback:
579 for<'a> Fn(
580 &'a mut crate::tool::ToolContext,
581 serde_json::Value,
582 ) -> WasmBoxedFuture<
583 'a,
584 Result<crate::tool::ToolOutput, crate::tool::ToolExecutionError>,
585 > + WasmCompatSend
586 + WasmCompatSync
587{
588}
589
590impl<F> ToolCallback for F where
591 F: for<'a> Fn(
592 &'a mut crate::tool::ToolContext,
593 serde_json::Value,
594 ) -> WasmBoxedFuture<
595 'a,
596 Result<crate::tool::ToolOutput, crate::tool::ToolExecutionError>,
597 > + WasmCompatSend
598 + WasmCompatSync
599{
600}
601
602pub struct RetrieveAdapter<I> {
606 index: I,
607 label: Option<String>,
608}
609
610impl<I> RetrieveAdapter<I> {
611 pub fn new(index: I) -> Self {
613 Self { index, label: None }
614 }
615
616 pub fn labelled(label: impl Into<String>, index: I) -> Self {
620 Self {
621 index,
622 label: Some(label.into()),
623 }
624 }
625
626 pub fn index(&self) -> &I {
628 &self.index
629 }
630}
631
632impl<I, F> Serve for RetrieveAdapter<I>
633where
634 I: VectorStoreIndex<Filter = F> + 'static,
635 F: DynamicSearchFilter + WasmCompatSend + WasmCompatSync + 'static,
636{
637 type Family = family::Retrieve;
638
639 fn descriptor(&self) -> HandlerDescriptor {
640 HandlerDescriptor {
641 key: self
642 .label
643 .as_deref()
644 .map_or_else(|| HandlerKey::from("retrieve"), crate::effect::retrieve_key),
645 family: FamilyDescriptor::Retrieve {},
646 layers: Vec::new(),
647 }
648 }
649
650 async fn serve(&self, kind: EffectKind, _dispatch: Dispatch) -> Reply {
651 match kind {
652 EffectKind::Retrieve { query } => {
653 let outcome = match query {
654 RetrieveQuery::TopN { req } => {
655 match req.try_map_filter(F::from_dynamic_filter) {
656 Ok(req) => self
657 .index
658 .top_n::<serde_json::Value>(req)
659 .await
660 .map(|results| {
661 Answered::Documents(RetrievedDocuments::Scored(
662 results
663 .into_iter()
664 .map(|result| {
665 (
666 result.score,
667 result.id,
668 F::normalize_dynamic_document(result.document),
669 )
670 })
671 .collect(),
672 ))
673 })
674 .map_err(ErrorReport::from),
675 Err(error) => Err(ErrorReport::from(VectorStoreError::from(error))),
676 }
677 }
678 RetrieveQuery::TopNIds { req } => {
679 match req.try_map_filter(F::from_dynamic_filter) {
680 Ok(req) => self
681 .index
682 .top_n_ids(req)
683 .await
684 .map(|results| {
685 Answered::Documents(RetrievedDocuments::Ids(
686 results
687 .into_iter()
688 .map(|result| (result.score, result.id))
689 .collect(),
690 ))
691 })
692 .map_err(ErrorReport::from),
693 Err(error) => Err(ErrorReport::from(VectorStoreError::from(error))),
694 }
695 }
696 };
697 Reply::Outcome(outcome)
698 }
699 other @ (EffectKind::Completion { .. }
700 | EffectKind::ToolCall { .. }
701 | EffectKind::Embed { .. }
702 | EffectKind::Rerank { .. }
703 | EffectKind::Custom { .. }) => {
704 Reply::Outcome(Err(wrong_family(EffectFamily::Retrieve, &other)))
705 }
706 }
707 }
708}