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}