"""NL2SQL 查询结果 → 客服口吻回复的渲染器。 不调用 LLM:自然语言摘要由 execute_query 的 summary_llm 生成(result.summary), 这里只负责把摘要 + Markdown 表格组装成客服回复,保证确定性降级。 """ from __future__ import annotations from typing import Any from nl2sql.contracts import DataQueryResult # 表格最多渲染的行数:超出部分提示"仅展示前 N 条",避免回复过长 _MAX_TABLE_ROWS = 20 _EMPTY_ANSWER = "暂时没有查到相关数据,您可以换个问法,或者问我基金知识、开户流程~" def render_markdown_table(columns: list[str], rows: list[dict[str, Any]]) -> str: """把结果行列渲染为 Markdown 表格;无数据返回空串。""" if not columns or not rows: return "" shown = rows[:_MAX_TABLE_ROWS] header = "| " + " | ".join(str(column) for column in columns) + " |" separator = "| " + " | ".join("---" for _ in columns) + " |" lines = [header, separator] for row in shown: cells = [str(row.get(column, "")) for column in columns] lines.append("| " + " | ".join(cells) + " |") return "\n".join(lines) def render_query_answer(result: DataQueryResult) -> str: """组装最终客服回复:摘要开头 + 数据表格 + 截断/收尾提示。""" if result.row_count == 0 or not result.rows: return _EMPTY_ANSWER parts: list[str] = [] summary = (result.summary or "").strip() if summary: parts.append(summary) table = render_markdown_table(result.columns, result.rows) if table: parts.append(table) if result.truncated or result.row_count > len(result.rows): shown = min(len(result.rows), _MAX_TABLE_ROWS) parts.append(f"结果较多,本次为您展示 {shown} 条(共 {result.row_count} 条),您可以缩小查询范围再看。") if not summary and len(result.rows) <= _MAX_TABLE_ROWS: parts.append(f"共为您查到 {result.row_count} 条记录。") return "\n\n".join(part for part in parts if part).strip() or _EMPTY_ANSWER