headless_lms_chatbot/azure_chatbot/azure/
transport.rs1use std::pin::Pin;
4
5use bytes::Bytes;
6use futures::StreamExt;
7use futures::stream::BoxStream;
8use headless_lms_base::config::ApplicationConfiguration;
9use tokio::io::AsyncBufReadExt;
10use tokio_stream::wrappers::LinesStream;
11use tokio_util::io::StreamReader;
12use tracing::trace;
13
14use super::protocol::LLMRequest;
15use crate::chatbot_error::ChatbotResult;
16use crate::llm_utils::make_streaming_llm_request;
17use crate::prelude::*;
18
19pub(crate) type ResponseLinesStream<'a> =
21 Pin<Box<LinesStream<StreamReader<BoxStream<'a, Result<Bytes, std::io::Error>>, Bytes>>>>;
22pub(crate) enum ResponseStreamType<'a> {
23 ToolCall(ResponseLinesStream<'a>),
24 TextResponse(ResponseLinesStream<'a>),
25}
26
27pub(crate) fn lines_from_byte_stream(
30 stream: BoxStream<'_, Result<Bytes, std::io::Error>>,
31) -> ResponseLinesStream<'_> {
32 Box::pin(LinesStream::new(StreamReader::new(stream).lines()))
33}
34
35pub(crate) async fn make_request_and_create_stream<'a>(
37 chat_request: &LLMRequest,
38 app_config: &ApplicationConfiguration,
39) -> ChatbotResult<ResponseLinesStream<'a>> {
40 let response = make_streaming_llm_request(chat_request, app_config).await?;
41
42 trace!("Receiving chat response with {:?}", response.version());
43
44 let stream = tokio_stream::StreamExt::timeout(response.bytes_stream(), STREAM_IDLE_TIMEOUT)
47 .map(|chunk| match chunk {
48 Ok(bytes) => bytes.map_err(std::io::Error::other),
49 Err(_) => Err(std::io::Error::new(
50 std::io::ErrorKind::TimedOut,
51 format!(
52 "The LLM stream sent nothing for {} seconds",
53 STREAM_IDLE_TIMEOUT.as_secs()
54 ),
55 )),
56 })
57 .boxed();
58
59 Ok(lines_from_byte_stream(stream))
60}