Skip to main content

openai_interface/models/
list.rs

1//! List the currently available models, and provide basic information about
2//! each one such as the owner and availability.
3//!
4//! The list is tested against the DeepSeek `GET /models` endpoint. Other
5//! OpenAI-compatible providers may return a different set of fields; unknown
6//! fields are ignored during deserialization.
7
8pub mod request {
9    use url::Url;
10
11    use crate::{
12        errors::OapiError,
13        rest::get::{Get, GetNoStream},
14    };
15
16    /// Request parameters for listing models. The endpoint takes no query
17    /// parameters.
18    #[derive(Debug, Clone, Copy, Default)]
19    pub struct ListModelsRequest;
20
21    impl Get for ListModelsRequest {
22        /// base_url should look like <https://api.openai.com/v1>
23        fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
24            let mut url =
25                Url::parse(base_url.trim_end_matches('/')).map_err(OapiError::UrlError)?;
26            url.path_segments_mut()
27                .map_err(|_| OapiError::UrlCannotBeBase(base_url.to_string()))?
28                .push("models");
29
30            Ok(url.to_string())
31        }
32    }
33
34    impl GetNoStream for ListModelsRequest {
35        type Response = super::response::ListModelsResponse;
36    }
37}
38
39pub mod response {
40    use serde::Deserialize;
41
42    /// The response of a model list request.
43    #[derive(Debug, Deserialize, Clone)]
44    pub struct ListModelsResponse {
45        /// The list of available models.
46        pub data: Vec<crate::models::Model>,
47        /// The object type, which is always `list`. Some OpenAI-compatible
48        /// providers omit this field.
49        pub object: Option<String>,
50    }
51
52    crate::impl_from_str!(ListModelsResponse);
53}
54
55#[cfg(test)]
56mod tests {
57    use super::request::ListModelsRequest;
58    use crate::rest::get::{Get, GetNoStream};
59
60    #[test]
61    fn test_build_url() {
62        let request = ListModelsRequest;
63        let url = request.build_url("https://api.openai.com/v1/").unwrap();
64        assert_eq!(url, "https://api.openai.com/v1/models");
65    }
66
67    /// Deserializes a model list response.
68    ///
69    /// Fixture is a verbatim response captured from
70    /// `GET https://api.deepseek.com/models` (2026-09-01). Note the absence
71    /// of the `created` field, which OpenAI returns but DeepSeek omits.
72    #[test]
73    fn test_parse_list_response() {
74        let content = r#"{"object":"list","data":[{"id":"deepseek-v4-flash","object":"model","owned_by":"deepseek"},{"id":"deepseek-v4-pro","object":"model","owned_by":"deepseek"},{"id":"deepseek-v4-flash-vision-exp","object":"model","owned_by":"deepseek"}]}"#;
75
76        let response: super::response::ListModelsResponse = content.parse().unwrap();
77        assert_eq!(response.data.len(), 3);
78        assert_eq!(response.data[0].id, "deepseek-v4-flash");
79        assert_eq!(response.data[0].object, crate::models::ModelObject::Model);
80        assert_eq!(response.data[0].owned_by, "deepseek");
81        assert_eq!(response.data[0].created, None);
82        assert_eq!(response.data[0].shutdown_date, None);
83        assert_eq!(response.data[2].id, "deepseek-v4-flash-vision-exp");
84    }
85
86    /// DeepSeek documents `GET /models`; run a live request when an API key
87    /// is available.
88    #[tokio::test]
89    async fn test_query_deepseek_models() -> Result<(), anyhow::Error> {
90        let Some(api_key) = std::env::var("DEEPSEEK_API_KEY")
91            .ok()
92            .map(|key| key.trim().to_string())
93            .filter(|key| !key.is_empty())
94        else {
95            println!("Skipping: set DEEPSEEK_API_KEY to run this test");
96            return Ok(());
97        };
98
99        const DEEPSEEK_BASE_URL: &str = "https://api.deepseek.com";
100
101        let response = ListModelsRequest
102            .get_response(
103                &crate::rest::default_client(),
104                DEEPSEEK_BASE_URL,
105                &crate::rest::RequestOptions::bearer(&api_key),
106            )
107            .await?;
108        assert!(!response.data.is_empty());
109        println!("DeepSeek models: {:?}", response.data);
110        Ok(())
111    }
112}