package renderers import ( "encoding/json" "fmt" "sort" "strings" "github.com/ollama/ollama/api" ) const ( olmo3DefaultSystemMessage = "You are a helpful function-calling AI assistant. " olmo3NoFunctionsMessage = "You do not currently have access to any functions. " olmo3WithFunctionsMessage = "You are provided with function signatures within XML tags. You may call one or more functions to assist with the user query. Output any function calls within XML tags. Do not make assumptions about what values to plug into functions." ) type Olmo3Renderer struct{} func (r *Olmo3Renderer) Render(messages []api.Message, tools []api.Tool, _ *api.ThinkValue) (string, error) { var sb strings.Builder var systemMessage *api.Message filteredMessages := make([]api.Message, 0, len(messages)) for i, message := range messages { if message.Role == "system" { if systemMessage == nil { systemMessage = &messages[i] } continue } filteredMessages = append(filteredMessages, message) } // Render system message if systemMessage != nil { // Custom system message - single newline after "system" sb.WriteString("<|im_start|>system\n") sb.WriteString(systemMessage.Content) if len(tools) > 0 { functionsJSON, err := marshalWithSpaces(tools) if err != nil { return "", err } sb.WriteString("") sb.WriteString(string(functionsJSON)) sb.WriteString("") } sb.WriteString("<|im_end|>\n") } else { // Default system message - single newline after "system" sb.WriteString("<|im_start|>system\n") sb.WriteString(olmo3DefaultSystemMessage) if len(tools) > 0 { functionsJSON, err := marshalWithSpaces(tools) if err != nil { return "", err } sb.WriteString(olmo3WithFunctionsMessage) sb.WriteString("") sb.WriteString(string(functionsJSON)) sb.WriteString("") } else { sb.WriteString(olmo3NoFunctionsMessage) sb.WriteString("") } sb.WriteString("<|im_end|>\n") } for i, message := range filteredMessages { lastMessage := i == len(filteredMessages)-1 switch message.Role { case "user": sb.WriteString("<|im_start|>user\n") sb.WriteString(message.Content) sb.WriteString("<|im_end|>\n") case "assistant": sb.WriteString("<|im_start|>assistant\n") if message.Content != "" { sb.WriteString(message.Content) } if len(message.ToolCalls) > 0 { sb.WriteString("") for j, tc := range message.ToolCalls { // Format as function_name(arg1="value1", arg2="value2") sb.WriteString(tc.Function.Name) sb.WriteString("(") // Get sorted keys for deterministic output keys := make([]string, 0, len(tc.Function.Arguments)) for k := range tc.Function.Arguments { keys = append(keys, k) } sort.Strings(keys) for k, key := range keys { if k > 0 { sb.WriteString(", ") } value, err := json.Marshal(tc.Function.Arguments[key]) if err != nil { return "", err } sb.WriteString(fmt.Sprintf("%s=%s", key, string(value))) } sb.WriteString(")") if j < len(message.ToolCalls)-1 { sb.WriteString("\n") } } sb.WriteString("") } // Add end tag unless it's the last message with content only (prefill) if !lastMessage || len(message.ToolCalls) > 0 { sb.WriteString("<|im_end|>\n") } case "tool": sb.WriteString("<|im_start|>environment\n") sb.WriteString(message.Content) sb.WriteString("<|im_end|>\n") } } // Add generation prompt if needed needsGenerationPrompt := true if len(filteredMessages) > 0 { lastMsg := filteredMessages[len(filteredMessages)-1] if lastMsg.Role == "assistant" && len(lastMsg.ToolCalls) == 0 && lastMsg.Content != "" { needsGenerationPrompt = false } } if needsGenerationPrompt { sb.WriteString("<|im_start|>assistant\n\n") } return sb.String(), nil }