mirror of
https://github.com/deepseek-ai/deepseek-recipe.git
synced 2026-10-28 17:17:08 +08:00
Keep historical Responses tool names distinct from namespace aliases
This commit is contained in:
parent
935817c66f
commit
32ca266005
@ -35,8 +35,9 @@
|
||||
//! output is placed right after that output. For V4, a namespace tool is named
|
||||
//! `{namespace}__{name}`, or `{namespace}{name}` when the namespace ends with
|
||||
//! or the name starts with an underscore, and `apply_patch` uses the V4
|
||||
//! description. The caller supplies model inference, tool execution, and HTTP
|
||||
//! transport.
|
||||
//! description. Flattened namespace names also avoid top-level names found in
|
||||
//! historical calls, including calls to tools no longer declared in the request.
|
||||
//! The caller supplies model inference, tool execution, and HTTP transport.
|
||||
//!
|
||||
//! [`WebSearchBehavior::Reject`]: crate::request::WebSearchBehavior::Reject
|
||||
//! [`ModelVersion::DeepseekV41Flash`]:
|
||||
|
||||
@ -83,6 +83,21 @@ impl ResponsesToolSet {
|
||||
model_version,
|
||||
response_options: ResponseOptions::default(),
|
||||
};
|
||||
if let Some(ResponsesInput::Items(items)) = input {
|
||||
converted
|
||||
.top_level_names
|
||||
.extend(items.iter().filter_map(|item| match item {
|
||||
ResponsesInputItem::Typed(ResponsesTypedInputItem::FunctionCall(call))
|
||||
if call.namespace.is_none() =>
|
||||
{
|
||||
Some(call.name.clone())
|
||||
}
|
||||
ResponsesInputItem::Typed(ResponsesTypedInputItem::CustomToolCall(call)) => {
|
||||
Some(call.name.clone())
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
}
|
||||
for (item_index, tools) in groups {
|
||||
let start = converted.client_tools.len();
|
||||
for (index, tool) in tools.into_iter().enumerate() {
|
||||
|
||||
139
deepseek-recipe/tests/responses_history.rs
Normal file
139
deepseek-recipe/tests/responses_history.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use deepseek_recipe::openai::ResponsesRequest;
|
||||
use deepseek_recipe::request::{ConversionOptions, ProtocolRequest};
|
||||
use deepseek_recipe_core::messages::InputMessage;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn convert_history(
|
||||
model: &str,
|
||||
bare_name: &str,
|
||||
reversed: bool,
|
||||
declared: bool,
|
||||
) -> (HashMap<String, String>, Vec<String>) {
|
||||
let mut calls = vec![
|
||||
json!({"type": "function_call", "call_id": "namespaced", "namespace": "ns", "name": "run", "arguments": "{}"}),
|
||||
json!({"type": "function_call", "call_id": "bare", "name": bare_name, "arguments": "{}"}),
|
||||
];
|
||||
if reversed {
|
||||
calls.reverse();
|
||||
}
|
||||
calls.extend([
|
||||
json!({"type": "function_call_output", "call_id": "namespaced", "output": "namespace result"}),
|
||||
json!({"type": "function_call_output", "call_id": "bare", "output": "top-level result"}),
|
||||
json!({"role": "user", "content": "continue"}),
|
||||
]);
|
||||
let tools: Vec<Value> = if declared {
|
||||
vec![
|
||||
json!({"type": "namespace", "name": "ns", "tools": [{"type": "function", "name": "run"}]}),
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let request: ResponsesRequest = serde_json::from_value(json!({
|
||||
"model": model,
|
||||
"reasoning": {"effort": "none"},
|
||||
"input": calls,
|
||||
"tools": tools,
|
||||
}))
|
||||
.unwrap();
|
||||
let converted = request.convert(ConversionOptions::default()).unwrap();
|
||||
let mut names = HashMap::new();
|
||||
for message in &converted.conversation.messages {
|
||||
if let InputMessage::Assistant {
|
||||
tool_calls: Some(calls),
|
||||
..
|
||||
} = message
|
||||
{
|
||||
for call in calls {
|
||||
names.insert(call.id.clone(), call.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if declared {
|
||||
let mapped = converted
|
||||
.response_options
|
||||
.namespace_tool_map
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
mapped.get(&names["namespaced"]),
|
||||
Some(&("ns".to_owned(), "run".to_owned()))
|
||||
);
|
||||
}
|
||||
let declarations = converted
|
||||
.conversation
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| tool.name.clone())
|
||||
.collect();
|
||||
(names, declarations)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_historical_custom_call_reserves_its_original_name() {
|
||||
let request: ResponsesRequest = serde_json::from_value(json!({
|
||||
"model": "DeepSeek-V4-Pro",
|
||||
"reasoning": {"effort": "none"},
|
||||
"tools": [{"type": "namespace", "name": "apply_", "tools": [{"type": "function", "name": "patch"}]}],
|
||||
"input": [
|
||||
{"type": "custom_tool_call", "call_id": "old_patch", "name": "apply_patch", "input": "old patch"},
|
||||
{"type": "custom_tool_call_output", "call_id": "old_patch", "output": "done"},
|
||||
{"role": "user", "content": "continue"}
|
||||
]
|
||||
})).unwrap();
|
||||
let converted = request.convert(ConversionOptions::default()).unwrap();
|
||||
assert_ne!(converted.conversation.tools[0].name, "apply_patch");
|
||||
let historical = converted
|
||||
.conversation
|
||||
.messages
|
||||
.iter()
|
||||
.find_map(|message| {
|
||||
if let InputMessage::Assistant {
|
||||
tool_calls: Some(calls),
|
||||
..
|
||||
} = message
|
||||
{
|
||||
calls.first()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(historical.name, "apply_patch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undeclared_historical_namespace_does_not_alias_a_bare_tool() {
|
||||
for (model, bare) in [
|
||||
("DeepSeek-V4-Pro", "ns__run"),
|
||||
("DeepSeek-V4.1-Flash", "ns::run"),
|
||||
] {
|
||||
for reversed in [false, true] {
|
||||
let (names, _) = convert_history(model, bare, reversed, false);
|
||||
assert_eq!(names["bare"], bare);
|
||||
assert_ne!(
|
||||
names["namespaced"], names["bare"],
|
||||
"{model}, reversed={reversed}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_namespace_reserves_historical_bare_names_before_flattening() {
|
||||
for (model, bare) in [
|
||||
("DeepSeek-V4-Pro", "ns__run"),
|
||||
("DeepSeek-V4.1-Flash", "ns::run"),
|
||||
] {
|
||||
for reversed in [false, true] {
|
||||
let (names, declarations) = convert_history(model, bare, reversed, true);
|
||||
assert_eq!(names["bare"], bare);
|
||||
assert_ne!(
|
||||
names["namespaced"], names["bare"],
|
||||
"{model}, reversed={reversed}"
|
||||
);
|
||||
assert_eq!(declarations, vec![names["namespaced"].clone()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user