ferrin_core/output/
mod.rs1use std::fmt;
7use std::sync::Arc;
8
9use ferrin_schema::JsonSchema;
10use ferrin_schema::Schema;
11use ferrin_spec::FinishReason;
12use ferrin_spec::JsonValue;
13use ferrin_spec::ResponseFormat;
14use ferrin_spec::ResponseMetadata;
15use ferrin_spec::Usage;
16use serde::de::DeserializeOwned;
17
18use crate::error::Error;
19
20mod local_refs;
21mod strategies;
22
23pub use strategies::ArrayOutput;
24pub use strategies::ChoiceOutput;
25pub use strategies::JsonOutput;
26pub use strategies::ObjectOutput;
27pub use strategies::TextOutput;
28
29#[derive(Debug, Clone, PartialEq)]
32pub struct OutputContext {
33 pub response: ResponseMetadata,
35 pub usage: Usage,
37 pub finish_reason: FinishReason,
39}
40
41pub trait OutputHandler<O>: Send + Sync + 'static {
46 fn response_format(&self) -> Option<ResponseFormat>;
48
49 fn wants_output(&self) -> bool {
51 true
52 }
53
54 fn parse_complete(&self, text: &str, ctx: &OutputContext) -> Result<O, Error>;
61
62 fn parse_partial(&self, text: &str) -> Option<JsonValue>;
65
66 fn typed_partial(&self, _value: &JsonValue) -> Option<O> {
69 None
70 }
71
72 fn parse_elements(&self, _text: &str) -> Option<Vec<JsonValue>> {
75 None
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default)]
82pub struct NoOutput;
83
84impl OutputHandler<()> for NoOutput {
85 fn response_format(&self) -> Option<ResponseFormat> {
86 None
87 }
88
89 fn wants_output(&self) -> bool {
90 false
91 }
92
93 fn parse_complete(&self, _text: &str, _ctx: &OutputContext) -> Result<(), Error> {
94 Ok(())
95 }
96
97 fn parse_partial(&self, _text: &str) -> Option<JsonValue> {
98 None
99 }
100}
101
102pub struct Output<T> {
104 handler: Arc<dyn OutputHandler<T>>,
105}
106
107impl<T> Clone for Output<T> {
108 fn clone(&self) -> Self {
109 Self {
110 handler: Arc::clone(&self.handler),
111 }
112 }
113}
114
115impl<T> fmt::Debug for Output<T> {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.write_str("Output(..)")
118 }
119}
120
121impl<T> Output<T> {
122 pub fn custom(handler: impl OutputHandler<T>) -> Self {
124 Self {
125 handler: Arc::new(handler),
126 }
127 }
128
129 #[must_use]
131 pub fn handler(&self) -> Arc<dyn OutputHandler<T>> {
132 Arc::clone(&self.handler)
133 }
134}
135
136impl Output<String> {
137 #[must_use]
139 pub fn text() -> Self {
140 Self::custom(TextOutput)
141 }
142
143 #[must_use]
145 pub fn choice(options: impl IntoIterator<Item = impl Into<String>>) -> Self {
146 Self::custom(ChoiceOutput::new(options))
147 }
148}
149
150impl<T: DeserializeOwned + JsonSchema + Send + Sync + 'static> Output<T> {
151 #[must_use]
153 pub fn object() -> Self {
154 Self::custom(ObjectOutput::new(Schema::<T>::derived()))
155 }
156}
157
158impl<T: DeserializeOwned + Send + Sync + 'static> Output<T> {
159 #[must_use]
161 pub fn object_with(schema: Schema<T>) -> Self {
162 Self::custom(ObjectOutput::new(schema))
163 }
164}
165
166impl<T: DeserializeOwned + JsonSchema + Send + Sync + 'static> Output<Vec<T>> {
167 #[must_use]
169 pub fn array() -> Self {
170 Self::custom(ArrayOutput::new(Schema::<T>::derived()))
171 }
172}
173
174impl<T: DeserializeOwned + Send + Sync + 'static> Output<Vec<T>> {
175 #[must_use]
177 pub fn array_with(element: Schema<T>) -> Self {
178 Self::custom(ArrayOutput::new(element))
179 }
180}
181
182impl Output<JsonValue> {
183 #[must_use]
185 pub fn json() -> Self {
186 Self::custom(JsonOutput::new(None))
187 }
188
189 #[must_use]
191 pub fn json_with_schema(schema: JsonValue) -> Self {
192 Self::custom(JsonOutput::new(Some(schema)))
193 }
194}
195
196pub trait ArrayElements {
198 type Element;
200}
201
202impl<T> ArrayElements for Vec<T> {
203 type Element = T;
204}
205
206#[derive(Debug, Clone, PartialEq)]
208pub struct PartialOutput<T> {
209 pub value: JsonValue,
211 pub typed: Option<T>,
213}