langchain_rust/llm/openai/
mod.rs1use std::pin::Pin;
2
3pub use async_openai::config::{AzureConfig, Config, OpenAIConfig};
4
5use async_openai::types::chat::{
6 ChatChoiceStream, ChatCompletionMessageToolCalls, ChatCompletionRequestAssistantMessageArgs,
7 ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImageArgs,
8 ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestToolMessageArgs,
9 ChatCompletionRequestUserMessageArgs, ChatCompletionRequestUserMessageContent,
10 ChatCompletionRequestUserMessageContentPart, ChatCompletionStreamOptions,
11 ChatCompletionToolChoiceOption, ChatCompletionTools, CreateChatCompletionRequest,
12 CreateChatCompletionRequestArgs, ResponseFormat, StopConfiguration,
13};
14use async_openai::{error::OpenAIError, Client};
15use async_trait::async_trait;
16use futures::{Stream, StreamExt};
17
18use crate::schemas::convert::{LangchainIntoOpenAI, TryLangchainIntoOpenAI};
19use crate::{
20 language_models::{llm::LLM, options::CallOptions, GenerateResult, LLMError, TokenUsage},
21 schemas::{
22 messages::{Message, MessageType},
23 StreamData, ToolCall,
24 },
25};
26
27#[derive(Clone)]
28pub enum OpenAIModel {
29 Gpt35,
30 Gpt4,
31 Gpt4Turbo,
32 Gpt4o,
33 Gpt4oMini,
34}
35
36impl ToString for OpenAIModel {
37 fn to_string(&self) -> String {
38 match self {
39 OpenAIModel::Gpt35 => "gpt-3.5-turbo".to_string(),
40 OpenAIModel::Gpt4 => "gpt-4".to_string(),
41 OpenAIModel::Gpt4Turbo => "gpt-4-turbo-preview".to_string(),
42 OpenAIModel::Gpt4o => "gpt-4o".to_string(),
43 OpenAIModel::Gpt4oMini => "gpt-4o-mini".to_string(),
44 }
45 }
46}
47
48impl Into<String> for OpenAIModel {
49 fn into(self) -> String {
50 self.to_string()
51 }
52}
53
54#[derive(Clone)]
55pub struct OpenAI<C: Config> {
56 config: C,
57 options: CallOptions,
58 model: String,
59}
60
61impl<C: Config> OpenAI<C> {
62 pub fn new(config: C) -> Self {
63 Self {
64 config,
65 options: CallOptions::default(),
66 model: OpenAIModel::Gpt4oMini.to_string(),
67 }
68 }
69
70 pub fn with_model<S: Into<String>>(mut self, model: S) -> Self {
71 self.model = model.into();
72 self
73 }
74
75 pub fn with_config(mut self, config: C) -> Self {
76 self.config = config;
77 self
78 }
79
80 pub fn with_options(mut self, options: CallOptions) -> Self {
81 self.options = options;
82 self
83 }
84}
85
86impl Default for OpenAI<OpenAIConfig> {
87 fn default() -> Self {
88 Self::new(OpenAIConfig::default())
89 }
90}
91
92#[async_trait]
93impl<C: Config + Clone + Send + Sync + 'static> LLM for OpenAI<C> {
94 async fn generate(&self, prompt: &[Message]) -> Result<GenerateResult, LLMError> {
95 let client = Client::with_config(self.config.clone());
96 let request = self.generate_request(prompt, self.options.streaming_func.is_some())?;
97 match &self.options.streaming_func {
98 Some(func) => {
99 let mut stream = client.chat().create_stream(request).await?;
100 let mut generate_result = GenerateResult::default();
101 while let Some(result) = stream.next().await {
102 match result {
103 Ok(response) => {
104 if let Some(usage) = response.usage {
105 generate_result.tokens = Some(TokenUsage {
106 prompt_tokens: usage.prompt_tokens,
107 completion_tokens: usage.completion_tokens,
108 total_tokens: usage.total_tokens,
109 });
110 }
111 for chat_choice in response.choices.iter() {
112 let chat_choice: ChatChoiceStream = chat_choice.clone();
113 {
114 let mut func = func.lock().await;
115 let _ = func(
116 serde_json::to_string(&chat_choice).unwrap_or("".into()),
117 )
118 .await;
119 }
120 if let Some(content) = chat_choice.delta.content {
121 generate_result.generation.push_str(&content);
122 }
123 }
124 }
125 Err(err) => {
126 eprintln!("Error from streaming response: {:?}", err);
127 }
128 }
129 }
130 Ok(generate_result)
131 }
132 None => {
133 let response = client.chat().create(request).await?;
134 let mut generate_result = GenerateResult::default();
135
136 if let Some(usage) = response.usage {
137 generate_result.tokens = Some(TokenUsage {
138 prompt_tokens: usage.prompt_tokens,
139 completion_tokens: usage.completion_tokens,
140 total_tokens: usage.total_tokens,
141 });
142 }
143
144 if let Some(choice) = &response.choices.first() {
145 generate_result.generation = choice.message.content.clone().unwrap_or_default();
146 if let Some(calls) = &choice.message.tool_calls {
147 let typed: Vec<ToolCall> = calls
148 .iter()
149 .filter_map(|c| match c {
150 ChatCompletionMessageToolCalls::Function(f) => Some(ToolCall::new(
151 &f.id,
152 &f.function.name,
153 &f.function.arguments,
154 )),
155 _ => None,
156 })
157 .collect();
158 if typed.is_empty() {
159 generate_result.generation =
161 serde_json::to_string(&calls).unwrap_or_default();
162 } else {
163 generate_result = generate_result.with_tool_calls(typed);
164 }
165 }
166 } else {
167 generate_result.generation = "".to_string();
168 }
169
170 Ok(generate_result)
171 }
172 }
173 }
174
175 async fn invoke(&self, prompt: &str) -> Result<String, LLMError> {
176 self.generate(&[Message::new_human_message(prompt)])
177 .await
178 .map(|res| res.generation)
179 }
180
181 async fn stream(
182 &self,
183 messages: &[Message],
184 ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamData, LLMError>> + Send>>, LLMError> {
185 let client = Client::with_config(self.config.clone());
186 let request = self.generate_request(messages, true)?;
187
188 let original_stream = client.chat().create_stream(request).await?;
189
190 let new_stream = original_stream.map(|result| match result {
191 Ok(completion) => {
192 let value_completion = serde_json::to_value(completion).map_err(LLMError::from)?;
193 let tokens = match value_completion.pointer("/usage") {
194 Some(usage) if !usage.is_null() => Some(
195 serde_json::from_value::<TokenUsage>(usage.clone())
196 .map_err(LLMError::from)?,
197 ),
198 _ => None,
199 };
200 let content = value_completion.pointer("/choices/0/delta/content");
203 if tokens.is_none() && content.is_none() {
204 return Err(LLMError::ContentNotFound(
205 "/choices/0/delta/content".to_string(),
206 ));
207 }
208 let content = content
209 .and_then(serde_json::Value::as_str)
210 .unwrap_or("")
211 .to_string();
212
213 Ok(StreamData::new(value_completion, tokens, content))
214 }
215 Err(e) => Err(LLMError::from(e)),
216 });
217
218 Ok(Box::pin(new_stream))
219 }
220
221 fn add_options(&mut self, options: CallOptions) {
222 self.options.merge_options(options)
223 }
224}
225
226impl<C: Config> OpenAI<C> {
227 fn to_openai_messages(
228 &self,
229 messages: &[Message],
230 ) -> Result<Vec<ChatCompletionRequestMessage>, LLMError> {
231 let mut openai_messages: Vec<ChatCompletionRequestMessage> = Vec::new();
232 for m in messages {
233 match m.message_type {
234 MessageType::AIMessage => openai_messages.push(match &m.tool_calls {
235 Some(value) => {
236 let function: Vec<ChatCompletionMessageToolCalls> =
237 serde_json::from_value(value.clone())?;
238 ChatCompletionRequestAssistantMessageArgs::default()
239 .tool_calls(function)
240 .content(m.content.clone())
241 .build()?
242 .into()
243 }
244 None => ChatCompletionRequestAssistantMessageArgs::default()
245 .content(m.content.clone())
246 .build()?
247 .into(),
248 }),
249 MessageType::HumanMessage => {
250 let content: ChatCompletionRequestUserMessageContent = match m.images.clone() {
251 Some(images) => {
252 let content: Result<
253 Vec<ChatCompletionRequestUserMessageContentPart>,
254 OpenAIError,
255 > = images
256 .into_iter()
257 .map(|image| {
258 Ok(ChatCompletionRequestMessageContentPartImageArgs::default()
259 .image_url(image.image_url)
260 .build()?
261 .into())
262 })
263 .collect();
264
265 content?.into()
266 }
267 None => m.content.clone().into(),
268 };
269
270 openai_messages.push(
271 ChatCompletionRequestUserMessageArgs::default()
272 .content(content)
273 .build()?
274 .into(),
275 )
276 }
277 MessageType::SystemMessage => openai_messages.push(
278 ChatCompletionRequestSystemMessageArgs::default()
279 .content(m.content.clone())
280 .build()?
281 .into(),
282 ),
283 MessageType::ToolMessage => {
284 openai_messages.push(
285 ChatCompletionRequestToolMessageArgs::default()
286 .content(m.content.clone())
287 .tool_call_id(m.id.clone().unwrap_or_default())
288 .build()?
289 .into(),
290 );
291 }
292 }
293 }
294 Ok(openai_messages)
295 }
296
297 fn generate_request(
298 &self,
299 messages: &[Message],
300 stream: bool,
301 ) -> Result<CreateChatCompletionRequest, LLMError> {
302 let messages: Vec<ChatCompletionRequestMessage> = self.to_openai_messages(messages)?;
303 let mut request_builder = CreateChatCompletionRequestArgs::default();
304 if let Some(temperature) = self.options.temperature {
305 request_builder.temperature(temperature);
306 }
307 if let Some(max_tokens) = self.options.max_tokens {
308 request_builder.max_tokens(max_tokens);
309 }
310 if stream {
311 request_builder.stream(true);
312 if let Some(include_usage) = self.options.stream_usage {
313 request_builder.stream_options(ChatCompletionStreamOptions {
314 include_usage: Some(include_usage),
315 include_obfuscation: None,
316 });
317 }
318 }
319 request_builder.model(self.model.to_string());
320 if let Some(stop_words) = &self.options.stop_words {
321 request_builder.stop(StopConfiguration::StringArray(stop_words.clone()));
322 }
323
324 if let Some(functions) = &self.options.functions {
325 let functions: Result<Vec<ChatCompletionTools>, OpenAIError> = functions
326 .clone()
327 .into_iter()
328 .map(|f| f.try_into_openai())
329 .collect();
330 request_builder.tools(functions?);
331 }
332
333 if let Some(behavior) = &self.options.function_call_behavior {
334 request_builder
335 .tool_choice::<ChatCompletionToolChoiceOption>(behavior.clone().into_openai());
336 }
337
338 if let Some(response_format) = &self.options.response_format {
339 request_builder
340 .response_format::<ResponseFormat>(response_format.clone().into_openai());
341 }
342
343 request_builder.messages(messages);
344 Ok(request_builder.build()?)
345 }
346}
347
348#[cfg(test)]
349mod mock_tests;
350
351#[cfg(test)]
352mod tests {
353 use crate::schemas::FunctionDefinition;
354
355 use super::*;
356
357 use base64::prelude::*;
358 use serde_json::json;
359 use std::sync::Arc;
360 use tokio::sync::Mutex;
361 use tokio::test;
362
363 #[test]
364 #[ignore]
365 async fn test_invoke() {
366 let message_complete = Arc::new(Mutex::new(String::new()));
367
368 let streaming_func = {
371 let message_complete = message_complete.clone();
372 move |content: String| {
373 let message_complete = message_complete.clone();
374 async move {
375 let mut message_complete_lock = message_complete.lock().await;
376 println!("Content: {:?}", content);
377 message_complete_lock.push_str(&content);
378 Ok(())
379 }
380 }
381 };
382 let options = CallOptions::new().with_streaming_func(streaming_func);
383 let open_ai = OpenAI::new(OpenAIConfig::default())
385 .with_model(OpenAIModel::Gpt35.to_string()) .with_options(options);
387
388 match open_ai.invoke("hola").await {
392 Ok(result) => {
393 println!("Generate Result: {:?}", result);
395 println!("Message Complete: {:?}", message_complete.lock().await);
396 }
397 Err(e) => {
398 eprintln!("Error calling generate: {:?}", e);
400 }
401 }
402 }
403
404 #[test]
405 #[ignore]
406 async fn test_generate_function() {
407 let message_complete = Arc::new(Mutex::new(String::new()));
408
409 let streaming_func = {
412 let message_complete = message_complete.clone();
413 move |content: String| {
414 let message_complete = message_complete.clone();
415 async move {
416 let content = serde_json::from_str::<ChatChoiceStream>(&content).unwrap();
417 if content.finish_reason.is_some() {
418 return Ok(());
419 }
420 let mut message_complete_lock = message_complete.lock().await;
421 println!("Content: {:?}", content);
422 message_complete_lock.push_str(&content.delta.content.unwrap());
423 Ok(())
424 }
425 }
426 };
427 let options = CallOptions::new().with_streaming_func(streaming_func);
429 let open_ai = OpenAI::new(OpenAIConfig::default())
431 .with_model(OpenAIModel::Gpt35.to_string()) .with_options(options);
433
434 let messages = vec![Message::new_human_message("Hello, how are you?")];
436
437 match open_ai.generate(&messages).await {
439 Ok(result) => {
440 println!("Generate Result: {:?}", result);
442 println!("Message Complete: {:?}", message_complete.lock().await);
443 }
444 Err(e) => {
445 eprintln!("Error calling generate: {:?}", e);
447 }
448 }
449 }
450
451 #[test]
452 #[ignore]
453 async fn test_openai_stream() {
454 let open_ai = OpenAI::default().with_model(OpenAIModel::Gpt35.to_string());
456
457 let messages = vec![Message::new_human_message("Hello, how are you?")];
459
460 open_ai
461 .stream(&messages)
462 .await
463 .unwrap()
464 .for_each(|result| async {
465 match result {
466 Ok(stream_data) => {
467 println!("Stream Data: {:?}", stream_data.content);
468 }
469 Err(e) => {
470 eprintln!("Error calling generate: {:?}", e);
471 }
472 }
473 })
474 .await;
475 }
476
477 #[test]
478 #[ignore]
479 async fn test_function() {
480 let mut functions = Vec::new();
481 functions.push(FunctionDefinition {
482 name: "cli".to_string(),
483 description: "Use the Ubuntu command line to preform any action you wish.".to_string(),
484 parameters: json!({
485 "type": "object",
486 "properties": {
487 "command": {
488 "type": "string",
489 "description": "The raw command you want executed"
490 }
491 },
492 "required": ["command"]
493 }),
494 });
495
496 let llm = OpenAI::default()
497 .with_model(OpenAIModel::Gpt35)
498 .with_config(OpenAIConfig::new())
499 .with_options(CallOptions::new().with_functions(functions));
500 let response = llm
501 .invoke("Use the command line to create a new rust project. Execute the first command.")
502 .await
503 .unwrap();
504 println!("{}", response)
505 }
506
507 #[test]
508 #[ignore]
509 async fn test_generate_with_image_message() {
510 let open_ai =
512 OpenAI::new(OpenAIConfig::default()).with_model(OpenAIModel::Gpt4o.to_string());
513
514 let image = std::fs::read("./src/llm/test_data/example.jpg").unwrap();
516 let image_base64 = BASE64_STANDARD.encode(image);
517
518 let image_urls = vec![format!("data:image/jpeg;base64,{image_base64}")];
520 let messages = vec![
521 Message::new_human_message("Describe this image"),
522 Message::new_human_message_with_images(image_urls),
523 ];
524
525 let response = open_ai.generate(&messages).await.unwrap();
527 println!("Response: {:?}", response);
528 }
529}