Spaces:
Running
Running
File size: 3,094 Bytes
e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 832430f e29c7ce 832430f 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 832430f e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce 5eb1b3d e29c7ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """
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) |