use crate::disagg::DisaggregationMode;
use crate::error::DynamoError;
use dynamo_llm::protocols::common::FinishReason;
use dynamo_llm::protocols::common::llm_backend::LLMEngineOutput;
use futures::StreamExt;
use futures::stream::BoxStream;
pub(crate) fn wrap(
stream: BoxStream<'static, Result<LLMEngineOutput, DynamoError>>,
mode: DisaggregationMode,
) -> BoxStream<'static, Result<LLMEngineOutput, DynamoError>> {
let mut terminal_seen = false;
Box::pin(async_stream::stream! {
let mut inner = stream;
while let Some(item) = inner.next().await {
assert!(
!terminal_seen,
"LLMEngine contract violation: item yielded after terminal item \
(a chunk with finish_reason set, or an Err, must be the last item)"
);
match &item {
Ok(chunk) if chunk.finish_reason.is_some() => {
if mode.is_encode()
&& !matches!(
chunk.finish_reason,
Some(FinishReason::Cancelled | FinishReason::Error(_))
)
{
assert!(
matches!(
chunk.encoder_result.as_ref(),
Some(v) if v.is_object()
),
"Encode-mode contract violation: non-cancelled terminal \
chunk must carry encoder_result: Some(Value::Object(_)). \
Use LLMEngineOutput::encode_terminal(map) or \
.with_encoder_result(map) instead of LLMEngineOutput::stop(). \
Got finish_reason={:?}, encoder_result={:?}",
chunk.finish_reason,
chunk.encoder_result,
);
}
terminal_seen = true;
}
Err(_) => terminal_seen = true,
_ => {}
}
yield item;
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::{FinishReason, chunk};
use futures::stream;
fn to_stream(
chunks: Vec<LLMEngineOutput>,
) -> BoxStream<'static, Result<LLMEngineOutput, DynamoError>> {
Box::pin(stream::iter(chunks.into_iter().map(Ok)))
}
fn to_stream_with_err(
chunks: Vec<Result<LLMEngineOutput, DynamoError>>,
) -> BoxStream<'static, Result<LLMEngineOutput, DynamoError>> {
Box::pin(stream::iter(chunks))
}
#[tokio::test]
async fn valid_stream_passes_through() {
let wrapped = wrap(
to_stream(vec![
chunk::token(1),
chunk::token(2),
LLMEngineOutput::length(),
]),
DisaggregationMode::Aggregated,
);
let collected: Vec<_> = wrapped.collect().await;
assert_eq!(collected.len(), 3);
}
#[tokio::test]
async fn valid_terminal_without_usage_passes() {
let wrapped = wrap(
to_stream(vec![chunk::token(1), LLMEngineOutput::cancelled()]),
DisaggregationMode::Aggregated,
);
let collected: Vec<_> = wrapped.collect().await;
assert_eq!(collected.len(), 2);
assert!(matches!(
collected[1].as_ref().unwrap().finish_reason,
Some(FinishReason::Cancelled)
));
}
#[tokio::test]
#[should_panic(expected = "item yielded after terminal item")]
async fn panics_on_chunk_after_terminal() {
let wrapped = wrap(
to_stream(vec![LLMEngineOutput::length(), chunk::token(2)]),
DisaggregationMode::Aggregated,
);
let _collected: Vec<_> = wrapped.collect().await;
}
#[tokio::test]
#[should_panic(expected = "item yielded after terminal item")]
async fn panics_on_chunk_after_err() {
let wrapped = wrap(
to_stream_with_err(vec![
Err(DynamoError::msg("typed failure")),
Ok(chunk::token(1)),
]),
DisaggregationMode::Aggregated,
);
let _collected: Vec<_> = wrapped.collect().await;
}
#[tokio::test]
#[should_panic(expected = "Encode-mode contract violation")]
async fn panics_on_encode_terminal_without_encoder_result() {
let wrapped = wrap(
to_stream(vec![LLMEngineOutput::stop()]),
DisaggregationMode::Encode,
);
let _collected: Vec<_> = wrapped.collect().await;
}
#[tokio::test]
async fn encode_terminal_with_encoder_result_passes() {
let mut map = serde_json::Map::new();
map.insert("uri".into(), serde_json::Value::String("nixl://e/0".into()));
let wrapped = wrap(
to_stream(vec![LLMEngineOutput::encode_terminal(map)]),
DisaggregationMode::Encode,
);
let collected: Vec<_> = wrapped.collect().await;
assert_eq!(collected.len(), 1);
assert!(matches!(
collected[0].as_ref().unwrap().finish_reason,
Some(FinishReason::Stop)
));
}
#[tokio::test]
async fn encode_mode_cancelled_terminal_without_encoder_result_passes() {
let wrapped = wrap(
to_stream(vec![LLMEngineOutput::cancelled()]),
DisaggregationMode::Encode,
);
let collected: Vec<_> = wrapped.collect().await;
assert_eq!(collected.len(), 1);
}
#[tokio::test]
async fn aggregated_terminal_without_encoder_result_passes() {
let wrapped = wrap(
to_stream(vec![LLMEngineOutput::stop()]),
DisaggregationMode::Aggregated,
);
let collected: Vec<_> = wrapped.collect().await;
assert_eq!(collected.len(), 1);
}
}