Skip to content
Draft
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
2 changes: 2 additions & 0 deletions tests/support_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ mod cleanup_tests;
mod test_channel_tests;
#[path = "support_unit_tests/trace_llm_contract_tests.rs"]
mod trace_llm_contract_tests;
#[path = "support_unit_tests/trace_llm_test_assertions.rs"]
mod trace_llm_test_assertions;
#[path = "support_unit_tests/trace_llm_test_fixtures.rs"]
mod trace_llm_test_fixtures;
#[path = "support_unit_tests/trace_llm_tests.rs"]
Expand Down
92 changes: 92 additions & 0 deletions tests/support_unit_tests/trace_llm_test_assertions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! Shared parameter objects and assertion helpers for the trace LLM tests.

use ironclaw::llm::{ChatMessage, Role, ToolCall};

#[derive(Copy, Clone, Debug)]
pub(crate) struct LlmCounterSnapshot {
pub(crate) calls: usize,
pub(crate) input_tokens: u32,
pub(crate) output_tokens: u32,
}

#[derive(Copy, Clone, Debug)]
pub(crate) struct LlmCounterMinima {
pub(crate) calls: usize,
pub(crate) input_tokens: u32,
pub(crate) output_tokens: u32,
}

#[derive(Copy, Clone, Debug)]
pub(crate) struct ExpectedToolCall<'a> {
pub(crate) name: &'a str,
pub(crate) id: &'a str,
}

#[derive(Copy, Clone, Debug)]
pub(crate) struct CapturedRequestsExpectation<'a> {
pub(crate) batches: usize,
pub(crate) last_user_contains: &'a str,
pub(crate) min_msgs_per_batch: usize,
}

#[track_caller]
pub(crate) fn assert_msg(role: Role, msg: &ChatMessage, contains: &str) {
assert_eq!(msg.role, role);
assert!(
msg.content.contains(contains),
"expected message content {:?} to contain {:?}",
msg.content,
contains
);
}

#[track_caller]
pub(crate) fn assert_captured_requests_shape(
captured: &[Vec<ChatMessage>],
expected: CapturedRequestsExpectation<'_>,
) {
assert_eq!(captured.len(), expected.batches);
assert!(
captured
.iter()
.all(|batch| batch.len() >= expected.min_msgs_per_batch),
"expected every captured batch to contain at least {} messages",
expected.min_msgs_per_batch
);
let last_batch = captured
.last()
.expect("captured requests should contain at least one batch");
let last_message = last_batch
.last()
.expect("captured request batch should contain at least one message");
assert_msg(Role::User, last_message, expected.last_user_contains);
}

#[track_caller]
pub(crate) fn assert_llm_counters(actual: LlmCounterSnapshot, min: LlmCounterMinima) {
assert!(
actual.calls >= min.calls,
"expected at least {} calls, got {}",
min.calls,
actual.calls
);
assert!(
actual.input_tokens >= min.input_tokens,
"expected at least {} input tokens, got {}",
min.input_tokens,
actual.input_tokens
);
assert!(
actual.output_tokens >= min.output_tokens,
"expected at least {} output tokens, got {}",
min.output_tokens,
actual.output_tokens
);
}

#[track_caller]
pub(crate) fn assert_tool_call(call: &ToolCall, expected: ExpectedToolCall<'_>) {
assert_eq!(call.name, expected.name);
assert_eq!(call.id, expected.id);
assert_eq!(call.arguments, serde_json::json!({"key": "value"}));
}
35 changes: 16 additions & 19 deletions tests/support_unit_tests/trace_llm_test_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ use ironclaw::llm::{ChatMessage, CompletionRequest, ToolCompletionRequest};
use crate::support::trace_provider::TraceLlm;
use crate::support::trace_types::LlmTrace;

#[derive(Copy, Clone, Debug)]
pub struct TextStepSpec<'a> {
pub content: &'a str,
pub input_tokens: u32,
pub output_tokens: u32,
}

/// Builds a text-response trace step.
///
/// `content`, `input_tokens`, and `output_tokens` populate the response; the
/// returned [`TraceStep`] has no request hint or expected tool results.
pub fn text_step(content: &str, input_tokens: u32, output_tokens: u32) -> TraceStep {
/// The returned [`TraceStep`] has no request hint or expected tool results.
pub fn text_step(spec: TextStepSpec<'_>) -> TraceStep {
TraceStep {
request_hint: None,
response: TraceResponse::Text {
content: content.to_string(),
input_tokens,
output_tokens,
content: spec.content.to_string(),
input_tokens: spec.input_tokens,
output_tokens: spec.output_tokens,
},
expected_tool_results: Vec::new(),
}
Expand Down Expand Up @@ -67,18 +73,9 @@ pub fn make_completion_request(user_msg: &str) -> CompletionRequest {

/// Builds a [`TraceLlm`] backed by a single text-response step.
///
/// `user_msg` seeds the trace turn, while `content`, `input_tokens`, and
/// `output_tokens` configure the returned provider's only replayable response.
pub fn single_text_step_llm(
user_msg: &str,
content: &str,
input_tokens: u32,
output_tokens: u32,
) -> TraceLlm {
let trace = LlmTrace::single_turn(
"test-model",
user_msg,
vec![text_step(content, input_tokens, output_tokens)],
);
/// `user_msg` seeds the trace turn, while `spec` configures the returned
/// provider's only replayable response.
pub fn single_text_step_llm(user_msg: &str, spec: TextStepSpec<'_>) -> TraceLlm {
let trace = LlmTrace::single_turn("test-model", user_msg, vec![text_step(spec)]);
TraceLlm::from_trace(trace)
}
Loading
Loading