fix(llm): add stop_sequences parity for tool completions (#1170)

* fix(llm): add stop_sequences parity for tool completions

* refactor(web-openai): dedupe request builders and satisfy no-panics gate

* test(llm): mark multiline assert with safety comment for CI gate

* test(llm): make safety-marked assert formatting-stable
This commit is contained in:
Nige
2026-03-14 13:06:48 -07:00
committed by GitHub
parent cc52a046c1
commit ffe384b66e
7 changed files with 81 additions and 51 deletions
+5 -2
View File
@@ -176,8 +176,11 @@ impl LlmProvider for BedrockProvider {
builder = builder.tool_config(tc);
}
if let Some(config) = build_inference_config(request.temperature, request.max_tokens, None)
{
if let Some(config) = build_inference_config(
request.temperature,
request.max_tokens,
request.stop_sequences.as_deref(),
) {
builder = builder.inference_config(config);
}
+6
View File
@@ -475,6 +475,7 @@ impl LlmProvider for NearAiChatProvider {
messages,
temperature: req.temperature,
max_tokens: req.max_tokens,
stop: req.stop_sequences,
tools: None,
tool_choice: None,
};
@@ -554,6 +555,7 @@ impl LlmProvider for NearAiChatProvider {
messages,
temperature: req.temperature,
max_tokens: req.max_tokens,
stop: req.stop_sequences,
tools: if tools.is_empty() { None } else { Some(tools) },
tool_choice: req.tool_choice,
};
@@ -680,6 +682,8 @@ struct ChatCompletionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
tools: Option<Vec<ChatCompletionTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_choice: Option<String>,
@@ -1666,6 +1670,7 @@ mod tests {
}],
temperature: None,
max_tokens: None,
stop: None,
tools: None,
tool_choice: None,
};
@@ -1687,6 +1692,7 @@ mod tests {
messages: vec![],
temperature: Some(0.7),
max_tokens: Some(1024),
stop: None,
tools: Some(vec![ChatCompletionTool {
tool_type: "function".to_string(),
function: ChatCompletionFunction {
+24 -3
View File
@@ -251,6 +251,7 @@ pub struct ToolCompletionRequest {
pub model: Option<String>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub stop_sequences: Option<Vec<String>>,
/// How to handle tool use: "auto", "required", or "none".
pub tool_choice: Option<String>,
/// Opaque metadata passed through to the provider (e.g. thread_id for chaining).
@@ -266,6 +267,7 @@ impl ToolCompletionRequest {
model: None,
max_tokens: None,
temperature: None,
stop_sequences: None,
tool_choice: None,
metadata: std::collections::HashMap::new(),
}
@@ -289,6 +291,12 @@ impl ToolCompletionRequest {
self
}
/// Set stop sequences.
pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = Some(stop_sequences);
self
}
/// Set tool choice mode.
pub fn with_tool_choice(mut self, choice: impl Into<String>) -> Self {
self.tool_choice = Some(choice.into());
@@ -504,8 +512,6 @@ pub fn strip_unsupported_completion_params(
/// This is the single helper function used by all providers to remove
/// parameters they don't support from tool calls, replacing duplicate stringly-typed logic.
///
/// Note: Only `Temperature` and `MaxTokens` are supported in `ToolCompletionRequest`.
/// `StopSequences` is only available in `CompletionRequest` and is not applicable to tool calls.
pub fn strip_unsupported_tool_params(
unsupported: &std::collections::HashSet<String>,
req: &mut ToolCompletionRequest,
@@ -519,7 +525,9 @@ pub fn strip_unsupported_tool_params(
if unsupported.contains(UnsupportedParam::MaxTokens.name()) {
req.max_tokens = None;
}
// Note: StopSequences is not a field in ToolCompletionRequest, so no action needed
if unsupported.contains(UnsupportedParam::StopSequences.name()) {
req.stop_sequences = None;
}
}
#[cfg(test)]
@@ -651,4 +659,17 @@ mod tests {
assert!(messages[2].tool_call_id.is_none());
assert!(messages[2].name.is_none());
}
#[test]
fn test_strip_unsupported_tool_params_strips_stop_sequences() {
let mut unsupported = std::collections::HashSet::new();
unsupported.insert(UnsupportedParam::StopSequences.name().to_string());
let mut req = ToolCompletionRequest::new(vec![ChatMessage::user("hello")], vec![]);
req.stop_sequences = Some(vec!["STOP".to_string()]);
strip_unsupported_tool_params(&unsupported, &mut req);
assert!(req.stop_sequences.is_none()); // safety: test assertion for explicit strip behavior
}
}
+1
View File
@@ -548,6 +548,7 @@ mod tests {
model: None,
max_tokens: None,
temperature: None,
stop_sequences: None,
tool_choice: None,
metadata: Default::default(),
};