feat: update test
This commit is contained in:
+4
-30
@@ -1,8 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::errors::OllamaError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ModelsResponse {
|
||||
pub models: Vec<ModelInfo>,
|
||||
@@ -28,37 +26,13 @@ pub struct LoadModelBody {
|
||||
pub keep_alive: Option<String>,
|
||||
}
|
||||
|
||||
impl LoadModelBody {
|
||||
pub fn parse_keep_alive(s: &str) -> Result<(), OllamaError> {
|
||||
let s = s.trim();
|
||||
|
||||
if s == "-1" || s.parse::<u64>().is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let split = s
|
||||
.find(|c: char| c.is_alphabetic())
|
||||
.ok_or_else(|| OllamaError::InvalidKeepAlive(s.to_string()))?;
|
||||
|
||||
let (num, unit) = s.split_at(split);
|
||||
|
||||
num.parse::<u64>()
|
||||
.map_err(|_| OllamaError::InvalidKeepAlive(s.to_string()))?;
|
||||
|
||||
match unit {
|
||||
"s" | "m" | "h" => Ok(()),
|
||||
_ => Err(OllamaError::InvalidKeepAlive(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UnloadModelResponse {
|
||||
pub model: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, ToSchema)]
|
||||
#[derive(Debug, Deserialize, Serialize, ToSchema, Default)]
|
||||
pub struct BaseLLMRequest {
|
||||
pub model: String,
|
||||
|
||||
@@ -89,13 +63,13 @@ pub struct CompletionRequest {
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompletionObject {
|
||||
TextCompletion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FinishReason {
|
||||
Stop,
|
||||
@@ -149,7 +123,7 @@ pub struct Message {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, ToSchema)]
|
||||
#[derive(Debug, Deserialize, Serialize, ToSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
System,
|
||||
|
||||
@@ -15,6 +15,9 @@ pub enum OllamaError {
|
||||
#[error("model '{0}' is not available — run `ollama pull {0}` first")]
|
||||
ModelNotFound(String),
|
||||
|
||||
#[error("keep_alive is required and cannot be empty")]
|
||||
MissingKeepAlive,
|
||||
|
||||
#[error(
|
||||
"invalid keep_alive format '{0}' — expected <number><unit> (e.g. 30s, 10m, 2h), a plain integer (seconds), or -1"
|
||||
)]
|
||||
|
||||
@@ -36,6 +36,10 @@ impl OllamaProvider {
|
||||
Ok(res.models.iter().any(|m| m.name == model))
|
||||
}
|
||||
|
||||
fn has_user_message(&self, messages: &[api::Message]) -> bool {
|
||||
messages.iter().any(|m| matches!(m.role, api::Role::User))
|
||||
}
|
||||
|
||||
fn extract_completion_params<'a>(
|
||||
&self,
|
||||
body: &'a api::CompletionRequest,
|
||||
@@ -56,6 +60,28 @@ impl OllamaProvider {
|
||||
Ok((&body.messages, model))
|
||||
}
|
||||
|
||||
pub fn parse_keep_alive(&self, s: &str) -> Result<(), OllamaError> {
|
||||
let s = s.trim();
|
||||
|
||||
if s == "-1" || s.parse::<u64>().is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let split = s
|
||||
.find(|c: char| c.is_alphabetic())
|
||||
.ok_or_else(|| OllamaError::InvalidKeepAlive(s.to_string()))?;
|
||||
|
||||
let (num, unit) = s.split_at(split);
|
||||
|
||||
num.parse::<u64>()
|
||||
.map_err(|_| OllamaError::InvalidKeepAlive(s.to_string()))?;
|
||||
|
||||
match unit {
|
||||
"s" | "m" | "h" => Ok(()),
|
||||
_ => Err(OllamaError::InvalidKeepAlive(s.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
// // ── public endpoints ─────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_models(&self) -> Result<api::ModelsResponse, OllamaError> {
|
||||
@@ -77,10 +103,14 @@ impl OllamaProvider {
|
||||
pub async fn load_model(
|
||||
&self,
|
||||
model: &str,
|
||||
keep_alive: &str,
|
||||
keep_alive: Option<&str>,
|
||||
) -> Result<api::LoadModelResponse, OllamaError> {
|
||||
let url = format!("{}/api/generate", self.base_url);
|
||||
|
||||
let keep_alive = keep_alive.ok_or(OllamaError::MissingKeepAlive)?;
|
||||
|
||||
self.parse_keep_alive(keep_alive)?;
|
||||
|
||||
let exists = self.model_exists(model).await?;
|
||||
if !exists {
|
||||
return Err(OllamaError::ModelNotFound(model.to_string()));
|
||||
@@ -99,7 +129,7 @@ impl OllamaProvider {
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?
|
||||
.json::<ollama::OllamaGenerateResponse>()
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
Ok(api::LoadModelResponse {
|
||||
@@ -130,7 +160,7 @@ impl OllamaProvider {
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?
|
||||
.json::<ollama::OllamaGenerateResponse>()
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
Ok(api::UnloadModelResponse {
|
||||
@@ -277,6 +307,10 @@ impl OllamaProvider {
|
||||
return Err(OllamaError::MissingMessages);
|
||||
}
|
||||
|
||||
if !self.has_user_message(&body.messages) {
|
||||
return Err(OllamaError::MissingMessages);
|
||||
}
|
||||
|
||||
if model.is_empty() {
|
||||
return Err(OllamaError::MissingModel);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ fn ollama_err(e: OllamaError) -> (axum::http::StatusCode, String) {
|
||||
axum::http::StatusCode::UNPROCESSABLE_ENTITY,
|
||||
format!("model '{m}' is not available — run `ollama pull {m}` first"),
|
||||
),
|
||||
OllamaError::MissingKeepAlive => (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
"keep alive is required and cannot be empty".to_string(),
|
||||
),
|
||||
OllamaError::InvalidKeepAlive(v) => (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
format!("invalid keep_alive '{v}'"),
|
||||
|
||||
@@ -20,13 +20,9 @@ pub async fn load_model(
|
||||
Path(model): Path<String>,
|
||||
Json(body): Json<api::LoadModelBody>,
|
||||
) -> Result<Json<api::LoadModelResponse>, (axum::http::StatusCode, String)> {
|
||||
let keep_alive = body.keep_alive.as_deref().unwrap_or("5m");
|
||||
|
||||
api::LoadModelBody::parse_keep_alive(keep_alive).map_err(ollama_err)?;
|
||||
|
||||
let response = state
|
||||
.ollama
|
||||
.load_model(&model, keep_alive)
|
||||
.load_model(&model, body.keep_alive.as_deref())
|
||||
.await
|
||||
.map_err(ollama_err)?;
|
||||
|
||||
@@ -64,6 +60,10 @@ fn ollama_err(e: OllamaError) -> (axum::http::StatusCode, String) {
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
"messages array with at least one user message is required".to_string(),
|
||||
),
|
||||
OllamaError::MissingKeepAlive => (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
"keep alive is required and cannot be empty".to_string(),
|
||||
),
|
||||
OllamaError::InvalidKeepAlive(v) => (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
format!("invalid keep_alive '{v}' — use 30s / 10m / 2h, a plain integer, or -1"),
|
||||
|
||||
Reference in New Issue
Block a user