Skip to content

Commit 8281b8f

Browse files
cptrodgersSevenannn
authored andcommitted
[Example]: Use serde, schemars to make structure output code easy (64bit#301)
* Add structured-outputs-schemars * Upadte naming and using crates infor
1 parent ba72832 commit 8281b8f

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "structured-outputs-schemars"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
async-openai = {path = "../../async-openai"}
9+
serde_json = "1.0.127"
10+
tokio = { version = "1.39.3", features = ["full"] }
11+
schemars = "0.8.21"
12+
serde = "1.0.130"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## Intro
2+
3+
Based on the 'Chain of thought' example from https://platform.openai.com/docs/guides/structured-outputs/introduction?lang=curl
4+
5+
Using `schemars` and `serde` reduces coding effort.
6+
7+
## Output
8+
9+
```
10+
cargo run | jq .
11+
```
12+
13+
```
14+
{
15+
"final_answer": "x = -3.75",
16+
"steps": [
17+
{
18+
"explanation": "Start with the equation given in the problem.",
19+
"output": "8x + 7 = -23"
20+
},
21+
{
22+
"explanation": "Subtract 7 from both sides to begin isolating the term with the variable x.",
23+
"output": "8x + 7 - 7 = -23 - 7"
24+
},
25+
{
26+
"explanation": "Simplify both sides. On the left-hand side, 7 - 7 equals 0, cancelling out, leaving the equation as follows.",
27+
"output": "8x = -30"
28+
},
29+
{
30+
"explanation": "Now, divide both sides by 8 to fully isolate x.",
31+
"output": "8x/8 = -30/8"
32+
},
33+
{
34+
"explanation": "Simplify the right side by performing the division. -30 divided by 8 is -3.75.",
35+
"output": "x = -3.75"
36+
}
37+
]
38+
}
39+
```
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use std::error::Error;
2+
3+
use async_openai::{
4+
types::{
5+
ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage,
6+
ChatCompletionRequestUserMessage, CreateChatCompletionRequestArgs, ResponseFormat,
7+
ResponseFormatJsonSchema,
8+
},
9+
Client,
10+
};
11+
use schemars::{schema_for, JsonSchema};
12+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
13+
14+
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
15+
#[serde(deny_unknown_fields)]
16+
pub struct Step {
17+
pub output: String,
18+
pub explanation: String,
19+
}
20+
21+
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
22+
#[serde(deny_unknown_fields)]
23+
pub struct MathReasoningResponse {
24+
pub final_answer: String,
25+
pub steps: Vec<Step>,
26+
}
27+
28+
pub async fn structured_output<T: serde::Serialize + DeserializeOwned + JsonSchema>(
29+
messages: Vec<ChatCompletionRequestMessage>,
30+
) -> Result<Option<T>, Box<dyn Error>> {
31+
let schema = schema_for!(T);
32+
let schema_value = serde_json::to_value(&schema)?;
33+
let response_format = ResponseFormat::JsonSchema {
34+
json_schema: ResponseFormatJsonSchema {
35+
description: None,
36+
name: "math_reasoning".into(),
37+
schema: Some(schema_value),
38+
strict: Some(true),
39+
},
40+
};
41+
42+
let request = CreateChatCompletionRequestArgs::default()
43+
.max_tokens(512u32)
44+
.model("gpt-4o-mini")
45+
.messages(messages)
46+
.response_format(response_format)
47+
.build()?;
48+
49+
let client = Client::new();
50+
let response = client.chat().create(request).await?;
51+
52+
for choice in response.choices {
53+
if let Some(content) = choice.message.content {
54+
return Ok(Some(serde_json::from_str::<T>(&content)?));
55+
}
56+
}
57+
58+
Ok(None)
59+
}
60+
61+
#[tokio::main]
62+
async fn main() -> Result<(), Box<dyn Error>> {
63+
// Expecting output schema
64+
// let schema = json!({
65+
// "type": "object",
66+
// "properties": {
67+
// "steps": {
68+
// "type": "array",
69+
// "items": {
70+
// "type": "object",
71+
// "properties": {
72+
// "explanation": { "type": "string" },
73+
// "output": { "type": "string" }
74+
// },
75+
// "required": ["explanation", "output"],
76+
// "additionalProperties": false
77+
// }
78+
// },
79+
// "final_answer": { "type": "string" }
80+
// },
81+
// "required": ["steps", "final_answer"],
82+
// "additionalProperties": false
83+
// });
84+
if let Some(response) = structured_output::<MathReasoningResponse>(vec![
85+
ChatCompletionRequestSystemMessage::from(
86+
"You are a helpful math tutor. Guide the user through the solution step by step.",
87+
)
88+
.into(),
89+
ChatCompletionRequestUserMessage::from("how can I solve 8x + 7 = -23").into(),
90+
])
91+
.await?
92+
{
93+
println!("{}", serde_json::to_string(&response).unwrap());
94+
}
95+
96+
Ok(())
97+
}

0 commit comments

Comments
 (0)