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 {
let token_ids = encoding.token_ids().to_vec();
// Step 4: Build tool constraints if needed
let tool_call_constraint = body_ref.tools.as_ref().and_then(|tools| {
utils::generate_tool_constraints(tools, &request.tool_choice, &request.model)
});
let tool_call_constraint = if let Some(tools) = body_ref.tools.as_ref() {
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)
let stop_decoder = utils::create_stop_decoder(
......
......@@ -158,51 +158,54 @@ pub fn generate_tool_constraints(
tools: &[Tool],
tool_choice: &Option<ToolChoice>,
_model: &str,
) -> Option<(String, String)> {
let choice = tool_choice.as_ref()?;
) -> Result<Option<(String, String)>, String> {
let Some(choice) = tool_choice.as_ref() else {
return Ok(None);
};
match choice {
// Specific function: Return parameters schema directly
// tools should already be filtered to contain only the specific function
ToolChoice::Function { .. } => {
if tools.is_empty() {
return None;
return Ok(None);
}
let tool = &tools[0];
// Return the tool's parameters schema directly (not wrapped in array)
let params_schema = serde_json::to_string(&tool.function.parameters).ok()?;
Some(("json_schema".to_string(), params_schema))
let params_schema = serde_json::to_string(&tool.function.parameters)
.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
ToolChoice::Value(ToolChoiceValue::Required) => {
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
ToolChoice::AllowedTools { mode, .. } => {
if mode == "required" {
if tools.is_empty() {
return None;
return Ok(None);
}
let schema = build_required_array_schema(tools)?;
Some(("json_schema".to_string(), schema))
Ok(Some(("json_schema".to_string(), schema)))
} else {
// "auto" mode - no constraint needed
None
Ok(None)
}
}
// "auto" or "none" - no constraint
_ => None,
_ => Ok(None),
}
}
/// Build JSON schema for required tool calls (array with minItems: 1)
/// 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
let mut any_of_schemas = Vec::new();
for tool in tools {
......@@ -228,11 +231,12 @@ pub fn build_required_array_schema(tools: &[Tool]) -> Option<String> {
if let Some(existing) = all_defs.get(def_name) {
// Check for conflicts
if existing != def_schema {
error!(
"Tool definition '{}' has multiple schemas, which is not supported",
let error_msg = format!(
"Tool definition '{}' has multiple conflicting schemas, which is not supported",
def_name
);
return None;
error!("{}", error_msg);
return Err(error_msg);
}
} else {
all_defs.insert(def_name.clone(), def_schema.clone());
......@@ -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)
......
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