Unverified Commit 4b694e7d authored by Chang Su's avatar Chang Su Committed by GitHub
Browse files

[router][grpc] Add error handling to `generate_tool_constraints` (#11562)

parent 9f1f699a
...@@ -106,9 +106,13 @@ impl PreparationStage { ...@@ -106,9 +106,13 @@ impl PreparationStage {
let token_ids = encoding.token_ids().to_vec(); let token_ids = encoding.token_ids().to_vec();
// Step 4: Build tool constraints if needed // Step 4: Build tool constraints if needed
let tool_call_constraint = body_ref.tools.as_ref().and_then(|tools| { let tool_call_constraint = if let Some(tools) = body_ref.tools.as_ref() {
utils::generate_tool_constraints(tools, &request.tool_choice, &request.model) utils::generate_tool_constraints(tools, &request.tool_choice, &request.model).map_err(
}); |e| utils::bad_request_error(format!("Invalid tool configuration: {}", e)),
)?
} else {
None
};
// Step 5: Create stop sequence decoder (build once, reuse in non-stream) // Step 5: Create stop sequence decoder (build once, reuse in non-stream)
let stop_decoder = utils::create_stop_decoder( let stop_decoder = utils::create_stop_decoder(
......
...@@ -158,51 +158,54 @@ pub fn generate_tool_constraints( ...@@ -158,51 +158,54 @@ pub fn generate_tool_constraints(
tools: &[Tool], tools: &[Tool],
tool_choice: &Option<ToolChoice>, tool_choice: &Option<ToolChoice>,
_model: &str, _model: &str,
) -> Option<(String, String)> { ) -> Result<Option<(String, String)>, String> {
let choice = tool_choice.as_ref()?; let Some(choice) = tool_choice.as_ref() else {
return Ok(None);
};
match choice { match choice {
// Specific function: Return parameters schema directly // Specific function: Return parameters schema directly
// tools should already be filtered to contain only the specific function // tools should already be filtered to contain only the specific function
ToolChoice::Function { .. } => { ToolChoice::Function { .. } => {
if tools.is_empty() { if tools.is_empty() {
return None; return Ok(None);
} }
let tool = &tools[0]; let tool = &tools[0];
// Return the tool's parameters schema directly (not wrapped in array) // Return the tool's parameters schema directly (not wrapped in array)
let params_schema = serde_json::to_string(&tool.function.parameters).ok()?; let params_schema = serde_json::to_string(&tool.function.parameters)
Some(("json_schema".to_string(), params_schema)) .map_err(|e| format!("Failed to serialize tool parameters: {}", e))?;
Ok(Some(("json_schema".to_string(), params_schema)))
} }
// Required: Array of tool calls with minItems: 1 // Required: Array of tool calls with minItems: 1
ToolChoice::Value(ToolChoiceValue::Required) => { ToolChoice::Value(ToolChoiceValue::Required) => {
let schema = build_required_array_schema(tools)?; let schema = build_required_array_schema(tools)?;
Some(("json_schema".to_string(), schema)) Ok(Some(("json_schema".to_string(), schema)))
} }
// AllowedTools with required mode: tools are already filtered // AllowedTools with required mode: tools are already filtered
ToolChoice::AllowedTools { mode, .. } => { ToolChoice::AllowedTools { mode, .. } => {
if mode == "required" { if mode == "required" {
if tools.is_empty() { if tools.is_empty() {
return None; return Ok(None);
} }
let schema = build_required_array_schema(tools)?; let schema = build_required_array_schema(tools)?;
Some(("json_schema".to_string(), schema)) Ok(Some(("json_schema".to_string(), schema)))
} else { } else {
// "auto" mode - no constraint needed // "auto" mode - no constraint needed
None Ok(None)
} }
} }
// "auto" or "none" - no constraint // "auto" or "none" - no constraint
_ => None, _ => Ok(None),
} }
} }
/// Build JSON schema for required tool calls (array with minItems: 1) /// Build JSON schema for required tool calls (array with minItems: 1)
/// Includes $defs consolidation from all tools (matching Python's behavior) /// Includes $defs consolidation from all tools (matching Python's behavior)
pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> { pub fn build_required_array_schema(tools: &[Tool]) -> Result<String, String> {
// Build anyOf schemas for each tool // Build anyOf schemas for each tool
let mut any_of_schemas = Vec::new(); let mut any_of_schemas = Vec::new();
for tool in tools { for tool in tools {
...@@ -228,11 +231,12 @@ pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> { ...@@ -228,11 +231,12 @@ pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> {
if let Some(existing) = all_defs.get(def_name) { if let Some(existing) = all_defs.get(def_name) {
// Check for conflicts // Check for conflicts
if existing != def_schema { if existing != def_schema {
error!( let error_msg = format!(
"Tool definition '{}' has multiple schemas, which is not supported", "Tool definition '{}' has multiple conflicting schemas, which is not supported",
def_name def_name
); );
return None; error!("{}", error_msg);
return Err(error_msg);
} }
} else { } else {
all_defs.insert(def_name.clone(), def_schema.clone()); all_defs.insert(def_name.clone(), def_schema.clone());
...@@ -260,7 +264,8 @@ pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> { ...@@ -260,7 +264,8 @@ pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> {
} }
} }
serde_json::to_string(&array_schema).ok() serde_json::to_string(&array_schema)
.map_err(|e| format!("Failed to serialize tool schema: {}", e))
} }
/// Filter tools based on tool_choice (shared by both routers) /// Filter tools based on tool_choice (shared by both routers)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment