switchyard_translation/
request_edit.rs1use serde_json::{Map, Value, json};
7use switchyard_protocol::{ContentBlock, InstructionBlock, LlmRequest, Message, Role, WireFormat};
8
9pub fn append_note(request: &mut LlmRequest, note: &str) {
12 request.messages.push(Message::text(Role::User, note));
14 request.temporary_note_count += 1;
15 request.preservation.requests.retain(|format, body| {
16 let Some(body) = body.as_object_mut() else {
17 return false;
18 };
19 let (key, content_type) = match format.as_str() {
20 "openai_chat" | "anthropic_messages" => ("messages", "text"),
21 "openai_responses" => ("input", "input_text"),
22 _ => return false,
23 };
24 if key == "input"
26 && let Some(Value::String(text)) = body.get_mut(key)
27 {
28 let original = std::mem::take(text);
29 body.insert(key.into(), json!([{"role":"user", "content":original}]));
30 }
31 if key == "input" && body.get(key).is_none_or(Value::is_null) {
32 body.insert(key.into(), Value::Array(Vec::new()));
33 }
34 let Some(messages) = body.get_mut(key).and_then(Value::as_array_mut) else {
35 return false;
36 };
37 if format.as_str() == WireFormat::AnthropicMessages.as_str()
38 && let Some(last) = messages.last_mut()
39 && last.get("role").and_then(Value::as_str) == Some("user")
40 {
41 match last.get_mut("content") {
43 Some(Value::String(text)) => {
44 let original = std::mem::take(text);
45 last["content"] = json!([
46 {"type":"text", "text":original},
47 {"type":"text", "text":note}
48 ]);
49 }
50 Some(Value::Array(blocks)) => blocks.push(json!({"type":"text", "text":note})),
51 _ => return false,
52 }
53 } else {
54 messages.push(json!({"role":"user", "content":[{"type":content_type,"text":note}]}));
55 }
56 true
57 });
58}
59
60pub fn prepend_system_prompt(request: &mut LlmRequest, prompt: &str) {
63 if matches!(request.instructions.as_slice(), [InstructionBlock { content, .. }]
66 if matches!(content.as_slice(), [ContentBlock::Text { text, .. }] if text.is_empty()))
67 {
68 request.instructions.clear();
69 }
70 request.instructions.insert(
71 0,
72 InstructionBlock {
73 role: Role::System,
74 content: vec![ContentBlock::Text {
75 responses: None,
76 text: prompt.to_string(),
77 }],
78 },
79 );
80 request.preservation.requests.retain(|format, body| {
81 let Some(body) = body.as_object_mut() else {
82 return false;
83 };
84 match format.as_str() {
85 "openai_chat" => prepend_openai_chat_system(body, prompt),
86 "openai_responses" => prepend_text_field(body, "instructions", prompt),
87 "anthropic_messages" => {
88 body.remove("switchyard_anthropic_request");
89 prepend_anthropic_system(body, prompt)
90 }
91 _ => false,
92 }
93 });
94}
95
96fn prepend_openai_chat_system(body: &mut Map<String, Value>, prompt: &str) -> bool {
98 let message = json!({"role": "system", "content": prompt});
99 match body.get_mut("messages") {
100 None | Some(Value::Null) => {
101 body.insert("messages".to_string(), Value::Array(vec![message]));
102 }
103 Some(Value::Array(messages)) => messages.insert(0, message),
104 Some(_) => return false,
105 }
106 true
107}
108
109fn prepend_text_field(body: &mut Map<String, Value>, key: &str, prompt: &str) -> bool {
111 match body.get_mut(key) {
112 None | Some(Value::Null) => {
113 body.insert(key.to_string(), Value::String(prompt.to_string()));
114 }
115 Some(Value::String(text)) if text.is_empty() => {
116 *text = prompt.to_string();
117 }
118 Some(Value::String(text)) => text.insert_str(0, &format!("{prompt}\n\n")),
119 Some(_) => return false,
120 }
121 true
122}
123
124fn prepend_anthropic_system(body: &mut Map<String, Value>, prompt: &str) -> bool {
126 if let Some(Value::Array(blocks)) = body.get_mut("system") {
127 blocks.insert(0, json!({"type":"text","text":prompt}));
128 true
129 } else {
130 prepend_text_field(body, "system", prompt)
131 }
132}