mirror of
https://github.com/ollama/ollama.git
synced 2026-01-19 12:57:56 -05:00
Compare commits
2 Commits
parth/decr
...
jmorganca/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ed7c7a4f | ||
|
|
9ff8e5a64d |
219
model/parsers/glm46.go
Normal file
219
model/parsers/glm46.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
"github.com/ollama/ollama/logutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
glm46CollectingContent glm46ParserState = iota
|
||||||
|
CollectingThinkingContent
|
||||||
|
CollectingToolContent
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
thinkingCloseTag = "</think>"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(gguo): add a field for isThinking
|
||||||
|
type GLM46Parser struct {
|
||||||
|
state qwenParserState
|
||||||
|
buffer strings.Builder
|
||||||
|
tools []api.Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLM46Parser) HasToolSupport() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gguo): changes this to reference an objects param
|
||||||
|
func (p *GLM46Parser) HasThinkingSupport() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLM46Parser) Init(tools []api.Tool, lastMessage *api.Message) []api.Tool {
|
||||||
|
p.tools = tools
|
||||||
|
// p.state = p.initialState()
|
||||||
|
return tools
|
||||||
|
}
|
||||||
|
|
||||||
|
type glm46EventThinkingContent struct {
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (glm46EventThinkingContent) isGLM46Event() {}
|
||||||
|
|
||||||
|
func (p *GLM46Parser) Add(s string, done bool) (content string, thinking string, calls []api.ToolCall, err error) {
|
||||||
|
p.buffer.WriteString(s)
|
||||||
|
events := p.parseEvents()
|
||||||
|
|
||||||
|
var toolCalls []api.ToolCall
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, event := range events {
|
||||||
|
switch event := event.(type) {
|
||||||
|
case glm46EventRawToolCall:
|
||||||
|
toolCall, err := parseJSONToolCall(event, p.tools)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("qwen tool call parsing failed", "error", err)
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
toolCalls = append(toolCalls, toolCall)
|
||||||
|
case glm46EventThinkingContent:
|
||||||
|
sb.WriteString(event.content)
|
||||||
|
case glm46EventContent:
|
||||||
|
// TODO(drifkin): if the same turn contains multiple interleaved content
|
||||||
|
// events, we naively append them together here.
|
||||||
|
sb.WriteString(event.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String(), "", toolCalls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLM46Parser) parseEvents() []glm46Event {
|
||||||
|
var all []glm46Event
|
||||||
|
|
||||||
|
keepLooping := true
|
||||||
|
for keepLooping {
|
||||||
|
var events []glm46Event
|
||||||
|
events, keepLooping = p.eat()
|
||||||
|
if len(events) > 0 {
|
||||||
|
all = append(all, events...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(all) > 0 {
|
||||||
|
slog.Log(context.TODO(), logutil.LevelTrace, "qwen events parsed", "events", all, "state", p.state, "buffer", p.buffer.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return all
|
||||||
|
}
|
||||||
|
|
||||||
|
func emitContentBeforeTag(p *GLM46Parser, events []glm46Event, tag string) []glm46Event {
|
||||||
|
split := strings.SplitN(p.buffer.String(), tag, 2)
|
||||||
|
before := split[0]
|
||||||
|
before = strings.TrimRightFunc(before, unicode.IsSpace)
|
||||||
|
if len(before) > 0 {
|
||||||
|
events = append(events, glm46EventContent{content: before})
|
||||||
|
}
|
||||||
|
after := split[1]
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(after)
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLM46Parser) eat() ([]glm46Event, bool) {
|
||||||
|
var events []glm46Event
|
||||||
|
|
||||||
|
switch p.state {
|
||||||
|
case glm46CollectingContent:
|
||||||
|
if strings.Contains(p.buffer.String(), toolOpenTag) {
|
||||||
|
events = emitContentBeforeTag(p, events, toolOpenTag)
|
||||||
|
p.state = glm46CollectingToolContent
|
||||||
|
return events, true
|
||||||
|
} else if overlapLen := overlap(p.buffer.String(), toolOpenTag); overlapLen > 0 {
|
||||||
|
beforePartialTag := p.buffer.String()[:len(p.buffer.String())-overlapLen]
|
||||||
|
trailingWhitespaceLen := trailingWhitespaceLen(beforePartialTag)
|
||||||
|
ambiguousStart := len(beforePartialTag) - trailingWhitespaceLen
|
||||||
|
|
||||||
|
unambiguous := p.buffer.String()[:ambiguousStart]
|
||||||
|
ambiguous := p.buffer.String()[ambiguousStart:]
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(ambiguous)
|
||||||
|
if len(unambiguous) > 0 { // why does qwen3coder not have this here
|
||||||
|
events = append(events, glm46EventContent{content: unambiguous})
|
||||||
|
}
|
||||||
|
return events, false
|
||||||
|
} else {
|
||||||
|
whitespaceLen := trailingWhitespaceLen(p.buffer.String())
|
||||||
|
ambiguousStart := len(p.buffer.String()) - whitespaceLen
|
||||||
|
|
||||||
|
unambiguous := p.buffer.String()[:ambiguousStart]
|
||||||
|
ambiguous := p.buffer.String()[ambiguousStart:]
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(ambiguous)
|
||||||
|
if len(unambiguous) > 0 {
|
||||||
|
events = append(events, glm46EventContent{content: unambiguous})
|
||||||
|
}
|
||||||
|
return events, false
|
||||||
|
}
|
||||||
|
case CollectingToolContent:
|
||||||
|
if strings.Contains(p.buffer.String(), glm46ToolCloseTag) {
|
||||||
|
split := strings.SplitN(p.buffer.String(), toolCloseTag, 2)
|
||||||
|
before := split[0]
|
||||||
|
if len(before) == 0 {
|
||||||
|
slog.Warn("qwen tool call closing tag found but no content before it")
|
||||||
|
}
|
||||||
|
|
||||||
|
after := strings.TrimLeftFunc(split[1], unicode.IsSpace)
|
||||||
|
events = append(events, glm46EventRawToolCall{raw: before})
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(after)
|
||||||
|
p.state = glm46CollectingContent
|
||||||
|
return events, true
|
||||||
|
} else {
|
||||||
|
return events, false
|
||||||
|
}
|
||||||
|
case glm46CollectingThinkingContent: // so we want to hip the unambiguous stuff
|
||||||
|
if strings.Contains(p.buffer.String(), thinkingCloseTag) {
|
||||||
|
split := strings.SplitN(p.buffer.String(), thinkingCloseTag, 2)
|
||||||
|
before := split[0]
|
||||||
|
if len(before) == 0 {
|
||||||
|
slog.Warn("qwen tool call closing tag found but no content before it")
|
||||||
|
}
|
||||||
|
after := strings.TrimLeftFunc(split[1], unicode.IsSpace)
|
||||||
|
if len(before) > 0 {
|
||||||
|
events = append(events, glm46EventThinkingContent{content: before})
|
||||||
|
}
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(after)
|
||||||
|
p.state = glm46CollectingContent
|
||||||
|
return events, true
|
||||||
|
} else if overlapLen := overlap(p.buffer.String(), thinkingCloseTag); overlapLen > 0 { // we see part of a close thinking tag
|
||||||
|
beforePartialTag := p.buffer.String()[:len(p.buffer.String())-overlapLen]
|
||||||
|
trailingWhitespaceLen := trailingWhitespaceLen(beforePartialTag)
|
||||||
|
ambiguousStart := len(beforePartialTag) - trailingWhitespaceLen
|
||||||
|
|
||||||
|
unambiguous := p.buffer.String()[:ambiguousStart]
|
||||||
|
ambiguous := p.buffer.String()[ambiguousStart:]
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(ambiguous)
|
||||||
|
if len(unambiguous) > 0 {
|
||||||
|
events = append(events, glm46EventThinkingContent{content: unambiguous})
|
||||||
|
}
|
||||||
|
return events, false
|
||||||
|
} else {
|
||||||
|
whitespaceLen := trailingWhitespaceLen(p.buffer.String())
|
||||||
|
ambiguousStart := len(p.buffer.String()) - whitespaceLen
|
||||||
|
|
||||||
|
unambiguous := p.buffer.String()[:ambiguousStart]
|
||||||
|
ambiguous := p.buffer.String()[ambiguousStart:]
|
||||||
|
p.buffer.Reset()
|
||||||
|
p.buffer.WriteString(ambiguous)
|
||||||
|
if len(unambiguous) > 0 {
|
||||||
|
events = append(events, glm46EventThinkingContent{content: unambiguous})
|
||||||
|
}
|
||||||
|
return events, false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJSONToolCall(raw glm46EventRawToolCall, tools []api.Tool) (api.ToolCall, error) {
|
||||||
|
var toolCallFunction api.ToolCallFunction
|
||||||
|
if err := json.Unmarshal([]byte(raw.raw), &toolCallFunction); err != nil {
|
||||||
|
return api.ToolCall{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toolCall := api.ToolCall{}
|
||||||
|
toolCall.Function = toolCallFunction
|
||||||
|
|
||||||
|
return toolCall, nil
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ func ParserForName(name string) Parser {
|
|||||||
case "qwen3-coder":
|
case "qwen3-coder":
|
||||||
parser := &Qwen3CoderParser{}
|
parser := &Qwen3CoderParser{}
|
||||||
return parser
|
return parser
|
||||||
|
case "glm-4.6":
|
||||||
|
parser := &GLM46Parser{}
|
||||||
|
return parser
|
||||||
case "passthrough":
|
case "passthrough":
|
||||||
return &PassthroughParser{}
|
return &PassthroughParser{}
|
||||||
case "harmony":
|
case "harmony":
|
||||||
|
|||||||
239
model/renderers/glm46_test.go
Normal file
239
model/renderers/glm46_test.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
package renderers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGLM46Renderer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
messages []api.Message
|
||||||
|
tools []api.Tool
|
||||||
|
thinkValue *api.ThinkValue
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
},
|
||||||
|
expected: `[gMASK]<sop><|user|>
|
||||||
|
Hello, how are you?<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic with system message",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant."},
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
},
|
||||||
|
expected: `[gMASK]<sop><|system|>
|
||||||
|
You are a helpful assistant.<|user|>
|
||||||
|
Hello, how are you?<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic with user assistant user",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "user", Content: "What is the capital of France?"},
|
||||||
|
{Role: "assistant", Thinking: "Let me analyze the request...", Content: "The capital of France is Paris."},
|
||||||
|
{Role: "user", Content: "Fantastic!"},
|
||||||
|
},
|
||||||
|
expected: `[gMASK]<sop><|user|>
|
||||||
|
What is the capital of France?<|assistant|>
|
||||||
|
The capital of France is Paris.<|user|>
|
||||||
|
Fantastic!<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tools",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant with access to tools."},
|
||||||
|
{Role: "user", Content: "What is the weather like in Tokyo?"},
|
||||||
|
},
|
||||||
|
tools: []api.Tool{
|
||||||
|
{
|
||||||
|
Type: "function",
|
||||||
|
Function: api.ToolFunction{
|
||||||
|
Name: "get_weather",
|
||||||
|
Description: "Get the current weather in a given location",
|
||||||
|
Parameters: api.ToolFunctionParameters{
|
||||||
|
Type: "object",
|
||||||
|
Required: []string{"location"},
|
||||||
|
Properties: map[string]api.ToolProperty{
|
||||||
|
"location": {
|
||||||
|
Type: api.PropertyType{"string"},
|
||||||
|
Description: "The city and state, e.g. San Francisco, CA",
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
Type: api.PropertyType{"string"},
|
||||||
|
Enum: []any{"celsius", "fahrenheit"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `[gMASK]<sop><|system|>
|
||||||
|
# Tools
|
||||||
|
|
||||||
|
You may call one or more functions to assist with the user query.
|
||||||
|
|
||||||
|
You are provided with function signatures within <tools></tools> XML tags:
|
||||||
|
<tools>
|
||||||
|
{"type":"function","function":{"name":"get_weather","description":"Get the current weather in a given location","parameters":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"},"unit":{"type":"string","description":"","enum":["celsius","fahrenheit"]}}}}}
|
||||||
|
</tools>
|
||||||
|
|
||||||
|
For each function call, output the function name and arguments within the following XML format:
|
||||||
|
<tool_call>{function-name}
|
||||||
|
<arg_key>{arg-key-1}</arg_key>
|
||||||
|
<arg_value>{arg-value-1}</arg_value>
|
||||||
|
<arg_key>{arg-key-2}</arg_key>
|
||||||
|
<arg_value>{arg-value-2}</arg_value>
|
||||||
|
...
|
||||||
|
</tool_call><|system|>
|
||||||
|
You are a helpful assistant with access to tools.<|user|>
|
||||||
|
What is the weather like in Tokyo?<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tool calls",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "system", Content: "You are a helpful assistant with access to tools."},
|
||||||
|
{Role: "user", Content: "What is the weather like in Tokyo?"},
|
||||||
|
{
|
||||||
|
Role: "assistant",
|
||||||
|
ToolCalls: []api.ToolCall{
|
||||||
|
{
|
||||||
|
Function: api.ToolCallFunction{
|
||||||
|
Name: "get_weather",
|
||||||
|
Arguments: api.ToolCallFunctionArguments{
|
||||||
|
"location": "Tokyo, Japan",
|
||||||
|
"unit": "celsius",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Function: api.ToolCallFunction{
|
||||||
|
Name: "get_weather",
|
||||||
|
Arguments: api.ToolCallFunctionArguments{
|
||||||
|
"location": "Japan",
|
||||||
|
"unit": "fahrenheit",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "tool",
|
||||||
|
Content: "{\"temperature\": 22, \"weather\": \"partly cloudy\", \"humidity\": 65}",
|
||||||
|
ToolName: "get_weather",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "tool",
|
||||||
|
Content: "{\"temperature\": 68, \"weather\": \"sunny\", \"humidity\": 75}",
|
||||||
|
ToolName: "get_weather",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "assistant",
|
||||||
|
Content: "The weather in Tokyo is currently partly cloudy with a temperature of 22°C and 65% humidity. It's a pleasant day with moderate temperatures.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tools: []api.Tool{
|
||||||
|
{
|
||||||
|
Type: "function",
|
||||||
|
Function: api.ToolFunction{
|
||||||
|
Name: "get_weather",
|
||||||
|
Description: "Get the current weather in a given location",
|
||||||
|
Parameters: api.ToolFunctionParameters{
|
||||||
|
Type: "object",
|
||||||
|
Required: []string{"location"},
|
||||||
|
Properties: map[string]api.ToolProperty{
|
||||||
|
"location": {
|
||||||
|
Type: api.PropertyType{"string"},
|
||||||
|
Description: "The city and state, e.g. San Francisco, CA",
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
Type: api.PropertyType{"string"},
|
||||||
|
Enum: []any{"celsius", "fahrenheit"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `[gMASK]<sop><|system|>
|
||||||
|
# Tools
|
||||||
|
|
||||||
|
You may call one or more functions to assist with the user query.
|
||||||
|
|
||||||
|
You are provided with function signatures within <tools></tools> XML tags:
|
||||||
|
<tools>
|
||||||
|
{"type":"function","function":{"name":"get_weather","description":"Get the current weather in a given location","parameters":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"},"unit":{"type":"string","description":"","enum":["celsius","fahrenheit"]}}}}}
|
||||||
|
</tools>
|
||||||
|
|
||||||
|
For each function call, output the function name and arguments within the following XML format:
|
||||||
|
<tool_call>{function-name}
|
||||||
|
<arg_key>{arg-key-1}</arg_key>
|
||||||
|
<arg_value>{arg-value-1}</arg_value>
|
||||||
|
<arg_key>{arg-key-2}</arg_key>
|
||||||
|
<arg_value>{arg-value-2}</arg_value>
|
||||||
|
...
|
||||||
|
</tool_call><|system|>
|
||||||
|
You are a helpful assistant with access to tools.<|user|>
|
||||||
|
What is the weather like in Tokyo?<|assistant|>
|
||||||
|
<think></think>
|
||||||
|
<tool_call>get_weather
|
||||||
|
<arg_key>location</arg_key>
|
||||||
|
<arg_value>Tokyo, Japan</arg_value>
|
||||||
|
<arg_key>unit</arg_key>
|
||||||
|
<arg_value>celsius</arg_value>
|
||||||
|
</tool_call>
|
||||||
|
<tool_call>get_weather
|
||||||
|
<arg_key>location</arg_key>
|
||||||
|
<arg_value>Japan</arg_value>
|
||||||
|
<arg_key>unit</arg_key>
|
||||||
|
<arg_value>fahrenheit</arg_value>
|
||||||
|
</tool_call><|observation|>
|
||||||
|
<tool_response>
|
||||||
|
{"temperature": 22, "weather": "partly cloudy", "humidity": 65}
|
||||||
|
</tool_response>
|
||||||
|
<tool_response>
|
||||||
|
{"temperature": 68, "weather": "sunny", "humidity": 75}
|
||||||
|
</tool_response><|assistant|>
|
||||||
|
<think></think>
|
||||||
|
The weather in Tokyo is currently partly cloudy with a temperature of 22°C and 65% humidity. It's a pleasant day with moderate temperatures.<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "think true",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
},
|
||||||
|
thinkValue: &api.ThinkValue{Value: true},
|
||||||
|
expected: `[gMASK]<sop><|user|>
|
||||||
|
Hello, how are you?<|assistant|>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "think false",
|
||||||
|
messages: []api.Message{
|
||||||
|
{Role: "user", Content: "Hello, how are you?"},
|
||||||
|
},
|
||||||
|
thinkValue: &api.ThinkValue{Value: false},
|
||||||
|
expected: `[gMASK]<sop><|user|>
|
||||||
|
Hello, how are you?/nothink<|assistant|>
|
||||||
|
<think></think>`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rendered, err := GLM46Renderer(tt.messages, tt.tools, tt.thinkValue)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(rendered, tt.expected); diff != "" {
|
||||||
|
t.Errorf("mismatch (-got +want):\n%s", diff)
|
||||||
|
t.Logf("Got:\n%s", rendered)
|
||||||
|
t.Logf("Expected:\n%s", tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
109
model/renderers/gml46.go
Normal file
109
model/renderers/gml46.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package renderers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GLM46Renderer(messages []api.Message, tools []api.Tool, thinkValue *api.ThinkValue) (string, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString("[gMASK]<sop>")
|
||||||
|
|
||||||
|
var lastUserIndex int
|
||||||
|
for i, message := range messages {
|
||||||
|
if message.Role == "user" {
|
||||||
|
lastUserIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tools) > 0 {
|
||||||
|
sb.WriteString("<|system|>\n")
|
||||||
|
sb.WriteString("# Tools\n\n")
|
||||||
|
sb.WriteString("You may call one or more functions to assist with the user query.\n\n")
|
||||||
|
sb.WriteString("You are provided with function signatures within <tools></tools> XML tags:\n")
|
||||||
|
sb.WriteString("<tools>\n")
|
||||||
|
for _, tool := range tools {
|
||||||
|
d, _ := json.Marshal(tool)
|
||||||
|
sb.WriteString(string(d) + "\n")
|
||||||
|
}
|
||||||
|
sb.WriteString("</tools>\n\n")
|
||||||
|
sb.WriteString("For each function call, output the function name and arguments within the following XML format:\n")
|
||||||
|
sb.WriteString("<tool_call>{function-name}\n")
|
||||||
|
sb.WriteString("<arg_key>{arg-key-1}</arg_key>\n")
|
||||||
|
sb.WriteString("<arg_value>{arg-value-1}</arg_value>\n")
|
||||||
|
sb.WriteString("<arg_key>{arg-key-2}</arg_key>\n")
|
||||||
|
sb.WriteString("<arg_value>{arg-value-2}</arg_value>\n")
|
||||||
|
sb.WriteString("...\n")
|
||||||
|
sb.WriteString("</tool_call>")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, message := range messages {
|
||||||
|
switch message.Role {
|
||||||
|
case "user":
|
||||||
|
sb.WriteString("<|user|>\n")
|
||||||
|
sb.WriteString(message.Content)
|
||||||
|
if thinkValue != nil && !thinkValue.Bool() && !strings.HasSuffix(message.Content, "/nothink") {
|
||||||
|
sb.WriteString("/nothink")
|
||||||
|
}
|
||||||
|
case "assistant":
|
||||||
|
sb.WriteString("<|assistant|>")
|
||||||
|
if i > lastUserIndex {
|
||||||
|
if message.Thinking != "" {
|
||||||
|
sb.WriteString("\n<think>" + message.Thinking + "</think>")
|
||||||
|
} else {
|
||||||
|
sb.WriteString("\n<think></think>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if message.Content != "" {
|
||||||
|
sb.WriteString("\n" + message.Content)
|
||||||
|
}
|
||||||
|
if len(message.ToolCalls) > 0 {
|
||||||
|
for _, toolCall := range message.ToolCalls {
|
||||||
|
sb.WriteString("\n<tool_call>" + toolCall.Function.Name + "\n")
|
||||||
|
for key, value := range toolCall.Function.Arguments {
|
||||||
|
sb.WriteString("<arg_key>" + key + "</arg_key>\n")
|
||||||
|
|
||||||
|
var valueStr string
|
||||||
|
if str, ok := value.(string); ok {
|
||||||
|
valueStr = str
|
||||||
|
} else {
|
||||||
|
jsonBytes, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
valueStr = fmt.Sprintf("%v", value)
|
||||||
|
} else {
|
||||||
|
valueStr = string(jsonBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("<arg_value>" + valueStr + "</arg_value>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("</tool_call>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "tool":
|
||||||
|
if i == 0 || messages[i-1].Role != "tool" {
|
||||||
|
sb.WriteString("<|observation|>")
|
||||||
|
}
|
||||||
|
sb.WriteString("\n<tool_response>\n")
|
||||||
|
sb.WriteString(message.Content)
|
||||||
|
sb.WriteString("\n</tool_response>")
|
||||||
|
case "system":
|
||||||
|
sb.WriteString("<|system|>\n")
|
||||||
|
sb.WriteString(message.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add generation prompt
|
||||||
|
sb.WriteString("<|assistant|>")
|
||||||
|
fmt.Println("thinkValue", thinkValue, thinkValue.Bool())
|
||||||
|
if thinkValue != nil && !thinkValue.Bool() {
|
||||||
|
sb.WriteString("\n<think></think>")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ func rendererForName(name string) rendererFunc {
|
|||||||
switch name {
|
switch name {
|
||||||
case "qwen3-coder":
|
case "qwen3-coder":
|
||||||
return Qwen3CoderRenderer
|
return Qwen3CoderRenderer
|
||||||
|
case "glm-4.6":
|
||||||
|
return GLM46Renderer
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user