use async_trait::async_trait;
use tokio::sync::mpsc;
use crate::{
CompletionError, CompletionModel, CompletionRequest, FinalResponse, ModelStream, StreamChunk,
ToolCall, Usage,
};
struct StreamRound {
request: CompletionRequest,
chunk_tx: mpsc::UnboundedSender<Result<StreamChunk, CompletionError>>,
}
#[derive(Clone)]
pub struct MockCompletionModel {
round_tx: mpsc::UnboundedSender<StreamRound>,
}
#[async_trait]
impl CompletionModel for MockCompletionModel {
async fn stream(&self, request: CompletionRequest) -> Result<ModelStream, CompletionError> {
let (chunk_tx, mut chunk_rx) = mpsc::unbounded_channel();
let _ = self.round_tx.send(StreamRound { request, chunk_tx });
let stream = async_stream::stream! {
while let Some(item) = chunk_rx.recv().await {
yield item;
}
};
Ok(Box::pin(stream))
}
}
pub struct MockModelController {
round_rx: mpsc::UnboundedReceiver<StreamRound>,
current_tx: Option<mpsc::UnboundedSender<Result<StreamChunk, CompletionError>>>,
}
impl MockModelController {
pub async fn next_request(&mut self) -> CompletionRequest {
let round = self.round_rx.recv().await.expect("model dropped");
self.current_tx = Some(round.chunk_tx);
round.request
}
pub fn try_next_request(&mut self) -> Option<CompletionRequest> {
match self.round_rx.try_recv() {
Ok(round) => {
self.current_tx = Some(round.chunk_tx);
Some(round.request)
}
Err(_) => None,
}
}
pub fn send_chunk(&self, chunk: StreamChunk) {
self.tx().send(Ok(chunk)).ok();
}
pub fn send_text(&self, text: &str) {
self.send_chunk(StreamChunk::Text(text.to_owned()));
}
pub fn send_tool_call(&self, id: &str, name: &str, args: serde_json::Value) {
self.send_chunk(StreamChunk::ToolCall(ToolCall::new(id, name, args)));
}
pub fn finish(&mut self) {
self.finish_with_usage(None);
}
pub fn finish_with_usage(&mut self, usage: Option<Usage>) {
self.send_chunk(StreamChunk::Final(FinalResponse { usage }));
self.current_tx.take(); }
pub fn drop_stream(&mut self) {
self.current_tx.take();
}
pub fn send_error(&self, err: CompletionError) {
self.tx().send(Err(err)).ok();
}
fn tx(&self) -> &mpsc::UnboundedSender<Result<StreamChunk, CompletionError>> {
self.current_tx.as_ref().expect("call next_request() first")
}
}
pub fn mock_model() -> (MockCompletionModel, MockModelController) {
let (round_tx, round_rx) = mpsc::unbounded_channel();
(
MockCompletionModel { round_tx },
MockModelController {
round_rx,
current_tx: None,
},
)
}