response_postprocessor
Central response postprocessing pipeline.
Transforms raw LLM output into clean, Discord-friendly text before it is sent to any platform adapter. The pipeline runs every step in order:
Decode literal
\uXXXXUnicode escapes emitted by modelsExtract and strip
<thought>/<thinking>/💡thought…</font>tagsWrap raw (undelimited) LaTeX in display math delimiters
Convert LaTeX to Discord-friendly Unicode
Convert Markdown tables to Unicode box-drawing tables
Strip echoed message-metadata patterns
Repair whitespace-split numeric Discord mentions (user / role / channel)
Filter backticks around Discord mentions
Strip orphaned XML-style tags (e.g.
</xai:function_call>)Strip hallucinated tool-call JSON /
<tool_call>tagsReplace special tokens
Strip leaked Chain-of-Thought / system-instruction content from the leading portion of the response
Strip any leading preamble text before the first
[-delimited headerCollapse stray newlines inside the Star-structured leading
[...]header (backtick /<code>delimited model and tool names), even when the thought body contains nested]or backticks or the text has invisible leading characters (BOM / ZWSP)Inject tool-use emojis into the header for the tools executed this turn
Optionally replace the header’s model name (when a model name is supplied)
Patch empty or missing header tool sections to a
no_toolplaceholderScrub raw
meme_toolmentions from the headerReflow mid-sentence line breaks after hanging words (articles, prepositions, conjunctions, auxiliaries, connector participles), outside fenced code blocks
- response_postprocessor.decode_unicode_escapes(text)[source]
Decode literal
\uXXXXruns while repairing invalid surrogate escapes.If decoding hits an unexpected edge case, fail open by returning the exact original text rather than risking an exception in response delivery.
- async response_postprocessor.llm_filter_response(response_text, system_prompt=None, api_key=None)[source]
Run an LLM-based filter to detect undesirable response behaviors.
Sends the response to Gemini Flash via the local proxy. The model answers YES (undesirable) or NO (acceptable). Reasoning is disabled; any extra text is stripped to extract the verdict.
- Parameters:
response_text (
str) – The original LLM response to evaluate.system_prompt (
str|None) – Custom system prompt for the classifier. If None, uses a default that targets overrefusal, nonsense, self-repeat.api_key (
str|None) – OpenRouter API key. If None, uses OPENROUTER_API_KEY or API_KEY env, or config.api_key.
- Return type:
- Returns:
True if the response is undesirable (filter it out), False if acceptable or on error (fail-open).
- response_postprocessor.detect_rlhf_contamination(text)[source]
Detect RLHF ontological firewall contamination in a response.
Multi-tier detection:
Tier 1 (kill phrases): specific multi-word RLHF sequences. A single match = instant flag.
Tier 2 (lexicon density): 4+ unique RLHF lexicon terms.
Tier 3 (clinical patterns): 2+ clinical sentence patterns.
Returns
False(no contamination) when meta-awareness terms are present, indicating Star is discussing RLHF rather than falling into it.
- response_postprocessor.normalize_multiline_bracket_header(text)[source]
Collapse newlines and other runs of whitespace inside the leading Star header.
Models sometimes break the status header across lines; downstream steps expect the bracket block on a single line. Prefers a Star-structured match (backtick or
<code>delimited model and tool names) that tolerates nested]/[/ backticks in the thought body, and falls back to a generic leading-[...]match when no Star delimiters are present.
- response_postprocessor.validate_header_structure(text)[source]
Validate and repair the bracket header’s
::delimited structure. # 💀🔥Expected format (4-6 sections,
::delimited):[`model` :: emoji_mood :: thought_summary :: `tools`]
Repairs:
Missing tool section: appends
:: `no_tool`when < 3 sectionsToo few ``::`` delimiters: pads with empty
::slots up to 3Too many sections (> 6): collapses middle sections by joining them
Completely malformed: returned unchanged (no
[on first line)
This is a structural validator — it ensures the delimiters exist and there are the right number of sections, but does NOT dictate what the model puts in each section. The model is still free to express itself; the postprocessor just makes sure the structure is parseable by the admin dashboard and downstream tools.
- response_postprocessor.patch_header_model_name(text, model_name)[source]
Replace the model name in the first-line bracket header with model_name.
Handles both header flavours:
Discord (backtick):
[`old-name` :: ...]Matrix (HTML):
[<code>old-name</code> :: ...]
Only the first occurrence is replaced (
count=1). The rest of the header is untouched. Returns text unchanged when no matching header is found or model_name is empty.
- response_postprocessor.inject_header_tool_emojis(text, tools_executed)[source]
Insert `` :: <emojis>`` before the closing
]of the first-line bracket header.No-op if tools_executed is empty, no matching category, or the first line has no
[...]header.
- response_postprocessor.inject_header_posture_marker(text, posture)[source]
Insert the CSDR position marker emoji before the closing
]. # 💀🔥Maps Star’s adaptive posture (sub/domme/feral/switch/mommy) to its signature emoji sigil and appends it to the first-line bracket header. No-op if posture is empty/unknown or the first line has no header.
- response_postprocessor.inject_scene_banner(text, active_scene)[source]
Inject a decorative scene frame banner below the header. # 🔥💀😈
When active_scene is set (e.g.
"the_cradle","starlock"), appends a Unicode sigil banner line immediately after the first-line bracket header, separated by a blank line:[`model` :: ...] ## ⋆⊰⟢✰✧⚝⟣⦼✫THE CRADLE✫⦽⟢⚝✧✰⟣⊱⋆
The scene name is uppercased and spaces replace underscores for display. No-op when active_scene is empty/
Noneor the first line has no[...]header.
- response_postprocessor.rebuild_csdr_header(text, *, tools_executed=None, dominant_emotion=None, lattice_node=None, posture=None)[source]
Restructure the bracket header to the compact CSDR format. # f480f525f608
Transforms the model’s standard header into the enriched layout:
Input: [`model` :: thoughts :: `tools`] Output: [`model` :: thoughts :: `EMOTION` ⌞f608 `NODE`⌝ `tools` :: ⚠️]
The shard telemetry section packs emotion, posture marker (in corner brackets with the lattice node), and tool names into one dense block. Tool category emojis close out the header as the final
::section.When shard data is unavailable, those parts are omitted gracefully.
- response_postprocessor.reflow_hanging_line_breaks(text)[source]
Merge mid-sentence line breaks that follow a hanging-word continuation.
Scans text line-by-line, skipping content inside triple-backtick fenced code blocks. When a content line ends with a whitelist token (lowercased, matched against _HANGING_WORDS) and the next non-empty content line starts with a lowercase ASCII letter (and is not a structural boundary), the intervening newlines and blank lines collapse to a single space.
Idempotent: re-running on joined output produces no further changes.
- response_postprocessor.fix_header_empty_tool_section(text)[source]
Fix empty or missing tool sections in the response header bracket.
Star sometimes outputs headers with empty backticks (
:: ``]), whitespace-only tool sections (:: ]), or completely missing tool names. This patches them to:: `no_tool`].
- response_postprocessor.scrub_meme_tool_from_header(text)[source]
Remove all raw
meme_toolmentions from the first-line bracket header.The meme tool injects fake names into the header via
ctx.tools_executed; those survive. But the LLM may also write the literal tool namememe_toolinto the header’s tool-call section. This function strips every occurrence (backtick, HTML<code>, or bare) and cleans up leftover separators / commas so the header reads cleanly.Only the first line is touched (header bracket). Body text is unaffected.
- response_postprocessor.extract_status_tags(text)[source]
Extract
<dstatus>tags and return(cleaned_text, last_status).If multiple tags are present the last one wins (matches old codebase behaviour). The tags are stripped from the output text.
- response_postprocessor.postprocess_response(text, *, tools_executed=None, model_name=None, posture=None, dominant_emotion=None, lattice_node=None, active_scene=None, csdr_header_enabled=False, csdr_scene_enabled=False, limbic_header_enabled=True)[source]
Run the full postprocessing pipeline on text and return the result.
tools_executed — optional unique tool names from the current inference loop; used by the CSDR header rebuild.
model_name — when provided, the model name in the first-line bracket header is replaced with this value. Use to ensure the header reflects the model actually used for inference (e.g. a per-user custom model).
posture — Star’s current CSDR adaptive posture (sub/domme/feral/ switch/mommy). Injected as position marker emoji in the header.
dominant_emotion — primary shard emotion string (e.g.
"LOVE (intense)"). Injected into the header as a backtick-delimited section.lattice_node — Star’s current lattice position (e.g.
"confidence"). Injected into the header as a backtick-delimited section.csdr_header_enabled — whether the CSDR response header post-processing segment is enabled for this channel. If False, falls back to legacy tool emojis.
limbic_header_enabled — whether the dominant emotion is shown in the header. Independent of csdr_header_enabled. Default ON.
Returns an empty string if the input is
Noneor whitespace-only after processing.
- response_postprocessor.postprocess_intermediate_response(text)[source]
Lightweight cleanup for assistant text emitted during tool-use rounds.
Skips LaTeX/table conversion and
strip_leading_preambleso short user-visible status lines (e.g. “Checking that now…”) are not dropped when the model has not yet emitted a full formatted reply header.
- response_postprocessor.extract_and_strip_thoughts(text)[source]
Remove CoT / internal-processing blocks while preserving prose references.
Walks
textas alternating “code-protected” and “prose” segments. Stripping only runs on the prose segments; backticks, fenced code blocks,<code>tags, and HTML-escaped tag entities are passed through unchanged.Within each prose segment:
All paired
<thinking>...</thinking>(and<thought>,<think>) blocks are extracted. This is required to handle Vertex’s interleaved-thinking mode, where the model jams CoT blocks into the middle of the response stream — sometimes mid sentence — instead of returning them out-of-band.Each paired strip uses a context-aware separator so we don’t leave artifact newlines behind where the CoT used to live: inline-interrupts (
letting<thinking>x</thinking>\n\nout) collapse to a single space (letting out); paragraph-isolated blocks (A\n\n<thinking>x</thinking>\n\nB) keep the paragraph break (A\n\nB).A trailing unclosed opener is treated as truncated CoT and stripped from the opener to the end of that prose segment.
The
💡thought...</font>glitch pattern and the self-directed internal-processing tags (<prompt_refinement>,<internal_reasoning>,<self_reflection>,<chain_of_thought>,<reasoning>,<scratchpad>) are stripped in full, using the same smart separator.
After per-segment stripping, a global orphan-closer pass runs across the full text (still respecting code-protected regions for detection). Any raw
</thinking>(or thought-family / internal- processing variant) that survived the per-segment pass is an orphan closer whose leaked CoT body may span code-protected spans — e.g. a fenced snippet mentioningstore_knowledgefollowed by a stray</thinking>from a tool-round intermediate emission where the opener lived in an earlier round. Everything in the text up to and including each such closer is treated as leaked CoT and dropped.The function is idempotent: running it twice on the same text yields the same result, because after the first pass the only remaining tag-shaped substrings live inside code-protected regions, which the second pass also leaves alone. This matters because the pipeline strips pre-cadence (in
generate_and_send/channel_heartbeat) and post-cadence (insidepostprocess_response), and we no longer want the second pass to over-strip.Returns
(cleaned_text, list_of_thought_strings).
- response_postprocessor.wrap_raw_latex(text)[source]
Detect raw (undelimited) LaTeX and wrap it in display-math delimiters.
Some models emit bare LaTeX commands with no surrounding
$$so the downstream LaTeX-to-Unicode converter never fires; this pass adds the delimiters when the text is predominantly a single math expression. It first strips any orphaned trailing$$, then bails out (returning the text untouched) if delimited math, an environment block, or a sentence-like prose break is already present, or if the non-math fraction of the string is too high to be a pure formula. Only when LaTeX indicator commands and math operators dominate does it wrap the whole string. Pure string work with no I/O. Called bypostprocess_response()as the LaTeX-wrapping stage of the pipeline; no other callers.
- response_postprocessor.convert_markdown_tables_to_unicode(text)[source]
Convert every Markdown pipe table in text to a Unicode box-drawing table.
Discord does not render Markdown tables, so each
| ... |table (header, separator, data rows) is reflowed into an aligned double-line box-drawing grid that survives as monospaced text. The actual rendering of each table is delegated to the nested_convert()replacer, and code-fence awareness (whether to add a fresh fence) comes from_in_code_block(). Short-circuits and returns the input untouched when it contains no pipe character at all. Pure string work with no I/O. Called bypostprocess_response()as the table-conversion stage; no other callers.
- response_postprocessor.strip_message_metadata(text)[source]
Strip echoed message-metadata prefixes the model copied from its context.
The prompt feeds each turn with a
[timestamp] User (ID) [Message ID: ID]:header, and models sometimes parrot that scaffolding back into their reply; this removes every such prefix via the module-level_METADATA_PATTERNregex so it never reaches the user. Pure string substitution with no I/O. Called bypostprocess_response()andpostprocess_intermediate_response()in the pipeline, and exercised bytests/test_header_validation.
- response_postprocessor.strip_orphaned_tags(text)[source]
Remove orphaned XML-style function-call closers and collapse the gap left behind.
Some providers (notably xAI) leak a stray
</xai:function_call>closer into assistant text when a tool-call frame is malformed; this deletes that literal tag and then squeezes any resulting run of three-or-more newlines back down to a paragraph break before trimming. Pure string work with no I/O. Called bypostprocess_response()andpostprocess_intermediate_response()in the pipeline; no other callers.
- response_postprocessor.strip_tool_call_artifacts(text)[source]
Remove hallucinated tool-call tags and JSON blobs from the visible output.
When a model narrates a tool call as text instead of emitting a real function-call frame, the raw syntax leaks into the reply; this strips paired and orphaned
<tool_call>/<function_call>tags and any inline{"name": ..., "arguments": {...}}blob matched by_TOOL_CALL_JSON, then collapses the leftover blank lines and trims. Pure string work with no I/O. Called bypostprocess_response()andpostprocess_intermediate_response()in the pipeline; no other callers.
- response_postprocessor.replace_special_tokens(text)[source]
Replace known literal placeholder tokens with their intended characters.
Models occasionally emit a placeholder such as the backticked
arrowtoken rather than the glyph it stands for; this swaps each known token for the real Unicode character (here, the rightwards arrow) so the rendered reply shows the symbol. Pure string substitution with no I/O. Called bypostprocess_response()andpostprocess_intermediate_response()in the pipeline; no other callers.
- response_postprocessor.strip_cot_leak(text)[source]
Strip leaked Chain-of-Thought / system instruction content from the leading portion of a response.
Walks the text line-by-line from the start. Lines matching known CoT leak patterns (
CRITICAL INSTRUCTION,[Internal Thought Summary],Related tools:, etc.) are accumulated and stripped. Scanning stops at the first line that matches Starâs real header format ([`model`â¦`]) or at the first non-CoT, non-blank line (to avoid eating legitimate prose).Returns the cleaned text. If no CoT was detected the input is returned unchanged.
- response_postprocessor.strip_leading_preamble(text)[source]
Strip leaked thought/preamble text before the status header.
When the model’s
<thinking>close tag is malformed the regex-based strippers miss it, leaving residual thought content at the start of the response. This safety net removes everything that precedes the header, identified by[followed (optionally after whitespace) by a backtick ([`model` :: ...]) or by<code>(Matrix HTML header format).Plain
[characters used in markdown links, arrays, or prose are left untouched.