Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
7202ced7
Unverified
Commit
7202ced7
authored
Feb 09, 2026
by
Vladislav Nosivskoy
Committed by
GitHub
Feb 09, 2026
Browse files
fix: DeepSeek V3.2 chat template FC and SO support (#6034)
Signed-off-by:
Vladislav Nosivskoy
<
vladnosiv@gmail.com
>
parent
4520b8f6
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
463 additions
and
3 deletions
+463
-3
lib/llm/src/preprocessor/prompt.rs
lib/llm/src/preprocessor/prompt.rs
+3
-0
lib/llm/src/preprocessor/prompt/deepseek_v32.rs
lib/llm/src/preprocessor/prompt/deepseek_v32.rs
+453
-3
lib/llm/src/preprocessor/prompt/template/oai.rs
lib/llm/src/preprocessor/prompt/template/oai.rs
+7
-0
No files found.
lib/llm/src/preprocessor/prompt.rs
View file @
7202ced7
...
@@ -63,6 +63,9 @@ pub trait OAIChatLikeRequest {
...
@@ -63,6 +63,9 @@ pub trait OAIChatLikeRequest {
fn
tool_choice
(
&
self
)
->
Option
<
Value
>
{
fn
tool_choice
(
&
self
)
->
Option
<
Value
>
{
None
None
}
}
fn
response_format
(
&
self
)
->
Option
<
Value
>
{
None
}
fn
should_add_generation_prompt
(
&
self
)
->
bool
;
fn
should_add_generation_prompt
(
&
self
)
->
bool
;
...
...
lib/llm/src/preprocessor/prompt/deepseek_v32.rs
View file @
7202ced7
...
@@ -445,13 +445,64 @@ impl super::OAIPromptFormatter for DeepSeekV32Formatter {
...
@@ -445,13 +445,64 @@ impl super::OAIPromptFormatter for DeepSeekV32Formatter {
let
messages_json
=
let
messages_json
=
serde_json
::
to_value
(
&
messages_value
)
.context
(
"Failed to convert messages to JSON"
)
?
;
serde_json
::
to_value
(
&
messages_value
)
.context
(
"Failed to convert messages to JSON"
)
?
;
let
messages_array
=
messages_json
let
mut
messages_array
=
messages_json
.as_array
()
.as_array
()
.context
(
"Messages is not an array"
)
?
;
.context
(
"Messages is not an array"
)
?
.clone
();
// Inject tools and response_format from request into the first system message
// DeepSeek V3.2 expects these to be part of the system message for prompt rendering
let
tools_json
=
req
.tools
()
.map
(|
t
|
serde_json
::
to_value
(
&
t
))
.transpose
()
.context
(
"Failed to convert tools to JSON"
)
?
;
let
response_format_json
=
req
.response_format
()
.map
(|
rf
|
serde_json
::
to_value
(
&
rf
))
.transpose
()
.context
(
"Failed to convert response_format to JSON"
)
?
;
if
tools_json
.is_some
()
||
response_format_json
.is_some
()
{
// Find or create system message
let
system_idx
=
messages_array
.iter
()
.position
(|
msg
|
msg
.get
(
"role"
)
.and_then
(|
r
|
r
.as_str
())
==
Some
(
"system"
));
if
let
Some
(
idx
)
=
system_idx
{
// Add to existing system message
if
let
Some
(
msg
)
=
messages_array
.get_mut
(
idx
)
&&
let
Some
(
obj
)
=
msg
.as_object_mut
()
{
if
let
Some
(
tools
)
=
tools_json
{
obj
.insert
(
"tools"
.to_string
(),
tools
);
}
if
let
Some
(
rf
)
=
response_format_json
{
obj
.insert
(
"response_format"
.to_string
(),
rf
);
}
}
}
else
{
// Create a system message if none exists
let
mut
system_msg
=
serde_json
::
json!
({
"role"
:
"system"
,
"content"
:
""
});
if
let
Some
(
obj
)
=
system_msg
.as_object_mut
()
{
if
let
Some
(
tools
)
=
tools_json
{
obj
.insert
(
"tools"
.to_string
(),
tools
);
}
if
let
Some
(
rf
)
=
response_format_json
{
obj
.insert
(
"response_format"
.to_string
(),
rf
);
}
}
messages_array
.insert
(
0
,
system_msg
);
}
}
// Encode with native implementation
// Encode with native implementation
encode_messages
(
encode_messages
(
messages_array
,
&
messages_array
,
self
.thinking_mode
,
self
.thinking_mode
,
true
,
// always add BOS token
true
,
// always add BOS token
)
)
...
@@ -511,4 +562,403 @@ mod tests {
...
@@ -511,4 +562,403 @@ mod tests {
assert
!
(
result
.contains
(
"get_weather"
));
assert
!
(
result
.contains
(
"get_weather"
));
assert
!
(
result
.contains
(
"<functions>"
));
assert
!
(
result
.contains
(
"<functions>"
));
}
}
// Mock request for testing OAIPromptFormatter implementation
struct
MockRequest
{
messages
:
JsonValue
,
tools
:
Option
<
JsonValue
>
,
response_format
:
Option
<
JsonValue
>
,
}
impl
MockRequest
{
fn
new
(
messages
:
JsonValue
)
->
Self
{
Self
{
messages
,
tools
:
None
,
response_format
:
None
,
}
}
fn
with_tools
(
mut
self
,
tools
:
JsonValue
)
->
Self
{
self
.tools
=
Some
(
tools
);
self
}
fn
with_response_format
(
mut
self
,
response_format
:
JsonValue
)
->
Self
{
self
.response_format
=
Some
(
response_format
);
self
}
}
impl
super
::
super
::
OAIChatLikeRequest
for
MockRequest
{
fn
model
(
&
self
)
->
String
{
"deepseek-v3.2"
.to_string
()
}
fn
messages
(
&
self
)
->
minijinja
::
value
::
Value
{
minijinja
::
value
::
Value
::
from_serialize
(
&
self
.messages
)
}
fn
tools
(
&
self
)
->
Option
<
minijinja
::
value
::
Value
>
{
self
.tools
.as_ref
()
.map
(
minijinja
::
value
::
Value
::
from_serialize
)
}
fn
response_format
(
&
self
)
->
Option
<
minijinja
::
value
::
Value
>
{
self
.response_format
.as_ref
()
.map
(
minijinja
::
value
::
Value
::
from_serialize
)
}
fn
should_add_generation_prompt
(
&
self
)
->
bool
{
true
}
}
#[test]
fn
test_formatter_injects_tools_into_existing_system_message
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
tools
=
json!
([{
"type"
:
"function"
,
"function"
:
{
"name"
:
"get_weather"
,
"description"
:
"Get current weather"
,
"parameters"
:
{
"type"
:
"object"
,
"properties"
:
{
"location"
:
{
"type"
:
"string"
,
"description"
:
"City name"
},
"unit"
:
{
"type"
:
"string"
,
"enum"
:
[
"celsius"
,
"fahrenheit"
]}
},
"required"
:
[
"location"
]
}
}
}]);
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are a helpful assistant."
},
{
"role"
:
"user"
,
"content"
:
"What's the weather in Moscow?"
}
]))
.with_tools
(
tools
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify tools were injected into the prompt
assert
!
(
result
.contains
(
"## Tools"
),
"Should contain Tools section header"
);
assert
!
(
result
.contains
(
"get_weather"
),
"Should contain function name"
);
assert
!
(
result
.contains
(
"<functions>"
),
"Should contain functions block"
);
assert
!
(
result
.contains
(
"</functions>"
),
"Should contain closing functions tag"
);
assert
!
(
result
.contains
(
"You are a helpful assistant."
),
"Should preserve original system content"
);
assert
!
(
result
.contains
(
&
format!
(
"<{}function_calls>"
,
tokens
::
DSML_TOKEN
)),
"Should contain DSML format instructions"
);
}
#[test]
fn
test_formatter_creates_system_message_for_tools_when_missing
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
tools
=
json!
([{
"type"
:
"function"
,
"function"
:
{
"name"
:
"get_current_time"
,
"description"
:
"Get current time in a timezone"
,
"parameters"
:
{
"type"
:
"object"
,
"properties"
:
{
"timezone"
:
{
"type"
:
"string"
}
},
"required"
:
[
"timezone"
]
}
}
}]);
// Request without system message
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"user"
,
"content"
:
"What time is it in Tokyo?"
}
]))
.with_tools
(
tools
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify tools were injected via auto-created system message
assert
!
(
result
.contains
(
"## Tools"
),
"Should contain Tools section even without explicit system message"
);
assert
!
(
result
.contains
(
"get_current_time"
),
"Should contain function name"
);
assert
!
(
result
.contains
(
"<functions>"
),
"Should contain functions block"
);
}
#[test]
fn
test_formatter_without_tools_does_not_add_tools_section
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are a helpful assistant."
},
{
"role"
:
"user"
,
"content"
:
"Hello!"
}
]));
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify no tools section was added
assert
!
(
!
result
.contains
(
"## Tools"
),
"Should not contain Tools section when no tools provided"
);
assert
!
(
!
result
.contains
(
"<functions>"
),
"Should not contain functions block when no tools provided"
);
assert
!
(
result
.contains
(
"You are a helpful assistant."
),
"Should preserve system content"
);
}
#[test]
fn
test_formatter_with_multiple_tools
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
tools
=
json!
([
{
"type"
:
"function"
,
"function"
:
{
"name"
:
"get_weather"
,
"description"
:
"Get current weather"
,
"parameters"
:
{
"type"
:
"object"
,
"properties"
:
{
"location"
:
{
"type"
:
"string"
}
}
}
}
},
{
"type"
:
"function"
,
"function"
:
{
"name"
:
"get_current_time"
,
"description"
:
"Get current time"
,
"parameters"
:
{
"type"
:
"object"
,
"properties"
:
{
"timezone"
:
{
"type"
:
"string"
}
}
}
}
}
]);
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are helpful."
},
{
"role"
:
"user"
,
"content"
:
"Weather and time in Moscow?"
}
]))
.with_tools
(
tools
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify both tools are present
assert
!
(
result
.contains
(
"get_weather"
),
"Should contain first function"
);
assert
!
(
result
.contains
(
"get_current_time"
),
"Should contain second function"
);
}
// ==================== Structured Output Tests ====================
#[test]
fn
test_formatter_injects_response_format_into_existing_system_message
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
response_format
=
json!
({
"type"
:
"json_schema"
,
"json_schema"
:
{
"name"
:
"city_info"
,
"strict"
:
true
,
"schema"
:
{
"type"
:
"object"
,
"properties"
:
{
"city"
:
{
"type"
:
"string"
},
"country"
:
{
"type"
:
"string"
},
"population"
:
{
"type"
:
"number"
}
},
"required"
:
[
"city"
,
"country"
,
"population"
]
}
}
});
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are a helpful assistant."
},
{
"role"
:
"user"
,
"content"
:
"Tell me about Moscow."
}
]))
.with_response_format
(
response_format
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify response format was injected into the prompt
assert
!
(
result
.contains
(
"## Response Format:"
),
"Should contain Response Format section header"
);
assert
!
(
result
.contains
(
"json_schema"
),
"Should contain json_schema type"
);
assert
!
(
result
.contains
(
"city_info"
),
"Should contain schema name"
);
assert
!
(
result
.contains
(
"You are a helpful assistant."
),
"Should preserve original system content"
);
}
#[test]
fn
test_formatter_creates_system_message_for_response_format_when_missing
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
response_format
=
json!
({
"type"
:
"json_schema"
,
"json_schema"
:
{
"name"
:
"weather_response"
,
"schema"
:
{
"type"
:
"object"
,
"properties"
:
{
"temperature"
:
{
"type"
:
"number"
},
"conditions"
:
{
"type"
:
"string"
}
}
}
}
});
// Request without system message
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"user"
,
"content"
:
"What's the weather?"
}
]))
.with_response_format
(
response_format
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify response format was injected via auto-created system message
assert
!
(
result
.contains
(
"## Response Format:"
),
"Should contain Response Format section even without explicit system message"
);
assert
!
(
result
.contains
(
"weather_response"
),
"Should contain schema name"
);
}
#[test]
fn
test_formatter_with_both_tools_and_response_format
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
tools
=
json!
([{
"type"
:
"function"
,
"function"
:
{
"name"
:
"search_database"
,
"description"
:
"Search the database"
,
"parameters"
:
{
"type"
:
"object"
,
"properties"
:
{
"query"
:
{
"type"
:
"string"
}
}
}
}
}]);
let
response_format
=
json!
({
"type"
:
"json_schema"
,
"json_schema"
:
{
"name"
:
"search_result"
,
"schema"
:
{
"type"
:
"object"
,
"properties"
:
{
"results"
:
{
"type"
:
"array"
},
"total_count"
:
{
"type"
:
"number"
}
}
}
}
});
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are a search assistant."
},
{
"role"
:
"user"
,
"content"
:
"Find documents about Rust."
}
]))
.with_tools
(
tools
)
.with_response_format
(
response_format
);
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify both tools and response format are present
assert
!
(
result
.contains
(
"## Tools"
),
"Should contain Tools section"
);
assert
!
(
result
.contains
(
"search_database"
),
"Should contain function name"
);
assert
!
(
result
.contains
(
"## Response Format:"
),
"Should contain Response Format section"
);
assert
!
(
result
.contains
(
"search_result"
),
"Should contain schema name"
);
assert
!
(
result
.contains
(
"You are a search assistant."
),
"Should preserve original system content"
);
}
#[test]
fn
test_formatter_without_response_format_does_not_add_response_format_section
()
{
use
super
::
super
::
OAIPromptFormatter
;
let
request
=
MockRequest
::
new
(
json!
([
{
"role"
:
"system"
,
"content"
:
"You are a helpful assistant."
},
{
"role"
:
"user"
,
"content"
:
"Hello!"
}
]));
let
formatter
=
DeepSeekV32Formatter
::
new_thinking
();
let
result
=
formatter
.render
(
&
request
)
.unwrap
();
// Verify no response format section was added
assert
!
(
!
result
.contains
(
"## Response Format:"
),
"Should not contain Response Format section when not provided"
);
}
}
}
lib/llm/src/preprocessor/prompt/template/oai.rs
View file @
7202ced7
...
@@ -238,6 +238,13 @@ impl OAIChatLikeRequest for NvCreateChatCompletionRequest {
...
@@ -238,6 +238,13 @@ impl OAIChatLikeRequest for NvCreateChatCompletionRequest {
}
}
}
}
fn
response_format
(
&
self
)
->
Option
<
Value
>
{
self
.inner
.response_format
.as_ref
()
.map
(
Value
::
from_serialize
)
}
fn
should_add_generation_prompt
(
&
self
)
->
bool
{
fn
should_add_generation_prompt
(
&
self
)
->
bool
{
// Using vLLM default behavior
// Using vLLM default behavior
true
true
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment