Skip to content
Merged
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
18 changes: 16 additions & 2 deletions crates/openfang-runtime/src/agent_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ pub async fn run_agent_loop(

// Execute each tool call with loop guard, timeout, and truncation
let mut tool_result_blocks = Vec::new();
for tool_call in &response.tool_calls {
for tool_call in deduplicate_tool_calls(&response) {
// Loop guard check
let verdict = loop_guard.check(&tool_call.name, &tool_call.input);
match &verdict {
Expand Down Expand Up @@ -1628,7 +1628,7 @@ pub async fn run_agent_loop_streaming(

// Execute each tool call with loop guard, timeout, and truncation
let mut tool_result_blocks = Vec::new();
for tool_call in &response.tool_calls {
for tool_call in deduplicate_tool_calls(&response) {
// Loop guard check
let verdict = loop_guard.check(&tool_call.name, &tool_call.input);
match &verdict {
Expand Down Expand Up @@ -2732,6 +2732,20 @@ fn try_parse_bare_json_tool_call(
parse_json_tool_call_object(&text[..end], tool_names)
}

/// Deduplicate tool calls from the response.
/// Returns a reference to the deduplicated tool calls.
pub fn deduplicate_tool_calls(response: &crate::llm_driver::CompletionResponse) -> Vec<&ToolCall> {
let mut hash_set = std::collections::HashSet::new();
let mut deduplicated = Vec::new();
for tool_call in &response.tool_calls {
let hash = LoopGuard::compute_hash(&tool_call.name, &tool_call.input);
if hash_set.insert(hash) {
deduplicated.push(tool_call);
}
}
deduplicated
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/openfang-runtime/src/loop_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl LoopGuard {
}

/// Compute a SHA-256 hash of the tool name and parameters.
fn compute_hash(tool_name: &str, params: &serde_json::Value) -> String {
pub fn compute_hash(tool_name: &str, params: &serde_json::Value) -> String {
let mut hasher = Sha256::new();
hasher.update(tool_name.as_bytes());
hasher.update(b"|");
Expand Down