package openai
import (
"fmt"
"html"
"regexp"
"strings"
)
func strictOutputContractInstruction(policy strictOutputPolicy) string {
if !policy.ContractInstruction {
return ""
}
resultTag := policy.XMLResultTag
if resultTag == "" {
resultTag = "result"
}
return fmt.Sprintf("Strict output contract: follow the client's XML tool protocol. When you complete the task, output exactly one <%s> block and no text outside that XML block. The <%s> content must be the direct final answer that should be shown to the end user. Do not summarize that you answered, completed, explained, greeted, or performed the task. Do not write phrases like \"I completed\", \"I explained\", \"I greeted\", \"summary\", or \"main points\" unless the user explicitly asks for a summary. Put the actual user-facing response inside <%s>.", policy.XMLCompletionTool, resultTag, resultTag)
}
func prependSystemMessage(messages []chatMessage, content string) []chatMessage {
out := make([]chatMessage, 0, len(messages)+1)
out = append(out, chatMessage{Role: "system", Content: content})
out = append(out, messages...)
return out
}
func normalizeCompletionOutput(policy strictOutputPolicy, content, reasoning string, preserveReasoning bool) (string, string, bool) {
var sanitized bool
content, sanitized = sanitizeKnownChatTemplateSentinels(content)
reasoning, reasoningSanitized := sanitizeKnownChatTemplateSentinels(reasoning)
sanitized = sanitized || reasoningSanitized
if !policy.Strict {
return content, reasoning, sanitized
}
normalized := normalizeStrictAgentContent(content)
if strings.TrimSpace(normalized) == "" && strings.TrimSpace(reasoning) != "" {
normalized = normalizeStrictAgentContent(reasoning)
}
if strings.TrimSpace(normalized) == "" {
normalized = "Model returned no assistant content."
}
if policy.XMLCompletionTool != "" && !startsWithXMLRoot(normalized) {
normalized = wrapXMLCompletion(policy, normalized)
}
if preserveReasoning {
return normalized, reasoning, sanitized || normalized != content
}
return normalized, "", sanitized || normalized != content || reasoning != ""
}
var knownChatTemplateSentinels = []string{
"<|mask_end|>",
}
func sanitizeKnownChatTemplateSentinels(s string) (string, bool) {
cleaned := s
for _, token := range knownChatTemplateSentinels {
cleaned = strings.ReplaceAll(cleaned, token, "")
}
if cleaned == s {
return s, false
}
return strings.TrimSpace(cleaned), true
}
type streamSentinelFilter struct {
pending string
}
func (f *streamSentinelFilter) Append(delta string) string {
if delta == "" {
return ""
}
f.pending += delta
flushLen := streamSentinelSafeFlushLen(f.pending)
out := f.pending[:flushLen]
f.pending = f.pending[flushLen:]
out, _ = sanitizeKnownChatTemplateSentinels(out)
return out
}
func (f *streamSentinelFilter) Flush() string {
out := f.pending
f.pending = ""
if isKnownSentinelPrefix(out) {
return ""
}
out, _ = sanitizeKnownChatTemplateSentinels(out)
return out
}
func streamSentinelSafeFlushLen(s string) int {
hold := 0
for _, token := range knownChatTemplateSentinels {
max := len(token) - 1
if max > len(s) {
max = len(s)
}
for n := max; n > 0; n-- {
if strings.HasSuffix(s, token[:n]) {
if n > hold {
hold = n
}
break
}
}
}
return len(s) - hold
}
func isKnownSentinelPrefix(s string) bool {
if s == "" {
return false
}
for _, token := range knownChatTemplateSentinels {
if strings.HasPrefix(token, s) {
return true
}
}
return false
}
var (
agentThinkingBlockRE = regexp.MustCompile(`(?is)]*>.*?`)
agentCodeFenceLineRE = regexp.MustCompile(`(?m)^\s*` + "```" + `[A-Za-z0-9_-]*\s*$`)
agentXMLOpenTagRE = regexp.MustCompile(`(?is)<\s*([A-Za-z_][A-Za-z0-9_.:-]*)\b[^>]*>`)
agentXMLCloseTagRE = regexp.MustCompile(`(?is)\s*([A-Za-z_][A-Za-z0-9_.:\s-]*)\s*>`)
completedUseToolRE = regexp.MustCompile("(?is)completed.{0,300}?use\\s+(?:the\\s+)?`?([A-Za-z_][A-Za-z0-9_.:-]*)`?\\s+tool")
xmlToolWithResultREPattern = `(?is)<\s*%s\b[^>]*>\s*<\s*([A-Za-z_][A-Za-z0-9_.:-]*)\b`
)
func normalizeStrictAgentContent(content string) string {
cleaned := strings.TrimSpace(content)
cleaned = agentThinkingBlockRE.ReplaceAllString(cleaned, "")
cleaned = agentCodeFenceLineRE.ReplaceAllString(cleaned, "")
cleaned = strings.TrimSpace(cleaned)
for strings.HasPrefix(cleaned, "---") {
cleaned = strings.TrimSpace(strings.TrimPrefix(cleaned, "---"))
}
cleaned = strings.TrimSpace(cleaned)
if block, ok := firstXMLRootBlock(cleaned); ok {
return block
}
return cleaned
}
func startsWithXMLRoot(content string) bool {
trimmed := strings.TrimSpace(content)
open := agentXMLOpenTagRE.FindStringIndex(trimmed)
return open != nil && open[0] == 0
}
func inferXMLCompletionContract(prompt string) (string, string) {
tool := ""
if match := completedUseToolRE.FindStringSubmatch(prompt); len(match) == 2 {
tool = match[1]
}
if tool == "" {
return "", ""
}
resultTag := inferXMLResultTag(prompt, tool)
if resultTag == "" {
resultTag = "result"
}
return tool, resultTag
}
func inferXMLResultTag(prompt, tool string) string {
toolPattern := regexp.QuoteMeta(tool)
re := regexp.MustCompile(fmt.Sprintf(xmlToolWithResultREPattern, toolPattern))
if match := re.FindStringSubmatch(prompt); len(match) == 2 {
return match[1]
}
return ""
}
func wrapXMLCompletion(policy strictOutputPolicy, content string) string {
resultTag := policy.XMLResultTag
if resultTag == "" {
resultTag = "result"
}
return "<" + policy.XMLCompletionTool + ">\n<" + resultTag + ">" + html.EscapeString(strings.TrimSpace(content)) + "" + resultTag + ">\n" + policy.XMLCompletionTool + ">"
}
func firstXMLRootBlock(content string) (string, bool) {
open := agentXMLOpenTagRE.FindStringSubmatchIndex(content)
if open == nil {
return "", false
}
root := content[open[2]:open[3]]
search := content[open[1]:]
for _, close := range agentXMLCloseTagRE.FindAllStringSubmatchIndex(search, -1) {
name := search[close[2]:close[3]]
if canonicalXMLName(name) != canonicalXMLName(root) {
continue
}
closeStart := open[1] + close[0]
block := content[open[0]:closeStart] + "" + root + ">"
return strings.TrimSpace(block), true
}
return "", false
}
func canonicalXMLName(name string) string {
var b strings.Builder
for _, r := range strings.ToLower(name) {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
b.WriteRune(r)
}
}
return b.String()
}