use async_trait::async_trait;
use regex::Regex;
use serde_json::Value;
use crate::tool_parser::{
errors::{ToolParserError, ToolParserResult},
state::ParseState,
traits::ToolParser,
types::{FunctionCall, StreamResult, ToolCall},
};
/// Step3 format parser for tool calls
///
/// Handles the Step3 specific format with steptml XML:
/// `<|tool_calls_begin|><|tool_call_begin|>function<|tool_sep|>{v}<|tool_call_end|><|tool_calls_end|>`
///
/// Features:
/// - Unicode token delimiters
/// - StepTML XML format for invocations
/// - Support for multiple sequential tool calls
pub struct Step3Parser {
/// Regex for extracting tool call blocks
tool_call_extractor: Regex,
/// Regex for extracting steptml invocations
invoke_extractor: Regex,
/// Regex for extracting parameters
param_extractor: Regex,
}
impl Step3Parser {
/// Create a new Step3 parser
pub fn new() -> Self {
// Pattern for individual tool calls
let tool_call_pattern = r"(?s)<|tool_call_begin|>.*?<|tool_call_end|>";
let tool_call_extractor = Regex::new(tool_call_pattern).expect("Valid regex pattern");
// Pattern for steptml invocations
let invoke_pattern = r#"(?s)(.+?)"#;
let invoke_extractor = Regex::new(invoke_pattern).expect("Valid regex pattern");
// Pattern for steptml parameters - using non-greedy match for values to handle < characters
let param_pattern = r#"(?s)(.+?)"#;
let param_extractor = Regex::new(param_pattern).expect("Valid regex pattern");
Self {
tool_call_extractor,
invoke_extractor,
param_extractor,
}
}
/// Check if text contains Step3 tool markers
fn has_tool_markers(&self, text: &str) -> bool {
text.contains("<|tool_calls_begin|>")
}
/// Parse parameters from steptml format
fn parse_steptml_parameters(
&self,
params_text: &str,
) -> ToolParserResult> {
let mut parameters = serde_json::Map::new();
for capture in self.param_extractor.captures_iter(params_text) {
let param_name = capture.get(1).map_or("", |m| m.as_str()).trim();
let param_value_str = capture.get(2).map_or("", |m| m.as_str()).trim();
// Try to parse the value as JSON first, fallback to string
let param_value = if let Ok(json_val) = serde_json::from_str::(param_value_str) {
json_val
} else {
// Try parsing as Python literal
if param_value_str == "true" || param_value_str == "True" {
Value::Bool(true)
} else if param_value_str == "false" || param_value_str == "False" {
Value::Bool(false)
} else if param_value_str == "null" || param_value_str == "None" {
Value::Null
} else if let Ok(num) = param_value_str.parse::() {
Value::Number(num.into())
} else if let Ok(num) = param_value_str.parse::() {
if let Some(n) = serde_json::Number::from_f64(num) {
Value::Number(n)
} else {
Value::String(param_value_str.to_string())
}
} else {
Value::String(param_value_str.to_string())
}
};
parameters.insert(param_name.to_string(), param_value);
}
Ok(parameters)
}
/// Parse a single tool call block
fn parse_tool_call(&self, block: &str) -> ToolParserResult