1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use crate::tools::ToolCallInfo;
use crate::{
error::TypeError,
prompt::{MessageNum, ModelSettings, ResponseContent},
tools::AgentToolDefinition,
Provider,
};
use potato_util::utils::TokenLogProbs;
use pyo3::prelude::*;
use pyo3::types::PyList;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::OnceLock;
pub static VAR_REGEX: OnceLock<Regex> = OnceLock::new();
pub fn get_var_regex() -> &'static Regex {
VAR_REGEX.get_or_init(|| Regex::new(r"\$?\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap())
}
use crate::prompt::builder::ProviderRequest;
/// Core trait that all message types must implement
pub trait PromptMessageExt:
Send + Sync + Clone + Serialize + for<'de> Deserialize<'de> + PartialEq
{
/// Bind a variable in the message content, returning a new instance
fn bind(&self, name: &str, value: &str) -> Result<Self, TypeError>
where
Self: Sized;
/// Bind a variable in-place
fn bind_mut(&mut self, name: &str, value: &str) -> Result<(), TypeError>;
/// Extract variables from the message content
fn extract_variables(&self) -> Vec<String>;
fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
}
/// Core trait that must be implemented for all request types
pub trait RequestAdapter {
/// Returns all messages in the request
fn messages(&self) -> &[MessageNum];
/// Returns a mutable reference to the messages in the request
fn messages_mut(&mut self) -> &mut Vec<MessageNum>;
/// Returns all system instructions in the request
fn system_instructions(&self) -> Vec<&MessageNum>;
/// Returns the response JSON schema if set
fn response_json_schema(&self) -> Option<&Value>;
/// Inserts a message at the specified index (or at the start if None)
fn insert_message(&mut self, message: MessageNum, idx: Option<usize>) {
self.messages_mut().insert(idx.unwrap_or(0), message);
}
/// Prepends system instructions to the messages
fn preprend_system_instructions(&mut self, messages: Vec<MessageNum>) -> Result<(), TypeError>;
/// Returns the system instructions as a Python list
/// # Arguments
/// * `py` - The Python GIL token
/// # Returns
/// Returns a Python list of system instruction messages
fn get_py_system_instructions<'py>(
&self,
py: Python<'py>,
) -> Result<Bound<'py, PyList>, TypeError>;
/// Returns the model settings for the request (python object)
fn model_settings<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
/// Converts the request to a JSON value for sending to the provider
fn to_request_body(&self) -> Result<Value, TypeError>;
/// Checks if the request matches the given provider
fn match_provider(&self, provider: &Provider) -> bool;
/// Builds a provider-specific request enum from the given parameters
/// The ProviderRequest enum encapsulates all supported provider request types and is an
/// attribute of the Prompt struct. ProviderRequest is built on instantiation of the Prompt
fn build_provider_enum(
messages: Vec<MessageNum>,
system_instructions: Vec<MessageNum>,
model: String,
settings: ModelSettings,
response_json_schema: Option<Value>,
) -> Result<ProviderRequest, TypeError>;
/// Sets the response JSON schema for the request
/// Typically used as part of workflows when adding tasks
fn set_response_json_schema(&mut self, response_json_schema: Option<Value>) -> ();
/// Adds tools to the request
fn add_tools(&mut self, tools: Vec<AgentToolDefinition>) -> Result<(), TypeError>;
}
pub trait ResponseAdapter {
/// Returns a string representation of the response
fn __str__(&self) -> String;
/// Checks if the response is empty
fn is_empty(&self) -> bool;
/// Converts the response to a Python object
fn to_bound_py_object<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
/// Returns the response ID
fn id(&self) -> &str;
/// Converts the response to a vector of MessageNum
fn to_message_num(&self) -> Result<Vec<MessageNum>, TypeError>;
// Get the token usage as a Python object
fn usage<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
/// Retrieves the first content choice from the response
fn get_content(&self) -> ResponseContent;
/// Retrieves the log probabilities from the response
fn get_log_probs(&self) -> Vec<TokenLogProbs>;
/// Returns the structured output of the response
/// For all response types the flow is as follows:
/// 1. Check if the response has content (string/text)
/// 2. If no content, return Python None
/// 3. If content exists, check if an output_type/model is provided
/// 4. If output_type/model is provided, attempt to convert the content to that type
/// 5. If conversion fails, attempt to construct a generic Python object from the content
/// 6. If no output_type/model is provided, return the content as a generic Python object
/// # Arguments
/// * `py`: The Python GIL token
/// * `output_type`: An optional Python type/model to convert the content into. This can be a pydantic model or any object
/// that implements model_validate_json that can parse from a JSON string.
/// # Returns
/// * `Result<Bound<'py, PyAny>, TypeError>`: The structured output as a Python object or an error
fn structured_output<'py>(
&self,
py: Python<'py>,
output_type: Option<&Bound<'py, PyAny>>,
) -> Result<Bound<'py, PyAny>, TypeError>;
/// Returns the structured output value as a serde_json::Value
fn structured_output_value(&self) -> Option<Value>;
/// Returns any tool calls made in the response, if applicable
fn tool_call_output(&self) -> Option<Value>;
/// Returns the output text of the response if available
fn response_text(&self) -> String;
/// Extracts tool calls from the response, if any.
/// Returns None if the response contains no tool calls.
fn extract_tool_calls(&self) -> Option<Vec<crate::tools::ToolCall>> {
None
}
fn model_name(&self) -> Option<&str>;
fn finish_reason(&self) -> Option<&str>;
fn input_tokens(&self) -> Option<i64>;
fn output_tokens(&self) -> Option<i64>;
fn total_tokens(&self) -> Option<i64>;
fn get_tool_calls(&self) -> Vec<ToolCallInfo>;
}
pub trait MessageResponseExt {
fn to_message_num(&self) -> Result<MessageNum, TypeError>;
}
pub trait MessageFactory: Sized {
fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
}
/// Trait for converting between different provider message formats
///
/// This trait enables conversion of messages between different LLM provider formats
/// (e.g., Anthropic MessageParam ↔ Google GeminiContent ↔ OpenAI ChatMessage).
///
/// Currently focused on text content conversion, with support for other content
/// types planned for future implementation.
pub trait MessageConversion {
/// Convert this message to an Anthropic MessageParam
///
/// # Errors
/// Returns `TypeError::UnsupportedConversion` if the message contains
/// content types that cannot be represented in Anthropic's format
fn to_anthropic_message(
&self,
) -> Result<crate::anthropic::v1::request::MessageParam, TypeError>;
/// Convert this message to a Google GeminiContent
///
/// # Errors
/// Returns `TypeError::UnsupportedConversion` if the message contains
/// content types that cannot be represented in Google's format
fn to_google_message(
&self,
) -> Result<crate::google::v1::generate::request::GeminiContent, TypeError>;
/// Convert this message to an OpenAI ChatMessage
///
/// # Errors
/// Returns `TypeError::UnsupportedConversion` if the message contains
/// content types that cannot be represented in OpenAI's format
fn to_openai_message(&self)
-> Result<crate::openai::v1::chat::request::ChatMessage, TypeError>;
}