Skip to content
Open
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: 1 addition & 1 deletion rust/src/chat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ mod tests {
)
.unwrap_err();

expect_test::expect!["tool parser `definitely_missing_tool_parser` is not registered (choose from: deepseek_v3, deepseek_v31, deepseek_v32, deepseek_v4, gemma4, glm45, glm47, granite4, hermes, hy_v3, hy_v4, inkling, internlm, kimi_k2, kimi_k3, llama3_json, llama4_json, minimax_m2, minimax_m3, mistral, phi4_mini_json, qwen3_coder, qwen3_xml, seed_oss)"].assert_eq(&error.to_report_string());
expect_test::expect!["tool parser `definitely_missing_tool_parser` is not registered (choose from: deepseek_v3, deepseek_v31, deepseek_v32, deepseek_v4, gemma4, glm45, glm47, granite4, hermes, hy_v3, hy_v4, inkling, internlm, kimi_k2, kimi_k3, llama3_json, llama4_json, minimax_m2, minimax_m3, mistral, olmo3, phi4_mini_json, qwen3_coder, qwen3_xml, seed_oss)"].assert_eq(&error.to_report_string());
}

#[test]
Expand Down
8 changes: 6 additions & 2 deletions rust/src/chat/src/parser/tool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ pub use vllm_parser::tool::{
DeepSeekV3ToolParser, DeepSeekV4ToolParser, DeepSeekV31ToolParser, DeepSeekV32ToolParser,
Glm45MoeToolParser, Glm47MoeToolParser, Granite4ToolParser, HermesToolParser,
Internlm2ToolParser, KimiK2ToolParser, Llama3JsonToolParser, MinimaxM2ToolParser,
MinimaxM3ToolParser, MistralToolParser, Phi4MiniJsonToolParser, Qwen3CoderToolParser,
Qwen3XmlToolParser, SeedOssToolParser, ToolParser, ToolParserError,
MinimaxM3ToolParser, MistralToolParser, Olmo3ToolParser, Phi4MiniJsonToolParser,
Qwen3CoderToolParser, Qwen3XmlToolParser, SeedOssToolParser, ToolParser, ToolParserError,
};

