Compare commits

..

11 Commits

Author SHA1 Message Date
1b37c8dafe Implement Clone for Stop
Some checks failed
CI / Check (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
2026-04-03 15:40:08 +01:00
19fd7ad712 Options implements Debug 2026-04-03 15:38:55 +01:00
8fc1268e81 Merge pull request #4 from andreban/feat/api-show
feat: implement api/show endpoint
2026-02-02 12:24:23 +00:00
de2efc51d5 Runs cargo fmt 2026-02-02 12:21:08 +00:00
5bad832353 feat: implement api/show endpoint 2026-02-02 10:19:42 +00:00
6f14e5e908 Merge pull request #3 from andreban/add-embed-endpoint
Add embed endpoint (POST /api/embed)
2026-02-01 21:27:47 +00:00
0f796f1a2f Add embed endpoint (POST /api/embed)
Implement the Ollama POST /api/embed endpoint for generating vector
embeddings from text input.

- Add EmbedInput, EmbedRequest, EmbedResponse types in src/types/embed.rs
- Add OllamaClient::embed() async method in src/lib.rs
- Register embed module in src/types/mod.rs
- Add usage example in examples/embed.rs
- Update README with embed endpoint documentation
2026-02-01 21:26:44 +00:00
b885ca3c1c Merge pull request #2 from andreban/update-readme-delete-endpoint
Update README with delete endpoint documentation
2026-02-01 21:19:10 +00:00
fe74d78645 Update README with delete endpoint documentation 2026-02-01 21:18:15 +00:00
c5abf87d9d Merge pull request #1 from andreban/add-delete-endpoint
Add delete model endpoint (DELETE /api/delete)
2026-02-01 21:15:53 +00:00
63c08c484c Add delete model endpoint (DELETE /api/delete)
Implement the Ollama DELETE /api/delete endpoint for removing models
from the server.

- Add DeleteRequest type with model field in src/types/delete.rs
- Add OllamaClient::delete() async method in src/lib.rs
- Register delete module in src/types/mod.rs
- Add usage example in examples/delete.rs
2026-02-01 21:14:26 +00:00
10 changed files with 705 additions and 12 deletions

View File

@@ -8,7 +8,8 @@ An async Rust client library for the [Ollama](https://ollama.com/) API. Provides
- Text generation and multi-turn chat conversations - Text generation and multi-turn chat conversations
- Structured JSON output with schema validation - Structured JSON output with schema validation
- Tool calling / function calling support - Tool calling / function calling support
- Model management (list, pull, inspect running models) - Model management (list, pull, delete, inspect running models)
- Text embeddings generation
- Builder pattern for constructing requests - Builder pattern for constructing requests
- Configurable generation parameters (temperature, top-k, top-p, and more) - Configurable generation parameters (temperature, top-k, top-p, and more)
- Thinking / reasoning mode support - Thinking / reasoning mode support
@@ -160,6 +161,9 @@ When the model decides to call a tool, the response `message.tool_calls` field w
| `generate(request)` | Generate text (streaming) | | `generate(request)` | Generate text (streaming) |
| `chat(request)` | Chat conversation (streaming) | | `chat(request)` | Chat conversation (streaming) |
| `pull(request)` | Pull/download a model (streaming) | | `pull(request)` | Pull/download a model (streaming) |
| `delete(request)` | Delete a model from the server |
| `show_model(request)` | Show information about a model |
| `embed(request)` | Generate vector embeddings |
**`OllamaClient::builder(server_address)`** -- `.connection_timeout(Duration)`, `.build()` **`OllamaClient::builder(server_address)`** -- `.connection_timeout(Duration)`, `.build()`
@@ -180,6 +184,10 @@ let client = OllamaClient::builder("http://localhost:11434")
**`PullRequest::builder(model)`** -- `.stream()` **`PullRequest::builder(model)`** -- `.stream()`
**`EmbedRequest::builder(model)`** -- `.input()`, `.inputs()`, `.truncate()`, `.dimensions()`, `.keep_alive()`, `.options()`
**`ShowModelRequest::new(model)`** -- `.verbose()`
### Generation Options ### Generation Options
Configure sampling parameters via `Options::builder()`: Configure sampling parameters via `Options::builder()`:
@@ -206,6 +214,9 @@ The `examples/` directory contains runnable programs:
| `structured_output` | JSON structured output with schema | | `structured_output` | JSON structured output with schema |
| `tool_call` | Function calling / tool use | | `tool_call` | Function calling / tool use |
| `pull` | Download a model | | `pull` | Download a model |
| `delete` | Delete a model |
| `show_model` | Show model information |
| `embed` | Generate text embeddings |
| `tags` | List available models | | `tags` | List available models |
| `ps` | List running models | | `ps` | List running models |
| `version` | Query server version | | `version` | Query server version |

16
examples/delete.rs Normal file
View File

@@ -0,0 +1,16 @@
use std::{env, error::Error};
use ollama_rs::OllamaClient;
use ollama_rs::types::delete::DeleteRequest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt().init();
let _ = dotenvy::dotenv();
let server_address = env::var("OLLAMA_SERVER")?;
let model = env::args().nth(1).expect("usage: delete <model>");
let ollama_client = OllamaClient::new(server_address);
ollama_client.delete(DeleteRequest::new(&model)).await?;
println!("Model '{}' deleted successfully", model);
Ok(())
}

24
examples/embed.rs Normal file
View File

@@ -0,0 +1,24 @@
use std::{env, error::Error};
use ollama_rs::OllamaClient;
use ollama_rs::types::embed::EmbedRequest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt().init();
let _ = dotenvy::dotenv();
let server_address = env::var("OLLAMA_SERVER")?;
let ollama_client = OllamaClient::new(server_address);
let request = EmbedRequest::builder("embeddinggemma")
.input("Generate embeddings for this text")
.build();
let response = ollama_client.embed(request).await?;
for (i, embedding) in response.embeddings.iter().enumerate() {
println!("Embedding {}: {} dimensions", i, embedding.len());
if embedding.len() >= 3 {
println!(" First 3 values: {:?}", &embedding[..3]);
}
}
Ok(())
}

