Spaces:
Sleeping
Sleeping
File size: 5,640 Bytes
b7f63db ef7c5ab b7f63db b1e8a83 b7f63db 23d234b b7f63db b1e8a83 b7f63db ef7c5ab b1e8a83 ef7c5ab b7f63db ef7c5ab b1e8a83 b7f63db 23d234b b7f63db ef7c5ab b1e8a83 ef7c5ab b7f63db | 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | """
Formatter node — converts agent responses to clean markdown.
Smart passthrough: skips the LLM call if the response is already
well-formatted or very short.
"""
from __future__ import annotations
import hashlib
import logging
import re
from langchain_core.messages import HumanMessage, SystemMessage
from src.agents.config import Config
from src.agents.formatter.prompt import FORMATTER_SYSTEM_PROMPT
from src.graphs.state import AgentState
from src.graphs.utils import sanitize_handoff_phrases
from src.diagnostics.response_boundary import update_trace
logger = logging.getLogger(__name__)
# Maximum length for "short" responses that skip formatting
_SHORT_RESPONSE_THRESHOLD = 120
# Structural quality: headers/lists MUST be at the start of a line
_STRUCTURAL_MARKERS = re.compile(
r"(^#{1,3}\s" # headers at line start
r"|^\s*[-*]\s" # bullets at line start
r"|^\d+\.\s" # numbered list at line start
r"|^\|.+\|$" # table rows
r")",
re.MULTILINE,
)
def _already_formatted(text: str) -> bool:
"""Return True if text is properly structured markdown.
Having **bold** alone is not enough — we need structural elements
(headers, lists, tables) on their own lines to consider the text
well-formatted.
"""
if len(text) <= _SHORT_RESPONSE_THRESHOLD:
return True
structural_matches = _STRUCTURAL_MARKERS.findall(text)
# Need at least 3 structural elements on proper lines
return len(structural_matches) >= 3
def _is_safe_formatted_output(original: str, formatted: str) -> bool:
"""Return True when formatter output safely preserves substantive content.
Formatting is presentation-only. A formatter result must never replace a
meaningful response with empty, malformed, or catastrophically truncated
output.
"""
original_clean = original.strip()
formatted_clean = formatted.strip()
if not formatted_clean:
return False
# A markdown marker without header text is never a valid final response.
if formatted_clean in {"#", "##", "###"}:
return False
# The formatter is instructed to preserve all information. Large content
# loss therefore indicates a failed or truncated formatter completion.
if len(original_clean) > _SHORT_RESPONSE_THRESHOLD:
minimum_safe_length = max(
_SHORT_RESPONSE_THRESHOLD,
len(original_clean) // 2,
)
if len(formatted_clean) < minimum_safe_length:
return False
return True
def formatter_node(state: AgentState) -> dict:
"""Format the agent response as clean markdown."""
response_text = state.get("final_response", "")
user_id = state.get("user_id")
conversation_id = state.get("conversation_id")
nodes = list(state.get("nodes_executed", []))
nodes.append("formatter_node")
def _digest(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()[:16]
logger.info(
"response.boundary.formatter_input length=%d sha256_16=%s",
len(response_text),
_digest(response_text),
)
formatter_trace = {
"input": {
"length": len(response_text),
"sha256_16": _digest(response_text),
},
}
# Always sanitize handoff phrases
response_text = sanitize_handoff_phrases(response_text)
# Smart passthrough — skip LLM if already formatted or very short
if not response_text or _already_formatted(response_text):
logger.info(
"response.boundary.formatter_output passthrough=true length=%d sha256_16=%s",
len(response_text),
_digest(response_text),
)
formatter_trace["output"] = {
"passthrough": True,
"length": len(response_text),
"sha256_16": _digest(response_text),
}
update_trace(
user_id,
conversation_id,
section="formatter",
value=formatter_trace,
)
return {
"final_response": response_text,
"nodes_executed": nodes,
}
# Use LLM to format
try:
from src.llm.tiers import ModelTier
llm = Config.get_llm(model=ModelTier.FORMATTER, with_cost_tracking=True)
result = llm.invoke([
SystemMessage(content=FORMATTER_SYSTEM_PROMPT),
HumanMessage(content=response_text),
])
formatted = result.content if isinstance(result.content, str) else response_text
# Final sanitization
formatted = sanitize_handoff_phrases(formatted)
if not _is_safe_formatted_output(response_text, formatted):
logger.warning(
"Formatter output failed integrity validation; using original response. "
"original_length=%d formatted_length=%d",
len(response_text),
len(formatted),
)
formatted = response_text
except Exception:
logger.exception("Formatter LLM call failed; using original response.")
formatted = response_text
logger.info(
"response.boundary.formatter_output passthrough=false length=%d sha256_16=%s",
len(formatted),
_digest(formatted),
)
formatter_trace["output"] = {
"passthrough": False,
"length": len(formatted),
"sha256_16": _digest(formatted),
}
update_trace(
user_id,
conversation_id,
section="formatter",
value=formatter_trace,
)
return {
"final_response": formatted,
"nodes_executed": nodes,
}
|