Strip Tatlock <think> reasoning before TTS and history
Test, Build and Push / test-gateway (push) Successful in 10s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped

Live smoke test showed /v1/chat/completions replies open with the
Steward's <think> block (Open WebUI convention) — without stripping,
the device would speak the internal monologue aloud.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 18:15:36 +02:00
co-authored by Claude Fable 5
parent 22f00fda0f
commit 15b8a1b900
2 changed files with 21 additions and 1 deletions
+11 -1
View File
@@ -1,9 +1,19 @@
"""Client for the Tatlock butler's OpenAI-compatible chat API."""
import re
import httpx
from .config import settings
# Tatlock prefixes replies with <think> reasoning (Open WebUI convention).
# Strip it: it must never reach TTS or the on-screen reply text.
_THINK_RE = re.compile(r"<think>.*?</think>\s*", re.DOTALL)
def strip_reasoning(text: str) -> str:
return _THINK_RE.sub("", text).strip()
class TatlockClient:
def __init__(self, base_url: str | None = None) -> None:
@@ -25,7 +35,7 @@ class TatlockClient:
},
)
response.raise_for_status()
reply = response.json()["choices"][0]["message"]["content"]
reply = strip_reasoning(response.json()["choices"][0]["message"]["content"])
self._history.append({"role": "assistant", "content": reply})
return reply
+10
View File
@@ -0,0 +1,10 @@
from desklock_gateway.tatlock import strip_reasoning
def test_strip_reasoning_removes_think_block() -> None:
raw = "<think>\nDELEGATE: none\nREASON: simple\n</think>\nGood evening. I am Tatlock."
assert strip_reasoning(raw) == "Good evening. I am Tatlock."
def test_strip_reasoning_passthrough_without_think() -> None:
assert strip_reasoning("Just an answer.") == "Just an answer."