impl ToolDefinition {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: Vec<ToolParameter>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
}
}
pub fn required_params(&self) -> impl Iterator<Item = &ToolParameter> {
self.parameters.iter().filter(|p| p.required)
}
pub fn optional_params(&self) -> impl Iterator<Item = &ToolParameter> {
self.parameters.iter().filter(|p| !p.required)
}
pub fn is_valid_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut chars = name.chars();
let first = chars.next().expect("name is non-empty");
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolChoice {
#[default]
Auto,
Required,
None,
Specific(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: String,
}
impl ToolCall {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
arguments: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
arguments: arguments.into(),
}
}
pub fn parse_arguments(&self) -> Result<serde_json::Value> {
serde_json::from_str(&self.arguments).map_err(|e| {
RealizarError::InvalidConfiguration(format!("Failed to parse tool arguments: {e}"))
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolResult {
pub tool_call_id: String,
pub content: String,
#[serde(default = "default_true")]
pub success: bool,
}
fn default_true() -> bool {
true
}
impl ToolResult {
pub fn success(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
tool_call_id: tool_call_id.into(),
content: content.into(),
success: true,
}
}
pub fn error(tool_call_id: impl Into<String>, error: impl Into<String>) -> Self {
Self {
tool_call_id: tool_call_id.into(),
content: error.into(),
success: false,
}
}
}
pub struct ToolCallParser {
tools: Vec<ToolDefinition>,
format: ToolCallFormat,
next_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ToolCallFormat {
#[default]
OpenAI,
Anthropic,
Hermes,
}
impl ToolCallParser {
pub fn new(tools: Vec<ToolDefinition>) -> Self {
Self {
tools,
format: ToolCallFormat::default(),
next_id: 0,
}
}
#[must_use]
pub fn with_format(mut self, format: ToolCallFormat) -> Self {
self.format = format;
self
}
pub fn generate_id(&mut self) -> String {
let id = format!("call_{}", self.next_id);
self.next_id += 1;
id
}
pub fn tool_names(&self) -> impl Iterator<Item = &str> {
self.tools.iter().map(|t| t.name.as_str())
}
pub fn get_tool(&self, name: &str) -> Option<&ToolDefinition> {
self.tools.iter().find(|t| t.name == name)
}
pub fn parse(&mut self, text: &str) -> Vec<ToolCall> {
match self.format {
ToolCallFormat::OpenAI => self.parse_openai(text),
ToolCallFormat::Anthropic => self.parse_anthropic(text),
ToolCallFormat::Hermes => self.parse_hermes(text),
}
}
fn try_extract_json_tool_call(&mut self, value: &serde_json::Value) -> Option<ToolCall> {
let name = value.get("name").and_then(|v| v.as_str())?;
let args = value.get("arguments")?;
if !self.tools.iter().any(|t| t.name == name) {
return None;
}
let arguments = if args.is_string() {
args.as_str().expect("checked is_string above").to_string()
} else {
args.to_string()
};
Some(ToolCall::new(self.generate_id(), name, arguments))
}
fn parse_openai(&mut self, text: &str) -> Vec<ToolCall> {
let mut calls = Vec::new();
let mut start = 0;
while let Some(pos) = text[start..].find('{') {
let abs_pos = start + pos;
if let Some(end) = find_matching_brace(&text[abs_pos..]) {
let json_str = &text[abs_pos..=(abs_pos + end)];
if let Ok(value) = serde_json::from_str::<serde_json::Value>(json_str) {
if let Some(call) = self.try_extract_json_tool_call(&value) {
calls.push(call);
}
}
start = abs_pos + end + 1;
} else {
start = abs_pos + 1;
}
}
calls
}
fn parse_anthropic(&mut self, text: &str) -> Vec<ToolCall> {
let mut calls = Vec::new();
let mut pos = 0;
while let Some(start) = text[pos..].find("<tool_use>") {
let abs_start = pos + start + 10; if let Some(end) = text[abs_start..].find("</tool_use>") {
let content = &text[abs_start..abs_start + end];
let name = extract_xml_tag(content, "name");
let input = extract_xml_tag(content, "input");
if let (Some(name), Some(input)) = (name, input) {
if self.tools.iter().any(|t| t.name == name) {
calls.push(ToolCall::new(self.generate_id(), name, input));
}
}
pos = abs_start + end + 11; } else {
break;
}
}
calls
}
fn parse_hermes(&mut self, text: &str) -> Vec<ToolCall> {
let mut calls = Vec::new();
let mut pos = 0;
while let Some(start) = text[pos..].find("<tool_call>") {
let abs_start = pos + start + 11; if let Some(end) = text[abs_start..].find("</tool_call>") {
let json_str = text[abs_start..abs_start + end].trim();
if let Ok(value) = serde_json::from_str::<serde_json::Value>(json_str) {
if let Some(call) = self.try_extract_json_tool_call(&value) {
calls.push(call);
}
}
pos = abs_start + end + 12; } else {
break;
}
}
calls
}
}
#[inline]
fn process_brace_char(c: char, in_string: bool) -> (i32, bool, bool) {
match c {
'\\' if in_string => (0, false, true),
'"' => (0, true, false),
'{' if !in_string => (1, false, false),
'}' if !in_string => (-1, false, false),
_ => (0, false, false),
}
}
fn find_matching_brace(s: &str) -> Option<usize> {
let mut depth = 0;
let mut in_string = false;
let mut escape_next = false;
for (i, c) in s.char_indices() {
if escape_next {
escape_next = false;
continue;
}
let (delta, toggle, escape) = process_brace_char(c, in_string);
depth += delta;
if toggle {
in_string = !in_string;
}
escape_next = escape;
if depth == 0 && delta < 0 {
return Some(i);
}
}
None
}
fn extract_xml_tag(content: &str, tag: &str) -> Option<String> {
let open_tag = format!("<{tag}>");
let close_tag = format!("</{tag}>");
if let Some(start) = content.find(&open_tag) {
let value_start = start + open_tag.len();
if let Some(end) = content[value_start..].find(&close_tag) {
return Some(content[value_start..value_start + end].to_string());
}
}
None
}
pub fn generate_tool_grammar(tools: &[ToolDefinition]) -> Grammar {
let mut grammar = Grammar::default();
add_json_whitespace_rules(&mut grammar);
let mut tool_alternatives = Vec::new();
for tool in tools {
let tool_rule = format!("tool_{}", tool.name);
let params_rule = format!("{tool_rule}_params");
generate_params_grammar(&mut grammar, ¶ms_rule, &tool.parameters);
let mut elements = vec![
GrammarElement::Char('{'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char('"'),
GrammarElement::Char('n'),
GrammarElement::Char('a'),
GrammarElement::Char('m'),
GrammarElement::Char('e'),
GrammarElement::Char('"'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char(':'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char('"'),
];
for c in tool.name.chars() {
elements.push(GrammarElement::Char(c));
}
elements.extend([
GrammarElement::Char('"'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char(','),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char('"'),
GrammarElement::Char('a'),
GrammarElement::Char('r'),
GrammarElement::Char('g'),
GrammarElement::Char('u'),
GrammarElement::Char('m'),
GrammarElement::Char('e'),
GrammarElement::Char('n'),
GrammarElement::Char('t'),
GrammarElement::Char('s'),
GrammarElement::Char('"'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char(':'),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::RuleRef(params_rule),
GrammarElement::RuleRef("ws".to_string()),
GrammarElement::Char('}'),
]);
grammar.add_rule(GrammarRule::new(
&tool_rule,
vec![GrammarAlternative::new(elements)],
));
tool_alternatives.push(GrammarAlternative::new(vec![GrammarElement::RuleRef(
tool_rule,
)]));
}
grammar.add_rule(GrammarRule::new("root", tool_alternatives));
grammar
}