use std::sync::Arc;
use async_trait::async_trait;
use crate::pipeline::traits::LlmMessage;
#[async_trait]
pub trait ContextGraftProvider: Send + Sync + 'static {
async fn graft_block(&self, conv_id: &str, query: &str) -> Vec<LlmMessage>;
}
pub(crate) fn splice_graft_block(messages: &mut Vec<LlmMessage>, block: Vec<LlmMessage>) {
if block.is_empty() {
return;
}
let head_len = messages.iter().take_while(|m| m.role == "system").count();
for (idx, gm) in (head_len..).zip(block) {
messages.insert(idx, gm);
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NullGraftProvider;
#[async_trait]
impl ContextGraftProvider for NullGraftProvider {
async fn graft_block(&self, _conv_id: &str, _query: &str) -> Vec<LlmMessage> {
Vec::new()
}
}
pub fn null_graft_provider() -> Arc<dyn ContextGraftProvider> {
Arc::new(NullGraftProvider)
}
#[cfg(test)]
mod tests {
use super::*;
fn msg(role: &str, content: &str) -> LlmMessage {
LlmMessage {
role: role.into(),
content: content.into(),
tool_call_id: None,
tool_calls: None,
}
}
#[test]
fn splice_after_leading_system_run() {
let mut messages = vec![
msg("system", "identity"),
msg("system", "memory"),
msg("user", "hello"),
msg("assistant", "hi"),
];
splice_graft_block(
&mut messages,
vec![
msg("system", "# Recalled\n\nfact A"),
msg("system", "fact B"),
],
);
assert_eq!(messages[0].content, "identity");
assert_eq!(messages[1].content, "memory");
assert_eq!(messages[2].content, "# Recalled\n\nfact A");
assert_eq!(messages[3].content, "fact B");
assert_eq!(messages[4].content, "hello");
assert_eq!(messages[5].content, "hi");
}
#[test]
fn splice_empty_block_is_noop() {
let mut messages = vec![msg("system", "s"), msg("user", "u")];
splice_graft_block(&mut messages, vec![]);
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].content, "s");
assert_eq!(messages[1].content, "u");
}
#[test]
fn splice_with_no_system_head_inserts_at_front() {
let mut messages = vec![msg("user", "u1"), msg("assistant", "a1")];
splice_graft_block(&mut messages, vec![msg("system", "grafted")]);
assert_eq!(messages[0].content, "grafted");
assert_eq!(messages[1].content, "u1");
}
#[tokio::test]
async fn null_provider_grafts_nothing() {
let p = NullGraftProvider;
assert!(p.graft_block("c1", "q").await.is_empty());
}
#[test]
fn graft_preserves_head_bytes_for_kv_reuse() {
let head = || {
vec![
msg("system", "sys prompt"),
msg("system", "# Relevant Memory"),
]
};
let mut t1 = head();
t1.push(msg("user", "q1"));
splice_graft_block(&mut t1, vec![msg("system", "graft A")]);
let mut t2 = head();
t2.push(msg("user", "q1"));
t2.push(msg("assistant", "a1"));
t2.push(msg("user", "q2"));
splice_graft_block(&mut t2, vec![msg("system", "graft B is different")]);
assert_eq!(t1[0].content, t2[0].content);
assert_eq!(t1[1].content, t2[1].content);
assert_eq!(t1[2].content, "graft A");
assert_eq!(t2[2].content, "graft B is different");
}
}