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