37
examples/show_model.rs Normal file
View File

@@ -0,0 +1,37 @@
use ollama_rs::OllamaClient;
use ollama_rs::types::show::ShowModelRequest;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = OllamaClient::default();
let model = "qwen3:8b";
println!("Requesting info for model: {}", model);
let request = ShowModelRequest::new(model.to_string());
let response = client.show_model(request).await?;
println!("Model Info:");
println!(
" License: {}",
response
.license
.as_deref()
.unwrap_or("N/A")
.lines()
.next()
.unwrap_or("")
); // First line only
println!(
" Modelfile: {} bytes",
response.modelfile.as_ref().map(|s| s.len()).unwrap_or(0)
);
println!(
" Parameters: {}",
response.parameters.as_ref().map(|s| s.len()).unwrap_or(0)
);
println!(" Template: {:?}", response.template.is_some());
println!(" Details: {:?}", response.details);
Ok(())
}

View File

@@ -93,9 +93,12 @@ use crate::{
error::{OllamaError, OllamaResult}, error::{OllamaError, OllamaResult},
types::{ types::{
chat::{ChatRequest, ChatResponse}, chat::{ChatRequest, ChatResponse},
delete::DeleteRequest,
embed::{EmbedRequest, EmbedResponse},
generate::{GenerateRequest, GenerateResponse}, generate::{GenerateRequest, GenerateResponse},
ps::PsResponse, ps::PsResponse,
pull::{PullRequest, PullResponse}, pull::{PullRequest, PullResponse},
show::{ShowModelRequest, ShowModelResponse},
tags::TagsResponse, tags::TagsResponse,
version::VersionResponse, version::VersionResponse,
}, },
@@ -247,6 +250,81 @@ impl OllamaClient {
.await?) .await?)
} }
/// Deletes a model from the Ollama server.
///
/// Calls `DELETE /api/delete`.
///
/// # Errors
///
/// Returns [`OllamaError::NetworkError`] if the server is unreachable, the model
/// does not exist, or the server returns a non-success status code.
///
/// # Examples
///
/// ```no_run
/// # use ollama_rs::OllamaClient;
/// # use ollama_rs::types::delete::DeleteRequest;
/// # async fn run() -> ollama_rs::error::OllamaResult<()> {
/// let client = OllamaClient::default();
/// let request = DeleteRequest::new("gemma3");
/// client.delete(request).await?;
/// println!("Model deleted successfully");
/// # Ok(())
/// # }
/// ```
pub async fn delete(&self, request: DeleteRequest) -> OllamaResult<()> {
let request_address = format!("{}/api/delete", self.server_address);
info!("Delete model: {}", request.model);
self.client
.delete(request_address)
.json(&request)
.send()
.await?
.error_for_status()?;
Ok(())
}
/// Generates vector embeddings for the given input text(s).
///
/// Calls `POST /api/embed`.
///
/// # Errors
///
/// Returns [`OllamaError::NetworkError`] if the server is unreachable or returns
/// a non-success status code.
///
/// # Examples
///
/// ```no_run
/// # use ollama_rs::OllamaClient;
/// # use ollama_rs::types::embed::EmbedRequest;
/// # async fn run() -> ollama_rs::error::OllamaResult<()> {
/// let client = OllamaClient::default();
/// let request = EmbedRequest::builder("embeddinggemma")
/// .input("Generate embeddings for this text")
/// .build();
///
/// let response = client.embed(request).await?;
/// for embedding in &response.embeddings {
/// println!("Dimension count: {}", embedding.len());
/// }
/// # Ok(())
/// # }
/// ```
pub async fn embed(&self, request: EmbedRequest) -> OllamaResult<EmbedResponse> {
let request_address = format!("{}/api/embed", self.server_address);
info!("Generate embeddings: {}", request.model);
Ok(self
.client
.post(request_address)
.json(&request)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
fn stream_response<R: Serialize, T: DeserializeOwned>( fn stream_response<R: Serialize, T: DeserializeOwned>(
&self, &self,
endpoint: String, endpoint: String,
@@ -379,6 +457,43 @@ impl OllamaClient {
let request_address = format!("{}/api/pull", self.server_address); let request_address = format!("{}/api/pull", self.server_address);
self.stream_response(request_address, request) self.stream_response(request_address, request)
} }
/// Shows information about a model.
///
/// Calls `POST /api/show`.
///
/// # Errors
///
/// Returns [`OllamaError::NetworkError`] if the server is unreachable or returns
/// a non-success status code.
///
/// # Examples
///
/// ```no_run
/// # use ollama_rs::OllamaClient;
/// # use ollama_rs::types::show::ShowModelRequest;
/// # async fn run() -> ollama_rs::error::OllamaResult<()> {
/// let client = OllamaClient::default();
/// let request = ShowModelRequest::new("llama3".to_string());
///
/// let response = client.show_model(request).await?;
/// println!("Model info: {:?}", response);
/// # Ok(())
/// # }
/// ```
pub async fn show_model(&self, request: ShowModelRequest) -> OllamaResult<ShowModelResponse> {
let request_address = format!("{}/api/show", self.server_address);
info!("Show model: {}", request.name);
Ok(self
.client
.post(request_address)
.json(&request)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
} }
/// A builder for constructing an [`OllamaClient`] with custom configuration. /// A builder for constructing an [`OllamaClient`] with custom configuration.

View File

@@ -9,7 +9,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Detailed metadata about a model, returned by the tags and ps endpoints. /// Detailed metadata about a model, returned by the tags, ps, and show endpoints.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ModelDetails { pub struct ModelDetails {
/// The model file format (e.g., `"gguf"`). /// The model file format (e.g., `"gguf"`).
@@ -84,7 +84,7 @@ pub enum ThinkLevel {
/// .stop(Stop::Single("END".to_string())) /// .stop(Stop::Single("END".to_string()))
/// .build(); /// .build();
/// ``` /// ```
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Options { pub struct Options {
/// Random seed for reproducible outputs. /// Random seed for reproducible outputs.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -220,7 +220,7 @@ impl OptionsBuilder {
/// let single = Stop::Single("END".to_string()); /// let single = Stop::Single("END".to_string());
/// let multiple = Stop::Multiple(vec!["END".to_string(), "STOP".to_string()]); /// let multiple = Stop::Multiple(vec!["END".to_string(), "STOP".to_string()]);
/// ``` /// ```
#[derive(Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum Stop { pub enum Stop {
/// A single stop sequence. /// A single stop sequence.

77
src/types/delete.rs Normal file
View File

@@ -0,0 +1,77 @@
//! Types for the model delete endpoint (`DELETE /api/delete`).
//!
//! Use [`DeleteRequest::new()`] to construct a request and pass it to
//! [`OllamaClient::delete()`](crate::OllamaClient::delete).
//!
//! # Examples
//!
//! ```no_run
//! # use ollama_rs::OllamaClient;
//! # use ollama_rs::types::delete::DeleteRequest;
//! # async fn run() -> ollama_rs::error::OllamaResult<()> {
//! let client = OllamaClient::default();
//! let request = DeleteRequest::new("gemma3");
//! client.delete(request).await?;
//! println!("Model deleted successfully");
//! # Ok(())
//! # }
//! ```
use serde::{Deserialize, Serialize};
/// A request to delete a model from the Ollama server (`DELETE /api/delete`).
///
/// # Examples
///
/// ```
/// use ollama_rs::types::delete::DeleteRequest;
///
/// let request = DeleteRequest::new("gemma3");
/// assert_eq!(request.model, "gemma3");
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct DeleteRequest {
/// The name of the model to delete (e.g., `"gemma3"`, `"llama3:latest"`).
pub model: String,
}
impl DeleteRequest {
/// Creates a new [`DeleteRequest`] for the given model name.
pub fn new<M: Into<String>>(model: M) -> Self {
Self {
model: model.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn new_creates_request() {
let request = DeleteRequest::new("gemma3");
assert_eq!(request.model, "gemma3");
}
#[test]
fn new_accepts_string() {
let request = DeleteRequest::new(String::from("llama3:latest"));
assert_eq!(request.model, "llama3:latest");
}
#[test]
fn request_serializes_correctly() {
let request = DeleteRequest::new("gemma3");
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json, json!({"model": "gemma3"}));
}
#[test]
fn request_deserializes_correctly() {
let json = json!({"model": "gemma3"});
let request: DeleteRequest = serde_json::from_value(json).unwrap();
assert_eq!(request.model, "gemma3");
}
}

306
src/types/embed.rs Normal file
View File

@@ -0,0 +1,306 @@
//! Types for the embedding endpoint (`POST /api/embed`).
//!
//! Use [`EmbedRequest::builder()`] to construct a request and pass it to
//! [`OllamaClient::embed()`](crate::OllamaClient::embed).
//!
//! # Examples
//!
//! ```no_run
//! # use ollama_rs::OllamaClient;
//! # use ollama_rs::types::embed::EmbedRequest;
//! # async fn run() -> ollama_rs::error::OllamaResult<()> {
//! let client = OllamaClient::default();
//! let request = EmbedRequest::builder("embeddinggemma")
//! .input("Generate embeddings for this text")
//! .build();
//!
//! let response = client.embed(request).await?;
//! println!("Embeddings: {:?}", response.embeddings);
//! # Ok(())
//! # }
//! ```
use serde::{Deserialize, Serialize};
use crate::types::common::Options;
/// The input text(s) to generate embeddings for.
///
/// Accepts either a single string or an array of strings. Serialized as an
/// untagged enum so both `"hello"` and `["hello", "world"]` are valid JSON
/// representations.
///
/// # Examples
///
/// ```
/// use ollama_rs::types::embed::EmbedInput;
///
/// let single = EmbedInput::Single("hello".to_string());
/// let multiple = EmbedInput::Multiple(vec!["hello".to_string(), "world".to_string()]);
/// ```
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EmbedInput {
/// A single text string.
Single(String),
/// Multiple text strings.
Multiple(Vec<String>),
}
/// A request to generate embeddings (`POST /api/embed`).
///
/// Construct via [`EmbedRequest::builder()`].
///
/// # Examples
///
/// ```
/// use ollama_rs::types::embed::EmbedRequest;
///
/// let request = EmbedRequest::builder("embeddinggemma")
/// .input("Generate embeddings for this text")
/// .build();
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct EmbedRequest {
/// The model name to use for generating embeddings.
pub model: String,
/// The text or array of texts to generate embeddings for.
pub input: EmbedInput,
/// If `true`, truncate inputs that exceed the context window. If `false`,
/// returns an error. Defaults to `true` on the server.
#[serde(skip_serializing_if = "Option::is_none")]
pub truncate: Option<bool>,
/// Number of dimensions to generate embeddings for.
#[serde(skip_serializing_if = "Option::is_none")]
pub dimensions: Option<u32>,
/// How long the model stays loaded in memory (e.g., `"5m"`, `"1h"`).
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_alive: Option<String>,
/// Runtime options for the embedding model.
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Options>,
}
impl EmbedRequest {
/// Returns an [`EmbedRequestBuilder`] for the given model name.
pub fn builder<M: Into<String>>(model: M) -> EmbedRequestBuilder {
EmbedRequestBuilder {
embed_request: EmbedRequest {
model: model.into(),
input: EmbedInput::Single(String::new()),
truncate: None,
dimensions: None,
keep_alive: None,
options: None,
},
}
}
}
/// A builder for constructing an [`EmbedRequest`].
///
/// Obtain a builder via [`EmbedRequest::builder()`].
///
/// # Examples
///
/// ```
/// use ollama_rs::types::embed::EmbedRequest;
///
/// let request = EmbedRequest::builder("embeddinggemma")
/// .input("hello world")
/// .truncate(true)
/// .build();
/// ```
pub struct EmbedRequestBuilder {
embed_request: EmbedRequest,
}
impl EmbedRequestBuilder {
/// Sets a single text string as the input.
pub fn input<S: Into<String>>(mut self, input: S) -> Self {
self.embed_request.input = EmbedInput::Single(input.into());
self
}
/// Sets multiple text strings as the input.
pub fn inputs<S: Into<String>>(mut self, inputs: Vec<S>) -> Self {
self.embed_request.input =
EmbedInput::Multiple(inputs.into_iter().map(|s| s.into()).collect());
self
}
/// Sets whether to truncate inputs that exceed the context window.
pub fn truncate(mut self, truncate: bool) -> Self {
self.embed_request.truncate = Some(truncate);
self
}
/// Sets the number of dimensions for the embeddings.
pub fn dimensions(mut self, dimensions: u32) -> Self {
self.embed_request.dimensions = Some(dimensions);
self
}
/// Sets how long the model stays loaded in memory (e.g., `"5m"`).
pub fn keep_alive<S: Into<String>>(mut self, keep_alive: S) -> Self {
self.embed_request.keep_alive = Some(keep_alive.into());
self
}
/// Sets runtime options for the embedding model.
pub fn options(mut self, options: Options) -> Self {
self.embed_request.options = Some(options);
self
}
/// Consumes the builder and returns the configured [`EmbedRequest`].
pub fn build(self) -> EmbedRequest {
self.embed_request
}
}
/// The response from the embedding endpoint.
#[derive(Debug, Serialize, Deserialize)]
pub struct EmbedResponse {
/// The model that produced the embeddings.
pub model: String,
/// The generated vector embeddings. Each inner `Vec<f64>` corresponds to
/// one input text, in the same order as the request.
pub embeddings: Vec<Vec<f64>>,
/// Total time spent generating embeddings, in nanoseconds.
#[serde(skip_serializing_if = "Option::is_none")]
pub total_duration: Option<u64>,
/// Time spent loading the model, in nanoseconds.
#[serde(skip_serializing_if = "Option::is_none")]
pub load_duration: Option<u64>,
/// Number of input tokens processed to generate the embeddings.
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_eval_count: Option<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn builder_minimal() {
let request = EmbedRequest::builder("embeddinggemma")
.input("hello")
.build();
assert_eq!(request.model, "embeddinggemma");
assert!(matches!(request.input, EmbedInput::Single(ref s) if s == "hello"));
assert!(request.truncate.is_none());
assert!(request.dimensions.is_none());
assert!(request.keep_alive.is_none());
assert!(request.options.is_none());
}
#[test]
fn builder_with_all_fields() {
let request = EmbedRequest::builder("embeddinggemma")
.inputs(vec!["hello", "world"])
.truncate(false)
.dimensions(256)
.keep_alive("10m")
.options(Options::builder().seed(42).build())
.build();
assert!(matches!(request.input, EmbedInput::Multiple(ref v) if v.len() == 2));
assert_eq!(request.truncate, Some(false));
assert_eq!(request.dimensions, Some(256));
assert_eq!(request.keep_alive, Some("10m".to_string()));
assert!(request.options.is_some());
}
#[test]
fn request_skips_none_fields() {
let request = EmbedRequest::builder("embeddinggemma")
.input("hello")
.build();
let json = serde_json::to_value(&request).unwrap();
let obj = json.as_object().unwrap();
assert!(obj.contains_key("model"));
assert!(obj.contains_key("input"));
assert!(!obj.contains_key("truncate"));
assert!(!obj.contains_key("dimensions"));
assert!(!obj.contains_key("keep_alive"));
assert!(!obj.contains_key("options"));
}
#[test]
fn request_serializes_single_input() {
let request = EmbedRequest::builder("embeddinggemma")
.input("hello")
.build();
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["input"], json!("hello"));
}
#[test]
fn request_serializes_multiple_inputs() {
let request = EmbedRequest::builder("embeddinggemma")
.inputs(vec!["hello", "world"])
.build();
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["input"], json!(["hello", "world"]));
}
#[test]
fn embed_input_single_round_trip() {
let input = EmbedInput::Single("test".to_string());
let json = serde_json::to_value(&input).unwrap();
assert_eq!(json, json!("test"));
let deserialized: EmbedInput = serde_json::from_value(json).unwrap();
assert!(matches!(deserialized, EmbedInput::Single(s) if s == "test"));
}
#[test]
fn embed_input_multiple_round_trip() {
let input = EmbedInput::Multiple(vec!["a".to_string(), "b".to_string()]);
let json = serde_json::to_value(&input).unwrap();
assert_eq!(json, json!(["a", "b"]));
let deserialized: EmbedInput = serde_json::from_value(json).unwrap();
assert!(matches!(deserialized, EmbedInput::Multiple(v) if v == vec!["a", "b"]));
}
#[test]
fn response_deserialize() {
let json = json!({
"model": "embeddinggemma",
"embeddings": [[0.010071029, -0.0017594862, 0.05007221]],
"total_duration": 14143917,
"load_duration": 1019500,
"prompt_eval_count": 8
});
let response: EmbedResponse = serde_json::from_value(json).unwrap();
assert_eq!(response.model, "embeddinggemma");
assert_eq!(response.embeddings.len(), 1);
assert_eq!(response.embeddings[0].len(), 3);
assert_eq!(response.total_duration, Some(14143917));
assert_eq!(response.load_duration, Some(1019500));
assert_eq!(response.prompt_eval_count, Some(8));
}
#[test]
fn response_deserialize_minimal() {
let json = json!({
"model": "embeddinggemma",
"embeddings": [[1.0, 2.0], [3.0, 4.0]]
});
let response: EmbedResponse = serde_json::from_value(json).unwrap();
assert_eq!(response.embeddings.len(), 2);
assert!(response.total_duration.is_none());
assert!(response.load_duration.is_none());
assert!(response.prompt_eval_count.is_none());
}
}

View File

@@ -3,10 +3,13 @@
//! Each submodule corresponds to an API endpoint: //! Each submodule corresponds to an API endpoint:
//! //!
//! | Module | Endpoint | Description | //! | Module | Endpoint | Description |
//! |--------------|-------------------|------------------------------------------| //! |--------------|-----------------------|------------------------------------------|
//! | [`chat`] | `POST /api/chat` | Multi-turn chat conversations | //! | [`chat`] | `POST /api/chat` | Multi-turn chat conversations |
//! | [`delete`] | `DELETE /api/delete` | Delete a model from the server |
//! | [`embed`] | `POST /api/embed` | Generate vector embeddings |
//! | [`generate`] | `POST /api/generate` | Single-prompt text generation | //! | [`generate`] | `POST /api/generate` | Single-prompt text generation |
//! | [`pull`] | `POST /api/pull` | Download models from the registry | //! | [`pull`] | `POST /api/pull` | Download models from the registry |
//! | [`show`] | `POST /api/show` | Show model details |
//! | [`tags`] | `GET /api/tags` | List available models | //! | [`tags`] | `GET /api/tags` | List available models |
//! | [`ps`] | `GET /api/ps` | List currently loaded/running models | //! | [`ps`] | `GET /api/ps` | List currently loaded/running models |
//! | [`version`] | `GET /api/version` | Query the server version | //! | [`version`] | `GET /api/version` | Query the server version |
@@ -17,8 +20,11 @@
pub mod chat; pub mod chat;
pub mod common; pub mod common;
pub mod delete;
pub mod embed;
pub mod generate; pub mod generate;
pub mod ps; pub mod ps;
pub mod pull; pub mod pull;
pub mod show;
pub mod tags; pub mod tags;
pub mod version; pub mod version;

101
src/types/show.rs Normal file
View File

@@ -0,0 +1,101 @@
//! Types for the show model information endpoint (`POST /api/show`).
//!
//! Use [`ShowModelRequest`] to construct a request and pass it to
//! [`OllamaClient::show_model`](crate::OllamaClient::show_model).
use serde::{Deserialize, Serialize};
use super::common::ModelDetails;
/// A request to the show model endpoint (`POST /api/show`).
#[derive(Debug, Serialize)]
pub struct ShowModelRequest {
/// The name of the model to retrieve information for.
pub name: String,
/// Whether to return full model details (e.g. system prompt, template).
#[serde(skip_serializing_if = "Option::is_none")]
pub verbose: Option<bool>,
}
impl ShowModelRequest {
/// Creates a new request for the given model name.
pub fn new(name: String) -> Self {
Self {
name,
verbose: None,
}
}
/// Enables verbose output, returning full model details.
pub fn verbose(mut self) -> Self {
self.verbose = Some(true);
self
}
}
/// A response from the show model endpoint.
#[derive(Debug, Deserialize)]
pub struct ShowModelResponse {
/// The license of the model.
pub license: Option<String>,
/// The modelfile content.
pub modelfile: Option<String>,
/// The model parameters (e.g., stop tokens, temperature defaults).
pub parameters: Option<String>,
/// The prompt template.
pub template: Option<String>,
/// The system prompt.
pub system: Option<String>,
/// Detailed model metadata.
pub details: Option<ModelDetails>,
/// Additional model information.
pub model_info: Option<serde_json::Map<String, serde_json::Value>>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn request_new_serialized() {
let request = ShowModelRequest::new("llama3".to_string());
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json, json!({"name": "llama3"}));
}
#[test]
fn request_verbose_serialized() {
let request = ShowModelRequest::new("llama3".to_string()).verbose();
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json, json!({"name": "llama3", "verbose": true}));
}
#[test]
fn response_deserialization() {
let json = json!({
"license": "MIT",
"modelfile": "FROM llama3",
"parameters": "temperature 0.7",
"template": "Please answer:",
"system": "You are helpful",
"details": {
"format": "gguf",
"family": "llama",
"families": ["llama"],
"parameter_size": "8B",
"quantization_level": "Q4_0"
}
});
let response: ShowModelResponse = serde_json::from_value(json).unwrap();
assert_eq!(response.license, Some("MIT".to_string()));
assert_eq!(response.modelfile, Some("FROM llama3".to_string()));
assert_eq!(response.parameters, Some("temperature 0.7".to_string()));
assert_eq!(response.template, Some("Please answer:".to_string()));
assert_eq!(response.system, Some("You are helpful".to_string()));
let details = response.details.unwrap();
assert_eq!(details.format, "gguf");
assert_eq!(details.family, "llama");
}
}