""" Response parser — splits a raw LLM response into typed segments. Segment types: 'dialogue' — plain spoken text 'action' — content inside ... 'thought' — content inside ... Example: segments = parse_response( "Looks around." "Hello." "This is getting strange..." ) # [ # {'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: # ... # ... # # re.DOTALL allows the content to span multiple lines. _PATTERN = re.compile( r"(.*?|.*?)", 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: ... ... 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("") and lower_part.endswith(""): content = part[len(""):-len("")].strip() if content: segments.append({ "type": "action", "content": content, }) elif lower_part.startswith("") and lower_part.endswith(""): content = part[len(""):-len("")].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)