openai_api_dispatch/queue/nats/
mod.rs1use std::env;
8
9use alloc::sync::Arc;
10use async_nats::{Client, Message, Subscriber};
11use tokio::sync::Mutex;
12use tokio_stream::StreamExt as _;
13
14use crate::{
15 queue::{QueueProducer, QueueWorker, Response, Task, WrappedTask},
16 utils,
17};
18
19#[cfg(test)]
20mod tests;
21
22#[derive(Debug, Clone)]
23pub struct NatsProducer {
30 client: Client,
31 default_model: Arc<Option<String>>,
32 prefix: String,
33}
34
35#[derive(Debug, Clone)]
36pub struct NatsWorker {
41 client: Client,
42 subscriber: Arc<Mutex<Subscriber>>,
43}
44
45fn format_subject(prefix: &str, model: &str) -> String {
46 format!("{prefix}{model}")
47}
48
49impl NatsProducer {
50 pub async fn from_env_or_default() -> anyhow::Result<Self> {
55 let default_model = utils::get_default_model();
56 let prefix =
57 env::var("OPENAI_API_NATS_PREFIX").unwrap_or_else(|_| "openai-api-queue/".into());
58 let url =
59 env::var("OPENAI_API_NATS_URL").unwrap_or_else(|_| "nats://localhost:4222".into());
60 let client = async_nats::ConnectOptions::new()
61 .request_timeout(None)
62 .connect(url)
63 .await?;
64
65 tracing::info!(
66 "nats client connected to `{:?}`",
67 client.server_info().connect_urls
68 );
69
70 Ok(Self {
71 client,
72 default_model,
73 prefix,
74 })
75 }
76
77 fn subject(&self, task: &Task) -> anyhow::Result<String> {
78 let model = task.model(&self.default_model)?;
79
80 Ok(format_subject(&self.prefix, &model))
81 }
82}
83
84impl NatsWorker {
85 pub async fn from_env_or_default() -> anyhow::Result<Self> {
91 let model = utils::get_default_model()
92 .as_ref()
93 .clone()
94 .ok_or_else(|| anyhow::anyhow!("no model provided for the nats worker."))?;
95 let prefix =
96 env::var("OPENAI_API_NATS_PREFIX").unwrap_or_else(|_| "openai-api-queue/".into());
97 let subject = format_subject(&prefix, &model);
98
99 let url =
100 env::var("OPENAI_API_NATS_URL").unwrap_or_else(|_| "nats://localhost:4222".into());
101 let client = async_nats::ConnectOptions::new()
102 .request_timeout(None)
103 .connect(url)
104 .await?;
105
106 let workers_group =
107 env::var("OPENAI_API_NATS_WORKERS_GROUP").unwrap_or_else(|_| "task_workers".into());
108 let subscriber = client
109 .queue_subscribe(subject.clone(), workers_group)
110 .await?;
111 let subscriber = Arc::new(Mutex::new(subscriber));
112
113 tracing::info!(
114 "nats worker connected to `{:?}` with subject `{subject}`",
115 client.server_info().connect_urls
116 );
117
118 Ok(Self { client, subscriber })
119 }
120}
121
122impl QueueProducer for NatsProducer {
123 type Message = Subscriber;
124
125 async fn send_task(&self, task: Task) -> anyhow::Result<Self::Message> {
126 tracing::debug!("nats queue; sending task `{}`", task.id,);
127
128 let subject = self.subject(&task)?;
129
130 tracing::debug!("nats queue; task `{}` subject `{subject}`", task.id,);
131
132 let payload = serde_json::to_vec(&task)?;
133
134 let inbox = self.client.new_inbox();
135 let subscriber = self.client.subscribe(inbox.clone()).await?;
136
137 self.client
138 .publish_with_reply(subject.to_string(), inbox, payload.into())
139 .await?;
140
141 tracing::debug!("nats queue; task `{}` sent", task.id,);
142
143 Ok(subscriber)
144 }
145
146 async fn receive_response(
147 &self,
148 mut message: Self::Message,
149 ) -> anyhow::Result<Option<Response>> {
150 match message.next().await {
151 Some(m) => Ok(Some(serde_json::from_slice(&m.payload)?)),
152 None => Ok(None),
153 }
154 }
155}
156
157impl QueueWorker for NatsWorker {
158 type Message = Message;
159
160 async fn receive_task(&self) -> anyhow::Result<Option<WrappedTask<Self::Message>>> {
161 let message = match self.subscriber.lock().await.next().await {
162 Some(m) => m,
163 None => return Ok(None),
164 };
165 let task = serde_json::from_slice(&message.payload)?;
166
167 Ok(Some(WrappedTask { message, task }))
168 }
169
170 async fn send_response(
171 &self,
172 message: Self::Message,
173 response: Response,
174 ) -> anyhow::Result<()> {
175 let payload = serde_json::to_vec(&response)?;
176 let reply_to = message
177 .reply
178 .ok_or_else(|| anyhow::anyhow!("message reply recipient empty"))?;
179
180 self.client.publish(reply_to, payload.into()).await?;
181
182 Ok(())
183 }
184}