1use serde_json::{json, Value};
4
5use crate::content::GeneratedContent;
6use crate::error::FMError;
7use crate::schema::{Generable, GenerationSchema};
8
9#[derive(Debug, Clone, PartialEq, Default)]
11pub struct Prompt {
12 segments: Vec<Segment>,
13}
14
15impl Prompt {
16 #[must_use]
18 pub const fn new() -> Self {
19 Self {
20 segments: Vec::new(),
21 }
22 }
23
24 #[must_use]
26 pub fn text(text: impl Into<String>) -> Self {
27 Self::from(text.into())
28 }
29
30 #[must_use]
32 pub fn structured(content: GeneratedContent) -> Self {
33 Self::from(content)
34 }
35
36 pub fn push_text(&mut self, text: impl Into<String>) {
38 self.segments.push(Segment::text(text));
39 }
40
41 pub fn push_structured(&mut self, source: impl Into<String>, content: GeneratedContent) {
43 self.segments.push(Segment::structure(source, content));
44 }
45
46 #[must_use]
48 pub fn segments(&self) -> &[Segment] {
49 &self.segments
50 }
51
52 #[must_use]
54 pub fn into_segments(self) -> Vec<Segment> {
55 self.segments
56 }
57
58 pub(crate) fn to_bridge_value(&self) -> Value {
59 json!({
60 "segments": self.segments.iter().map(Segment::to_bridge_value).collect::<Vec<_>>()
61 })
62 }
63
64 pub(crate) fn to_bridge_json(&self) -> Result<String, FMError> {
65 serde_json::to_string(&self.to_bridge_value()).map_err(|error| {
66 FMError::InvalidArgument(format!("prompt is not JSON-serializable: {error}"))
67 })
68 }
69}
70
71impl From<String> for Prompt {
72 fn from(text: String) -> Self {
73 Self {
74 segments: vec![Segment::text(text)],
75 }
76 }
77}
78
79impl From<&str> for Prompt {
80 fn from(text: &str) -> Self {
81 Self::from(text.to_owned())
82 }
83}
84
85impl From<GeneratedContent> for Prompt {
86 fn from(content: GeneratedContent) -> Self {
87 Self {
88 segments: vec![Segment::structure("GeneratedContent", content)],
89 }
90 }
91}
92
93impl From<Vec<Segment>> for Prompt {
94 fn from(segments: Vec<Segment>) -> Self {
95 Self { segments }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Default)]
101pub struct Instructions {
102 segments: Vec<Segment>,
103}
104
105impl Instructions {
106 #[must_use]
108 pub const fn new() -> Self {
109 Self {
110 segments: Vec::new(),
111 }
112 }
113
114 pub fn push_text(&mut self, text: impl Into<String>) {
116 self.segments.push(Segment::text(text));
117 }
118
119 pub fn push_structured(&mut self, source: impl Into<String>, content: GeneratedContent) {
121 self.segments.push(Segment::structure(source, content));
122 }
123
124 #[must_use]
126 pub fn segments(&self) -> &[Segment] {
127 &self.segments
128 }
129
130 #[must_use]
132 pub fn into_segments(self) -> Vec<Segment> {
133 self.segments
134 }
135
136 pub(crate) fn to_bridge_value(&self) -> Value {
137 json!({
138 "segments": self.segments.iter().map(Segment::to_bridge_value).collect::<Vec<_>>()
139 })
140 }
141
142 pub(crate) fn to_bridge_json(&self) -> Result<String, FMError> {
143 serde_json::to_string(&self.to_bridge_value()).map_err(|error| {
144 FMError::InvalidArgument(format!("instructions are not JSON-serializable: {error}"))
145 })
146 }
147}
148
149impl From<String> for Instructions {
150 fn from(text: String) -> Self {
151 Self {
152 segments: vec![Segment::text(text)],
153 }
154 }
155}
156
157impl From<&str> for Instructions {
158 fn from(text: &str) -> Self {
159 Self::from(text.to_owned())
160 }
161}
162
163impl From<GeneratedContent> for Instructions {
164 fn from(content: GeneratedContent) -> Self {
165 Self {
166 segments: vec![Segment::structure("GeneratedContent", content)],
167 }
168 }
169}
170
171impl From<Vec<Segment>> for Instructions {
172 fn from(segments: Vec<Segment>) -> Self {
173 Self { segments }
174 }
175}
176
177#[derive(Debug, Clone, PartialEq)]
179pub enum Segment {
180 Text(TextSegment),
181 Structure(StructuredSegment),
182}
183
184impl Segment {
185 #[must_use]
187 pub fn text(text: impl Into<String>) -> Self {
188 Self::Text(TextSegment::new(text))
189 }
190
191 #[must_use]
193 pub fn structure(source: impl Into<String>, content: GeneratedContent) -> Self {
194 Self::Structure(StructuredSegment::new(source, content))
195 }
196
197 pub(crate) fn to_bridge_value(&self) -> Value {
198 match self {
199 Self::Text(segment) => json!({
200 "kind": "text",
201 "text": segment.text,
202 }),
203 Self::Structure(segment) => json!({
204 "kind": "structure",
205 "source": segment.source,
206 "contentJSON": segment.content.json_string().expect("generated content must serialize")
207 }),
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct TextSegment {
215 pub id: Option<String>,
216 pub text: String,
217}
218
219impl TextSegment {
220 #[must_use]
222 pub fn new(text: impl Into<String>) -> Self {
223 Self {
224 id: None,
225 text: text.into(),
226 }
227 }
228}
229
230#[derive(Debug, Clone, PartialEq)]
232pub struct StructuredSegment {
233 pub id: Option<String>,
234 pub source: String,
235 pub content: GeneratedContent,
236}
237
238impl StructuredSegment {
239 #[must_use]
241 pub fn new(source: impl Into<String>, content: GeneratedContent) -> Self {
242 Self {
243 id: None,
244 source: source.into(),
245 content,
246 }
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct ResponseFormat {
253 name: Option<String>,
254 schema: GenerationSchema,
255}
256
257impl ResponseFormat {
258 #[must_use]
260 pub fn json_schema(schema: GenerationSchema) -> Self {
261 Self { name: None, schema }
262 }
263
264 pub fn generating<T>() -> Result<Self, FMError>
270 where
271 T: Generable,
272 {
273 Ok(Self::json_schema(T::generation_schema()?))
274 }
275
276 pub(crate) fn from_transcript_json_value(value: &Value) -> Result<Self, FMError> {
277 let schema = value
278 .get("jsonSchema")
279 .and_then(|json_schema| json_schema.get("schema"))
280 .ok_or_else(|| {
281 FMError::DecodingFailure("response format is missing jsonSchema.schema".into())
282 })?;
283 let name = value
284 .get("jsonSchema")
285 .and_then(|json_schema| json_schema.get("name"))
286 .and_then(Value::as_str)
287 .map(ToOwned::to_owned);
288 Ok(Self {
289 name,
290 schema: GenerationSchema::from_json_schema_unchecked(
291 serde_json::to_string(schema).map_err(|error| {
292 FMError::InvalidArgument(format!(
293 "response format schema is not valid JSON: {error}"
294 ))
295 })?,
296 ),
297 })
298 }
299
300 #[must_use]
302 pub fn with_name(mut self, name: impl Into<String>) -> Self {
303 self.name = Some(name.into());
304 self
305 }
306
307 #[must_use]
309 pub fn name(&self) -> String {
310 self.name
311 .clone()
312 .or_else(|| self.schema.name())
313 .unwrap_or_else(|| "GeneratedContent".to_string())
314 }
315
316 #[must_use]
318 pub const fn schema(&self) -> &GenerationSchema {
319 &self.schema
320 }
321
322 pub(crate) fn to_transcript_json_value(&self) -> Value {
323 let schema_value: Value = serde_json::from_str(self.schema.json_schema())
324 .expect("validated generation schema must always be valid JSON");
325 json!({
326 "type": "jsonSchema",
327 "jsonSchema": {
328 "name": self.name(),
329 "schema": schema_value,
330 }
331 })
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct ToolDefinition {
338 pub name: String,
339 pub description: String,
340 pub parameters: GenerationSchema,
341}
342
343impl ToolDefinition {
344 #[must_use]
346 pub fn new(
347 name: impl Into<String>,
348 description: impl Into<String>,
349 parameters: GenerationSchema,
350 ) -> Self {
351 Self {
352 name: name.into(),
353 description: description.into(),
354 parameters,
355 }
356 }
357
358 pub(crate) fn to_transcript_json_value(&self) -> Value {
359 let parameters: Value = serde_json::from_str(self.parameters.json_schema())
360 .expect("validated generation schema must always be valid JSON");
361 json!({
362 "type": "function",
363 "function": {
364 "name": self.name,
365 "description": self.description,
366 "parameters": parameters,
367 }
368 })
369 }
370}
371
372pub trait ToPrompt {
374 fn to_prompt(self) -> Result<Prompt, FMError>;
376}
377
378impl ToPrompt for Prompt {
379 fn to_prompt(self) -> Result<Prompt, FMError> {
380 Ok(self)
381 }
382}
383
384impl ToPrompt for &Prompt {
385 fn to_prompt(self) -> Result<Prompt, FMError> {
386 Ok(self.clone())
387 }
388}
389
390impl ToPrompt for String {
391 fn to_prompt(self) -> Result<Prompt, FMError> {
392 Ok(Prompt::from(self))
393 }
394}
395
396impl ToPrompt for &str {
397 fn to_prompt(self) -> Result<Prompt, FMError> {
398 Ok(Prompt::from(self))
399 }
400}
401
402impl ToPrompt for GeneratedContent {
403 fn to_prompt(self) -> Result<Prompt, FMError> {
404 Ok(Prompt::from(self))
405 }
406}
407
408impl ToPrompt for &GeneratedContent {
409 fn to_prompt(self) -> Result<Prompt, FMError> {
410 Ok(Prompt::from(self.clone()))
411 }
412}
413
414pub trait ToInstructions {
416 fn to_instructions(self) -> Result<Instructions, FMError>;
418}
419
420impl ToInstructions for Instructions {
421 fn to_instructions(self) -> Result<Instructions, FMError> {
422 Ok(self)
423 }
424}
425
426impl ToInstructions for &Instructions {
427 fn to_instructions(self) -> Result<Instructions, FMError> {
428 Ok(self.clone())
429 }
430}
431
432impl ToInstructions for String {
433 fn to_instructions(self) -> Result<Instructions, FMError> {
434 Ok(Instructions::from(self))
435 }
436}
437
438impl ToInstructions for &str {
439 fn to_instructions(self) -> Result<Instructions, FMError> {
440 Ok(Instructions::from(self))
441 }
442}
443
444impl ToInstructions for GeneratedContent {
445 fn to_instructions(self) -> Result<Instructions, FMError> {
446 Ok(Instructions::from(self))
447 }
448}
449
450impl ToInstructions for &GeneratedContent {
451 fn to_instructions(self) -> Result<Instructions, FMError> {
452 Ok(Instructions::from(self.clone()))
453 }
454}