diff --git a/README.md b/README.md index ba1f5c5..13a20ac 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ When the model decides to call a tool, the response `message.tool_calls` field w | `chat(request)` | Chat conversation (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()` @@ -185,6 +186,8 @@ let client = OllamaClient::builder("http://localhost:11434") **`EmbedRequest::builder(model)`** -- `.input()`, `.inputs()`, `.truncate()`, `.dimensions()`, `.keep_alive()`, `.options()` +**`ShowModelRequest::new(model)`** -- `.verbose()` + ### Generation Options Configure sampling parameters via `Options::builder()`: @@ -212,6 +215,7 @@ The `examples/` directory contains runnable programs: | `tool_call` | Function calling / tool use | | `pull` | Download a model | | `delete` | Delete a model | +| `show_model` | Show model information | | `embed` | Generate text embeddings | | `tags` | List available models | | `ps` | List running models | diff --git a/examples/show_model.rs b/examples/show_model.rs new file mode 100644 index 0000000..a5d8fe4 --- /dev/null +++ b/examples/show_model.rs @@ -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> { + 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(()) +} diff --git a/src/lib.rs b/src/lib.rs index bcbe680..ffa15c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ use crate::{ generate::{GenerateRequest, GenerateResponse}, ps::PsResponse, pull::{PullRequest, PullResponse}, + show::{ShowModelRequest, ShowModelResponse}, tags::TagsResponse, version::VersionResponse, }, @@ -456,6 +457,43 @@ impl OllamaClient { let request_address = format!("{}/api/pull", self.server_address); 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 { + 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. diff --git a/src/types/common.rs b/src/types/common.rs index 85df597..f5fdc25 100644 --- a/src/types/common.rs +++ b/src/types/common.rs @@ -9,7 +9,7 @@ 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)] pub struct ModelDetails { /// The model file format (e.g., `"gguf"`). diff --git a/src/types/mod.rs b/src/types/mod.rs index 7916e4d..444cefa 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,6 +9,7 @@ //! | [`embed`] | `POST /api/embed` | Generate vector embeddings | //! | [`generate`] | `POST /api/generate` | Single-prompt text generation | //! | [`pull`] | `POST /api/pull` | Download models from the registry | +//! | [`show`] | `POST /api/show` | Show model details | //! | [`tags`] | `GET /api/tags` | List available models | //! | [`ps`] | `GET /api/ps` | List currently loaded/running models | //! | [`version`] | `GET /api/version` | Query the server version | @@ -24,5 +25,6 @@ pub mod embed; pub mod generate; pub mod ps; pub mod pull; +pub mod show; pub mod tags; pub mod version; diff --git a/src/types/show.rs b/src/types/show.rs new file mode 100644 index 0000000..91f49e3 --- /dev/null +++ b/src/types/show.rs @@ -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, +} + +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, + /// The modelfile content. + pub modelfile: Option, + /// The model parameters (e.g., stop tokens, temperature defaults). + pub parameters: Option, + /// The prompt template. + pub template: Option, + /// The system prompt. + pub system: Option, + /// Detailed model metadata. + pub details: Option, + /// Additional model information. + pub model_info: Option>, +} + +#[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"); + } +}