1use crate::tools::ToolCallInfo;
2use crate::{
3 error::TypeError,
4 prompt::{MessageNum, ModelSettings, ResponseContent},
5 tools::AgentToolDefinition,
6 Provider,
7};
8use potato_util::utils::TokenLogProbs;
9use pyo3::prelude::*;
10use pyo3::types::PyList;
11use regex::Regex;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::sync::OnceLock;
15
16pub static VAR_REGEX: OnceLock<Regex> = OnceLock::new();
17pub fn get_var_regex() -> &'static Regex {
18 VAR_REGEX.get_or_init(|| Regex::new(r"\$?\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap())
19}
20
21pub static MEDIA_REGEX: OnceLock<Regex> = OnceLock::new();
22pub fn get_media_regex() -> &'static Regex {
23 MEDIA_REGEX.get_or_init(|| Regex::new(r"\$\{media:([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap())
24}
25
26pub(crate) fn split_text_on_media(text: &str, regex: &Regex) -> Vec<String> {
27 let mut out: Vec<String> = Vec::new();
28 let mut last = 0usize;
29 for m in regex.find_iter(text) {
30 if m.start() > last {
31 out.push(text[last..m.start()].to_string());
32 }
33 out.push(m.as_str().to_string());
34 last = m.end();
35 }
36 if last < text.len() {
37 out.push(text[last..].to_string());
38 }
39 out
40}
41
42pub(crate) fn extract_token_name(token: &str) -> String {
43 token
44 .strip_prefix("${media:")
45 .and_then(|s| s.strip_suffix('}'))
46 .unwrap_or("")
47 .to_string()
48}
49use crate::prompt::builder::ProviderRequest;
50pub trait PromptMessageExt:
52 Send + Sync + Clone + Serialize + for<'de> Deserialize<'de> + PartialEq
53{
54 fn bind(&self, name: &str, value: &str) -> Result<Self, TypeError>
56 where
57 Self: Sized;
58
59 fn bind_mut(&mut self, name: &str, value: &str) -> Result<(), TypeError>;
61
62 fn extract_variables(&self) -> Vec<String>;
64
65 fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
66
67 fn extract_media_variables(&self) -> Vec<String> {
68 Vec::new()
69 }
70
71 fn split_media_placeholders(&mut self) -> Result<(), TypeError> {
72 Ok(())
73 }
74
75 fn bind_media_mut(
76 &mut self,
77 _token: &str,
78 _media: &crate::prompt::media::MediaRef,
79 _provider: &crate::Provider,
80 ) -> Result<bool, TypeError> {
81 Ok(false)
82 }
83}
84
85pub trait RequestAdapter {
87 fn messages(&self) -> &[MessageNum];
89 fn messages_mut(&mut self) -> &mut Vec<MessageNum>;
91 fn system_instructions(&self) -> Vec<&MessageNum>;
93 fn response_json_schema(&self) -> Option<&Value>;
95 fn insert_message(&mut self, message: MessageNum, idx: Option<usize>) {
97 self.messages_mut().insert(idx.unwrap_or(0), message);
98 }
99 fn preprend_system_instructions(&mut self, messages: Vec<MessageNum>) -> Result<(), TypeError>;
101
102 fn get_py_system_instructions<'py>(
108 &self,
109 py: Python<'py>,
110 ) -> Result<Bound<'py, PyList>, TypeError>;
111 fn model_settings<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
113
114 fn to_request_body(&self) -> Result<Value, TypeError>;
116 fn match_provider(&self, provider: &Provider) -> bool;
118 fn build_provider_enum(
122 messages: Vec<MessageNum>,
123 system_instructions: Vec<MessageNum>,
124 model: String,
125 settings: ModelSettings,
126 response_json_schema: Option<Value>,
127 ) -> Result<ProviderRequest, TypeError>;
128
129 fn set_response_json_schema(&mut self, response_json_schema: Option<Value>) -> ();
132
133 fn add_tools(&mut self, tools: Vec<AgentToolDefinition>) -> Result<(), TypeError>;
135}
136
137pub trait ResponseAdapter {
138 fn __str__(&self) -> String;
140
141 fn is_empty(&self) -> bool;
143
144 fn to_bound_py_object<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
146
147 fn id(&self) -> &str;
149
150 fn to_message_num(&self) -> Result<Vec<MessageNum>, TypeError>;
152
153 fn usage<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
155
156 fn get_content(&self) -> ResponseContent;
158
159 fn get_log_probs(&self) -> Vec<TokenLogProbs>;
161
162 fn structured_output<'py>(
177 &self,
178 py: Python<'py>,
179 output_type: Option<&Bound<'py, PyAny>>,
180 ) -> Result<Bound<'py, PyAny>, TypeError>;
181
182 fn structured_output_value(&self) -> Option<Value>;
184
185 fn tool_call_output(&self) -> Option<Value>;
187
188 fn response_text(&self) -> String;
190
191 fn extract_tool_calls(&self) -> Option<Vec<crate::tools::ToolCall>> {
194 None
195 }
196 fn model_name(&self) -> Option<&str>;
197
198 fn finish_reason(&self) -> Option<&str>;
199
200 fn input_tokens(&self) -> Option<i64>;
201
202 fn output_tokens(&self) -> Option<i64>;
203
204 fn total_tokens(&self) -> Option<i64>;
205
206 fn get_tool_calls(&self) -> Vec<ToolCallInfo>;
207}
208
209pub trait MessageResponseExt {
210 fn to_message_num(&self) -> Result<MessageNum, TypeError>;
211}
212
213pub trait MessageFactory: Sized {
214 fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
215}
216
217pub trait MessageConversion {
225 fn to_anthropic_message(
231 &self,
232 ) -> Result<crate::anthropic::v1::request::MessageParam, TypeError>;
233
234 fn to_google_message(
240 &self,
241 ) -> Result<crate::google::v1::generate::request::GeminiContent, TypeError>;
242
243 fn to_openai_message(&self)
249 -> Result<crate::openai::v1::chat::request::ChatMessage, TypeError>;
250}