1use serde_json::{json, Value};
4
5use crate::content::GeneratedContent;
6use crate::error::FMError;
7use crate::schema::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 {
189 id: None,
190 text: text.into(),
191 })
192 }
193
194 #[must_use]
196 pub fn structure(source: impl Into<String>, content: GeneratedContent) -> Self {
197 Self::Structure(StructuredSegment {
198 id: None,
199 source: source.into(),
200 content,
201 })
202 }
203
204 pub(crate) fn to_bridge_value(&self) -> Value {
205 match self {
206 Self::Text(segment) => json!({
207 "kind": "text",
208 "text": segment.text,
209 }),
210 Self::Structure(segment) => json!({
211 "kind": "structure",
212 "source": segment.source,
213 "contentJSON": segment.content.json_string().expect("generated content must serialize")
214 }),
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct TextSegment {
222 pub id: Option<String>,
223 pub text: String,
224}
225
226#[derive(Debug, Clone, PartialEq)]
228pub struct StructuredSegment {
229 pub id: Option<String>,
230 pub source: String,
231 pub content: GeneratedContent,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct ResponseFormat {
237 name: Option<String>,
238 schema: GenerationSchema,
239}
240
241impl ResponseFormat {
242 #[must_use]
244 pub fn json_schema(schema: GenerationSchema) -> Self {
245 Self { name: None, schema }
246 }
247
248 pub(crate) fn from_transcript_json_value(value: &Value) -> Result<Self, FMError> {
249 let schema = value
250 .get("jsonSchema")
251 .and_then(|json_schema| json_schema.get("schema"))
252 .ok_or_else(|| {
253 FMError::DecodingFailure("response format is missing jsonSchema.schema".into())
254 })?;
255 let name = value
256 .get("jsonSchema")
257 .and_then(|json_schema| json_schema.get("name"))
258 .and_then(Value::as_str)
259 .map(ToOwned::to_owned);
260 Ok(Self {
261 name,
262 schema: GenerationSchema::from_json_schema_unchecked(
263 serde_json::to_string(schema).map_err(|error| {
264 FMError::InvalidArgument(format!(
265 "response format schema is not valid JSON: {error}"
266 ))
267 })?,
268 ),
269 })
270 }
271
272 #[must_use]
274 pub fn with_name(mut self, name: impl Into<String>) -> Self {
275 self.name = Some(name.into());
276 self
277 }
278
279 #[must_use]
281 pub const fn schema(&self) -> &GenerationSchema {
282 &self.schema
283 }
284
285 pub(crate) fn to_transcript_json_value(&self) -> Value {
286 let schema_value: Value = serde_json::from_str(self.schema.json_schema())
287 .expect("validated generation schema must always be valid JSON");
288 json!({
289 "type": "jsonSchema",
290 "jsonSchema": {
291 "name": self
292 .name
293 .clone()
294 .or_else(|| self.schema.name())
295 .unwrap_or_else(|| "GeneratedContent".to_string()),
296 "schema": schema_value,
297 }
298 })
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
304pub struct ToolDefinition {
305 pub name: String,
306 pub description: String,
307 pub parameters: GenerationSchema,
308}
309
310impl ToolDefinition {
311 #[must_use]
313 pub fn new(
314 name: impl Into<String>,
315 description: impl Into<String>,
316 parameters: GenerationSchema,
317 ) -> Self {
318 Self {
319 name: name.into(),
320 description: description.into(),
321 parameters,
322 }
323 }
324
325 pub(crate) fn to_transcript_json_value(&self) -> Value {
326 let parameters: Value = serde_json::from_str(self.parameters.json_schema())
327 .expect("validated generation schema must always be valid JSON");
328 json!({
329 "type": "function",
330 "function": {
331 "name": self.name,
332 "description": self.description,
333 "parameters": parameters,
334 }
335 })
336 }
337}
338
339pub trait ToPrompt {
341 fn to_prompt(self) -> Result<Prompt, FMError>;
343}
344
345impl ToPrompt for Prompt {
346 fn to_prompt(self) -> Result<Prompt, FMError> {
347 Ok(self)
348 }
349}
350
351impl ToPrompt for &Prompt {
352 fn to_prompt(self) -> Result<Prompt, FMError> {
353 Ok(self.clone())
354 }
355}
356
357impl ToPrompt for String {
358 fn to_prompt(self) -> Result<Prompt, FMError> {
359 Ok(Prompt::from(self))
360 }
361}
362
363impl ToPrompt for &str {
364 fn to_prompt(self) -> Result<Prompt, FMError> {
365 Ok(Prompt::from(self))
366 }
367}
368
369impl ToPrompt for GeneratedContent {
370 fn to_prompt(self) -> Result<Prompt, FMError> {
371 Ok(Prompt::from(self))
372 }
373}
374
375impl ToPrompt for &GeneratedContent {
376 fn to_prompt(self) -> Result<Prompt, FMError> {
377 Ok(Prompt::from(self.clone()))
378 }
379}
380
381pub trait ToInstructions {
383 fn to_instructions(self) -> Result<Instructions, FMError>;
385}
386
387impl ToInstructions for Instructions {
388 fn to_instructions(self) -> Result<Instructions, FMError> {
389 Ok(self)
390 }
391}
392
393impl ToInstructions for &Instructions {
394 fn to_instructions(self) -> Result<Instructions, FMError> {
395 Ok(self.clone())
396 }
397}
398
399impl ToInstructions for String {
400 fn to_instructions(self) -> Result<Instructions, FMError> {
401 Ok(Instructions::from(self))
402 }
403}
404
405impl ToInstructions for &str {
406 fn to_instructions(self) -> Result<Instructions, FMError> {
407 Ok(Instructions::from(self))
408 }
409}
410
411impl ToInstructions for GeneratedContent {
412 fn to_instructions(self) -> Result<Instructions, FMError> {
413 Ok(Instructions::from(self))
414 }
415}
416
417impl ToInstructions for &GeneratedContent {
418 fn to_instructions(self) -> Result<Instructions, FMError> {
419 Ok(Instructions::from(self.clone()))
420 }
421}