Skip to main content

lc_core/tools/
structured_output.rs

1// src/core/tools/structured_output.rs
2//! Structured output utilities for type-safe LLM responses.
3
4use crate::language_models::LLMResult;
5use schemars::JsonSchema;
6use serde::de::DeserializeOwned;
7use std::marker::PhantomData;
8
9/// Wrapper for parsing structured JSON output from LLM responses.
10pub struct StructuredOutput<T> {
11    result: LLMResult,
12    schema: serde_json::Value,
13    _phantom: PhantomData<T>,
14}
15
16impl<T: DeserializeOwned + JsonSchema> StructuredOutput<T> {
17    /// Creates a new StructuredOutput from an LLM result.
18    pub fn new(result: LLMResult) -> Self {
19        use schemars::schema_for;
20        let schema = serde_json::to_value(schema_for!(T)).unwrap_or(serde_json::Value::Null);
21        Self {
22            result,
23            schema,
24            _phantom: PhantomData,
25        }
26    }
27
28    /// Parses the LLM response into the target type.
29    ///
30    /// Handles markdown code blocks (```json ... ```) wrapping the JSON,
31    /// which LLMs commonly produce.
32    pub fn parse(&self) -> Result<T, serde_json::Error> {
33        let content = self.result.content.trim();
34
35        // Strip markdown code block wrapping (L5)
36        let json_str = if content.starts_with("```") {
37            // Find the end of the opening fence (may include language tag like ```json)
38            let after_fence = if let Some(newline_pos) = content.find('\n') {
39                &content[newline_pos + 1..]
40            } else {
41                content
42            };
43            // Find the closing fence
44            if let Some(end_pos) = after_fence.find("```") {
45                after_fence[..end_pos].trim()
46            } else {
47                after_fence.trim()
48            }
49        } else {
50            content
51        };
52
53        serde_json::from_str(json_str)
54    }
55
56    /// Returns the raw response content.
57    pub fn raw_content(&self) -> &str {
58        &self.result.content
59    }
60
61    /// Returns the JSON schema for the output type.
62    pub fn schema(&self) -> &serde_json::Value {
63        &self.schema
64    }
65}