Spaces:
Running
Running
Download engine/parser.py from cereeenn120/CharacterChatBot: direct link, hf CLI and curl.
- Browser
- Download file 3.09 kB
-
https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/parser.py
- Command line
-
hf download hf://spaces/cereeenn120/CharacterChatBot/engine/parser.py
-
curl -L -o parser.py https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/parser.py
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) |