llm/providers/openai/
provider.rs1use async_openai::{Client, config::Config, types::chat::CreateChatCompletionRequest};
2use async_stream;
3use std::error::Error;
4use tokio_stream::StreamExt;
5use tracing::{debug, error};
6
7use super::{
8 mappers::{map_messages, map_tools},
9 streaming::process_completion_stream,
10};
11use crate::provider::error_stream;
12use crate::{Context, LlmError, LlmResponseStream, StreamingModelProvider};
13
14pub trait OpenAiChatProvider {
17 type Config: Config + Clone + 'static;
18
19 fn client(&self) -> &Client<Self::Config>;
20 fn model(&self) -> &str;
21 fn provider_name(&self) -> &str;
22}
23
24impl<T: OpenAiChatProvider + Send + Sync> StreamingModelProvider for T {
25 fn stream_response(&self, context: &Context) -> LlmResponseStream {
26 let client = self.client().clone();
27 let model = self.model().to_string();
28 let prompt_cache_key = context.prompt_cache_key().map(String::from);
29 let messages = match map_messages(context.messages()) {
30 Ok(messages) => messages,
31 Err(e) => return error_stream(e),
32 };
33 let message_count = messages.len();
34 let tools = if context.tools().is_empty() {
35 None
36 } else {
37 match map_tools(context.tools(), None) {
38 Ok(t) => Some(t),
39 Err(e) => return error_stream(e),
40 }
41 };
42
43 Box::pin(async_stream::stream! {
44 debug!("Starting chat completion stream for model: {model}");
45
46 let req = CreateChatCompletionRequest {
47 model: model.clone(),
48 messages,
49 tools,
50 stream: Some(true),
51 prompt_cache_key,
52 ..Default::default()
53 };
54
55 debug!(
56 "Making request to Ollama API with model: {model} and {message_count} messages"
57 );
58
59 let stream = match client.chat().create_stream(req).await {
60 Ok(stream) => {
61 debug!("Successfully created stream from Ollama API");
62 stream
63 }
64 Err(e) => {
65 error!("Failed to create stream from Ollama API: {:?}", e);
66
67 if let Some(reqwest_err) =
69 e.source().and_then(|s| s.downcast_ref::<reqwest::Error>())
70 {
71 if let Some(url) = reqwest_err.url() {
72 error!("Request URL was: {url}");
73 }
74 if let Some(status) = reqwest_err.status() {
75 error!("HTTP status: {status}");
76 }
77 }
78
79 yield Err(LlmError::ApiRequest(e.to_string()));
80 return;
81 }
82 };
83
84 let stream = stream.map(|result| {
85 result.map_err(|e| LlmError::ApiError(e.to_string()))
86 });
87
88 let mut shared_stream = Box::pin(process_completion_stream(stream));
89 while let Some(result) = shared_stream.next().await {
90 yield result;
91 }
92 })
93 }
94
95 fn context_window(&self) -> Option<u32> {
96 None
97 }
98
99 fn display_name(&self) -> String {
100 let model = self.model();
101 if model.is_empty() { self.provider_name().to_string() } else { format!("{} ({model})", self.provider_name()) }
102 }
103}