use crate::parser::ParserFactory;
Expand Down Expand Up @@ -40,6 +40,7 @@ pub mod names {
pub const MINIMAX_M2: &str = "minimax_m2";
pub const MINIMAX_M3: &str = "minimax_m3";
pub const MISTRAL: &str = "mistral";
pub const OLMO3: &str = "olmo3";
pub const PHI4_MINI_JSON: &str = "phi4_mini_json";
pub const QWEN3_CODER: &str = "qwen3_coder";
pub const QWEN3_XML: &str = "qwen3_xml";
Expand Down Expand Up @@ -87,12 +88,15 @@ impl ToolParserFactory {
.register_parser::<MinimaxM2ToolParser>(names::MINIMAX_M2)
.register_parser::<MinimaxM3ToolParser>(names::MINIMAX_M3)
.register_parser::<MistralToolParser>(names::MISTRAL)
.register_parser::<Olmo3ToolParser>(names::OLMO3)
.register_parser::<Phi4MiniJsonToolParser>(names::PHI4_MINI_JSON)
.register_parser::<Qwen3XmlToolParser>(names::QWEN3_XML)
.register_parser::<Qwen3CoderToolParser>(names::QWEN3_CODER)
.register_parser::<SeedOssToolParser>(names::SEED_OSS);

factory
.register_pattern("olmo-3", names::OLMO3)
.register_pattern("olmo3", names::OLMO3)
.register_pattern("mistral-", names::MISTRAL)
.register_pattern("mixtral-", names::MISTRAL)
.register_pattern("qwen3-coder", names::QWEN3_CODER)
Expand Down
4 changes: 4 additions & 0 deletions rust/src/chat/src/parser/tool/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ fn factory_new_resolves_default_patterns() {
factory.resolve_name_for_model("ByteDance-Seed/Seed-OSS-36B-Instruct"),
Some(names::SEED_OSS)
);
assert_eq!(
factory.resolve_name_for_model("allenai/Olmo-3-7B-Instruct"),
Some(names::OLMO3)
);

// InternLM2 positive: both dashed and underscored versioned names route.
assert_eq!(
Expand Down
79 changes: 79 additions & 0 deletions rust/src/chat/tests/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,19 @@ impl RoundtripCase {
}
}

/// Olmo 3 newline-separated Python-style function calls.
fn olmo3() -> Self {
Self {
model_id: "allenai/Olmo-3-7B-Instruct",
assistant_stop_suffix: "<|endoftext|>",
tool_call_parser: ParserSelection::Auto,
reasoning_parser: ParserSelection::None,
thinking_behavior: ThinkingBehavior::Always { value: false },
json_fmt: compact_json_fmt(),
sort_json_keys: false,
}
}

/// DeepSeek V4 DSML tool-call format.
fn deepseek_v4() -> Self {
Self {
Expand Down Expand Up @@ -384,6 +397,7 @@ roundtrip_tests! {
qwen35 => [reasoning_and_content, tool_call_mix],
minimax_m25 => [reasoning_and_content, tool_call_mix],
minimax_m3 => [reasoning_and_content, tool_call_mix],
olmo3 => [tool_calls_only],
deepseek_v4 => [reasoning_and_content, tool_call_mix],
deepseek_v32 => [tool_call_mix],
glm45 => [reasoning_and_content, tool_call_mix],
Expand Down Expand Up @@ -539,6 +553,71 @@ async fn run_roundtrip_tool_call_mix(
Ok(())
}

/// Run the fixed content+multiple-tools fixture for models without reasoning.
async fn run_roundtrip_tool_calls_only(
case: &RoundtripCase,
backends: &vllm_chat::LoadedModelBackends,
) -> Result<()> {
let request = roundtrip_request(
"roundtrip-tools-only",
vec![ChatMessage::text(
ChatRole::User,
"Check Shanghai weather and add 1.0 plus 2.",
)],
test_tools(),
Some(false),
case.thinking_behavior,
);
let expected_text = "I will call the tools.";

let result = run_roundtrip(
case,
backends,
&request,
AssistantMessage {
content: vec![
AssistantContentBlock::Text {
text: expected_text.to_string(),
},
AssistantContentBlock::ToolCall(AssistantToolCall {
id: "functions.get_weather:0".to_string(),
name: "get_weather".to_string(),
arguments: r#"{"location":"Shanghai"}"#.to_string(),
}),
AssistantContentBlock::ToolCall(AssistantToolCall {
id: "functions.add:1".to_string(),
name: "add".to_string(),
arguments: r#"{"y":1.0,"x":2,"items":["left","right"]}"#.to_string(),
}),
],
},
)
.await?;

assert!(result.parsed_message.reasoning().is_none());
assert_eq!(result.parsed_message.text().trim(), expected_text);

let tool_calls = result.parsed_message.tool_calls().collect::<Vec<_>>();
assert_eq!(tool_calls.len(), 2);
assert_eq!(tool_calls[0].name, "get_weather");
assert_eq!(
tool_calls[0].arguments,
expected_arguments(case, r#"{"location": "Shanghai"}"#)?,
);
assert_eq!(tool_calls[1].name, "add");
assert_eq!(
tool_calls[1].arguments,
expected_arguments(case, r#"{"y": 1.0, "x": 2, "items": ["left", "right"]}"#)?,
);

assert_eq!(
result.rerendered_closed_completion,
result.closed_completion
);

Ok(())
}

/// Compact JSON argument formatting used by JSON-native parsers/renderers.
fn compact_json_fmt() -> JsonFmt {
JsonFmt::new()
Expand Down
2 changes: 2 additions & 0 deletions rust/src/parser/src/tool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) mod json;
mod kimi_k2;
mod minimax_m2;
mod minimax_m3;
mod olmo3;
mod parameters;
mod qwen_coder;
mod seed_oss;
Expand All @@ -32,6 +33,7 @@ pub use json::{
pub use kimi_k2::KimiK2ToolParser;
pub use minimax_m2::MinimaxM2ToolParser;
pub use minimax_m3::MinimaxM3ToolParser;
pub use olmo3::Olmo3ToolParser;
pub use qwen_coder::Qwen3CoderToolParser;
pub use seed_oss::SeedOssToolParser;
use serde::{Deserialize, Serialize};
Expand Down
Loading
Loading