Skip to main content

uni_db/api/
xervo.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4use std::sync::Arc;
5
6use crate::api::Uni;
7use uni_common::{Result, UniError};
8use uni_xervo::runtime::ModelRuntime;
9pub use uni_xervo::traits::{
10    AudioOutput, ContentBlock, GeneratedImage, GenerationOptions, GenerationResult, ImageInput,
11    Message, MessageRole, TokenUsage,
12};
13
14fn into_uni_error<E: std::fmt::Display>(err: E) -> UniError {
15    UniError::Internal(anyhow::anyhow!(err.to_string()))
16}
17
18fn not_configured() -> UniError {
19    UniError::Internal(anyhow::anyhow!("Uni-Xervo runtime is not configured"))
20}
21
22/// Facade for using Uni-Xervo runtime from the Uni API surface.
23///
24/// Returned by [`Uni::xervo()`]. When no Xervo catalog was configured at
25/// build time, the facade is still returned (infallible accessor) but all
26/// operation methods will return an error. Use [`is_available()`](Self::is_available)
27/// to check upfront.
28#[derive(Clone)]
29pub struct UniXervo {
30    runtime: Option<Arc<ModelRuntime>>,
31}
32
33impl UniXervo {
34    /// Whether a Xervo runtime is configured and available for use.
35    pub fn is_available(&self) -> bool {
36        self.runtime.is_some()
37    }
38
39    /// Embed text inputs using a configured model alias.
40    pub async fn embed(&self, alias: &str, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
41        let runtime = self.runtime.as_ref().ok_or_else(not_configured)?;
42        let embedder = runtime.embedding(alias).await.map_err(into_uni_error)?;
43        embedder.embed(texts.to_vec()).await.map_err(into_uni_error)
44    }
45
46    /// Generate using a configured model alias with structured messages.
47    pub async fn generate(
48        &self,
49        alias: &str,
50        messages: &[Message],
51        options: GenerationOptions,
52    ) -> Result<GenerationResult> {
53        let runtime = self.runtime.as_ref().ok_or_else(not_configured)?;
54        let generator = runtime.generator(alias).await.map_err(into_uni_error)?;
55        generator
56            .generate(messages, options)
57            .await
58            .map_err(into_uni_error)
59    }
60
61    /// Generate text using plain string messages (convenience wrapper).
62    ///
63    /// Each string is treated as a user message. For multi-role conversations
64    /// or multimodal inputs, use [`generate`](Self::generate) with [`Message`] directly.
65    pub async fn generate_text(
66        &self,
67        alias: &str,
68        messages: &[&str],
69        options: GenerationOptions,
70    ) -> Result<GenerationResult> {
71        let structured: Vec<Message> = messages.iter().map(|s| Message::user(*s)).collect();
72        self.generate(alias, &structured, options).await
73    }
74
75    /// Access the underlying Uni-Xervo runtime, if configured.
76    pub fn raw_runtime(&self) -> Option<&Arc<ModelRuntime>> {
77        self.runtime.as_ref()
78    }
79}
80
81impl Uni {
82    /// Access Uni-Xervo runtime facade configured for this database.
83    ///
84    /// Always succeeds — returns a facade even when no Xervo catalog is
85    /// configured. Individual methods (`embed`, `generate`, etc.) will return
86    /// an error in that case. Use [`UniXervo::is_available()`] to check upfront.
87    pub fn xervo(&self) -> UniXervo {
88        UniXervo {
89            runtime: self.inner.xervo_runtime.clone(),
90        }
91    }
92}