kind_openai_schema/
lib.rs

1//! A procedural macro for deriving an OpenAI-compatible JSON schema for a Rust
2//! struct.
3
4use std::fmt::Display;
5
6pub use kind_openai_schema_impl::OpenAISchema;
7use serde::{ser::Serializer, Deserialize, Serialize};
8use serde_json::value::RawValue;
9
10/// An OpenAI-compatible JSON schema produced by the `OpenAI` schema derive macro.
11#[derive(Debug, Clone, Copy)]
12pub struct GeneratedOpenAISchema(&'static str);
13
14impl Display for GeneratedOpenAISchema {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        f.write_str(self.0)
17    }
18}
19
20impl From<String> for GeneratedOpenAISchema {
21    fn from(schema: String) -> Self {
22        let schema = Box::leak(schema.into_boxed_str());
23        Self(schema)
24    }
25}
26
27impl Serialize for GeneratedOpenAISchema {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let raw = RawValue::from_string(self.0.to_owned()).map_err(serde::ser::Error::custom)?;
33        raw.serialize(serializer)
34    }
35}
36
37/// Any type that can be used as a structured chat completion.
38///
39/// Docstrings on the top level of a type will automatically be consumed and provided to the schema,
40/// as well as any docstrings on fields of said struct.
41///
42/// Additionally, `serde(skip)` and `serde(rename)` on fields works perfectly fine.
43///
44/// For example:
45/// ```rust
46/// #[derive(Deserialize, OpenAISchema)]
47/// /// Hello friends
48/// struct SuperComplexSchema {
49///    // The first one.
50///    optional_string: Option<String>,
51///    #[serde(rename = "not_so_regular_string")]
52///    regular_string: String,
53///    #[serde(skip)]
54///    regular_string_2: String,
55///    int: i32,
56///    basic_enum: BasicEnum,
57/// }
58///
59/// #[derive(Deserialize, OpenAISchema)]
60/// /// A basic enum.
61/// enum BasicEnum {
62///    #[serde(rename = "variant1")]
63///    Variant1,
64///    #[serde(skip)]
65///    Variant4,
66///    Variant2,
67/// }
68/// ```
69/// Will produce the following schema:
70/// ```json
71/// {
72///   "name": "SuperComplexSchema",
73///   "description": "Hello friends",
74///   "strict": true,
75///   "schema": {
76///     "type": "object",
77///     "additionalProperties": false,
78///     "properties": {
79///       "optional_string": {
80///         "description": "The first one.",
81///         "type": ["string", "null"]
82///       },
83///       "not_so_regular_string": { "type": "string" },
84///       "int": { "type": "integer" },
85///       "basic_enum": { "enum": ["variant1", "Variant2"], "type": "string" }
86///     },
87///     "required": ["optional_string", "not_so_regular_string", "int", "basic_enum"]
88///   }
89/// }
90/// ```
91///
92/// OpenAI's JSON schema implements a stricter and more limited subset of the JSON schema spec
93/// to make it easier for the model to interpret. In addition to that, the proc macro implementation
94/// is not 100% complete so there are still some things that are supported that we need to implement, too.
95///
96/// As such, there are some rules which must be followed (most of which are caught by compiler errors. If they
97/// aren't, please file an issue!):
98///
99/// - The derive can be used on both structs and enums, but only structs can be provided to a structured completion;
100///   enums must be used as a field in a containing struct.
101/// - Enums must be unit variants. Enums with int descriminants (for example `enum MyEnum { Variant1 = 1, Variant2 = 2 }`) are also
102///   allowed, but they must be annotated with `repr(i32)` or similar, and derive `Deserialize_repr` from `serde_repr`.
103/// - Struct fields are allowed to be any of the following types:
104///     - `String`
105///     - All int types, (`i32`, `i64`, `u32`, `u64`, `isize`, `usize`, etc.)
106///     - `f32` and `f64`
107///     - `bool`
108///     - Any unit enum type which also derives `OpenAISchema`
109///     - `Vec<T>` where `T` is any of the above types
110///     - `Option<T>` where `T` is any of the above types
111pub trait OpenAISchema: for<'de> Deserialize<'de> {
112    fn openai_schema() -> GeneratedOpenAISchema;
113}
114
115/// A subordinate type that can be used as a field in an OpenAI schema but not as the schema itself.
116/// (`enum`s and eventually `struct`s when supported using `$ref`). This is still derived by `OpenAISchema`,
117/// so for all intents and purposes you can pretend that this type doesn't exist.
118pub trait SubordinateOpenAISchema {
119    /// Partial schema that will be filled in in the top level schema.
120    fn subordinate_openai_schema() -> &'static str;
121}