CharacterChatBot / engine /parser.py
cereeenn120's picture
Add logger with verbosity
832430f
Raw History Blame Contribute Delete
3.09 kB
"""
Response parser β€” splits a raw LLM response into typed segments.
Segment types:
'dialogue' β€” plain spoken text
'action' β€” content inside <action>...</action>
'thought' β€” content inside <thought>...</thought>
Example:
segments = parse_response(
"<action>Looks around.</action>"
"Hello."
"<thought>This is getting strange...</thought>"
)
# [
# {'type': 'action', 'content': 'Looks around.'},
# {'type': 'dialogue', 'content': 'Hello.'},
# {'type': 'thought', 'content': 'This is getting strange...'},
# ]
"""
import logging
import re
from typing import TypedDict
logger = logging.getLogger(__name__)
# Matches:
# <action>...</action>
# <thought>...</thought>
#
# re.DOTALL allows the content to span multiple lines.
_PATTERN = re.compile(
r"(<action>.*?</action>|<thought>.*?</thought>)",
re.DOTALL | re.IGNORECASE,
)
class Segment(TypedDict):
type: str # 'dialogue' | 'action' | 'thought'
content: str # text without surrounding tags
def parse_response(text: str) -> list[Segment]:
"""
Parse a raw character response into a list of typed segments.
Supported formats:
<action>...</action>
<thought>...</thought>
plain dialogue text
Preserves reading order.
Skips empty parts.
"""
segments: list[Segment] = []
for part in _PATTERN.split(text):
part = part.strip()
if not part:
continue
lower_part = part.lower()
if lower_part.startswith("<action>") and lower_part.endswith("</action>"):
content = part[len("<action>"):-len("</action>")].strip()
if content:
segments.append({
"type": "action",
"content": content,
})
elif lower_part.startswith("<thought>") and lower_part.endswith("</thought>"):
content = part[len("<thought>"):-len("</thought>")].strip()
if content:
segments.append({
"type": "thought",
"content": content,
})
else:
segments.append({
"type": "dialogue",
"content": part,
})
type_counts = {t: sum(1 for s in segments if s["type"] == t) for t in ("dialogue", "action", "thought")}
logger.debug(f"[Parser] {len(text)} chars β†’ {len(segments)} segments {type_counts}")
return segments
def segments_to_markdown(segments: list[Segment]) -> str:
"""
Convert segments into a Gradio-friendly markdown string.
Conventions:
action β†’ italic text
thought β†’ italic text with an em dash
dialogue β†’ plain text
"""
lines: list[str] = []
for seg in segments:
if seg["type"] == "action":
lines.append(f"*({seg['content']})*")
elif seg["type"] == "thought":
lines.append(f"*β€” {seg['content']}*")
else:
lines.append(seg["content"])
return "\n".join(lines)