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 strategies;
21
22pub use strategies::ArrayOutput;
23pub use strategies::ChoiceOutput;
24pub use strategies::JsonOutput;
25pub use strategies::ObjectOutput;
26pub use strategies::TextOutput;
27
28#[derive(Debug, Clone, PartialEq)]
31pub struct OutputContext {
32 pub response: ResponseMetadata,
34 pub usage: Usage,
36 pub finish_reason: FinishReason,
38}
39
40pub trait OutputHandler<O>: Send + Sync + 'static {
45 fn response_format(&self) -> Option<ResponseFormat>;
47
48 fn wants_output(&self) -> bool {
50 true
51 }
52
53 fn parse_complete(&self, text: &str, ctx: &OutputContext) -> Result<O, Error>;
60
61 fn parse_partial(&self, text: &str) -> Option<JsonValue>;
64
65 fn typed_partial(&self, _value: &JsonValue) -> Option<O> {
68 None
69 }
70
71 fn parse_elements(&self, _text: &str) -> Option<Vec<JsonValue>> {
74 None
75 }
76}
77
78#[derive(Debug, Clone, Copy, Default)]
81pub struct NoOutput;
82
83impl OutputHandler<()> for NoOutput {
84 fn response_format(&self) -> Option<ResponseFormat> {
85 None
86 }
87
88 fn wants_output(&self) -> bool {
89 false
90 }
91
92 fn parse_complete(&self, _text: &str, _ctx: &OutputContext) -> Result<(), Error> {
93 Ok(())
94 }
95
96 fn parse_partial(&self, _text: &str) -> Option<JsonValue> {
97 None
98 }
99}
100
101pub struct Output<T> {
103 handler: Arc<dyn OutputHandler<T>>,
104}
105
106impl<T> Clone for Output<T> {
107 fn clone(&self) -> Self {
108 Self {
109 handler: Arc::clone(&self.handler),
110 }
111 }
112}
113
114impl<T> fmt::Debug for Output<T> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.write_str("Output(..)")
117 }
118}
119
120impl<T> Output<T> {
121 pub fn custom(handler: impl OutputHandler<T>) -> Self {
123 Self {
124 handler: Arc::new(handler),
125 }
126 }
127
128 #[must_use]
130 pub fn handler(&self) -> Arc<dyn OutputHandler<T>> {
131 Arc::clone(&self.handler)
132 }
133}
134
135impl Output<String> {
136 #[must_use]
138 pub fn text() -> Self {
139 Self::custom(TextOutput)
140 }
141
142 #[must_use]
144 pub fn choice(options: impl IntoIterator<Item = impl Into<String>>) -> Self {
145 Self::custom(ChoiceOutput::new(options))
146 }
147}
148
149impl<T: DeserializeOwned + JsonSchema + Send + Sync + 'static> Output<T> {
150 #[must_use]
152 pub fn object() -> Self {
153 Self::custom(ObjectOutput::new(Schema::<T>::derived()))
154 }
155}
156
157impl<T: DeserializeOwned + Send + Sync + 'static> Output<T> {
158 #[must_use]
160 pub fn object_with(schema: Schema<T>) -> Self {
161 Self::custom(ObjectOutput::new(schema))
162 }
163}
164
165impl<T: DeserializeOwned + JsonSchema + Send + Sync + 'static> Output<Vec<T>> {
166 #[must_use]
168 pub fn array() -> Self {
169 Self::custom(ArrayOutput::new(Schema::<T>::derived()))
170 }
171}
172
173impl<T: DeserializeOwned + Send + Sync + 'static> Output<Vec<T>> {
174 #[must_use]
176 pub fn array_with(element: Schema<T>) -> Self {
177 Self::custom(ArrayOutput::new(element))
178 }
179}
180
181impl Output<JsonValue> {
182 #[must_use]
184 pub fn json() -> Self {
185 Self::custom(JsonOutput::new(None))
186 }
187
188 #[must_use]
190 pub fn json_with_schema(schema: JsonValue) -> Self {
191 Self::custom(JsonOutput::new(Some(schema)))
192 }
193}
194
195pub trait ArrayElements {
197 type Element;
199}
200
201impl<T> ArrayElements for Vec<T> {
202 type Element = T;
203}
204
205#[derive(Debug, Clone, PartialEq)]
207pub struct PartialOutput<T> {
208 pub value: JsonValue,
210 pub typed: Option<T>,
212}