From 7cce5aac76898099b954ee91f748f052d23e3253 Mon Sep 17 00:00:00 2001 From: Parth Sareen Date: Thu, 21 Aug 2025 13:56:22 -0700 Subject: [PATCH] harmony: move harmony parsing into a package (#12016) --- {server => harmony}/harmonyparser.go | 25 ++++----------- {server => harmony}/harmonyparser_test.go | 2 +- server/routes.go | 37 +++++++++++++++-------- 3 files changed, 32 insertions(+), 32 deletions(-) rename {server => harmony}/harmonyparser.go (95%) rename {server => harmony}/harmonyparser_test.go (99%) diff --git a/server/harmonyparser.go b/harmony/harmonyparser.go similarity index 95% rename from server/harmonyparser.go rename to harmony/harmonyparser.go index 4405cea440..e724966146 100644 --- a/server/harmonyparser.go +++ b/harmony/harmonyparser.go @@ -1,10 +1,9 @@ -package server +package harmony import ( "context" "fmt" "log/slog" - "slices" "strings" "unicode" @@ -20,18 +19,6 @@ const ( 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 { switch s { // 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 type HarmonyMessageHandler struct { state harmonyMessageState - harmonyParser *HarmonyParser - functionNameMap *FunctionNameMap + HarmonyParser *HarmonyParser + FunctionNameMap *FunctionNameMap } // NewHarmonyMessageHandler creates a new message handler func NewHarmonyMessageHandler() *HarmonyMessageHandler { return &HarmonyMessageHandler{ state: harmonyMessageState_Normal, - harmonyParser: &HarmonyParser{ + HarmonyParser: &HarmonyParser{ MessageStartTag: "<|start|>", MessageEndTag: "<|end|>", HeaderEndTag: "<|message|>", }, - functionNameMap: NewFunctionNameMap(), + FunctionNameMap: NewFunctionNameMap(), } } @@ -301,7 +288,7 @@ func (h *HarmonyMessageHandler) AddContent(content string, toolParser *HarmonyTo thinkingSb := strings.Builder{} toolContentSb := strings.Builder{} - events := h.harmonyParser.AddContent(content) + events := h.HarmonyParser.AddContent(content) for _, event := range events { switch event := event.(type) { case HarmonyEventHeaderComplete: diff --git a/server/harmonyparser_test.go b/harmony/harmonyparser_test.go similarity index 99% rename from server/harmonyparser_test.go rename to harmony/harmonyparser_test.go index 8a22f34041..b988a018f3 100644 --- a/server/harmonyparser_test.go +++ b/harmony/harmonyparser_test.go @@ -1,4 +1,4 @@ -package server +package harmony import ( "fmt" diff --git a/server/routes.go b/server/routes.go index 60b7e3e841..cc8913537e 100644 --- a/server/routes.go +++ b/server/routes.go @@ -32,6 +32,7 @@ import ( "github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/format" "github.com/ollama/ollama/fs/ggml" + "github.com/ollama/ollama/harmony" "github.com/ollama/ollama/llm" "github.com/ollama/ollama/logutil" "github.com/ollama/ollama/openai" @@ -45,6 +46,18 @@ import ( "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 { return slices.Contains(strings.Split(os.Getenv("OLLAMA_EXPERIMENT"), ","), name) } @@ -194,12 +207,12 @@ func (s *Server) GenerateHandler(c *gin.Context) { return } - useHarmony := shouldUseHarmony(*m) && !req.Raw - var harmonyMessageHandler *HarmonyMessageHandler - var harmonyToolParser *HarmonyToolCallAccumulator + useHarmony := shouldUseHarmony(m) && !req.Raw + var harmonyMessageHandler *harmony.HarmonyMessageHandler + var harmonyToolParser *harmony.HarmonyToolCallAccumulator if useHarmony { - harmonyMessageHandler = NewHarmonyMessageHandler() - harmonyMessageHandler.harmonyParser.AddImplicitStart() + harmonyMessageHandler = harmony.NewHarmonyMessageHandler() + harmonyMessageHandler.HarmonyParser.AddImplicitStart() harmonyToolParser = harmonyMessageHandler.CreateToolParser() } @@ -1603,19 +1616,19 @@ func (s *Server) ChatHandler(c *gin.Context) { } msgs = filterThinkTags(msgs, m) - var harmonyMessageHandler *HarmonyMessageHandler - var harmonyToolParser *HarmonyToolCallAccumulator + var harmonyMessageHandler *harmony.HarmonyMessageHandler + var harmonyToolParser *harmony.HarmonyToolCallAccumulator - useHarmony := shouldUseHarmony(*m) + useHarmony := shouldUseHarmony(m) processedTools := req.Tools if useHarmony { - harmonyMessageHandler = NewHarmonyMessageHandler() + harmonyMessageHandler = harmony.NewHarmonyMessageHandler() var lastMessage *api.Message if len(msgs) > 0 { lastMessage = &msgs[len(msgs)-1] } - harmonyMessageHandler.harmonyParser.AddImplicitStartOrPrefill(lastMessage) + harmonyMessageHandler.HarmonyParser.AddImplicitStartOrPrefill(lastMessage) harmonyToolParser = harmonyMessageHandler.CreateToolParser() // 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)) copy(processedTools, req.Tools) 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() if toolName != nil { *toolName = strings.TrimPrefix(*toolName, "functions.") - *toolName = harmonyMessageHandler.functionNameMap.OriginalFromConverted(*toolName) + *toolName = harmonyMessageHandler.FunctionNameMap.OriginalFromConverted(*toolName) var args api.ToolCallFunctionArguments if err := json.Unmarshal([]byte(toolContent), &args); err != nil { errStr := fmt.Sprintf("error parsing tool call: raw='%s', err=%s", toolContent, err.Error())