@@ -554,35 +554,64 @@ func getScopeOrToolStart(format *XMLToolCallFormat) string {
554554 return format .ToolStart
555555}
556556
557+ // ParseResult holds tool calls and any non-tool-call content extracted by the parser.
558+ type ParseResult struct {
559+ ToolCalls []FuncCallResults
560+ Content string
561+ }
562+
557563// tryParseXMLFromScopeStart finds the first occurrence of scopeStart (or format.ToolStart),
558- // splits the input there, and parses only the suffix as XML tool calls. Returns (toolCalls , true)
559- // if any tool calls were parsed, else (nil , false). This mimics llama.cpp's PEG order so that
564+ // splits the input there, and parses only the suffix as XML tool calls. Returns (result , true)
565+ // if any tool calls were parsed, else (empty , false). This mimics llama.cpp's PEG order so that
560566// reasoning or content before the tool block does not cause "whitespace only before scope" to fail.
561- func tryParseXMLFromScopeStart (s string , format * XMLToolCallFormat , isPartial bool ) ([] FuncCallResults , bool ) {
567+ func tryParseXMLFromScopeStart (s string , format * XMLToolCallFormat , isPartial bool ) (ParseResult , bool ) {
562568 if format == nil {
563- return nil , false
569+ return ParseResult {} , false
564570 }
565571 scopeStart := getScopeOrToolStart (format )
566572 if scopeStart == "" {
567- return nil , false
573+ return ParseResult {} , false
568574 }
569575 idx := strings .Index (s , scopeStart )
570576 if idx < 0 {
571- return nil , false
577+ return ParseResult {} , false
572578 }
573579 toolCallsPart := s [idx :]
574580 parser := NewChatMsgParser (toolCallsPart , isPartial )
575581 success , err := parser .TryConsumeXMLToolCalls (format )
576582 if err != nil {
577583 if _ , ok := err .(* ChatMsgPartialException ); ok && isPartial {
578- return parser .ToolCalls (), len (parser .ToolCalls ()) > 0
584+ tc := parser .ToolCalls ()
585+ if len (tc ) > 0 {
586+ return ParseResult {ToolCalls : tc , Content : buildContent (s [:idx ], parser )}, true
587+ }
579588 }
580- return nil , false
589+ return ParseResult {} , false
581590 }
582591 if success && len (parser .ToolCalls ()) > 0 {
583- return parser .ToolCalls (), true
592+ return ParseResult {
593+ ToolCalls : parser .ToolCalls (),
594+ Content : buildContent (s [:idx ], parser ),
595+ }, true
596+ }
597+ return ParseResult {}, false
598+ }
599+
600+ // buildContent assembles the non-tool-call content from the text before the tool
601+ // block, any content tracked by the parser, and any unconsumed trailing text.
602+ func buildContent (before string , parser * ChatMsgParser ) string {
603+ var parts []string
604+ if b := strings .TrimSpace (before ); b != "" {
605+ parts = append (parts , b )
606+ }
607+ if pc := strings .TrimSpace (parser .Content ()); pc != "" {
608+ parts = append (parts , pc )
609+ }
610+ remaining := parser .Input ()[parser .Pos ():]
611+ if t := strings .TrimSpace (remaining ); t != "" {
612+ parts = append (parts , t )
584613 }
585- return nil , false
614+ return strings . Join ( parts , " " )
586615}
587616
588617// ParseXMLIterative parses XML tool calls using the iterative parser
@@ -592,15 +621,15 @@ func tryParseXMLFromScopeStart(s string, format *XMLToolCallFormat, isPartial bo
592621func ParseXMLIterative (s string , format * XMLToolCallFormat , isPartial bool ) ([]FuncCallResults , error ) {
593622 // Try split-on-scope first so reasoning/content before tool block is skipped
594623 if format != nil {
595- if results , ok := tryParseXMLFromScopeStart (s , format , isPartial ); ok {
596- return results , nil
624+ if pr , ok := tryParseXMLFromScopeStart (s , format , isPartial ); ok {
625+ return pr . ToolCalls , nil
597626 }
598627 } else {
599628 formats := getAllXMLFormats ()
600629 for _ , fmtPreset := range formats {
601630 if fmtPreset .format != nil {
602- if results , ok := tryParseXMLFromScopeStart (s , fmtPreset .format , isPartial ); ok {
603- return results , nil
631+ if pr , ok := tryParseXMLFromScopeStart (s , fmtPreset .format , isPartial ); ok {
632+ return pr . ToolCalls , nil
604633 }
605634 }
606635 }
0 commit comments