anchor_chain/models/
openai.rs1use std::fmt;
7
8use async_openai::types::{
9 ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs,
10 ChatCompletionRequestUserMessageContent, CreateChatCompletionRequestArgs,
11 CreateCompletionRequestArgs, CreateEmbeddingRequestArgs, Prompt,
12};
13use async_trait::async_trait;
14#[cfg(feature = "tracing")]
15use tracing::instrument;
16
17use crate::error::AnchorChainError;
18use crate::models::embedding_model::EmbeddingModel;
19use crate::node::Node;
20
21#[derive(Debug)]
23pub enum OpenAIModel<T>
24where
25 T: Into<Prompt>,
26{
27 GPT3_5Turbo(OpenAIChatModel<T>),
29 GPT3_5TurboInstruct(OpenAIInstructModel<T>),
31 GPT4Turbo(OpenAIChatModel<T>),
33}
34
35impl<T> OpenAIModel<T>
36where
37 T: Into<Prompt>,
38{
39 pub async fn new_gpt4_turbo(system_prompt: String) -> Self {
44 OpenAIModel::GPT3_5Turbo(
45 OpenAIChatModel::new(system_prompt, "gpt-4-turbo-preview".to_string()).await,
46 )
47 }
48
49 pub async fn new_gpt3_5_turbo(system_prompt: String) -> Self {
54 OpenAIModel::GPT4Turbo(
55 OpenAIChatModel::new(system_prompt, "gpt-3.5-turbo".to_string()).await,
56 )
57 }
58
59 pub async fn new_gpt3_5_turbo_instruct() -> Self {
61 OpenAIModel::GPT3_5TurboInstruct(
62 OpenAIInstructModel::new("gpt-3.5-turbo-instruct-0914".to_string()).await,
63 )
64 }
65}
66
67#[async_trait]
68impl<T> Node for OpenAIModel<T>
69where
70 T: Send + Sync + fmt::Debug,
71 T: Into<Prompt> + Into<ChatCompletionRequestUserMessageContent>,
72{
73 type Input = T;
75 type Output = String;
77
78 #[cfg_attr(feature = "tracing", instrument(skip(self)))]
80 async fn process(&self, input: Self::Input) -> Result<Self::Output, AnchorChainError> {
81 match self {
82 OpenAIModel::GPT3_5Turbo(model) => model.process(input).await,
83 OpenAIModel::GPT4Turbo(model) => model.process(input).await,
84 OpenAIModel::GPT3_5TurboInstruct(model) => model.process(input).await,
85 }
86 }
87}
88
89pub struct OpenAIChatModel<T> {
95 system_prompt: String,
96 model: String,
97 client: async_openai::Client<async_openai::config::OpenAIConfig>,
98 _phantom: std::marker::PhantomData<T>,
99}
100
101impl<T> OpenAIChatModel<T> {
102 async fn new(system_prompt: String, model: String) -> Self {
134 let config = async_openai::config::OpenAIConfig::new();
135 let client = async_openai::Client::with_config(config);
136 OpenAIChatModel {
137 system_prompt,
138 client,
139 model,
140 _phantom: std::marker::PhantomData,
141 }
142 }
143
144 pub async fn new_with_key(system_prompt: String, model: String, api_key: String) -> Self {
149 let config = async_openai::config::OpenAIConfig::new().with_api_key(api_key);
150 let client = async_openai::Client::with_config(config);
151 OpenAIChatModel {
152 system_prompt,
153 client,
154 model,
155 _phantom: std::marker::PhantomData,
156 }
157 }
158}
159
160#[async_trait]
161impl<T> Node for OpenAIChatModel<T>
162where
163 T: Into<ChatCompletionRequestUserMessageContent> + fmt::Debug + Send + Sync,
164{
165 type Input = T;
166 type Output = String;
167
168 #[cfg_attr(feature = "tracing", instrument(skip(self), fields(model = self.model.as_str(), system_prompt = self.system_prompt.as_str())))]
173 async fn process(&self, input: Self::Input) -> Result<Self::Output, AnchorChainError> {
174 let system_prompt = ChatCompletionRequestSystemMessageArgs::default()
175 .content(self.system_prompt.clone())
176 .build()?
177 .into();
178
179 let input = ChatCompletionRequestUserMessageArgs::default()
180 .content(input)
181 .build()?
182 .into();
183
184 let request = CreateChatCompletionRequestArgs::default()
185 .max_tokens(512u16)
186 .model(&self.model)
187 .messages([system_prompt, input])
188 .build()?;
189
190 let response = self.client.chat().create(request).await?;
191 if response.choices.is_empty() {
192 return Err(AnchorChainError::EmptyResponseError);
193 }
194
195 let content = response
196 .choices
197 .first()
198 .ok_or(AnchorChainError::EmptyResponseError)?
199 .message
200 .clone()
201 .content
202 .ok_or(AnchorChainError::EmptyResponseError)?;
203
204 Ok(content)
205 }
206}
207
208impl<T> fmt::Debug for OpenAIChatModel<T> {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 f.debug_struct("OpenAI")
211 .field("system_prompt", &self.system_prompt)
212 .finish()
213 }
214}
215
216pub struct OpenAIInstructModel<T>
218where
219 T: Into<Prompt>,
220{
221 model: String,
223 client: async_openai::Client<async_openai::config::OpenAIConfig>,
225 _phantom: std::marker::PhantomData<T>,
226}
227
228impl<T> OpenAIInstructModel<T>
229where
230 T: Into<Prompt>,
231{
232 #[allow(dead_code)]
240 async fn new(model: String) -> Self {
241 let config = async_openai::config::OpenAIConfig::new();
242 let client = async_openai::Client::with_config(config);
243 OpenAIInstructModel {
244 client,
245 model,
246 _phantom: std::marker::PhantomData,
247 }
248 }
249
250 #[allow(dead_code)]
254 pub async fn new_with_key(model: String, api_key: String) -> Self {
255 let config = async_openai::config::OpenAIConfig::new().with_api_key(api_key);
256 let client = async_openai::Client::with_config(config);
257 OpenAIInstructModel {
258 client,
259 model,
260 _phantom: std::marker::PhantomData,
261 }
262 }
263}
264
265#[async_trait]
266impl<T> Node for OpenAIInstructModel<T>
267where
268 T: Into<Prompt> + fmt::Debug + Send + Sync,
269{
270 type Input = T;
271 type Output = String;
272
273 #[cfg_attr(feature = "tracing", instrument(skip(self), fields(model = self.model.as_str())))]
278 async fn process(&self, input: Self::Input) -> Result<Self::Output, AnchorChainError> {
279 let request = CreateCompletionRequestArgs::default()
280 .model(&self.model)
281 .prompt(input)
282 .temperature(0.8)
283 .max_tokens(512u16)
284 .build()?;
285
286 let response = self.client.completions().create(request).await?;
287
288 let content = response
289 .choices
290 .first()
291 .ok_or(AnchorChainError::EmptyResponseError)?
292 .text
293 .clone();
294
295 Ok(content)
296 }
297}
298
299impl<T> fmt::Debug for OpenAIInstructModel<T>
300where
301 T: Into<Prompt>,
302{
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 f.debug_struct("OpenAI").finish()
305 }
306}
307
308pub struct OpenAIEmbeddingModel {
310 model: String,
312 client: async_openai::Client<async_openai::config::OpenAIConfig>,
314}
315
316impl Default for OpenAIEmbeddingModel {
317 fn default() -> Self {
318 OpenAIEmbeddingModel {
319 model: "text-embedding-3-large".to_string(),
320 client: async_openai::Client::with_config(async_openai::config::OpenAIConfig::new()),
321 }
322 }
323}
324
325impl OpenAIEmbeddingModel {
326 #[allow(dead_code)]
335 async fn new(model: String) -> Self {
336 let config = async_openai::config::OpenAIConfig::new();
337 let client = async_openai::Client::with_config(config);
338 OpenAIEmbeddingModel { client, model }
339 }
340
341 #[allow(dead_code)]
345 async fn new_with_key(model: String, api_key: String) -> Self {
346 let config = async_openai::config::OpenAIConfig::new().with_api_key(api_key);
347 let client = async_openai::Client::with_config(config);
348 OpenAIEmbeddingModel { client, model }
349 }
350}
351
352#[async_trait]
353impl Node for OpenAIEmbeddingModel {
354 type Input = Vec<String>;
355 type Output = Vec<Vec<f32>>;
356
357 #[cfg_attr(feature = "tracing", instrument(skip(self), fields(model = self.model.as_str())))]
362 async fn process(&self, input: Self::Input) -> Result<Self::Output, AnchorChainError> {
363 let request = CreateEmbeddingRequestArgs::default()
364 .model(&self.model)
365 .input(input)
366 .build()?;
367
368 let response = self.client.embeddings().create(request).await?;
369
370 Ok(response
371 .data
372 .iter()
373 .map(|data| data.embedding.clone())
374 .collect())
375 }
376}
377
378#[async_trait]
379impl EmbeddingModel for OpenAIEmbeddingModel {
380 #[cfg_attr(feature = "tracing", instrument(skip(self), fields(model = self.model.as_str())))]
381 async fn embed(&self, input: String) -> Result<Vec<f32>, AnchorChainError> {
382 self.process(vec![input])
383 .await?
384 .first()
385 .ok_or(AnchorChainError::EmptyResponseError)
386 .cloned()
387 }
388
389 fn dimensions(&self) -> usize {
390 3072
391 }
392}
393
394impl fmt::Debug for OpenAIEmbeddingModel {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 f.debug_struct("OpenAI").finish()
397 }
398}