Skip to content

Commit 53874c7

Browse files
Fix OpenAI API compatibility for frontend integration (Issue #113) (#155)
- Remove extra fields (root, parent, permission) from /v1/models response - Create separate ListModel struct for models endpoint that matches OpenAI spec exactly - Update created timestamp to use proper system time instead of 0 - Update all tests to use the new ListModel structure Root cause: Frontend applications like Open WebUI and AnythingLLM expect strict OpenAI API compliance. The extra fields in shimmy's Model struct were causing them to reject the API as incompatible. Fix ensures /v1/models returns only: id, object, created, owned_by - matching OpenAI specification exactly.
1 parent af49c7a commit 53874c7

3 files changed

Lines changed: 34 additions & 40 deletions

File tree

src/openai_compat.rs

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,15 @@ pub struct Delta {
8686
#[derive(Debug, Serialize, Deserialize)]
8787
pub struct ModelsResponse {
8888
pub object: String,
89-
pub data: Vec<Model>,
89+
pub data: Vec<ListModel>,
90+
}
91+
92+
#[derive(Debug, Serialize, Deserialize)]
93+
pub struct ListModel {
94+
pub id: String,
95+
pub object: String,
96+
pub created: u64,
97+
pub owned_by: String,
9098
}
9199

92100
#[derive(Debug, Serialize, Deserialize)]
@@ -108,14 +116,14 @@ pub async fn models(State(state): State<Arc<AppState>>) -> impl IntoResponse {
108116
.registry
109117
.list_all_available()
110118
.into_iter()
111-
.map(|name| Model {
112-
id: name.clone(),
119+
.map(|name| ListModel {
120+
id: name,
113121
object: "model".to_string(),
114-
created: 0, // Fixed timestamp for simplicity
122+
created: std::time::SystemTime::now()
123+
.duration_since(std::time::UNIX_EPOCH)
124+
.unwrap_or_default()
125+
.as_secs(),
115126
owned_by: "shimmy".to_string(),
116-
permission: None, // No fine-grained permissions for local models
117-
root: Some(name), // The model itself is the root
118-
parent: None, // Local models don't have parent models
119127
})
120128
.collect();
121129

@@ -714,23 +722,17 @@ mod tests {
714722
let models_response = ModelsResponse {
715723
object: "list".to_string(),
716724
data: vec![
717-
Model {
725+
ListModel {
718726
id: "model1".to_string(),
719727
object: "model".to_string(),
720728
created: 1234567890,
721729
owned_by: "shimmy".to_string(),
722-
permission: None,
723-
root: None,
724-
parent: None,
725730
},
726-
Model {
731+
ListModel {
727732
id: "model2".to_string(),
728733
object: "model".to_string(),
729734
created: 1234567890,
730735
owned_by: "shimmy".to_string(),
731-
permission: None,
732-
root: None,
733-
parent: None,
734736
},
735737
],
736738
};
@@ -1040,23 +1042,17 @@ mod tests {
10401042
let models_response = ModelsResponse {
10411043
object: "list".to_string(),
10421044
data: vec![
1043-
Model {
1045+
ListModel {
10441046
id: "test-model-1".to_string(),
10451047
object: "model".to_string(),
1046-
created: 0,
1048+
created: 1234567890,
10471049
owned_by: "shimmy".to_string(),
1048-
permission: None,
1049-
root: None,
1050-
parent: None,
10511050
},
1052-
Model {
1051+
ListModel {
10531052
id: "test-model-2".to_string(),
10541053
object: "model".to_string(),
1055-
created: 0,
1054+
created: 1234567890,
10561055
owned_by: "shimmy".to_string(),
1057-
permission: None,
1058-
root: None,
1059-
parent: None,
10601056
},
10611057
],
10621058
};

tests/regression/issue_113_openai_api.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,11 @@ mod issue_113_tests {
6464
#[test]
6565
fn test_openai_models_response_structure() {
6666
// Test that ModelsResponse matches OpenAI spec
67-
let model = Model {
67+
let model = shimmy::openai_compat::ListModel {
6868
id: "test-model".to_string(),
6969
object: "model".to_string(),
7070
created: 1640995200,
7171
owned_by: "shimmy".to_string(),
72-
permission: None,
73-
root: Some("test-model".to_string()),
74-
parent: None,
7572
};
7673

7774
let response = ModelsResponse {

tests/regression_tests.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -198,23 +198,17 @@ mod regression_tests {
198198
let models_response = ModelsResponse {
199199
object: "list".to_string(),
200200
data: vec![
201-
Model {
201+
openai_compat::ListModel {
202202
id: "qwen3-4b-instruct".to_string(),
203203
object: "model".to_string(),
204-
created: 0,
204+
created: 1234567890,
205205
owned_by: "shimmy".to_string(),
206-
permission: None,
207-
root: Some("qwen3-4b-instruct".to_string()),
208-
parent: None,
209206
},
210-
Model {
207+
openai_compat::ListModel {
211208
id: "llama-7b".to_string(),
212209
object: "model".to_string(),
213-
created: 0,
210+
created: 1234567890,
214211
owned_by: "shimmy".to_string(),
215-
permission: None,
216-
root: Some("llama-7b".to_string()),
217-
parent: None,
218212
},
219213
],
220214
};
@@ -345,9 +339,16 @@ mod regression_tests {
345339
assert!(json.get("parent").is_none());
346340

347341
// Test ModelsResponse structure
342+
let list_model = openai_compat::ListModel {
343+
id: "test-model".to_string(),
344+
object: "model".to_string(),
345+
created: 1640995200,
346+
owned_by: "shimmy".to_string(),
347+
};
348+
348349
let response = ModelsResponse {
349350
object: "list".to_string(),
350-
data: vec![model],
351+
data: vec![list_model],
351352
};
352353

353354
let response_json = serde_json::to_value(&response).unwrap();

0 commit comments

Comments
 (0)