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 "content": segment
207 .content
208 .to_bridge_value()
209 .expect("generated content bridge payload must serialize")
210 }),
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct TextSegment {
218 pub id: Option<String>,
219 pub text: String,
220}
221
222impl TextSegment {
223 #[must_use]
225 pub fn new(text: impl Into<String>) -> Self {
226 Self {
227 id: None,
228 text: text.into(),
229 }
230 }
231}
232
233#[derive(Debug, Clone, PartialEq)]
235pub struct StructuredSegment {
236 pub id: Option<String>,
237 pub source: String,
238 pub content: GeneratedContent,
239}
240
241impl StructuredSegment {
242 #[must_use]
244 pub fn new(source: impl Into<String>, content: GeneratedContent) -> Self {
245 Self {
246 id: None,
247 source: source.into(),
248 content,
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct ResponseFormat {
256 name: Option<String>,
257 schema: GenerationSchema,
258}
259
260impl ResponseFormat {
261 #[must_use]
263 pub fn json_schema(schema: GenerationSchema) -> Self {
264 Self { name: None, schema }
265 }
266
267 pub fn generating<T>() -> Result<Self, FMError>
273 where
274 T: Generable,
275 {
276 Ok(Self::json_schema(T::generation_schema()?))
277 }
278
279 pub(crate) fn from_transcript_json_value(value: &Value) -> Result<Self, FMError> {
280 let schema = value
281 .get("jsonSchema")
282 .and_then(|json_schema| json_schema.get("schema"))
283 .ok_or_else(|| {
284 FMError::DecodingFailure("response format is missing jsonSchema.schema".into())
285 })?;
286 let name = value
287 .get("jsonSchema")
288 .and_then(|json_schema| json_schema.get("name"))
289 .and_then(Value::as_str)
290 .map(ToOwned::to_owned);
291 Ok(Self {
292 name,
293 schema: GenerationSchema::from_json_schema_unchecked(
294 serde_json::to_string(schema).map_err(|error| {
295 FMError::InvalidArgument(format!(
296 "response format schema is not valid JSON: {error}"
297 ))
298 })?,
299 ),
300 })
301 }
302
303 #[must_use]
305 pub fn with_name(mut self, name: impl Into<String>) -> Self {
306 self.name = Some(name.into());
307 self
308 }
309
310 #[must_use]
312 pub fn name(&self) -> String {
313 self.name
314 .clone()
315 .or_else(|| self.schema.name())
316 .unwrap_or_else(|| "GeneratedContent".to_string())
317 }
318
319 #[must_use]
321 pub const fn schema(&self) -> &GenerationSchema {
322 &self.schema
323 }
324
325 pub(crate) fn to_transcript_json_value(&self) -> Value {
326 let schema_value: Value = serde_json::from_str(self.schema.json_schema())
327 .expect("validated generation schema must always be valid JSON");
328 json!({
329 "type": "jsonSchema",
330 "jsonSchema": {
331 "name": self.name(),
332 "schema": schema_value,
333 }
334 })
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct ToolDefinition {
341 pub name: String,
342 pub description: String,
343 pub parameters: GenerationSchema,
344}
345
346impl ToolDefinition {
347 #[must_use]
349 pub fn new(
350 name: impl Into<String>,
351 description: impl Into<String>,
352 parameters: GenerationSchema,
353 ) -> Self {
354 Self {
355 name: name.into(),
356 description: description.into(),
357 parameters,
358 }
359 }
360
361 pub(crate) fn to_transcript_json_value(&self) -> Value {
362 let parameters: Value = serde_json::from_str(self.parameters.json_schema())
363 .expect("validated generation schema must always be valid JSON");
364 json!({
365 "type": "function",
366 "function": {
367 "name": self.name,
368 "description": self.description,
369 "parameters": parameters,
370 }
371 })
372 }
373}
374
375pub trait ToPrompt {
377 fn to_prompt(self) -> Result<Prompt, FMError>;
379}
380
381impl ToPrompt for Prompt {
382 fn to_prompt(self) -> Result<Prompt, FMError> {
383 Ok(self)
384 }
385}
386
387impl ToPrompt for &Prompt {
388 fn to_prompt(self) -> Result<Prompt, FMError> {
389 Ok(self.clone())
390 }
391}
392
393impl ToPrompt for String {
394 fn to_prompt(self) -> Result<Prompt, FMError> {
395 Ok(Prompt::from(self))
396 }
397}
398
399impl ToPrompt for &str {
400 fn to_prompt(self) -> Result<Prompt, FMError> {
401 Ok(Prompt::from(self))
402 }
403}
404
405impl ToPrompt for GeneratedContent {
406 fn to_prompt(self) -> Result<Prompt, FMError> {
407 Ok(Prompt::from(self))
408 }
409}
410
411impl ToPrompt for &GeneratedContent {
412 fn to_prompt(self) -> Result<Prompt, FMError> {
413 Ok(Prompt::from(self.clone()))
414 }
415}
416
417pub trait ToInstructions {
419 fn to_instructions(self) -> Result<Instructions, FMError>;
421}
422
423impl ToInstructions for Instructions {
424 fn to_instructions(self) -> Result<Instructions, FMError> {
425 Ok(self)
426 }
427}
428
429impl ToInstructions for &Instructions {
430 fn to_instructions(self) -> Result<Instructions, FMError> {
431 Ok(self.clone())
432 }
433}
434
435impl ToInstructions for String {
436 fn to_instructions(self) -> Result<Instructions, FMError> {
437 Ok(Instructions::from(self))
438 }
439}
440
441impl ToInstructions for &str {
442 fn to_instructions(self) -> Result<Instructions, FMError> {
443 Ok(Instructions::from(self))
444 }
445}
446
447impl ToInstructions for GeneratedContent {
448 fn to_instructions(self) -> Result<Instructions, FMError> {
449 Ok(Instructions::from(self))
450 }
451}
452
453impl ToInstructions for &GeneratedContent {
454 fn to_instructions(self) -> Result<Instructions, FMError> {
455 Ok(Instructions::from(self.clone()))
456 }
457}