harmony: move harmony parsing into a package (#12016)

This commit is contained in:
Parth Sareen
2025-08-21 13:56:22 -07:00
committed by GitHub
parent 4ae4f47b16
commit 7cce5aac76
3 changed files with 32 additions and 32 deletions

View File

@@ -1,10 +1,9 @@
package server package harmony
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"slices"
"strings" "strings"
"unicode" "unicode"
@@ -20,18 +19,6 @@ const (
harmonyParserState_ParsingContent harmonyParserState_ParsingContent
) )
func shouldUseHarmony(model Model) bool {
if slices.Contains([]string{"gptoss", "gpt-oss"}, model.Config.ModelFamily) {
// heuristic to check whether the template expects to be parsed via harmony:
// search for harmony tags that are nearly always used
if model.Template.Contains("<|start|>") && model.Template.Contains("<|end|>") {
return true
}
}
return false
}
func (s harmonyParserState) String() string { func (s harmonyParserState) String() string {
switch s { switch s {
// we're looking for the message start tag // we're looking for the message start tag
@@ -277,20 +264,20 @@ const (
// This is a higher level interface that maps harmony concepts into ollama concepts // This is a higher level interface that maps harmony concepts into ollama concepts
type HarmonyMessageHandler struct { type HarmonyMessageHandler struct {
state harmonyMessageState state harmonyMessageState
harmonyParser *HarmonyParser HarmonyParser *HarmonyParser
functionNameMap *FunctionNameMap FunctionNameMap *FunctionNameMap
} }
// NewHarmonyMessageHandler creates a new message handler // NewHarmonyMessageHandler creates a new message handler
func NewHarmonyMessageHandler() *HarmonyMessageHandler { func NewHarmonyMessageHandler() *HarmonyMessageHandler {
return &HarmonyMessageHandler{ return &HarmonyMessageHandler{
state: harmonyMessageState_Normal, state: harmonyMessageState_Normal,
harmonyParser: &HarmonyParser{ HarmonyParser: &HarmonyParser{
MessageStartTag: "<|start|>", MessageStartTag: "<|start|>",
MessageEndTag: "<|end|>", MessageEndTag: "<|end|>",
HeaderEndTag: "<|message|>", HeaderEndTag: "<|message|>",
}, },
functionNameMap: NewFunctionNameMap(), FunctionNameMap: NewFunctionNameMap(),
} }
} }
@@ -301,7 +288,7 @@ func (h *HarmonyMessageHandler) AddContent(content string, toolParser *HarmonyTo
thinkingSb := strings.Builder{} thinkingSb := strings.Builder{}
toolContentSb := strings.Builder{} toolContentSb := strings.Builder{}
events := h.harmonyParser.AddContent(content) events := h.HarmonyParser.AddContent(content)
for _, event := range events { for _, event := range events {
switch event := event.(type) { switch event := event.(type) {
case HarmonyEventHeaderComplete: case HarmonyEventHeaderComplete:

View File

@@ -1,4 +1,4 @@
package server package harmony
import ( import (
"fmt" "fmt"

View File

@@ -32,6 +32,7 @@ import (
"github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/envconfig"
"github.com/ollama/ollama/format" "github.com/ollama/ollama/format"
"github.com/ollama/ollama/fs/ggml" "github.com/ollama/ollama/fs/ggml"
"github.com/ollama/ollama/harmony"
"github.com/ollama/ollama/llm" "github.com/ollama/ollama/llm"
"github.com/ollama/ollama/logutil" "github.com/ollama/ollama/logutil"
"github.com/ollama/ollama/openai" "github.com/ollama/ollama/openai"
@@ -45,6 +46,18 @@ import (
"github.com/ollama/ollama/version" "github.com/ollama/ollama/version"
) )
func shouldUseHarmony(model *Model) bool {
if slices.Contains([]string{"gptoss", "gpt-oss"}, model.Config.ModelFamily) {
// heuristic to check whether the template expects to be parsed via harmony:
// search for harmony tags that are nearly always used
if model.Template.Contains("<|start|>") && model.Template.Contains("<|end|>") {
return true
}
}
return false
}
func experimentEnabled(name string) bool { func experimentEnabled(name string) bool {
return slices.Contains(strings.Split(os.Getenv("OLLAMA_EXPERIMENT"), ","), name) return slices.Contains(strings.Split(os.Getenv("OLLAMA_EXPERIMENT"), ","), name)
} }
@@ -194,12 +207,12 @@ func (s *Server) GenerateHandler(c *gin.Context) {
return return
} }
useHarmony := shouldUseHarmony(*m) && !req.Raw useHarmony := shouldUseHarmony(m) && !req.Raw
var harmonyMessageHandler *HarmonyMessageHandler var harmonyMessageHandler *harmony.HarmonyMessageHandler
var harmonyToolParser *HarmonyToolCallAccumulator var harmonyToolParser *harmony.HarmonyToolCallAccumulator
if useHarmony { if useHarmony {
harmonyMessageHandler = NewHarmonyMessageHandler() harmonyMessageHandler = harmony.NewHarmonyMessageHandler()
harmonyMessageHandler.harmonyParser.AddImplicitStart() harmonyMessageHandler.HarmonyParser.AddImplicitStart()
harmonyToolParser = harmonyMessageHandler.CreateToolParser() harmonyToolParser = harmonyMessageHandler.CreateToolParser()
} }
@@ -1603,19 +1616,19 @@ func (s *Server) ChatHandler(c *gin.Context) {
} }
msgs = filterThinkTags(msgs, m) msgs = filterThinkTags(msgs, m)
var harmonyMessageHandler *HarmonyMessageHandler var harmonyMessageHandler *harmony.HarmonyMessageHandler
var harmonyToolParser *HarmonyToolCallAccumulator var harmonyToolParser *harmony.HarmonyToolCallAccumulator
useHarmony := shouldUseHarmony(*m) useHarmony := shouldUseHarmony(m)
processedTools := req.Tools processedTools := req.Tools
if useHarmony { if useHarmony {
harmonyMessageHandler = NewHarmonyMessageHandler() harmonyMessageHandler = harmony.NewHarmonyMessageHandler()
var lastMessage *api.Message var lastMessage *api.Message
if len(msgs) > 0 { if len(msgs) > 0 {
lastMessage = &msgs[len(msgs)-1] lastMessage = &msgs[len(msgs)-1]
} }
harmonyMessageHandler.harmonyParser.AddImplicitStartOrPrefill(lastMessage) harmonyMessageHandler.HarmonyParser.AddImplicitStartOrPrefill(lastMessage)
harmonyToolParser = harmonyMessageHandler.CreateToolParser() harmonyToolParser = harmonyMessageHandler.CreateToolParser()
// make a copy of tools to pass to the chat prompt. Function names may be // make a copy of tools to pass to the chat prompt. Function names may be
@@ -1623,7 +1636,7 @@ func (s *Server) ChatHandler(c *gin.Context) {
processedTools = make([]api.Tool, len(req.Tools)) processedTools = make([]api.Tool, len(req.Tools))
copy(processedTools, req.Tools) copy(processedTools, req.Tools)
for i, tool := range processedTools { for i, tool := range processedTools {
processedTools[i].Function.Name = harmonyMessageHandler.functionNameMap.ConvertAndAdd(tool.Function.Name) processedTools[i].Function.Name = harmonyMessageHandler.FunctionNameMap.ConvertAndAdd(tool.Function.Name)
} }
} }
@@ -1705,7 +1718,7 @@ func (s *Server) ChatHandler(c *gin.Context) {
toolName, toolContent := harmonyToolParser.Drain() toolName, toolContent := harmonyToolParser.Drain()
if toolName != nil { if toolName != nil {
*toolName = strings.TrimPrefix(*toolName, "functions.") *toolName = strings.TrimPrefix(*toolName, "functions.")
*toolName = harmonyMessageHandler.functionNameMap.OriginalFromConverted(*toolName) *toolName = harmonyMessageHandler.FunctionNameMap.OriginalFromConverted(*toolName)
var args api.ToolCallFunctionArguments var args api.ToolCallFunctionArguments
if err := json.Unmarshal([]byte(toolContent), &args); err != nil { if err := json.Unmarshal([]byte(toolContent), &args); err != nil {
errStr := fmt.Sprintf("error parsing tool call: raw='%s', err=%s", toolContent, err.Error()) errStr := fmt.Sprintf("error parsing tool call: raw='%s', err=%s", toolContent, err.Error())