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
//! [`RepairStrategy`] -- a hook that can salvage a structured-output request
//! whose reply didn't decode, instead of just failing.
use async_trait;
use Value;
use crateError;
/// Given a failed structured-output attempt, tries to produce a JSON value
/// matching the requested schema anyway.
///
/// Attach one with
/// [`PendingStructuredRequest::with_repair`](super::PendingStructuredRequest::with_repair).
/// When [`Error::StructuredDecode`] happens, `error` is passed to
/// [`repair`](Self::repair) along with the exact text the provider sent
/// (`error`'s own `raw` field, pulled out here for convenience) -- return
/// `Some(value)` to use it as the result instead of failing, or `None` to
/// give up and return the original error. Only tried once per request: a
/// repair that itself produces something that still doesn't match the
/// schema is returned to the caller as-is rather than looping indefinitely.
///
/// A simple repair strips a Markdown code fence some models wrap JSON in
/// even when explicitly asked not to:
///
/// ```
/// use async_trait::async_trait;
/// use llmprism::error::Error;
/// use llmprism::structured::RepairStrategy;
/// use serde_json::Value;
///
/// struct StripCodeFence;
///
/// #[async_trait]
/// impl RepairStrategy for StripCodeFence {
/// async fn repair(&self, raw: &str, _error: &Error) -> Option<Value> {
/// let stripped = raw.trim().trim_start_matches("```json").trim_start_matches("```").trim_end_matches("```");
/// serde_json::from_str(stripped.trim()).ok()
/// }
/// }
/// ```
///
/// A repair can also be worth reaching for the model itself to fix -- since
/// `repair` is async, it's free to make its own provider call (for example,
/// resending the conversation with the malformed reply and an instruction to
/// correct it) rather than only doing local text surgery.