Repository navigation
Connorli/fix func call parsing #1045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -57,16 +57,15 @@ impl KimiK2Parser { | |||||
| reason = "regex patterns are compile-time string literals" | ||||||
| )] | ||||||
| pub fn new() -> Self { | ||||||
| // Pattern for complete tool calls | ||||||
| let tool_call_pattern = r"<\|tool_call_begin\|>\s*(?P<tool_call_id>[\w\.]+:\d+)\s*<\|tool_call_argument_begin\|>\s*(?P<function_arguments>\{.*?\})\s*<\|tool_call_end\|>"; | ||||||
| // Supports alternative delimiters: <|func_start|>/<|func_end|>; (?s) for multi-line JSON | ||||||
| let tool_call_pattern = r"(?s)<\|tool_call_begin\|>\s*(?P<tool_call_id>[\w\.]+:\d+)\s*(?:<\|tool_call_argument_begin\|>\s*|<\|func_start\|>\s*)?(?P<function_arguments>\{.*?\})\s*(?:<\|tool_call_end\|>|<\|func_end\|>)"; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regex pattern
Suggested change
References
|
||||||
| let tool_call_extractor = Regex::new(tool_call_pattern).expect("Valid regex pattern"); | ||||||
|
|
||||||
| // Pattern for streaming (partial) tool calls | ||||||
| let stream_pattern = r"<\|tool_call_begin\|>\s*(?P<tool_call_id>[\w\.]+:\d+)\s*<\|tool_call_argument_begin\|>\s*(?P<function_arguments>\{.*)"; | ||||||
| let stream_pattern = r"(?s)<\|tool_call_begin\|>\s*(?P<tool_call_id>[\w\.]+:\d+)\s*(?:<\|tool_call_argument_begin\|>\s*|<\|func_start\|>\s*)?(?P<function_arguments>\{.*)"; | ||||||
| let stream_tool_call_extractor = Regex::new(stream_pattern).expect("Valid regex pattern"); | ||||||
|
|
||||||
| // Pattern for removing completed tool calls | ||||||
| let end_pattern = r"<\|tool_call_begin\|>.*?<\|tool_call_end\|>"; | ||||||
| let end_pattern = r"<\|tool_call_begin\|>.*?(?:<\|tool_call_end\|>|<\|func_end\|>)"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The stream parser now supports multiline JSON via Useful? React with 👍 / 👎. |
||||||
| let tool_call_end_pattern = Regex::new(end_pattern).expect("Valid regex pattern"); | ||||||
|
Comment on lines
67
to
69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate the kimik2.rs file
fd -t f 'kimik2\.rs'Repository: lightseekorg/smg Length of output: 147 🏁 Script executed: # Read the relevant sections of kimik2.rs
cat -n crates/tool_parser/src/parsers/kimik2.rs | sed -n '50,100p'Repository: lightseekorg/smg Length of output: 2682 🏁 Script executed: # Now read the buffer-drain logic around lines 280-300
cat -n crates/tool_parser/src/parsers/kimik2.rs | sed -n '280,310p'Repository: lightseekorg/smg Length of output: 1879 🏁 Script executed: # Check lines 260-275 to verify the completeness check
cat -n crates/tool_parser/src/parsers/kimik2.rs | sed -n '260,275p'Repository: lightseekorg/smg Length of output: 1002 🏁 Script executed: # Let me verify the proposed fix by checking if there are any other usages of tool_call_end_pattern
rg -n "tool_call_end_pattern" crates/tool_parser/src/parsers/kimik2.rsRepository: lightseekorg/smg Length of output: 327 🏁 Script executed: # Check test file to understand expected multi-line scenarios
cat -n crates/tool_parser/tests/tool_parser_kimik2.rs | head -100Repository: lightseekorg/smg Length of output: 3994 🏁 Script executed: # Look for tests with multi-line JSON arguments
rg -n "\\\\n" crates/tool_parser/tests/tool_parser_kimik2.rs | head -20Repository: lightseekorg/smg Length of output: 264 🏁 Script executed: # Search for tests with actual newlines inside JSON payloads
rg -B5 -A5 'tool_call_argument_begin.*?\{' crates/tool_parser/tests/tool_parser_kimik2.rs | head -50Repository: lightseekorg/smg Length of output: 2092 🏁 Script executed: # Verify the Regex behavior by checking if there are any multiline payload tests
rg -n "func_end" crates/tool_parser/tests/tool_parser_kimik2.rsRepository: lightseekorg/smg Length of output: 42 🏁 Script executed: # Check if there are any tests using <|func_start|>/<|func_end|> delimiters
rg -n "func_start" crates/tool_parser/tests/tool_parser_kimik2.rsRepository: lightseekorg/smg Length of output: 42 🏁 Script executed: # Check the is_complete_json logic to see when buffer drain is triggered
cat -n crates/tool_parser/src/parsers/kimik2.rs | sed -n '270,295p'Repository: lightseekorg/smg Length of output: 1722 Add The extractor regexes at lines 61 and 64 use 🐛 Fix- let end_pattern = r"<\|tool_call_begin\|>.*?(?:<\|tool_call_end\|>|<\|func_end\|>)";
+ let end_pattern = r"(?s)<\|tool_call_begin\|>.*?(?:<\|tool_call_end\|>|<\|func_end\|>)";🤖 Prompt for AI Agents |
||||||
|
|
||||||
| // Robust parser for ids like "functions.search:0" or fallback "search:0" | ||||||
|
|
@@ -181,7 +180,7 @@ impl ToolParser for KimiK2Parser { | |||||
| // No tool markers detected - return all buffered content as normal text | ||||||
| let mut normal_text = std::mem::take(&mut self.buffer); | ||||||
| // Remove end tokens if present | ||||||
| for e_token in ["<|tool_calls_section_end|>", "<|tool_call_end|>"] { | ||||||
| for e_token in ["<|tool_calls_section_end|>", "<|tool_call_end|>", "<|func_end|>"] { | ||||||
| normal_text = normal_text.replace(e_token, ""); | ||||||
| } | ||||||
| return Ok(StreamingParseResult { | ||||||
|
|
@@ -242,13 +241,14 @@ impl ToolParser for KimiK2Parser { | |||||
| function_args | ||||||
| }; | ||||||
|
|
||||||
| // Split by end token before sending (like Python does) | ||||||
| let parsed_args_diff = | ||||||
| if let Some(pos) = argument_diff.find("<|tool_call_end|>") { | ||||||
| &argument_diff[..pos] | ||||||
| } else { | ||||||
| argument_diff | ||||||
| }; | ||||||
| // Split by end token before sending | ||||||
| let end_pos = argument_diff.find("<|tool_call_end|>") | ||||||
| .or_else(|| argument_diff.find("<|func_end|>")); | ||||||
| let parsed_args_diff = if let Some(pos) = end_pos { | ||||||
| &argument_diff[..pos] | ||||||
| } else { | ||||||
| argument_diff | ||||||
| }; | ||||||
|
|
||||||
| if !parsed_args_diff.is_empty() { | ||||||
| calls.push(ToolCallItem { | ||||||
|
|
@@ -265,8 +265,9 @@ impl ToolParser for KimiK2Parser { | |||||
| } | ||||||
|
|
||||||
| // Check completeness - split by end token first | ||||||
| let parsed_args = if let Some(pos) = function_args.find("<|tool_call_end|>") | ||||||
| { | ||||||
| let end_pos2 = function_args.find("<|tool_call_end|>") | ||||||
| .or_else(|| function_args.find("<|func_end|>")); | ||||||
| let parsed_args = if let Some(pos) = end_pos2 { | ||||||
| &function_args[..pos] | ||||||
| } else { | ||||||
| function_args | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,7 +17,7 @@ use tokio::{ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync::{watch, Mutex}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task::JoinHandle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tracing::{debug, info}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tracing::{debug, info, warn}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| core::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -85,6 +85,36 @@ impl IntoResponse for EngineMetricsResult { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Collect gRPC worker metrics by aggregating `PROMETHEUS_MULTIPROC_DIR` via a python3 subprocess. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn collect_prometheus_multiproc_metrics() -> Result<String, String> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let dir = std::env::var("PROMETHEUS_MULTIPROC_DIR").map_err(|_| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "PROMETHEUS_MULTIPROC_DIR not set; cannot collect metrics from gRPC workers".to_string() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = tokio::process::Command::new("python3") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .args([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "-c", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "import sys\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from prometheus_client import CollectorRegistry, generate_latest\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from prometheus_client.multiprocess import MultiProcessCollector\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| registry = CollectorRegistry()\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MultiProcessCollector(registry)\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.stdout.buffer.write(generate_latest(registry))\n", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .env("PROMETHEUS_MULTIPROC_DIR", &dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .output() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|e| format!("failed to run python3 prometheus collector: {e}"))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !output.status.success() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let stderr = String::from_utf8_lossy(&output.stderr); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Err(format!("python3 prometheus collector failed: {stderr}")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String::from_utf8(output.stdout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|e| format!("prometheus collector output is not valid UTF-8: {e}")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+89
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spawning a References
Comment on lines
+88
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Runtime dependency on Python requires documentation. This function introduces a runtime dependency on Consider adding a doc comment noting the prerequisites: 📝 Suggested documentation-/// Collect gRPC worker metrics by aggregating `PROMETHEUS_MULTIPROC_DIR` via a python3 subprocess.
+/// Collect gRPC worker metrics by aggregating `PROMETHEUS_MULTIPROC_DIR` via a python3 subprocess.
+///
+/// # Prerequisites
+/// - `PROMETHEUS_MULTIPROC_DIR` environment variable must be set (typically by the Python orchestrator)
+/// - `python3` must be available in PATH
+/// - `prometheus_client` Python package must be installed
+///
+/// # Errors
+/// Returns an error string if the env var is missing, subprocess fails, or output is not valid UTF-8.
async fn collect_prometheus_multiproc_metrics() -> Result<String, String> {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+88
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Nit: This spawns a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub struct WorkerManager; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| impl WorkerManager { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -273,22 +303,49 @@ impl WorkerManager { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return EngineMetricsResult::Err("No available workers".to_string()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let responses = fan_out(&workers, client, "metrics", reqwest::Method::GET).await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut metric_packs = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for resp in responses { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(r) = resp.result { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if r.status().is_success() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(text) = r.text().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metric_packs.push(MetricPack { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| labels: vec![("worker_addr".into(), resp.url)], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metrics_text: text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let http_workers: Vec<_> = workers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .iter() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter(|w| matches!(w.connection_mode(), ConnectionMode::Http)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .cloned() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let has_grpc = workers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .iter() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .any(|w| matches!(w.connection_mode(), ConnectionMode::Grpc)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !http_workers.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let responses = fan_out(&http_workers, client, "metrics", reqwest::Method::GET).await; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for resp in responses { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(r) = resp.result { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if r.status().is_success() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(text) = r.text().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metric_packs.push(MetricPack { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| labels: vec![("worker_addr".into(), resp.url)], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metrics_text: text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if has_grpc { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match collect_prometheus_multiproc_metrics().await { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(text) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metric_packs.push(MetricPack { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| labels: vec![], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metrics_text: text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warn!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Failed to collect gRPC worker metrics from PROMETHEUS_MULTIPROC_DIR: {e}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if metric_packs.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return EngineMetricsResult::Err("All backend requests failed".to_string()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,7 +148,16 @@ impl ResponseProcessor { | |
| _ => false, | ||
| }; | ||
|
|
||
| if used_json_schema { | ||
| if self.configured_tool_parser.is_some() && tool_parser_available { | ||
| // Explicitly configured parser takes priority (models may emit native tokens regardless of tool_choice) | ||
| (tool_calls, processed_text) = self | ||
| .parse_tool_calls( | ||
| &processed_text, | ||
| &original_request.model, | ||
| history_tool_calls_count, | ||
| ) | ||
| .await; | ||
| } else if used_json_schema { | ||
| (tool_calls, processed_text) = utils::parse_json_schema_response( | ||
| &processed_text, | ||
| original_request.tool_choice.as_ref(), | ||
|
|
@@ -183,7 +192,58 @@ impl ResponseProcessor { | |
| utils::convert_proto_to_openai_logprobs(proto_logprobs, tokenizer) | ||
| }); | ||
|
|
||
| // Step 5: Build ChatCompletionMessage (proper response message type) | ||
| // Strip leaked chatml tokens only when a model-specific parser is configured | ||
| if self.configured_tool_parser.is_some() || self.configured_reasoning_parser.is_some() { | ||
| for token in [ | ||
| "<|im_end|>", | ||
| "<|im_start|>", | ||
| "<|im_user|>", | ||
| "<|im_assistant|>", | ||
| "<|im_system|>", | ||
| "<|im_middle|>", | ||
| ] { | ||
| processed_text = processed_text.replace(token, ""); | ||
| } | ||
| processed_text = processed_text.trim().to_string(); | ||
|
Comment on lines
+196
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Nit: This chatml-stripping block is now duplicated in 4 places (here, line ~720 in this file, and two sites in streaming.rs) with subtly different token lists — the streaming "regular content emission" path also strips |
||
| } | ||
|
Comment on lines
+196
to
+208
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for stripping leaked ChatML tokens is duplicated across multiple files and functions. This duplication makes the code harder to maintain. Extract this duplicated logic into a shared helper function to improve maintainability and reduce redundancy. References
|
||
|
|
||
| let is_json_response = matches!( | ||
| &original_request.response_format, | ||
| Some(openai_protocol::common::ResponseFormat::JsonObject) | ||
| | Some(openai_protocol::common::ResponseFormat::JsonSchema { .. }) | ||
| ); | ||
| if is_json_response { | ||
| if processed_text.starts_with("```json") || processed_text.starts_with("```JSON") { | ||
| if let Some(start) = processed_text.find('\n') { | ||
| let inner = &processed_text[start + 1..]; | ||
| if let Some(end) = inner.rfind("```") { | ||
| processed_text = inner[..end].trim().to_string(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if processed_text.starts_with('{') { | ||
| let mut depth = 0i32; | ||
| let mut in_string = false; | ||
| let mut escape = false; | ||
| let mut json_end = None; | ||
| for (i, ch) in processed_text.char_indices() { | ||
| if escape { escape = false; continue; } | ||
| if ch == '\\' && in_string { escape = true; continue; } | ||
| if ch == '"' { in_string = !in_string; continue; } | ||
| if in_string { continue; } | ||
| if ch == '{' { depth += 1; } else if ch == '}' { | ||
| depth -= 1; | ||
| if depth == 0 { json_end = Some(i + 1); break; } | ||
| } | ||
| } | ||
| if let Some(end) = json_end { | ||
| processed_text = processed_text[..end].to_string(); | ||
| } | ||
| } | ||
|
Comment on lines
+215
to
+243
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Nit: Two minor edge cases in the JSON cleanup:
Neither is likely to bite in practice, just flagging for awareness. |
||
| } | ||
|
|
||
| // Build ChatCompletionMessage (proper response message type) | ||
| let chat_message = ChatCompletionMessage { | ||
| role: "assistant".to_string(), | ||
| content: if processed_text.is_empty() { | ||
|
|
@@ -621,7 +681,17 @@ impl ResponseProcessor { | |
| Some(messages::ToolChoice::Tool { .. } | messages::ToolChoice::Any { .. }) | ||
| ); | ||
|
|
||
| if used_json_schema { | ||
| if self.configured_tool_parser.is_some() && tool_parser_available { | ||
| (tool_calls, processed_text) = self | ||
| .parse_tool_calls( | ||
| &processed_text, | ||
| &messages_request.model, | ||
| utils::message_utils::get_history_tool_calls_count_messages( | ||
| &messages_request, | ||
| ), | ||
| ) | ||
| .await; | ||
| } else if used_json_schema { | ||
| // Bridge Messages ToolChoice to Chat ToolChoice for reuse | ||
| let chat_tool_choice = messages_request | ||
| .tool_choice | ||
|
|
@@ -647,7 +717,22 @@ impl ResponseProcessor { | |
| } | ||
| } | ||
|
|
||
| // Step 3: Build content blocks | ||
| // Strip leaked chatml tokens only when a model-specific parser is configured | ||
| if self.configured_tool_parser.is_some() || self.configured_reasoning_parser.is_some() { | ||
| for token in [ | ||
| "<|im_end|>", | ||
| "<|im_start|>", | ||
| "<|im_user|>", | ||
| "<|im_assistant|>", | ||
| "<|im_system|>", | ||
| "<|im_middle|>", | ||
| ] { | ||
| processed_text = processed_text.replace(token, ""); | ||
| } | ||
| processed_text = processed_text.trim().to_string(); | ||
| } | ||
|
|
||
| // Build content blocks | ||
| let mut content_blocks: Vec<messages::ContentBlock> = Vec::new(); | ||
|
|
||
| // Thinking block first (if present) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tool call section marker
<|tool_calls_section_begin|>is hardcoded in theBaseReasoningParser. This marker is specific to certain models (like Kimi) and its presence in the base parser violates modularity. It should ideally be part of theParserConfigso it can be customized per model without modifying the base implementation.