Skip to content

Commit da50450

Browse files
committed
some refactoring and docs
1 parent 8bd9088 commit da50450

File tree

6 files changed

+112
-94
lines changed

6 files changed

+112
-94
lines changed

moly-kit/src/clients.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
// TODO: Maybe `json` feature flag can be avoided by using Makepad's microserde.
22
#[cfg(feature = "json")]
33
pub mod moly;
4+
pub mod multi;
5+
6+
#[cfg(feature = "json")]
7+
pub use moly::*;
8+
pub use multi::*;

moly-kit/src/clients/moly.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ use crate::{protocol::*, utils::sse::rsplit_once_terminator};
1212

1313
/// A model from the models endpoint.
1414
#[derive(Clone, Debug, Deserialize, PartialEq)]
15-
pub struct Model {
15+
struct Model {
1616
id: String,
1717
}
1818

1919
/// Response from the models endpoint.
2020
#[derive(Clone, Debug, Deserialize, PartialEq)]
21-
pub struct Models {
21+
struct Models {
2222
pub data: Vec<Model>,
2323
}
2424

@@ -32,15 +32,15 @@ pub struct Models {
3232
/// And SiliconFlow may set `content` to a `null` value, that's why the custom deserializer
3333
/// is needed.
3434
#[derive(Clone, Debug, Deserialize, PartialEq)]
35-
pub struct IncomingMessage {
35+
struct IncomingMessage {
3636
#[serde(default)]
3737
#[serde(deserialize_with = "deserialize_null_default")]
3838
pub content: String,
3939
}
4040

4141
/// A message being sent to the completions endpoint.
4242
#[derive(Clone, Debug, Serialize)]
43-
pub struct OutcomingMessage {
43+
struct OutcomingMessage {
4444
pub content: String,
4545
pub role: Role,
4646
}
@@ -65,7 +65,7 @@ impl TryFrom<Message> for OutcomingMessage {
6565

6666
/// Role of a message that is part of the conversation context.
6767
#[derive(Clone, Debug, Serialize, Deserialize)]
68-
pub enum Role {
68+
enum Role {
6969
/// OpenAI o1 models seems to expect `developer` instead of `system` according
7070
/// to the documentation. But it also seems like `system` is converted to `developer`
7171
/// internally.
@@ -78,13 +78,13 @@ pub enum Role {
7878
}
7979

8080
#[derive(Clone, Debug, Deserialize)]
81-
pub struct Choice {
81+
struct Choice {
8282
pub delta: IncomingMessage,
8383
}
8484

8585
/// Response from the completions endpoint.
8686
#[derive(Clone, Debug, Deserialize)]
87-
pub struct Completation {
87+
struct Completation {
8888
pub choices: Vec<Choice>,
8989
}
9090

@@ -94,6 +94,7 @@ struct MolyClientInner {
9494
headers: HeaderMap,
9595
}
9696

97+
/// A client capable of interacting with Moly Server and other OpenAI-compatible APIs.
9798
#[derive(Debug)]
9899
pub struct MolyClient(Arc<Mutex<MolyClientInner>>);
99100

@@ -110,6 +111,7 @@ impl From<MolyClientInner> for MolyClient {
110111
}
111112

112113
impl MolyClient {
114+
/// Creates a new client with the given OpenAI-compatible API URL.
113115
pub fn new(url: String) -> Self {
114116
let mut headers = HeaderMap::new();
115117
headers.insert("Content-Type", "application/json".parse().unwrap());

moly-kit/src/clients/multi.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::protocol::*;
2+
pub use crate::utils::asynchronous::{moly_future, moly_stream, MolyFuture, MolyStream};
3+
use std::sync::{Arc, Mutex};
4+
5+
/// A client that can be composed from multiple subclients to interact with all of them as one.
6+
#[derive(Clone)]
7+
pub struct MultiClient {
8+
clients_with_bots: Arc<Mutex<Vec<(Box<dyn BotClient>, Vec<Bot>)>>>,
9+
}
10+
11+
impl MultiClient {
12+
pub fn new() -> Self {
13+
MultiClient {
14+
clients_with_bots: Arc::new(Mutex::new(Vec::new())),
15+
}
16+
}
17+
18+
pub fn add_client(&mut self, client: Box<dyn BotClient>) {
19+
self.clients_with_bots
20+
.lock()
21+
.unwrap()
22+
.push((client, Vec::new()));
23+
}
24+
}
25+
26+
impl BotClient for MultiClient {
27+
fn send_stream(
28+
&mut self,
29+
bot: &BotId,
30+
messages: &[Message],
31+
) -> MolyStream<'static, Result<String, ()>> {
32+
let mut client = self
33+
.clients_with_bots
34+
.lock()
35+
.unwrap()
36+
.iter()
37+
.find_map(|(client, bots)| {
38+
if bots.iter().any(|b| b.id == *bot) {
39+
Some(client.clone())
40+
} else {
41+
None
42+
}
43+
})
44+
.expect("no client for bot");
45+
46+
client.send_stream(&bot, messages)
47+
}
48+
49+
fn clone_box(&self) -> Box<dyn BotClient> {
50+
Box::new(self.clone())
51+
}
52+
53+
fn bots(&self) -> MolyFuture<'static, Result<Vec<Bot>, ()>> {
54+
let clients_with_bots = self.clients_with_bots.clone();
55+
56+
let future = async move {
57+
let clients = clients_with_bots
58+
.lock()
59+
.unwrap()
60+
.iter()
61+
.map(|(client, _)| client.clone())
62+
.collect::<Vec<_>>();
63+
64+
let bot_futures = clients.iter().map(|client| client.bots());
65+
let results = futures::future::join_all(bot_futures).await;
66+
67+
let mut zipped_bots = Vec::new();
68+
let mut flat_bots = Vec::new();
69+
70+
for result in results {
71+
// TODO: Let's ignore any errored sub-client for now.
72+
let client_bots = result.unwrap_or_default();
73+
zipped_bots.push(client_bots.clone());
74+
flat_bots.extend(client_bots);
75+
}
76+
77+
*clients_with_bots.lock().unwrap() = clients
78+
.into_iter()
79+
.zip(zipped_bots.iter().cloned())
80+
.collect();
81+
82+
Ok(flat_bots)
83+
};
84+
85+
moly_future(future)
86+
}
87+
}

moly-kit/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ pub mod protocol;
5858
pub mod utils;
5959
pub mod widgets;
6060

61+
pub use clients::*;
6162
pub use protocol::*;
6263
pub use widgets::*;

moly-kit/src/protocol.rs

+8-84
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub struct Message {
7878
pub is_writing: bool,
7979
}
8080

81-
/// An interface to talk to bots.
81+
/// A standard interface to fetch bots information and send messages to them.
8282
///
8383
/// Warning: Expect this to be cloned to avoid borrow checking issues with
8484
/// makepad's widgets. Also, it may be cloned inside async contexts. So keep this
@@ -144,6 +144,13 @@ struct InnerBotRepo {
144144
bots: Vec<Bot>,
145145
}
146146

147+
/// A sharable wrapper around a [BotClient] that holds loadeed bots and provides
148+
/// synchronous APIs to access them, mainly from the UI.
149+
///
150+
/// Passed down through widgets from this crate.
151+
///
152+
/// Separate chat widgets can share the same [BotRepo] to avoid loading the same
153+
/// bots multiple times.
147154
pub struct BotRepo(Arc<Mutex<InnerBotRepo>>);
148155

149156
impl Clone for BotRepo {
@@ -197,86 +204,3 @@ impl<T: BotClient + 'static> From<T> for BotRepo {
197204
})))
198205
}
199206
}
200-
201-
#[derive(Clone)]
202-
pub struct MultiBotClient {
203-
clients_with_bots: Arc<Mutex<Vec<(Box<dyn BotClient>, Vec<Bot>)>>>,
204-
}
205-
206-
impl MultiBotClient {
207-
pub fn new() -> Self {
208-
MultiBotClient {
209-
clients_with_bots: Arc::new(Mutex::new(Vec::new())),
210-
}
211-
}
212-
213-
pub fn add_client(&mut self, client: Box<dyn BotClient>) {
214-
self.clients_with_bots
215-
.lock()
216-
.unwrap()
217-
.push((client, Vec::new()));
218-
}
219-
}
220-
221-
impl BotClient for MultiBotClient {
222-
fn send_stream(
223-
&mut self,
224-
bot: &BotId,
225-
messages: &[Message],
226-
) -> MolyStream<'static, Result<String, ()>> {
227-
let mut client = self
228-
.clients_with_bots
229-
.lock()
230-
.unwrap()
231-
.iter()
232-
.find_map(|(client, bots)| {
233-
if bots.iter().any(|b| b.id == *bot) {
234-
Some(client.clone())
235-
} else {
236-
None
237-
}
238-
})
239-
.expect("no client for bot");
240-
241-
client.send_stream(&bot, messages)
242-
}
243-
244-
fn clone_box(&self) -> Box<dyn BotClient> {
245-
Box::new(self.clone())
246-
}
247-
248-
fn bots(&self) -> MolyFuture<'static, Result<Vec<Bot>, ()>> {
249-
let clients_with_bots = self.clients_with_bots.clone();
250-
251-
let future = async move {
252-
let clients = clients_with_bots
253-
.lock()
254-
.unwrap()
255-
.iter()
256-
.map(|(client, _)| client.clone())
257-
.collect::<Vec<_>>();
258-
259-
let bot_futures = clients.iter().map(|client| client.bots());
260-
let results = futures::future::join_all(bot_futures).await;
261-
262-
let mut zipped_bots = Vec::new();
263-
let mut flat_bots = Vec::new();
264-
265-
for result in results {
266-
// TODO: Let's ignore any errored sub-client for now.
267-
let client_bots = result.unwrap_or_default();
268-
zipped_bots.push(client_bots.clone());
269-
flat_bots.extend(client_bots);
270-
}
271-
272-
*clients_with_bots.lock().unwrap() = clients
273-
.into_iter()
274-
.zip(zipped_bots.iter().cloned())
275-
.collect();
276-
277-
Ok(flat_bots)
278-
};
279-
280-
moly_future(future)
281-
}
282-
}

moly-mini/src/demo_chat.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use makepad_widgets::*;
2-
use moly_kit::clients::moly::*;
32
use moly_kit::utils::asynchronous::spawn;
4-
use moly_kit::{protocol::*, ChatTask, ChatWidgetExt};
3+
use moly_kit::*;
54

65
use crate::bot_selector::BotSelectorWidgetExt;
76

@@ -73,7 +72,7 @@ impl Widget for DemoChat {
7372
let mut openai = MolyClient::new(openai_url.into());
7473
openai.set_key(OPEN_AI_KEY.unwrap_or(""));
7574

76-
let mut client = MultiBotClient::new();
75+
let mut client = MultiClient::new();
7776
client.add_client(Box::new(moly));
7877
client.add_client(Box::new(ollama));
7978
client.add_client(Box::new(openai));

0 commit comments

Comments
 (0)