Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/tinyinference-llm/src/providers/openai/mod_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,28 @@ fn merge_system_preserves_user_image_blocks() {
}
}

#[test]
fn folded_system_messages_keep_their_json_blocks_as_sent_unfolded() {
// A system message's JSON block is serialized on the pass-through path;
// hoisting or merging it into the user turn must not drop it, and its
// blocks join exactly as the pass-through path joins them.
use crate::message::SystemMessage;
let structured = Message::System(SystemMessage {
content: vec![
ContentBlock::Text("Context: ".to_string()),
ContentBlock::Json(serde_json::json!({"tenant": "acme"})),
],
..Default::default()
});
let sent_as_is = r#"Context: {"tenant":"acme"}"#;

let hoisted = single_system_message(&[Message::user("hi"), structured.clone()]);
assert_eq!(hoisted[0].text(), sent_as_is);

let merged = merge_system_into_user(&[structured, Message::user("hi")]);
assert_eq!(merged[0].text(), format!("{sent_as_is}\n\nhi"));
}

#[test]
fn merge_system_is_noop_without_system_messages() {
let input = vec![Message::user("just user")];
Expand Down
13 changes: 8 additions & 5 deletions crates/tinyinference-llm/src/providers/openai/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,20 @@ pub(super) fn auth_headers(auth: &AuthStyle, api_key: &str) -> Vec<(String, Stri
}
}

/// Concatenates the text of a message's content blocks (non-text blocks — images,
/// json, thinking — are ignored). Used to gather system-prompt text for merging.
/// A system message's content as the pass-through path sends it
/// (`translate_text_content`): text verbatim, JSON serialized, reasoning
/// dropped, blocks joined with nothing between them. Folding with anything
/// else would make a hoisted or merged prompt differ from the same prompt sent
/// as-is, and would silently drop JSON blocks.
fn content_text(content: &[ContentBlock]) -> String {
content
.iter()
.filter_map(|block| match block {
ContentBlock::Text(text) => Some(text.as_str()),
ContentBlock::Text(text) => Some(text.clone()),
ContentBlock::Json(value) => Some(value.to_string()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
.collect()
}

/// Folds all `system` messages into the first `user` message and drops the
Expand Down
Loading