[router][grpc] Replace Vec<(String, String, String)> with ExtractedToolCall (#16598)
This commit is contained in:
@@ -114,10 +114,18 @@ pub(super) fn prepare_chat_tools_and_choice(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tool call extracted from a ChatCompletionResponse
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) struct ExtractedToolCall {
|
||||||
|
pub call_id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub arguments: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract all tool calls from chat response (for parallel tool call support)
|
/// Extract all tool calls from chat response (for parallel tool call support)
|
||||||
pub(super) fn extract_all_tool_calls_from_chat(
|
pub(super) fn extract_all_tool_calls_from_chat(
|
||||||
response: &crate::protocols::chat::ChatCompletionResponse,
|
response: &crate::protocols::chat::ChatCompletionResponse,
|
||||||
) -> Vec<(String, String, String)> {
|
) -> Vec<ExtractedToolCall> {
|
||||||
// Check if response has choices with tool calls
|
// Check if response has choices with tool calls
|
||||||
let Some(choice) = response.choices.first() else {
|
let Some(choice) = response.choices.first() else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -128,16 +136,14 @@ pub(super) fn extract_all_tool_calls_from_chat(
|
|||||||
if let Some(tool_calls) = &message.tool_calls {
|
if let Some(tool_calls) = &message.tool_calls {
|
||||||
tool_calls
|
tool_calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tool_call| {
|
.map(|tool_call| ExtractedToolCall {
|
||||||
(
|
call_id: tool_call.id.clone(),
|
||||||
tool_call.id.clone(),
|
name: tool_call.function.name.clone(),
|
||||||
tool_call.function.name.clone(),
|
arguments: tool_call
|
||||||
tool_call
|
.function
|
||||||
.function
|
.arguments
|
||||||
.arguments
|
.clone()
|
||||||
.clone()
|
.unwrap_or_else(|| "{}".to_string()),
|
||||||
.unwrap_or_else(|| "{}".to_string()),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use super::{
|
|||||||
common::{
|
common::{
|
||||||
build_mcp_list_tools_item, build_next_request, convert_mcp_tools_to_chat_tools,
|
build_mcp_list_tools_item, build_next_request, convert_mcp_tools_to_chat_tools,
|
||||||
extract_all_tool_calls_from_chat, load_conversation_history, prepare_chat_tools_and_choice,
|
extract_all_tool_calls_from_chat, load_conversation_history, prepare_chat_tools_and_choice,
|
||||||
ToolLoopState,
|
ExtractedToolCall, ToolLoopState,
|
||||||
},
|
},
|
||||||
conversions,
|
conversions,
|
||||||
};
|
};
|
||||||
@@ -232,9 +232,10 @@ pub(super) async fn execute_tool_loop(
|
|||||||
// Separate MCP and function tool calls
|
// Separate MCP and function tool calls
|
||||||
let mcp_tool_names: std::collections::HashSet<&str> =
|
let mcp_tool_names: std::collections::HashSet<&str> =
|
||||||
mcp_tools.iter().map(|t| t.name.as_ref()).collect();
|
mcp_tools.iter().map(|t| t.name.as_ref()).collect();
|
||||||
let (mcp_tool_calls, function_tool_calls): (Vec<_>, Vec<_>) = tool_calls
|
let (mcp_tool_calls, function_tool_calls): (Vec<ExtractedToolCall>, Vec<_>) =
|
||||||
.into_iter()
|
tool_calls
|
||||||
.partition(|(_, tool_name, _)| mcp_tool_names.contains(tool_name.as_str()));
|
.into_iter()
|
||||||
|
.partition(|tc| mcp_tool_names.contains(tc.name.as_str()));
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
"Separated tool calls: {} MCP, {} function",
|
"Separated tool calls: {} MCP, {} function",
|
||||||
@@ -312,18 +313,18 @@ pub(super) async fn execute_tool_loop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute all MCP tools
|
// Execute all MCP tools
|
||||||
for (call_id, tool_name, args_json_str) in mcp_tool_calls {
|
for tool_call in mcp_tool_calls {
|
||||||
trace!(
|
trace!(
|
||||||
"Calling MCP tool '{}' (call_id: {}) with args: {}",
|
"Calling MCP tool '{}' (call_id: {}) with args: {}",
|
||||||
tool_name,
|
tool_call.name,
|
||||||
call_id,
|
tool_call.call_id,
|
||||||
args_json_str
|
tool_call.arguments
|
||||||
);
|
);
|
||||||
|
|
||||||
let tool_start = Instant::now();
|
let tool_start = Instant::now();
|
||||||
let (output_str, success, error) = match ctx
|
let (output_str, success, error) = match ctx
|
||||||
.mcp_manager
|
.mcp_manager
|
||||||
.call_tool(tool_name.as_str(), args_json_str.as_str())
|
.call_tool(tool_call.name.as_str(), tool_call.arguments.as_str())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => match serde_json::to_string(&result) {
|
Ok(result) => match serde_json::to_string(&result) {
|
||||||
@@ -348,12 +349,12 @@ pub(super) async fn execute_tool_loop(
|
|||||||
// Record MCP tool metrics
|
// Record MCP tool metrics
|
||||||
Metrics::record_mcp_tool_duration(
|
Metrics::record_mcp_tool_duration(
|
||||||
¤t_request.model,
|
¤t_request.model,
|
||||||
&tool_name,
|
&tool_call.name,
|
||||||
tool_duration,
|
tool_duration,
|
||||||
);
|
);
|
||||||
Metrics::record_mcp_tool_call(
|
Metrics::record_mcp_tool_call(
|
||||||
¤t_request.model,
|
¤t_request.model,
|
||||||
&tool_name,
|
&tool_call.name,
|
||||||
if success {
|
if success {
|
||||||
metrics_labels::RESULT_SUCCESS
|
metrics_labels::RESULT_SUCCESS
|
||||||
} else {
|
} else {
|
||||||
@@ -363,9 +364,9 @@ pub(super) async fn execute_tool_loop(
|
|||||||
|
|
||||||
// Record the call in state
|
// Record the call in state
|
||||||
state.record_call(
|
state.record_call(
|
||||||
call_id,
|
tool_call.call_id,
|
||||||
tool_name,
|
tool_call.name,
|
||||||
args_json_str,
|
tool_call.arguments,
|
||||||
output_str,
|
output_str,
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use uuid::Uuid;
|
|||||||
use super::{
|
use super::{
|
||||||
common::{
|
common::{
|
||||||
build_next_request, convert_mcp_tools_to_chat_tools, extract_all_tool_calls_from_chat,
|
build_next_request, convert_mcp_tools_to_chat_tools, extract_all_tool_calls_from_chat,
|
||||||
prepare_chat_tools_and_choice, ToolLoopState,
|
prepare_chat_tools_and_choice, ExtractedToolCall, ToolLoopState,
|
||||||
},
|
},
|
||||||
conversions,
|
conversions,
|
||||||
};
|
};
|
||||||
@@ -630,9 +630,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
// Separate MCP and function tool calls
|
// Separate MCP and function tool calls
|
||||||
let mcp_tool_names: std::collections::HashSet<&str> =
|
let mcp_tool_names: std::collections::HashSet<&str> =
|
||||||
mcp_tools.iter().map(|t| t.name.as_ref()).collect();
|
mcp_tools.iter().map(|t| t.name.as_ref()).collect();
|
||||||
let (mcp_tool_calls, function_tool_calls): (Vec<_>, Vec<_>) = tool_calls
|
let (mcp_tool_calls, function_tool_calls): (Vec<ExtractedToolCall>, Vec<_>) =
|
||||||
.into_iter()
|
tool_calls
|
||||||
.partition(|(_, tool_name, _)| mcp_tool_names.contains(tool_name.as_str()));
|
.into_iter()
|
||||||
|
.partition(|tc| mcp_tool_names.contains(tc.name.as_str()));
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
"Separated tool calls: {} MCP, {} function",
|
"Separated tool calls: {} MCP, {} function",
|
||||||
@@ -659,15 +660,15 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process each MCP tool call
|
// Process each MCP tool call
|
||||||
for (call_id, tool_name, args_json_str) in mcp_tool_calls {
|
for tool_call in mcp_tool_calls {
|
||||||
state.total_calls += 1;
|
state.total_calls += 1;
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
"Executing tool call {}/{}: {} (call_id: {})",
|
"Executing tool call {}/{}: {} (call_id: {})",
|
||||||
state.total_calls,
|
state.total_calls,
|
||||||
state.total_calls,
|
state.total_calls,
|
||||||
tool_name,
|
tool_call.name,
|
||||||
call_id
|
tool_call.call_id
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allocate output_index for this mcp_call item
|
// Allocate output_index for this mcp_call item
|
||||||
@@ -678,7 +679,7 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item = json!({
|
let item = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "mcp_call",
|
"type": "mcp_call",
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"server_label": state.server_label,
|
"server_label": state.server_label,
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"arguments": ""
|
"arguments": ""
|
||||||
@@ -693,25 +694,31 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
emitter.send_event(&event, &tx)?;
|
emitter.send_event(&event, &tx)?;
|
||||||
|
|
||||||
// Emit mcp_call_arguments.delta (simulate streaming by sending full arguments)
|
// Emit mcp_call_arguments.delta (simulate streaming by sending full arguments)
|
||||||
let event =
|
let event = emitter.emit_mcp_call_arguments_delta(
|
||||||
emitter.emit_mcp_call_arguments_delta(output_index, &item_id, &args_json_str);
|
output_index,
|
||||||
|
&item_id,
|
||||||
|
&tool_call.arguments,
|
||||||
|
);
|
||||||
emitter.send_event(&event, &tx)?;
|
emitter.send_event(&event, &tx)?;
|
||||||
|
|
||||||
// Emit mcp_call_arguments.done
|
// Emit mcp_call_arguments.done
|
||||||
let event =
|
let event = emitter.emit_mcp_call_arguments_done(
|
||||||
emitter.emit_mcp_call_arguments_done(output_index, &item_id, &args_json_str);
|
output_index,
|
||||||
|
&item_id,
|
||||||
|
&tool_call.arguments,
|
||||||
|
);
|
||||||
emitter.send_event(&event, &tx)?;
|
emitter.send_event(&event, &tx)?;
|
||||||
|
|
||||||
// Execute the MCP tool - manager handles parsing and type coercion
|
// Execute the MCP tool - manager handles parsing and type coercion
|
||||||
trace!(
|
trace!(
|
||||||
"Calling MCP tool '{}' with args: {}",
|
"Calling MCP tool '{}' with args: {}",
|
||||||
tool_name,
|
tool_call.name,
|
||||||
args_json_str
|
tool_call.arguments
|
||||||
);
|
);
|
||||||
let tool_start = Instant::now();
|
let tool_start = Instant::now();
|
||||||
let (output_str, success, error) = match ctx
|
let (output_str, success, error) = match ctx
|
||||||
.mcp_manager
|
.mcp_manager
|
||||||
.call_tool(tool_name.as_str(), args_json_str.as_str())
|
.call_tool(tool_call.name.as_str(), tool_call.arguments.as_str())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => match serde_json::to_string(&result) {
|
Ok(result) => match serde_json::to_string(&result) {
|
||||||
@@ -724,10 +731,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item_done = json!({
|
let item_done = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "mcp_call",
|
"type": "mcp_call",
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"server_label": state.server_label,
|
"server_label": state.server_label,
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"arguments": args_json_str,
|
"arguments": tool_call.arguments,
|
||||||
"output": output
|
"output": output
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -749,10 +756,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item_done = json!({
|
let item_done = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "mcp_call",
|
"type": "mcp_call",
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"server_label": state.server_label,
|
"server_label": state.server_label,
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
"arguments": args_json_str,
|
"arguments": tool_call.arguments,
|
||||||
"error": &err
|
"error": &err
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -776,10 +783,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item_done = json!({
|
let item_done = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "mcp_call",
|
"type": "mcp_call",
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"server_label": state.server_label,
|
"server_label": state.server_label,
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
"arguments": args_json_str,
|
"arguments": tool_call.arguments,
|
||||||
"error": &err_str
|
"error": &err_str
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -795,10 +802,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let tool_duration = tool_start.elapsed();
|
let tool_duration = tool_start.elapsed();
|
||||||
|
|
||||||
// Record MCP tool metrics
|
// Record MCP tool metrics
|
||||||
Metrics::record_mcp_tool_duration(&model, &tool_name, tool_duration);
|
Metrics::record_mcp_tool_duration(&model, &tool_call.name, tool_duration);
|
||||||
Metrics::record_mcp_tool_call(
|
Metrics::record_mcp_tool_call(
|
||||||
&model,
|
&model,
|
||||||
&tool_name,
|
&tool_call.name,
|
||||||
if success {
|
if success {
|
||||||
metrics_labels::RESULT_SUCCESS
|
metrics_labels::RESULT_SUCCESS
|
||||||
} else {
|
} else {
|
||||||
@@ -808,9 +815,9 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
|
|
||||||
// Record the call in state
|
// Record the call in state
|
||||||
state.record_call(
|
state.record_call(
|
||||||
call_id,
|
tool_call.call_id,
|
||||||
tool_name,
|
tool_call.name,
|
||||||
args_json_str,
|
tool_call.arguments,
|
||||||
output_str,
|
output_str,
|
||||||
success,
|
success,
|
||||||
error,
|
error,
|
||||||
@@ -825,7 +832,7 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Emit function_tool_call events for each function tool
|
// Emit function_tool_call events for each function tool
|
||||||
for (call_id, tool_name, args_json_str) in function_tool_calls {
|
for tool_call in function_tool_calls {
|
||||||
// Allocate output_index for this function_tool_call item
|
// Allocate output_index for this function_tool_call item
|
||||||
let (output_index, item_id) =
|
let (output_index, item_id) =
|
||||||
emitter.allocate_output_index(OutputItemType::FunctionCall);
|
emitter.allocate_output_index(OutputItemType::FunctionCall);
|
||||||
@@ -834,8 +841,8 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item = json!({
|
let item = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "function_tool_call",
|
"type": "function_tool_call",
|
||||||
"call_id": call_id,
|
"call_id": tool_call.call_id,
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"arguments": ""
|
"arguments": ""
|
||||||
});
|
});
|
||||||
@@ -848,7 +855,7 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let event = emitter.emit_function_call_arguments_delta(
|
let event = emitter.emit_function_call_arguments_delta(
|
||||||
output_index,
|
output_index,
|
||||||
&item_id,
|
&item_id,
|
||||||
&args_json_str,
|
&tool_call.arguments,
|
||||||
);
|
);
|
||||||
emitter.send_event(&event, &tx)?;
|
emitter.send_event(&event, &tx)?;
|
||||||
|
|
||||||
@@ -856,7 +863,7 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let event = emitter.emit_function_call_arguments_done(
|
let event = emitter.emit_function_call_arguments_done(
|
||||||
output_index,
|
output_index,
|
||||||
&item_id,
|
&item_id,
|
||||||
&args_json_str,
|
&tool_call.arguments,
|
||||||
);
|
);
|
||||||
emitter.send_event(&event, &tx)?;
|
emitter.send_event(&event, &tx)?;
|
||||||
|
|
||||||
@@ -864,10 +871,10 @@ async fn execute_tool_loop_streaming_internal(
|
|||||||
let item_complete = json!({
|
let item_complete = json!({
|
||||||
"id": item_id,
|
"id": item_id,
|
||||||
"type": "function_tool_call",
|
"type": "function_tool_call",
|
||||||
"call_id": call_id,
|
"call_id": tool_call.call_id,
|
||||||
"name": tool_name,
|
"name": tool_call.name,
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"arguments": args_json_str
|
"arguments": tool_call.arguments
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit output_item.done
|
// Emit output_item.done
|
||||||
|
|||||||
Reference in New Issue
Block a user