From 845165b3a55ab9dbfd0ee905c40f43073bbea333 Mon Sep 17 00:00:00 2001 From: "Michael A. Kuykendall" Date: Mon, 13 Oct 2025 10:01:00 -0500 Subject: [PATCH] fix(api): enhance OpenAI API compatibility for frontend integration (Issue #113) - Add optional OpenAI-standard fields to Model struct: permission, root, parent - Use skip_serializing_if to omit null fields for cleaner JSON responses - Populate root field with model name for standard OpenAI compatibility - Update regression tests to verify enhanced Model structure - Improves compatibility with frontend tools that expect full OpenAI API format Resolves Issue #113: OpenAI API compatibility for frontends Signed-off-by: Michael A. Kuykendall --- src/openai_compat.rs | 11 ++++++++++- tests/regression_tests.rs | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/openai_compat.rs b/src/openai_compat.rs index 912d461..e7d4da0 100644 --- a/src/openai_compat.rs +++ b/src/openai_compat.rs @@ -77,6 +77,12 @@ pub struct Model { pub object: String, pub created: u64, pub owned_by: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub permission: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub root: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub parent: Option, } pub async fn models(State(state): State>) -> impl IntoResponse { @@ -85,10 +91,13 @@ pub async fn models(State(state): State>) -> impl IntoResponse { .list_all_available() .into_iter() .map(|name| Model { - id: name, + id: name.clone(), object: "model".to_string(), created: 0, // Fixed timestamp for simplicity owned_by: "shimmy".to_string(), + permission: None, // No fine-grained permissions for local models + root: Some(name), // The model itself is the root + parent: None, // Local models don't have parent models }) .collect(); diff --git a/tests/regression_tests.rs b/tests/regression_tests.rs index 9d75618..a7ffea9 100644 --- a/tests/regression_tests.rs +++ b/tests/regression_tests.rs @@ -203,12 +203,18 @@ mod regression_tests { object: "model".to_string(), created: 0, owned_by: "shimmy".to_string(), + permission: None, + root: Some("qwen3-4b-instruct".to_string()), + parent: None, }, Model { id: "llama-7b".to_string(), object: "model".to_string(), created: 0, owned_by: "shimmy".to_string(), + permission: None, + root: Some("llama-7b".to_string()), + parent: None, }, ], }; @@ -316,12 +322,15 @@ mod regression_tests { // Test the fix for Issue #113 - OpenAI API compatibility for frontends use shimmy::openai_compat::{Model, ModelsResponse}; - // Test basic Model structure (enhanced fields are on PR branch) + // Test enhanced Model structure with frontend compatibility fields let model = Model { id: "test-model".to_string(), object: "model".to_string(), created: 1640995200, owned_by: "shimmy".to_string(), + permission: None, + root: Some("test-model".to_string()), + parent: None, }; // Test serialization works correctly @@ -330,6 +339,10 @@ mod regression_tests { assert_eq!(json["owned_by"], "shimmy"); assert_eq!(json["object"], "model"); assert_eq!(json["created"], 1640995200); + assert_eq!(json["root"], "test-model"); + // Optional fields should be omitted when None (due to skip_serializing_if) + assert!(json.get("permission").is_none()); + assert!(json.get("parent").is_none()); // Test ModelsResponse structure let response = ModelsResponse {