refs #732: separate report data and rendering requests
This commit is contained in:
@@ -2894,6 +2894,104 @@ def _clean_presentation_title(value: Any) -> str:
|
||||
return text or "업무 현황"
|
||||
|
||||
|
||||
def _fallback_presentation_data_query(question: str) -> str:
|
||||
text = " ".join(str(question or "").split()).strip()
|
||||
text = re.sub(
|
||||
r"\s*(?:그리고\s*)?(?:이걸|이를|그\s*결과를|결과를)\s*"
|
||||
r"(?:HMM\s*)?(?:HTML|리포트|보고서|대시보드|차트)\s*"
|
||||
r"(?:형식)?(?:으로|로)?\s*(?:만들어|생성해|작성해|보여)\s*"
|
||||
r"(?:줘|주세요)?\s*[.!?]?\s*$",
|
||||
"",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
text = re.sub(
|
||||
r"(?i)HTML\s*(?:형식)?(?:으로|로)?\s*",
|
||||
"",
|
||||
text,
|
||||
)
|
||||
text = re.sub(
|
||||
r"(?:HMM\s*)?(?:리포트|보고서|대시보드|차트)\s*"
|
||||
r"(?:형식)?(?:으로|로)?\s*(?:만들어|생성해|작성해)\s*"
|
||||
r"(?:줘|주세요)?",
|
||||
"보여줘",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
return " ".join(text.split()).strip() or str(question or "").strip()
|
||||
|
||||
|
||||
def _plan_presentation_data_query(
|
||||
*,
|
||||
question: str,
|
||||
route: RoutedMcpTool,
|
||||
model_profile_key: str,
|
||||
) -> str:
|
||||
"""Separate the data request from its presentation-format request."""
|
||||
|
||||
fallback = _fallback_presentation_data_query(question)
|
||||
try:
|
||||
profile = resolve_model_profile(model_profile_key)
|
||||
client = build_oci_genai_completion_client(
|
||||
profile.model_id,
|
||||
profile.answer_model_region,
|
||||
profile.answer_model_endpoint,
|
||||
)
|
||||
text = client.complete(
|
||||
system_prompt=(
|
||||
"Rewrite the Korean request as a data-retrieval question for the selected "
|
||||
"MCP tool. Preserve every business subject, identifier, filter, period, and "
|
||||
"requested metric. Remove only presentation-format and artifact-generation "
|
||||
"instructions such as HTML, report, dashboard, chart, render, 보여주는 형식, "
|
||||
"or 만들어줘. Do not answer the question, write SQL, or request a token. "
|
||||
"The rewritten query must ask for ordinary structured data rows, never HTML "
|
||||
"tags, Markdown, or preformatted table rows. Return only schema JSON."
|
||||
),
|
||||
user_prompt=json.dumps(
|
||||
{
|
||||
"question": question,
|
||||
"selected_tool": route.tool.name,
|
||||
"tool_description": route.tool.description[:1200],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
response_schema={
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["data_query"],
|
||||
"properties": {"data_query": {"type": "string"}},
|
||||
},
|
||||
max_tokens=300,
|
||||
temperature=temperature_for_model_profile(profile),
|
||||
)
|
||||
parsed = json.loads(text)
|
||||
if isinstance(parsed, Mapping):
|
||||
planned = _clean_agent_tool_query(parsed.get("data_query"), fallback)
|
||||
return _fallback_presentation_data_query(planned or fallback)
|
||||
except Exception:
|
||||
pass
|
||||
return fallback
|
||||
|
||||
|
||||
def _mcp_rows_contain_presentation_markup(value: Any) -> bool:
|
||||
rows = _mcp_structured_rows(value)
|
||||
if not rows:
|
||||
return False
|
||||
for row in rows:
|
||||
if not isinstance(row, Mapping):
|
||||
continue
|
||||
keys = {str(key).casefold() for key in row}
|
||||
if keys & {"htmlrow", "html", "markdown"}:
|
||||
return True
|
||||
if any(
|
||||
isinstance(item, str)
|
||||
and re.search(r"<(?:tr|td|table|div)\b", item, flags=re.IGNORECASE)
|
||||
for item in row.values()
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _plan_presentation_title(
|
||||
*,
|
||||
question: str,
|
||||
@@ -3995,6 +4093,12 @@ def run_mcp_agent_loop(
|
||||
|
||||
started = perf_counter()
|
||||
presentation_title = ""
|
||||
if wants_presentation and not observations and not _is_presentation_route(route):
|
||||
tool_query = _plan_presentation_data_query(
|
||||
question=question,
|
||||
route=route,
|
||||
model_profile_key=model_profile_key,
|
||||
)
|
||||
if _is_presentation_route(route):
|
||||
source_step = next(
|
||||
(
|
||||
@@ -4025,6 +4129,14 @@ def run_mcp_agent_loop(
|
||||
)
|
||||
parsed = _content_text_json(raw_result)
|
||||
mcp_result = parsed if parsed is not None else raw_result
|
||||
if (
|
||||
wants_presentation
|
||||
and not _is_presentation_route(route)
|
||||
and _mcp_rows_contain_presentation_markup(mcp_result)
|
||||
):
|
||||
raise PublicMcpError(
|
||||
"데이터 조회 결과가 구조화 행이 아니라 표현용 마크업으로 반환됐습니다."
|
||||
)
|
||||
elapsed = perf_counter() - started
|
||||
summary = _mcp_summary(mcp_result)
|
||||
step = {
|
||||
|
||||
Reference in New Issue
Block a user