Skip to main content

rig_core/providers/
llamafile.rs

1//! Llamafile API client and Rig integration
2//!
3//! [Llamafile](https://github.com/Mozilla-Ocho/llamafile) is a Mozilla Builders project
4//! that distributes LLMs as single-file executables. When started, it exposes an
5//! OpenAI-compatible API at `http://localhost:8080/v1`.
6//!
7//! # Example
8//! ```rust,ignore
9//! use rig_core::providers::llamafile;
10//! use rig_core::completion::Prompt;
11//!
12//! // Create a new Llamafile client (defaults to http://localhost:8080)
13//! let client = llamafile::Client::from_url("http://localhost:8080")?;
14//!
15//! // Create an agent with a preamble
16//! let agent = client
17//!     .agent(llamafile::LLAMA_CPP)
18//!     .preamble("You are a helpful assistant.")
19//!     .build();
20//!
21//! // Prompt the agent and print the response
22//! let response = agent.prompt("Hello!").await?;
23//! println!("{response}");
24//! ```
25
26use crate::client::{
27    self, Capabilities, Capable, DebugExt, Nothing, Provider, ProviderBuilder, ProviderClient,
28    Transport,
29};
30use crate::http_client::{self, HttpClientExt};
31use crate::providers::openai;
32
33// ================================================================
34// Main Llamafile Client
35// ================================================================
36const LLAMAFILE_API_BASE_URL: &str = "http://localhost:8080";
37
38/// The default model identifier reported by llamafile.
39pub const LLAMA_CPP: &str = "LLaMA_CPP";
40
41#[derive(Debug, Default, Clone, Copy)]
42pub struct LlamafileExt;
43
44#[derive(Debug, Default, Clone, Copy)]
45pub struct LlamafileBuilder;
46
47impl Provider for LlamafileExt {
48    type Builder = LlamafileBuilder;
49    const VERIFY_PATH: &'static str = "/models";
50
51    // Llamafile clients are constructed from a bare host URL
52    // (e.g. `http://localhost:8080`) while the shared OpenAI-compatible
53    // endpoints are relative to `/v1`.
54    fn build_uri(&self, base_url: &str, path: &str, _transport: Transport) -> String {
55        let base_url = base_url.trim_end_matches('/');
56        format!("{base_url}/v1/{}", path.trim_start_matches('/'))
57    }
58}
59
60impl openai::completion::OpenAICompatibleProvider for LlamafileExt {
61    const PROVIDER_NAME: &'static str = "llamafile";
62
63    type StreamingUsage = openai::Usage;
64
65    // llama.cpp-based servers can emit a whole tool call in one streaming chunk.
66    const EMITS_COMPLETE_SINGLE_CHUNK_TOOL_CALLS: bool = true;
67
68    type Response = openai::CompletionResponse;
69}
70
71impl<H> Capabilities<H> for LlamafileExt {
72    type Completion = Capable<openai::completion::GenericCompletionModel<LlamafileExt, H>>;
73    type Embeddings = Capable<openai::embedding::GenericEmbeddingModel<LlamafileExt, H>>;
74    type Transcription = Nothing;
75    type ModelListing = Nothing;
76    #[cfg(feature = "image")]
77    type ImageGeneration = Nothing;
78    #[cfg(feature = "audio")]
79    type AudioGeneration = Nothing;
80    type Rerank = Nothing;
81}
82
83impl DebugExt for LlamafileExt {}
84
85impl ProviderBuilder for LlamafileBuilder {
86    type Extension<H>
87        = LlamafileExt
88    where
89        H: HttpClientExt;
90    type ApiKey = Nothing;
91
92    const BASE_URL: &'static str = LLAMAFILE_API_BASE_URL;
93
94    fn build<H>(
95        _builder: &client::ClientBuilder<Self, Self::ApiKey, H>,
96    ) -> http_client::Result<Self::Extension<H>>
97    where
98        H: HttpClientExt,
99    {
100        Ok(LlamafileExt)
101    }
102}
103
104pub type Client<H = reqwest::Client> = client::Client<LlamafileExt, H>;
105pub type ClientBuilder<H = crate::markers::Missing> =
106    client::ClientBuilder<LlamafileBuilder, Nothing, H>;
107
108/// Llamafile completion model, driven by the shared OpenAI Chat Completions path.
109pub type CompletionModel<H = reqwest::Client> =
110    openai::completion::GenericCompletionModel<LlamafileExt, H>;
111
112/// Llamafile embedding model, driven by the shared OpenAI embeddings path.
113pub type EmbeddingModel<H = reqwest::Client> =
114    openai::embedding::GenericEmbeddingModel<LlamafileExt, H>;
115
116impl Client {
117    /// Create a client pointing at the given llamafile base URL
118    /// (e.g. `http://localhost:8080`).
119    pub fn from_url(base_url: &str) -> crate::client::ProviderClientResult<Self> {
120        Self::builder()
121            .api_key(Nothing)
122            .base_url(base_url)
123            .build()
124            .map_err(Into::into)
125    }
126}
127
128impl ProviderClient for Client {
129    type Input = Nothing;
130    type Error = crate::client::ProviderClientError;
131
132    fn from_env() -> Result<Self, Self::Error> {
133        let api_base = crate::client::required_env_var("LLAMAFILE_API_BASE_URL")?;
134        Self::from_url(&api_base)
135    }
136
137    fn from_val(_: Self::Input) -> Result<Self, Self::Error> {
138        Self::builder().api_key(Nothing).build().map_err(Into::into)
139    }
140}
141
142// ================================================================
143// Tests
144// ================================================================
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::client::Nothing;
149
150    #[test]
151    fn test_client_initialization() {
152        let _client =
153            crate::providers::llamafile::Client::new(Nothing).expect("Client::new() failed");
154        let _client_from_builder = crate::providers::llamafile::Client::builder()
155            .api_key(Nothing)
156            .build()
157            .expect("Client::builder() failed");
158    }
159
160    #[test]
161    fn test_client_from_url() {
162        let _client = crate::providers::llamafile::Client::from_url("http://localhost:8080");
163    }
164
165    #[test]
166    fn test_build_uri_routes_through_v1() {
167        let ext = LlamafileExt;
168        assert_eq!(
169            ext.build_uri(
170                "http://localhost:8080",
171                "/chat/completions",
172                Transport::Http
173            ),
174            "http://localhost:8080/v1/chat/completions"
175        );
176        assert_eq!(
177            ext.build_uri("http://localhost:8080/", "/embeddings", Transport::Http),
178            "http://localhost:8080/v1/embeddings"
179        );
180        assert_eq!(
181            ext.build_uri(
182                "http://localhost:8080",
183                LlamafileExt::VERIFY_PATH,
184                Transport::Http
185            ),
186            "http://localhost:8080/v1/models"
187        );
188    }
189}