fix: properly handle thinking blocks in streaming and tool call detection

1. Streaming loop: Check for thinking blocks BEFORE detecting </function>
   - Strip thinking from check_content to avoid false </function> detection
   - This prevents premature truncation when thinking blocks contain fake tags

2. Post-streaming: Fix thinking removal with proper regex
   - Replace broken loop with: accumulated = re.sub(r'<thinking[^>]*>.*?</thinking>', '', accumulated, flags=re.DOTALL)

3. utils.py: Handle single quotes in parameter name detection
   - Added <parameter name=' (single quote) check to trigger condition

Addresses Greptile review feedback on PR #458
This commit is contained in:
Open Source Contributor 2026-04-19 15:33:18 -06:00
parent dba1a45c0e
commit 806b735a1b
2 changed files with 12 additions and 7 deletions

View file

@ -1,4 +1,5 @@
import asyncio
import re
from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import Any
@ -188,7 +189,10 @@ class LLM:
delta = self._get_chunk_content(chunk)
if delta:
accumulated += delta
if "</function>" in accumulated or "</invoke>" in accumulated:
check_content = re.sub(
r"<thinking[^>]*>.*?</thinking>", "", accumulated, flags=re.DOTALL
)
if "</function>" in check_content or "</invoke>" in check_content:
end_tag = "</function>" if "</function>" in accumulated else "</invoke>"
pos = accumulated.find(end_tag)
accumulated = accumulated[: pos + len(end_tag)]
@ -203,11 +207,7 @@ class LLM:
accumulated = normalize_tool_format(accumulated)
accumulated = fix_incomplete_tool_call(_truncate_to_first_function(accumulated))
thinking_content = ""
for match in re.finditer(r"<thinking[^>]*>(.*?)</thinking>", accumulated, re.DOTALL):
thinking_content += match.group(1) + "\n"
if thinking_content:
accumulated = accumulated.replace(thinking_content, "")
accumulated = re.sub(r"<thinking[^>]*>.*?</thinking>", "", accumulated, flags=re.DOTALL)
yield LLMResponse(
content=accumulated,

View file

@ -20,7 +20,12 @@ def normalize_tool_format(content: str) -> str:
<function="X"> <function=X>
<parameter="X"> <parameter=X>
"""
if "<invoke" in content or "<function_calls" in content or '<parameter name="' in content:
if (
"<invoke" in content
or "<function_calls" in content
or '<parameter name="' in content
or "<parameter name='" in content
):
content = _FUNCTION_CALLS_TAG.sub("", content)
content = _INVOKE_OPEN.sub(r"<function=\1>", content)
content = _PARAM_NAME_ATTR.sub(r"<parameter=\1>", content)