From 7d28a5f0b3066cc3082609ef0310505ed799325c Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk Date: Thu, 22 Oct 2026 06:00:09 +0200 Subject: [PATCH 1/2] Switch to Rust 1.99.1 and satisfy its clippy lints --- .../src/classify_sample_loop.rs | 6 ++--- .../tests/chat_protocol.rs | 5 ++-- .../tests/context_state.rs | 4 +-- .../tests/embedding_models.rs | 4 +-- .../tests/model_introspection.rs | 18 ++++++------- .../tests/multimodal_vision.rs | 3 ++- .../tests/structured_chat_output.rs | 26 +++++++++---------- .../src/parsed_tool_call.rs | 4 +-- .../src/chat_message_parse_outcome.rs | 3 ++- llama-cpp-bindings/src/chat_message_parser.rs | 20 +++++++------- .../mistral3_arrow_args.rs | 2 +- llama-cpp-bindings/src/gguf_context.rs | 19 ++++++-------- .../src/json_schema_to_grammar.rs | 4 +-- .../src/llama_backend_device.rs | 2 +- llama-cpp-bindings/src/llama_batch.rs | 6 ++--- llama-cpp-bindings/src/model/params.rs | 4 +-- .../src/mtmd/mtmd_default_marker.rs | 2 +- .../src/sampled_token_classifier.rs | 8 +++--- llama-cpp-bindings/src/sampling.rs | 5 +++- .../src/tool_call_format/bracketed_args.rs | 9 ++++--- .../src/tool_call_format/json_object.rs | 11 ++++---- .../tool_call_format/key_value_xml_tags.rs | 5 ++-- .../src/tool_call_format/paired_quote_args.rs | 9 ++++--- .../src/tool_call_format/xml_function_tags.rs | 7 ++--- rust-toolchain.toml | 2 +- 25 files changed, 98 insertions(+), 90 deletions(-) diff --git a/llama-cpp-bindings-tests/src/classify_sample_loop.rs b/llama-cpp-bindings-tests/src/classify_sample_loop.rs index e6fedf598..941eed4db 100644 --- a/llama-cpp-bindings-tests/src/classify_sample_loop.rs +++ b/llama-cpp-bindings-tests/src/classify_sample_loop.rs @@ -123,7 +123,7 @@ mod tests { assert_eq!(outcome.observed_tool_call, 1); assert_eq!(outcome.generated_raw, "{"); - assert!(outcome.content_stream.is_empty()); + assert_eq!(outcome.content_stream, ""); } #[test] @@ -149,8 +149,8 @@ mod tests { }); assert_eq!(outcome.observed_undeterminable, 1); - assert!(outcome.content_stream.is_empty()); - assert!(outcome.reasoning_stream.is_empty()); + assert_eq!(outcome.content_stream, ""); + assert_eq!(outcome.reasoning_stream, ""); } #[test] diff --git a/llama-cpp-bindings-tests/tests/chat_protocol.rs b/llama-cpp-bindings-tests/tests/chat_protocol.rs index 6419a2c29..4827b03f6 100644 --- a/llama-cpp-bindings-tests/tests/chat_protocol.rs +++ b/llama-cpp-bindings-tests/tests/chat_protocol.rs @@ -6,6 +6,7 @@ use llama_cpp_bindings::ChatTemplateError; use llama_cpp_bindings::ParseChatMessageError; use llama_cpp_bindings::model::LlamaChatMessage; use llama_cpp_bindings_tests::build_user_prompt_with_media_marker::build_user_prompt_with_media_marker; +use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_test_harness::LlamaFixture; use llama_cpp_test_harness::llama_test; @@ -254,7 +255,7 @@ fn parses_pure_content_response(fixture: &LlamaFixture<'_>) -> Result<()> { let ChatMessageParseOutcome::Recognized(parsed) = outcome else { bail!("expected Recognized for plain content; got Unrecognized"); }; - assert!(parsed.tool_calls.is_empty()); + assert_eq!(parsed.tool_calls, Vec::::new()); assert!(!parsed.is_empty()); assert!(parsed.content.contains("hello world")); @@ -348,7 +349,7 @@ fn parses_empty_input_yields_empty_message(fixture: &LlamaFixture<'_>) -> Result let ChatMessageParseOutcome::Recognized(parsed) = outcome else { bail!("expected Recognized for empty input; got Unrecognized"); }; - assert!(parsed.tool_calls.is_empty()); + assert_eq!(parsed.tool_calls, Vec::::new()); Ok(()) } diff --git a/llama-cpp-bindings-tests/tests/context_state.rs b/llama-cpp-bindings-tests/tests/context_state.rs index 1a0651fe5..2461b825e 100644 --- a/llama-cpp-bindings-tests/tests/context_state.rs +++ b/llama-cpp-bindings-tests/tests/context_state.rs @@ -232,7 +232,7 @@ fn decode_and_get_logits(fixture: &LlamaFixture<'_>) -> Result<()> { assert!(decode_result.is_ok()); let logits = context.get_logits()?; - assert!(!logits.is_empty()); + assert_ne!(logits, Vec::::new()); Ok(()) } @@ -282,7 +282,7 @@ fn token_data_array_has_entries_after_decode(fixture: &LlamaFixture<'_>) -> Resu let token_data_array = context.token_data_array()?; - assert!(!token_data_array.data.is_empty()); + assert_ne!(token_data_array.data, Vec::::new()); Ok(()) } diff --git a/llama-cpp-bindings-tests/tests/embedding_models.rs b/llama-cpp-bindings-tests/tests/embedding_models.rs index 584f82707..44058b205 100644 --- a/llama-cpp-bindings-tests/tests/embedding_models.rs +++ b/llama-cpp-bindings-tests/tests/embedding_models.rs @@ -597,8 +597,8 @@ fn embedding_model_exposes_tool_call_markers(fixture: &LlamaFixture<'_>) -> Resu .tool_call_markers()? .expect("Qwen3 embedding model must expose tool-call markers"); - assert!(!markers.open.is_empty()); - assert!(!markers.close.is_empty()); + assert_ne!(markers.open, ""); + assert_ne!(markers.close, ""); Ok(()) } diff --git a/llama-cpp-bindings-tests/tests/model_introspection.rs b/llama-cpp-bindings-tests/tests/model_introspection.rs index 2561f7eb8..49ac9116f 100644 --- a/llama-cpp-bindings-tests/tests/model_introspection.rs +++ b/llama-cpp-bindings-tests/tests/model_introspection.rs @@ -464,7 +464,7 @@ fn meta_count_returns_positive(fixture: &LlamaFixture<'_>) -> Result<()> { )] fn meta_key_by_index_returns_valid_key(fixture: &LlamaFixture<'_>) -> Result<()> { let key = fixture.model.meta_key_by_index(0)?; - assert!(!key.is_empty()); + assert_ne!(key, ""); Ok(()) } @@ -502,7 +502,7 @@ fn meta_key_by_index_returns_valid_key(fixture: &LlamaFixture<'_>) -> Result<()> )] fn meta_val_str_by_index_returns_valid_value(fixture: &LlamaFixture<'_>) -> Result<()> { let value = fixture.model.meta_val_str_by_index(0)?; - assert!(!value.is_empty()); + assert_ne!(value, ""); Ok(()) } @@ -618,7 +618,7 @@ fn meta_val_str_returns_value_for_known_key(fixture: &LlamaFixture<'_>) -> Resul let model = fixture.model; let first_key = model.meta_key_by_index(0)?; let value = model.meta_val_str(&first_key)?; - assert!(!value.is_empty()); + assert_ne!(value, ""); Ok(()) } @@ -1184,12 +1184,12 @@ fn token_attr_returns_attrs_for_bos(fixture: &LlamaFixture<'_>) -> Result<()> { fn str_to_token_roundtrip(fixture: &LlamaFixture<'_>) -> Result<()> { let model = fixture.model; let tokens = model.str_to_token("hello world", AddBos::Never, ParseSpecialTokens::Always)?; - assert!(!tokens.is_empty()); + assert_ne!(tokens, Vec::::new()); let mut decoder = encoding_rs::UTF_8.new_decoder(); let piece = model.token_to_piece(&SampledToken::Content(tokens[0]), &mut decoder, false, None)?; - assert!(!piece.is_empty()); + assert_ne!(piece, ""); Ok(()) } @@ -1375,7 +1375,7 @@ fn token_to_piece_bytes_returns_bytes_for_known_token(fixture: &LlamaFixture<'_> let tokens = model.str_to_token("hello", AddBos::Never, ParseSpecialTokens::Always)?; let bytes = model.token_to_piece_bytes(tokens[0], 32, false, None)?; - assert!(!bytes.is_empty()); + assert_ne!(bytes, Vec::::new()); Ok(()) } @@ -1563,7 +1563,7 @@ fn token_to_piece_decodes_reasoning_variant(fixture: &LlamaFixture<'_>) -> Resul None, )?; - assert!(!piece.is_empty()); + assert_ne!(piece, ""); Ok(()) } @@ -1607,7 +1607,7 @@ fn token_to_piece_decodes_tool_call_variant(fixture: &LlamaFixture<'_>) -> Resul let piece = model.token_to_piece(&SampledToken::ToolCall(tokens[0]), &mut decoder, true, None)?; - assert!(!piece.is_empty()); + assert_ne!(piece, ""); Ok(()) } @@ -1655,7 +1655,7 @@ fn token_to_piece_decodes_undeterminable_variant(fixture: &LlamaFixture<'_>) -> None, )?; - assert!(!piece.is_empty()); + assert_ne!(piece, ""); Ok(()) } diff --git a/llama-cpp-bindings-tests/tests/multimodal_vision.rs b/llama-cpp-bindings-tests/tests/multimodal_vision.rs index 7c6da117c..e59561f7f 100644 --- a/llama-cpp-bindings-tests/tests/multimodal_vision.rs +++ b/llama-cpp-bindings-tests/tests/multimodal_vision.rs @@ -18,6 +18,7 @@ use llama_cpp_bindings::mtmd::MtmdInputText; use llama_cpp_bindings::mtmd::NonCausalChunkMicroBatchMismatch; use llama_cpp_bindings::mtmd::mtmd_default_marker; use llama_cpp_bindings::sampling::LlamaSampler; +use llama_cpp_bindings::token::LlamaToken; use llama_cpp_bindings_tests::build_user_prompt_with_media_marker::build_user_prompt_with_media_marker; use llama_cpp_bindings_tests::chunk_token_breakdown::ChunkTokenBreakdown; use llama_cpp_bindings_tests::classify_sample_loop::ClassifySampleLoop; @@ -290,7 +291,7 @@ fn text_chunk_returns_text_tokens(fixture: &LlamaFixture<'_>) -> Result<()> { .text_tokens_of(first_chunk.chunk_type()?) .ok_or_else(|| anyhow::anyhow!("the first chunk of a text prompt must carry tokens"))?; - assert!(!tokens.is_empty()); + assert_ne!(tokens, Vec::::new()); Ok(()) } diff --git a/llama-cpp-bindings-tests/tests/structured_chat_output.rs b/llama-cpp-bindings-tests/tests/structured_chat_output.rs index e67347f5c..aff130ad7 100644 --- a/llama-cpp-bindings-tests/tests/structured_chat_output.rs +++ b/llama-cpp-bindings-tests/tests/structured_chat_output.rs @@ -996,7 +996,7 @@ What is 2 + 2? let usage = classifier.usage(); - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert_eq!(outcome.observed_reasoning, 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(usage.reasoning_tokens, 0); @@ -1080,7 +1080,7 @@ What is 2 + 2? let usage = classifier.usage(); let parsed = parse_partial_reasoning_response(model, &outcome.generated_raw)?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert!(usage.reasoning_tokens > 0); assert_eq!(outcome.observed_undeterminable, 0); @@ -1253,7 +1253,7 @@ fn mistral3_classifier_does_not_emit_reasoning_for_thinking_disabled_prompt( let usage = classifier.usage(); - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert_eq!(outcome.observed_reasoning, 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(usage.reasoning_tokens, 0); @@ -1334,7 +1334,7 @@ to the user.[/THINK]Here, provide a self-contained response.[/SYSTEM_PROMPT]\ let usage = classifier.usage(); let parsed = parse_partial_reasoning_response(model, &outcome.generated_raw)?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert!(usage.reasoning_tokens > 0); assert_eq!(outcome.observed_undeterminable, 0); @@ -1343,7 +1343,7 @@ to the user.[/THINK]Here, provide a self-contained response.[/SYSTEM_PROMPT]\ usage.completion_tokens(), outcome.observed_content + outcome.observed_reasoning, ); - assert!(!parsed.reasoning_content.is_empty()); + assert_ne!(parsed.reasoning_content, ""); assert_eq!(outcome.reasoning_stream, parsed.reasoning_content); assert_eq!(outcome.content_stream, parsed.content); @@ -1459,7 +1459,7 @@ fn qwen35_chat_inference_emits_reasoning_when_template_auto_opens( } .run()?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(outcome.observed_tool_call, 0); @@ -1599,7 +1599,7 @@ What is 2 + 2?<|im_end|> let usage = classifier.usage(); - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert_eq!(outcome.observed_reasoning, 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(usage.reasoning_tokens, 0); @@ -1683,7 +1683,7 @@ What is 2 + 2?<|im_end|> let usage = classifier.usage(); let parsed = parse_partial_reasoning_response(model, &outcome.generated_raw)?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert!(usage.reasoning_tokens > 0); assert_eq!(outcome.observed_undeterminable, 0); @@ -2048,14 +2048,14 @@ fn qwen36_chat_inference_emits_reasoning_when_template_auto_opens( } .run()?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert!(outcome.observed_content > 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(outcome.observed_tool_call, 0); let parsed = parse_partial_reasoning_response(model, &outcome.generated_raw)?; - assert!(!parsed.content.is_empty()); + assert_ne!(parsed.content, ""); let usage = classifier.into_usage(); assert_eq!(usage.prompt_tokens, prompt_token_count); @@ -2136,7 +2136,7 @@ What is 2 + 2?<|im_end|> let usage = classifier.usage(); - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert_eq!(outcome.observed_reasoning, 0); assert_eq!(outcome.observed_undeterminable, 0); assert_eq!(usage.reasoning_tokens, 0); @@ -2220,7 +2220,7 @@ What is 2 + 2?<|im_end|> let usage = classifier.usage(); let parsed = parse_partial_reasoning_response(model, &outcome.generated_raw)?; - assert!(!outcome.generated_raw.is_empty()); + assert_ne!(outcome.generated_raw, ""); assert!(outcome.observed_reasoning > 0); assert!(usage.reasoning_tokens > 0); assert_eq!(outcome.observed_undeterminable, 0); @@ -2230,7 +2230,7 @@ What is 2 + 2?<|im_end|> outcome.observed_content + outcome.observed_reasoning, ); - assert!(!parsed.reasoning_content.is_empty()); + assert_ne!(parsed.reasoning_content, ""); assert_eq!(outcome.reasoning_stream, parsed.reasoning_content); assert_eq!(outcome.content_stream, parsed.content); diff --git a/llama-cpp-bindings-types/src/parsed_tool_call.rs b/llama-cpp-bindings-types/src/parsed_tool_call.rs index 27f693707..c98e8adbf 100644 --- a/llama-cpp-bindings-types/src/parsed_tool_call.rs +++ b/llama-cpp-bindings-types/src/parsed_tool_call.rs @@ -46,8 +46,8 @@ mod tests { fn default_is_empty_strings_and_invalid_arguments() { let parsed = ParsedToolCall::default(); - assert!(parsed.id.is_empty()); - assert!(parsed.name.is_empty()); + assert_eq!(parsed.id, ""); + assert_eq!(parsed.name, ""); assert_eq!( parsed.arguments, ToolCallArguments::InvalidJson(String::new()) diff --git a/llama-cpp-bindings/src/chat_message_parse_outcome.rs b/llama-cpp-bindings/src/chat_message_parse_outcome.rs index 2cd064d1a..833fbc596 100644 --- a/llama-cpp-bindings/src/chat_message_parse_outcome.rs +++ b/llama-cpp-bindings/src/chat_message_parse_outcome.rs @@ -11,6 +11,7 @@ pub enum ChatMessageParseOutcome { #[cfg(test)] mod tests { use llama_cpp_bindings_types::ParsedChatMessage; + use llama_cpp_bindings_types::ParsedToolCall; use super::ChatMessageParseOutcome; use crate::raw_chat_message::RawChatMessage; @@ -37,7 +38,7 @@ mod tests { ChatMessageParseOutcome::Recognized(parsed) => { assert_eq!(parsed.content, "content"); assert_eq!(parsed.reasoning_content, "reasoning"); - assert!(parsed.tool_calls.is_empty()); + assert_eq!(parsed.tool_calls, Vec::::new()); saw_recognized = true; } ChatMessageParseOutcome::Unrecognized(raw) => { diff --git a/llama-cpp-bindings/src/chat_message_parser.rs b/llama-cpp-bindings/src/chat_message_parser.rs index 69f2f073d..ddad6491f 100644 --- a/llama-cpp-bindings/src/chat_message_parser.rs +++ b/llama-cpp-bindings/src/chat_message_parser.rs @@ -1640,7 +1640,7 @@ mod tests { let ReasoningSplit { reasoning, content } = split_reasoning_prefix("answerrest", None, Some(""), false); - assert!(reasoning.is_empty()); + assert_eq!(reasoning, ""); assert_eq!(content, "answer"); } @@ -1653,7 +1653,7 @@ mod tests { let ReasoningSplit { reasoning, content } = split_reasoning_prefix("plain answer", Some(&markers), Some(""), false); - assert!(reasoning.is_empty()); + assert_eq!(reasoning, ""); assert_eq!(content, "plain answer"); } @@ -1666,7 +1666,7 @@ mod tests { let ReasoningSplit { reasoning, content } = split_reasoning_prefix("unterminated", Some(&markers), Some(""), false); - assert!(reasoning.is_empty()); + assert_eq!(reasoning, ""); assert_eq!(content, "unterminated"); } @@ -1697,7 +1697,7 @@ mod tests { split_reasoning_prefix("unfinished", Some(&markers), None, true); assert_eq!(reasoning, "unfinished"); - assert!(content.is_empty()); + assert_eq!(content, ""); } #[test] @@ -1729,7 +1729,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "unfinished", Some(&markers), false); assert_eq!(parsed.content, "parsed content"); - assert!(parsed.reasoning_content.is_empty()); + assert_eq!(parsed.reasoning_content, ""); } #[test] @@ -1764,7 +1764,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "complete response", Some(&markers), true); - assert!(parsed.content.is_empty()); + assert_eq!(parsed.content, ""); assert_eq!(parsed.reasoning_content, "parsed reasoning"); } @@ -1779,7 +1779,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "\n\nreasoning", Some(&markers), true); - assert!(parsed.content.is_empty()); + assert_eq!(parsed.content, ""); assert_eq!(parsed.reasoning_content, "\n\nreasoning"); } @@ -1820,7 +1820,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "complete response", Some(&markers), true); - assert!(parsed.content.is_empty()); + assert_eq!(parsed.content, ""); assert_eq!(parsed.reasoning_content, "parsed reasoning"); } @@ -1836,7 +1836,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "unfinished", Some(&markers), true); assert_eq!(parsed.content, "parsed content"); - assert!(parsed.reasoning_content.is_empty()); + assert_eq!(parsed.reasoning_content, ""); } #[test] @@ -1850,7 +1850,7 @@ mod tests { restore_partial_reasoning(&mut parsed, "unfinished", Some(&markers), true); - assert!(parsed.content.is_empty()); + assert_eq!(parsed.content, ""); assert_eq!(parsed.reasoning_content, "unfinished"); } diff --git a/llama-cpp-bindings/src/chat_template_tool_calls/mistral3_arrow_args.rs b/llama-cpp-bindings/src/chat_template_tool_calls/mistral3_arrow_args.rs index 5295a6085..f930eb9ea 100644 --- a/llama-cpp-bindings/src/chat_template_tool_calls/mistral3_arrow_args.rs +++ b/llama-cpp-bindings/src/chat_template_tool_calls/mistral3_arrow_args.rs @@ -41,7 +41,7 @@ mod tests { Mistral3ArrowArgsFormat::detect(template).expect("Mistral 3 template must be detected"); assert_eq!(markers.open, "[TOOL_CALLS]"); - assert!(markers.close.is_empty()); + assert_eq!(markers.close, ""); assert_eq!( markers.args_shape, ToolCallArgsShape::BracketedJson(BracketedJsonShape { diff --git a/llama-cpp-bindings/src/gguf_context.rs b/llama-cpp-bindings/src/gguf_context.rs index 5cb972208..32ec8cd72 100644 --- a/llama-cpp-bindings/src/gguf_context.rs +++ b/llama-cpp-bindings/src/gguf_context.rs @@ -381,16 +381,13 @@ impl GgufContext { file.read_exact_at(&mut bytes, (data_offset + tensor_offset) as u64) .map_err(read_failed)?; - Ok(bytes - .chunks_exact(F32_SIZE_IN_BYTES) - .map(|value_bytes| { - f32::from_ne_bytes([ - value_bytes[0], - value_bytes[1], - value_bytes[2], - value_bytes[3], - ]) - }) + let (value_byte_chunks, _bytes_after_the_last_value) = + bytes.as_chunks::(); + + Ok(value_byte_chunks + .iter() + .copied() + .map(f32::from_ne_bytes) .collect()) } } @@ -712,7 +709,7 @@ mod tests { let index = context.find_key("general.architecture").unwrap(); let value = context.val_str(index).unwrap(); - assert!(!value.is_empty()); + assert_ne!(value, ""); } #[test] diff --git a/llama-cpp-bindings/src/json_schema_to_grammar.rs b/llama-cpp-bindings/src/json_schema_to_grammar.rs index 561e2ba52..22cdc0536 100644 --- a/llama-cpp-bindings/src/json_schema_to_grammar.rs +++ b/llama-cpp-bindings/src/json_schema_to_grammar.rs @@ -116,7 +116,7 @@ mod tests { let schema = r#"{"type": "object", "properties": {"name": {"type": "string"}}}"#; let grammar = json_schema_to_grammar(schema).expect("schema converts to grammar"); - assert!(!grammar.is_empty()); + assert_ne!(grammar, ""); } #[test] @@ -140,7 +140,7 @@ mod tests { let schema = r#"{"type": "string"}"#; let grammar = json_schema_to_grammar(schema).expect("schema converts to grammar"); - assert!(!grammar.is_empty()); + assert_ne!(grammar, ""); } #[test] diff --git a/llama-cpp-bindings/src/llama_backend_device.rs b/llama-cpp-bindings/src/llama_backend_device.rs index 7a168301f..b1108ed17 100644 --- a/llama-cpp-bindings/src/llama_backend_device.rs +++ b/llama-cpp-bindings/src/llama_backend_device.rs @@ -86,6 +86,6 @@ mod tests { let devices = LlamaBackendDevice::list_all(); assert!(!devices.is_empty()); assert_eq!(devices[0].index, 0); - assert!(!devices[0].name.is_empty()); + assert_ne!(devices[0].name, ""); } } diff --git a/llama-cpp-bindings/src/llama_batch.rs b/llama-cpp-bindings/src/llama_batch.rs index bde6482c2..8088bff80 100644 --- a/llama-cpp-bindings/src/llama_batch.rs +++ b/llama-cpp-bindings/src/llama_batch.rs @@ -216,7 +216,7 @@ mod tests { let batch = LlamaBatch::new(BatchCapacity::try_from(16).unwrap(), 1); assert_eq!(batch.n_tokens(), 0); - assert!(batch.initialized_logits.is_empty()); + assert_eq!(batch.initialized_logits, Vec::::new()); } #[test] @@ -230,7 +230,7 @@ mod tests { batch.clear(); assert_eq!(batch.n_tokens(), 0); - assert!(batch.initialized_logits.is_empty()); + assert_eq!(batch.initialized_logits, Vec::::new()); } #[test] @@ -255,7 +255,7 @@ mod tests { batch .add(&SampledToken::Content(LlamaToken::new(1)), 0, &[0], false) .unwrap(); - assert!(batch.initialized_logits.is_empty()); + assert_eq!(batch.initialized_logits, Vec::::new()); batch .add(&SampledToken::Content(LlamaToken::new(2)), 1, &[0], true) diff --git a/llama-cpp-bindings/src/model/params.rs b/llama-cpp-bindings/src/model/params.rs index 83b5d3f8b..21976bedb 100644 --- a/llama-cpp-bindings/src/model/params.rs +++ b/llama-cpp-bindings/src/model/params.rs @@ -517,7 +517,7 @@ mod tests { assert!(!params.vocab_only()); assert_eq!(params.load_mode(), Ok(LlamaLoadMode::Auto)); assert_eq!(params.split_mode(), Ok(LlamaSplitMode::Layer)); - assert!(params.devices().is_empty()); + assert_eq!(params.devices(), Vec::::new()); } #[test] @@ -685,7 +685,7 @@ mod tests { let params = LlamaModelParams::default().with_devices(&[]); assert!(params.is_ok()); - assert!(params.unwrap().devices().is_empty()); + assert_eq!(params.unwrap().devices(), Vec::::new()); } #[test] diff --git a/llama-cpp-bindings/src/mtmd/mtmd_default_marker.rs b/llama-cpp-bindings/src/mtmd/mtmd_default_marker.rs index 215e24abd..facff884a 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_default_marker.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_default_marker.rs @@ -30,7 +30,7 @@ mod tests { #[test] fn returns_non_empty_marker() { let marker = mtmd_default_marker().expect("llama.cpp marker must be valid UTF-8"); - assert!(!marker.is_empty()); + assert_ne!(marker, ""); } #[test] diff --git a/llama-cpp-bindings/src/sampled_token_classifier.rs b/llama-cpp-bindings/src/sampled_token_classifier.rs index 8c9c8c0ca..ed7dcc149 100644 --- a/llama-cpp-bindings/src/sampled_token_classifier.rs +++ b/llama-cpp-bindings/src/sampled_token_classifier.rs @@ -748,7 +748,7 @@ mod tests { push_pending(&mut classifier, 300, "::new()); push_pending(&mut classifier, 301, "_call>"); classifier.try_consume_marker_at_tail(); @@ -782,7 +782,7 @@ mod tests { push_pending(&mut classifier, 300, "::new()); push_pending(&mut classifier, 301, ">"); classifier.try_consume_marker_at_tail(); @@ -1057,7 +1057,7 @@ mod tests { drained(&mut classifier); } let drained = finished(&mut classifier); - assert!(drained.is_empty()); + assert_eq!(drained, Vec::::new()); assert_eq!(classifier.usage().reasoning_tokens, 0); assert_eq!(classifier.usage().content_tokens, 0); @@ -1335,7 +1335,7 @@ mod tests { let outcomes = feed_json_string(&mut classifier, r#"{"name":"f","arguments":{}}"#, 100); - assert!(!outcomes.is_empty()); + assert_ne!(outcomes, Vec::::new()); let sections = outcome_sections(&outcomes); assert!( sections diff --git a/llama-cpp-bindings/src/sampling.rs b/llama-cpp-bindings/src/sampling.rs index d8671e9f6..d035316ce 100644 --- a/llama-cpp-bindings/src/sampling.rs +++ b/llama-cpp-bindings/src/sampling.rs @@ -843,7 +843,10 @@ mod tests { let result = LlamaSampler::sanitize_trigger_patterns(&patterns); assert!(result.is_ok()); - assert!(result.expect("valid trigger patterns").is_empty()); + assert_eq!( + result.expect("valid trigger patterns"), + Vec::::new() + ); } #[test] diff --git a/llama-cpp-bindings/src/tool_call_format/bracketed_args.rs b/llama-cpp-bindings/src/tool_call_format/bracketed_args.rs index d4d95e909..0410fccd9 100644 --- a/llama-cpp-bindings/src/tool_call_format/bracketed_args.rs +++ b/llama-cpp-bindings/src/tool_call_format/bracketed_args.rs @@ -98,6 +98,7 @@ pub fn parse( #[cfg(test)] mod tests { use llama_cpp_bindings_types::BracketedJsonShape; + use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ToolCallArgsShape; use llama_cpp_bindings_types::ToolCallArguments; use llama_cpp_bindings_types::ToolCallMarkers; @@ -223,7 +224,7 @@ mod tests { ) .expect("whitespace-name input must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -237,14 +238,14 @@ mod tests { ) .expect("empty-separator shape must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] fn returns_empty_vec_for_empty_body() { let parsed = parse("", &mistral3_markers(), &mistral3_shape()).expect("empty body must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -255,6 +256,6 @@ mod tests { &mistral3_shape(), ) .expect("body without separator must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } } diff --git a/llama-cpp-bindings/src/tool_call_format/json_object.rs b/llama-cpp-bindings/src/tool_call_format/json_object.rs index 19c206bd9..ed3c28856 100644 --- a/llama-cpp-bindings/src/tool_call_format/json_object.rs +++ b/llama-cpp-bindings/src/tool_call_format/json_object.rs @@ -79,6 +79,7 @@ pub fn parse( #[cfg(test)] mod tests { use llama_cpp_bindings_types::JsonObjectShape; + use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ToolCallArguments; use serde_json::json; @@ -164,13 +165,13 @@ mod tests { #[test] fn rejects_input_without_open_brace() { let parsed = parse("plain content", &qwen3_shape()).expect("must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] fn rejects_array_instead_of_object() { let parsed = parse("[1, 2, 3]", &qwen3_shape()).expect("must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -178,14 +179,14 @@ mod tests { let err = parse(r#"{"name": "f", "arguments": {"a": }"#, &qwen3_shape()).unwrap_err(); let JsonObjectFailure::InvalidJson { message } = err; - assert!(!message.is_empty()); + assert_ne!(message, ""); } #[test] fn returns_empty_when_object_is_not_a_tool_call_shape() { let parsed = parse("{ \"foo\": 1 }", &qwen3_shape()).expect("must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -193,6 +194,6 @@ mod tests { let mut shape = qwen3_shape(); shape.name_field.clear(); let parsed = parse(r#"{"name": "x", "arguments": {}}"#, &shape).expect("must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } } diff --git a/llama-cpp-bindings/src/tool_call_format/key_value_xml_tags.rs b/llama-cpp-bindings/src/tool_call_format/key_value_xml_tags.rs index d1ba7cb81..b645a6a1c 100644 --- a/llama-cpp-bindings/src/tool_call_format/key_value_xml_tags.rs +++ b/llama-cpp-bindings/src/tool_call_format/key_value_xml_tags.rs @@ -164,6 +164,7 @@ pub fn parse( #[cfg(test)] mod tests { use llama_cpp_bindings_types::KeyValueXmlTagsShape; + use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ToolCallArgsShape; use llama_cpp_bindings_types::ToolCallArguments; use llama_cpp_bindings_types::ToolCallMarkers; @@ -317,7 +318,7 @@ mod tests { fn returns_empty_for_body_without_open_marker() { let parsed = parse("plain text", &glm47_markers(), &glm47_shape()).expect("must parse empty"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -326,6 +327,6 @@ mod tests { shape.value_close.clear(); let body = "fkv"; let parsed = parse(body, &glm47_markers(), &shape).expect("must parse empty"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } } diff --git a/llama-cpp-bindings/src/tool_call_format/paired_quote_args.rs b/llama-cpp-bindings/src/tool_call_format/paired_quote_args.rs index d4edea68f..297a444af 100644 --- a/llama-cpp-bindings/src/tool_call_format/paired_quote_args.rs +++ b/llama-cpp-bindings/src/tool_call_format/paired_quote_args.rs @@ -206,6 +206,7 @@ pub fn parse( #[cfg(test)] mod tests { use llama_cpp_bindings_types::PairedQuoteShape; + use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ToolCallArgsShape; use llama_cpp_bindings_types::ToolCallArguments; use llama_cpp_bindings_types::ToolCallMarkers; @@ -352,14 +353,14 @@ mod tests { #[test] fn returns_empty_vec_for_empty_body() { let parsed = parse("", &gemma4_markers(), &gemma4_shape()).expect("empty body must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] fn returns_empty_vec_when_body_lacks_separator() { let parsed = parse("no separator anywhere", &gemma4_markers(), &gemma4_shape()) .expect("body without separator must parse"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -461,7 +462,7 @@ mod tests { ) .expect("empty tool name must yield no calls"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -480,6 +481,6 @@ mod tests { ) .expect("empty separator must yield no calls"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } } diff --git a/llama-cpp-bindings/src/tool_call_format/xml_function_tags.rs b/llama-cpp-bindings/src/tool_call_format/xml_function_tags.rs index 860874b72..b865bba66 100644 --- a/llama-cpp-bindings/src/tool_call_format/xml_function_tags.rs +++ b/llama-cpp-bindings/src/tool_call_format/xml_function_tags.rs @@ -169,6 +169,7 @@ pub fn parse( #[cfg(test)] mod tests { + use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ToolCallArguments; use llama_cpp_bindings_types::XmlTagsShape; use serde_json::json; @@ -312,13 +313,13 @@ mod tests { fn returns_empty_when_body_has_no_function_tag() { let parsed = parse("plain text without function tags", &xml_shape()).expect("must parse empty"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] fn returns_empty_for_empty_body() { let parsed = parse("", &xml_shape()).expect("must parse empty"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] @@ -327,7 +328,7 @@ mod tests { shape.function_close.clear(); let body = "1"; let parsed = parse(body, &shape).expect("must parse empty"); - assert!(parsed.is_empty()); + assert_eq!(parsed, Vec::::new()); } #[test] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 38ab2c6bd..10427b943 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.95.0" +channel = "1.99.1" components = ["clippy", "rustfmt"] From 6c524e304718a025a1fea8f5d65c699c02430210 Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk Date: Thu, 22 Oct 2026 06:00:09 +0200 Subject: [PATCH 2/2] End the classified section at an end-of-generation token in the prompt, so a finished turn without a close marker does not swallow the next generation --- .../tests/structured_chat_output.rs | 29 +++++++++++++++++++ .../src/sampled_token_classifier.rs | 7 +++++ 2 files changed, 36 insertions(+) diff --git a/llama-cpp-bindings-tests/tests/structured_chat_output.rs b/llama-cpp-bindings-tests/tests/structured_chat_output.rs index aff130ad7..7f0b3edad 100644 --- a/llama-cpp-bindings-tests/tests/structured_chat_output.rs +++ b/llama-cpp-bindings-tests/tests/structured_chat_output.rs @@ -1528,6 +1528,35 @@ fn qwen35_shared_reasoning_close_and_tool_call_open_is_one_transition( Ok(()) } +#[llama_test( + model_source = HuggingFace("unsloth/Ministral-3-3B-Reasoning-2512-GGUF", "Ministral-3-3B-Reasoning-2512-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 2048, + n_batch = 512, + n_ubatch = 128, +)] +fn mistral3_tool_call_turn_finished_in_the_prompt_leaves_the_classifier_pending( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let model = fixture.model; + let mut finished_tool_call_turn = model.str_to_token( + "[TOOL_CALLS]read[ARGS]{\"path\":\"marker.txt\"}", + AddBos::Never, + ParseSpecialTokens::Always, + )?; + + finished_tool_call_turn.push(model.token_eos().context("model declares an EOS token")?); + + let mut classifier = model.sampled_token_classifier(BareJsonToolCalls::Detect)?; + + classifier.ingest_prompt_tokens(&finished_tool_call_turn); + + assert_eq!(classifier.current_section(), SampledTokenSection::Pending); + + Ok(()) +} + #[llama_test( model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), n_gpu_layers = 999, diff --git a/llama-cpp-bindings/src/sampled_token_classifier.rs b/llama-cpp-bindings/src/sampled_token_classifier.rs index ed7dcc149..59fa49a75 100644 --- a/llama-cpp-bindings/src/sampled_token_classifier.rs +++ b/llama-cpp-bindings/src/sampled_token_classifier.rs @@ -205,6 +205,13 @@ impl<'model> SampledTokenClassifier<'model> { return; } + if self.model.is_eog_token(&SampledToken::Content(token)) { + self.pending.clear(); + self.section = SampledTokenSection::Pending; + + return; + } + self.pending.push_back(PendingToken { token, decoded: String::new(),