Add DeepSeek3Renderer with variant support

- Add DeepSeek3Renderer struct with IsThinking and Variant fields
- Add Deepseek31 variant constant for DeepSeek v3.1 template support
- Include deepseek31.jinja template for proper rendering
- Update renderer factory to use new DeepSeek3Renderer structure
- All tests pass locally with new renderer implementation
This commit is contained in:
Grace Guo 2025-12-15 14:45:44 -08:00
parent 43ff6ba343
commit 786d9d06db
2 changed files with 203 additions and 0 deletions

View File

@ -0,0 +1,121 @@
package renderers
import (
"encoding/json"
"strings"
"github.com/ollama/ollama/api"
)
type DeepSeek3Variant int
const (
Deepseek31 DeepSeek3Variant = iota
)
type DeepSeek3Renderer struct {
IsThinking bool
Variant DeepSeek3Variant
}
func (r *DeepSeek3Renderer) Render(messages []api.Message, tools []api.Tool, thinkValue *api.ThinkValue) (string, error) {
var sb strings.Builder
// thinking is enabled: model must support it AND user must request it
thinking := r.IsThinking && (thinkValue != nil && thinkValue.Bool())
// extract system messages first
var systemPrompt strings.Builder
isFirstSystemPrompt := true
for _, message := range messages {
if message.Role == "system" {
if isFirstSystemPrompt {
systemPrompt.WriteString(message.Content)
isFirstSystemPrompt = false
} else {
systemPrompt.WriteString("\n\n" + message.Content)
}
}
}
sb.WriteString("<begin▁of▁sentence>" + systemPrompt.String())
// state tracking
isTool := false
isLastUser := false
for _, message := range messages {
switch message.Role {
case "user":
isTool = false
isLastUser = true
sb.WriteString("<User>" + message.Content)
case "assistant":
if len(message.ToolCalls) > 0 {
if isLastUser {
sb.WriteString("<Assistant></think>")
}
isLastUser = false
isTool = false
if message.Content != "" {
sb.WriteString(message.Content)
}
sb.WriteString("<tool▁calls▁begin>")
for _, toolCall := range message.ToolCalls {
sb.WriteString("<tool▁call▁begin>" + toolCall.Function.Name + "<tool▁sep>")
argsJSON, _ := json.Marshal(toolCall.Function.Arguments)
sb.WriteString(string(argsJSON))
sb.WriteString("<tool▁call▁end>")
}
sb.WriteString("<tool▁calls▁end><end▁of▁sentence>")
} else {
if isLastUser {
sb.WriteString("<Assistant>")
// message["prefix"] is defined and message["prefix"] and thinking
// message.Thinking != "" represents message["prefix"] being defined
if message.Thinking != "" && thinking {
sb.WriteString("<think>")
} else {
sb.WriteString("</think>")
}
}
isLastUser = false
content := message.Content
if isTool {
sb.WriteString(content + "<end▁of▁sentence>")
isTool = false
} else {
if strings.Contains(content, "</think>") {
parts := strings.SplitN(content, "</think>", 2)
if len(parts) > 1 {
content = parts[1]
}
}
sb.WriteString(content + "<end▁of▁sentence>")
}
}
case "tool":
isLastUser = false
isTool = true
sb.WriteString("<tool▁output▁begin>" + message.Content + "<tool▁output▁end>")
}
}
if isLastUser && !isTool {
sb.WriteString("<Assistant>")
if thinking {
sb.WriteString("<think>")
} else {
sb.WriteString("</think>")
}
}
return sb.String(), nil
}

View File

@ -0,0 +1,82 @@
{%- if not add_generation_prompt is defined -%}
{%- set add_generation_prompt = false -%}
{%- endif -%}
{%- if not thinking is defined -%}
{%- set thinking = false -%}
{%- endif -%}
{%- set ns = namespace(is_first=false, is_tool=false, system_prompt="", is_first_sp=true, is_last_user=false) -%}
{%- for message in messages -%}
{%- if message["role"] == "system" -%}
{%- if ns.is_first_sp -%}
{%- set ns.system_prompt = ns.system_prompt + message["content"] -%}
{%- set ns.is_first_sp = false -%}
{%- else -%}
{%- set ns.system_prompt = ns.system_prompt + "\n\n" + message["content"] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{- bos_token -}}
{{- ns.system_prompt -}}
{%- for message in messages -%}
{%- if message["role"] == "user" -%}
{%- set ns.is_tool = false -%}
{%- set ns.is_first = false -%}
{%- set ns.is_last_user = true -%}
{{- "<User>" + message["content"] -}}
{%- endif -%}
{%- if message["role"] == "assistant" and message["tool_calls"] is defined and message["tool_calls"] is not none -%}
{%- if ns.is_last_user -%}
{{- "<Assistant></think>" -}}
{%- endif -%}
{%- set ns.is_last_user = false -%}
{%- set ns.is_first = false -%}
{%- set ns.is_tool = false -%}
{%- for tool in message["tool_calls"] -%}
{%- if not ns.is_first -%}
{%- if message["content"] is none -%}
{{- "<tool▁calls▁begin><tool▁call▁begin>" + tool["function"]["name"] + "<tool▁sep>" + tool["function"]["arguments"] + "<tool▁call▁end>" -}}
{%- else -%}
{{- message["content"] + "<tool▁calls▁begin><tool▁call▁begin>" + tool["function"]["name"] + "<tool▁sep>" + tool["function"]["arguments"] + "<tool▁call▁end>" -}}
{%- endif -%}
{%- set ns.is_first = true -%}
{%- else -%}
{{- "<tool▁call▁begin>" + tool["function"]["name"] + "<tool▁sep>" + tool["function"]["arguments"] + "<tool▁call▁end>" -}}
{%- endif -%}
{%- endfor -%}
{{- "<tool▁calls▁end><end▁of▁sentence>" -}}
{%- endif -%}
{%- if message["role"] == "assistant" and (message["tool_calls"] is not defined or message["tool_calls"] is none) -%}
{%- if ns.is_last_user -%}
{{- "<Assistant>" -}}
{%- if message["prefix"] is defined and message["prefix"] and thinking -%}
{{- "<think>" -}}
{%- else -%}
{{- "</think>" -}}
{%- endif -%}
{%- endif -%}
{%- set ns.is_last_user = false -%}
{%- if ns.is_tool -%}
{{- message["content"] + "<end▁of▁sentence>" -}}
{%- set ns.is_tool = false -%}
{%- else -%}
{%- set content = message["content"] -%}
{%- if "</think>" in content -%}
{%- set content = content.split("</think>", 1)[1] -%}
{%- endif -%}
{{- content + "<end▁of▁sentence>" -}}
{%- endif -%}
{%- endif -%}
{%- if message["role"] == "tool" -%}
{%- set ns.is_last_user = false -%}
{%- set ns.is_tool = true -%}
{{- "<tool▁output▁begin>" + message["content"] + "<tool▁output▁end>" -}}
{%- endif -%}
{%- endfor -%}
{%- if add_generation_prompt and ns.is_last_user and not ns.is_tool -%}
{{- "<Assistant>" -}}
{%- if not thinking -%}
{{- "</think>" -}}
{%- else -%}
{{- "<think>" -}}
{%- endif -%}
{%- endif -%}