🎯 本章核心问题 如何构建一个真正”智能”的对话系统?
普通聊天机器人的问题:
❌ 无记忆 :每轮对话都是独立的,无法理解”上面”、”之前”等指代
❌ 输出单一 :只返回文字,没有可视化
❌ 无法总结 :聊了 10 轮后无法生成整体报告
我们的解决方案 :
✅ 多轮上下文管理 :自动维护最近 10 轮对话历史
✅ 智能图表推荐 :根据 SQL 特征自动选择最佳可视化方式
✅ 一键报告生成 :聚合多次查询结果,输出专业分析报告
📐 架构总览
智能对话引擎架构
核心模块一览
上下文、LLM、SQL、图表推荐与报告生成
💬 一、多轮上下文管理(核心亮点) 1.1 为什么需要上下文? 场景对比 :
对话类型
无上下文(单轮)
有上下文(多轮)
用户输入
“按地区细分”
“上面的数据按地区再细分一下”
系统理解
❓ “什么数据?”
✅ 知道是”订单总额数据”
生成的 SQL
SELECT region, SUM(amount) FROM orders GROUP BY region
✅ 同上 + 时间过滤继承自上一轮
1.2 会话生命周期管理 app/services/chat_engine.py
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 async def create_session (db: AsyncSession, data: ChatSessionCreate ) -> ChatSession: """创建新会话""" session = ChatSession( datasource_id=data.datasource_id, title=data.title or "New Chat" , ) db.add(session) await db.flush() await db.refresh(session) return sessionasync def get_session_with_messages (db: AsyncSession, session_id: int ): """加载会话及其所有消息""" result = await db.execute( select(ChatSession).where(ChatSession.id == session_id) ) session = result.scalar_one_or_none() if session: msg_result = await db.execute( select(ChatMessage) .where(ChatMessage.session_id == session_id) .order_by(ChatMessage.created_at) ) session.messages = list (msg_result.scalars().all ()) return sessionasync def delete_session (db: AsyncSession, session_id: int ) -> bool : """删除会话及所有关联消息""" from sqlalchemy import delete await db.execute( delete(ChatMessage).where(ChatMessage.session_id == session_id) ) result = await db.execute( delete(ChatSession).where(ChatSession.id == session_id) ) return result.rowcount > 0
💡 数据模型设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class ChatSession (Base ): __tablename__ = "chat_sessions" id = Column(Integer, primary_key=True , autoincrement=True ) datasource_id = Column(Integer, ForeignKey("datasources.id" )) title = Column(String(200 ), default="New Chat" ) created_at = Column(DateTime, default=datetime.utcnow)class ChatMessage (Base ): __tablename__ = "chat_messages" id = Column(Integer, primary_key=True , autoincrement=True ) session_id = Column(Integer, ForeignKey("chat_sessions.id" )) role = Column(String(20 )) content = Column(Text) sql_query = Column(Text, nullable=True ) query_result = Column(Text, nullable=True ) chart_config = Column(Text, nullable=True ) created_at = Column(DateTime, default=datetime.utcnow)
1.3 上下文窗口控制策略 app/services/chat_engine.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async def chat (db: AsyncSession, session_id: int , user_message: str ) -> ChatMessage: history_result = await db.execute( select(ChatMessage) .where(ChatMessage.session_id == session_id) .order_by(ChatMessage.created_at) ) history = list (history_result.scalars().all ()) recent_history = history[-10 :] conversation = [] for msg in recent_history: conversation.append({ "role" : msg.role, "content" : msg.content }) conversation.append({"role" : "user" , "content" : user_message})
🎯 为什么选择 10 条而不是全部?
策略
Token 消耗
上下文完整性
适用场景
全量历史
⚠️ 可能超限(>80K tokens)
✅ 完整
短对话(<20轮)
最近 N 条(当前方案)
✅ 可控(~5K tokens)
✅ 够用
长对话(50+轮)
摘要压缩
✅ 极低
❌ 可能丢失细节
超长对话(100+轮)
我们的选择 :history[-10:] 平衡了成本和效果。
💡 进阶优化:滑动窗口 + 摘要 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def build_smart_context (history, max_tokens=6000 ): """ 智能上下文组装策略: - 始终保留前 2 轮(建立初始上下文) - 始终保留最近 8 轮(保持连贯性) - 中间的用摘要替代(如果太长) """ if len (history) <= 10 : return history[-10 :] head = history[:4 ] tail = history[-6 :] middle_summary = summarize_messages(history[4 :-6 ]) return head + [{"role" : "system" , "content" : f"...(中间{len (history)-10 } 轮已摘要): {middle_summary} " }] + tail
🤖 二、LLM 对话集成与 Prompt 构建 2.1 System Prompt 设计 app/services/chat_engine.py
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 async def chat (db: AsyncSession, session_id: int , user_message: str ): semantic_model = await get_semantic_model(db, session.datasource_id) system_prompt = f"""你是一个专业的数据分析助手。你帮助用户从数据库中查询和分析数据。 你可以访问以下数据库结构: {json.dumps(semantic_model, ensure_ascii=False , indent=2 )} 当用户提出数据相关问题时,请按以下格式回复: 1. 简要说明你将要查询什么数据 2. 用 ```sql ``` 代码块包裹生成的 SQL 查询语句 3. 尽量提供有价值的分析和洞察 如果用户的问题与数据无关,请正常回复。""" conversation_text = "" for msg in conversation: role = msg["role" ] content = msg["content" ] conversation_text += f"\n{role} : {content} " gateway = LLMGateway() assistant_content = await gateway.chat_completion([ {"role" : "system" , "content" : system_prompt}, {"role" : "user" , "content" : conversation_text.strip()}, ])
💡 System Prompt 的三层结构
角色定义、语义模型注入、输出格式约束
2.2 实际对话案例演示 Round 1 :
1 2 3 4 5 6 7 8 9 10 11 12 👤 用户:查询最近7天的订单总额 🤖 助手: 我将为您查询最近7天(2026-05-15 至 2026-05-22)的订单总额。 ```sql SELECT DATE(created_at) as date, SUM(total_amount) as daily_total FROM orders WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(created_at) ORDER BY date DESC;
📊 分析发现:
近7天订单总额为 ¥1,234,567
5月18日达到峰值(¥234,567),可能是促销活动导致
整体呈上升趋势,日均增长 12%
👤 用户:上面的数据按地区再细分一下
🤖 助手:(理解”上面”= 订单数据) 好的,我将按地区细分近7天的订单销售额。
1 2 3 4 5 6 7 8 SELECT r.name as region, SUM (o.total_amount) as total_salesFROM orders oJOIN users u ON o.user_id = u.idJOIN regions r ON u.region_id = r.idWHERE o.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY )GROUP BY r.id, r.nameORDER BY total_sales DESC ;
📊 地区排名:
华东区:¥456,789(37%)
南区:¥345,678(28%)
北区:¥234,567(19%) …
👤 用户:帮我生成一份分析报告
🤖 助手: 正在基于我们刚才的对话数据生成专业分析报告…
📊 销售数据分析报告(2026-05-15 ~ 2026-05-22) 一、关键发现 ✅ 本周销售总额突破 120 万,环比增长 15% ✅ 华东区贡献最大,占总量 37% …
(详见下文”报告生成”章节)
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 --- ## 🔍 三、SQL 提取与执行流程 ### 3.1 从 LLM 响应中提取 SQL `app/services/chat_engine.py` ```python import re async def chat(...): # ... (LLM 调用) sql_query = None query_result = None chart_config = None # 正则提取 SQL 代码块 sql_match = re.search(r'```sql\s*(.*?)```', assistant_content, re.DOTALL) if sql_match: sql_query = sql_match.group(1).strip() # 安全校验 if validate_sql(sql_query): try: # 执行查询 query_result = json.dumps( await execute_query(sql_query, session.datasource_id), ensure_ascii=False, default=str, # 处理 datetime 等特殊类型 ) # 智能图表推荐 chart_config = _suggest_chart( sql_query, json.loads(query_result) ) except Exception as e: query_result = json.dumps({"error": str(e)})
3.2 错误处理策略
场景
处理方式
用户体验
响应中无 SQL
纯文本回复(如闲聊、解释性问题)
显示文字即可
SQL 验证失败
不执行,记录 WARNING 日志
提示”生成的SQL存在风险”
SQL 执行异常
捕获异常,返回错误信息
显示”查询出错:表不存在”
查询结果为空
正常返回空列表
显示”暂无数据”
📊 四、智能图表推荐算法(_suggest_chart) 4.1 设计思路 核心原则 :根据 SQL 结构特征 和 返回数据的统计特征 自动选择最合适的图表类型。
4.2 算法实现 app/services/chat_engine.py
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 def _suggest_chart (sql: str , data: list [dict ] ) -> Optional [dict ]: """ 智能图表推荐算法 Args: sql: 生成的 SQL 语句 data: 查询结果 [{col: val}, ...] Returns: 图表配置字典,或 None(无法推荐时) """ if not data or len (data) == 0 : return None sql_lower = sql.lower() has_date = any (kw in sql_lower for kw in [ "date" , "time" , "month" , "year" , "week" , "day" , "created_at" , "updated_at" ]) has_group_by = "group by" in sql_lower columns = list (data[0 ].keys()) numeric_cols = [] string_cols = [] for col in columns: values = [row.get(col) for row in data if row.get(col) is not None ] if values and all (isinstance (v, (int , float )) for v in values): numeric_cols.append(col) else : string_cols.append(col) if has_date and numeric_cols: return { "type" : "line_chart" , "x_field" : string_cols[0 ] if string_cols else columns[0 ], "y_fields" : numeric_cols, } elif has_group_by and numeric_cols: if len (data) <= 8 : return { "type" : "pie_chart" , "label_field" : string_cols[0 ] if string_cols else columns[0 ], "value_field" : numeric_cols[0 ], } else : return { "type" : "bar_chart" , "x_field" : string_cols[0 ] if string_cols else columns[0 ], "y_fields" : numeric_cols, } elif numeric_cols and len (data) == 1 : return { "type" : "metric_card" , "metrics" : {col: data[0 ].get(col) for col in numeric_cols}, } return { "type" : "table" , "columns" : columns, }
4.3 推荐规则矩阵
SQL 特征
数据特征
推荐图表
使用场景
包含 DATE/TIME + 数值列
时间序列
📈 line_chart
销售趋势、访问量走势
GROUP BY + ≤8 个分类
维度+度量
🥧 pie_chart
市场份额、品类占比
GROUP BY + >8 个分类
维度+度量
📊 bar_chart
各地区排名、TOP N
无 GROUP BY + 单行
单个/多个 KPI
🔢 metric_card
总额、平均值、计数
其他复杂情况
多列明细
📋 table
原始数据展示
4.4 实际案例演示 案例 1:时间趋势分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SELECT DATE (created_at) as date , SUM (amount) as salesFROM ordersWHERE created_at >= '2026-05-01' GROUP BY DATE (created_at); [ {"date": "2026-05-01", "sales": 12345 }, {"date": "2026-05-02", "sales": 15678 }, ... ] { "type": "line_chart", "x_field": "date", "y_fields": ["sales"] }
前端渲染效果 :ECharts 折线图,X 轴为日期,Y 轴为销售额。
案例 2:品类占比分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SELECT c.name as category, SUM (oi.amount) as totalFROM order_items oiJOIN products p ON oi.product_id = p.idJOIN categories c ON p.category_id = c.idGROUP BY c.id; [ {"category": "电子产品", "total": 128500 }, {"category": "服装配饰", "total": 89200 }, {"category": "食品饮料", "total": 56800 }, {"category": "家居用品", "total": 34500 }, {"category": "图书文具", "total": 12300 } ] { "type": "pie_chart", "label_field": "category", "value_field": "total" }
前端渲染效果 :ECharts 饼图,显示各品类销售占比。
案例 3:KPI 指标卡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SELECT COUNT (* ) as total_orders, AVG (total_amount) as avg_order_value, SUM (total_amount) as revenueFROM ordersWHERE status = 'paid' ; [{"total_orders": 1234 , "avg_order_value": 256.8 , "revenue": 316800 }] { "type": "metric_card", "metrics": { "total_orders": 1234 , "avg_order_value": 256.8 , "revenue": 316800 } }
前端渲染效果 :3 个大号数字卡片,分别显示订单数、客单价、总收入。
📝 五、报告生成器(generate_report_script) 5.1 应用场景 当用户经过多轮对话探索数据后,希望得到一份结构化的总结报告 :
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 👤 用户:帮我生成一份分析报告 🤖 助手: # 📊 销售数据分析报告 **报告周期**:2026-05-15 ~ 2026-05-22 **生成时间**:2026-05-22 14:30 ## 一、🎯 核心发现 1. **本周销售总额突破 120 万**,环比增长 15%,创近半年新高 2. **华东区表现突出**,贡献了 37% 的营收,主要得益于"618预热"活动 3. **电子产品品类持续领跑**,占总销售额的 42%,其中手机配件增长最快(+28%) ## 二、📈 关键指标分析 | 指标 | 本周数值 | 环比变化 | 趋势 | |------|---------|---------|------| | 总销售额 | ¥1,234,567 | ↑15% | 📈 强劲增长 | | 订单量 | 4,567 笔 | ↑8% | 📈 稳定上升 | | 客单价 | ¥270.3 | ↑6.5% | 📈 持续优化 | ... ## 三、💡 行动建议 1. **加大华东区投入**:考虑增加该区域库存和营销预算 2. **复制成功经验**:分析华东区的爆款商品,推广到其他区域 3. **关注低增长品类**:图书文具品类连续 3 周下滑,需调查原因 ...
5.2 实现原理 app/services/chat_engine.py
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 async def generate_report_script ( db: AsyncSession, session_id: int , metrics: list [str ], style: str = "brief" ) -> str : """ 生成数据分析报告 Args: session_id: 会话 ID(用于获取历史查询结果) metrics: 需要重点分析的指标 style: 报告详细程度 Returns: Markdown 格式的报告文本 """ msg_result = await db.execute( select(ChatMessage) .where( ChatMessage.session_id == session_id, ChatMessage.query_result.isnot(None ) ) .order_by(ChatMessage.created_at.desc()) .limit(5 ) ) messages = list (msg_result.scalars().all ()) data = {} for msg in messages: if msg.query_result: try : data[msg.sql_query or "query" ] = json.loads(msg.query_result) except json.JSONDecodeError: pass gateway = LLMGateway() report = await gateway.generate_report_script(data, metrics) return report
5.3 LLM Prompt 设计 app/services/llm_gateway.py
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 async def generate_report_script (self, data: dict , metrics: list [str ] ) -> str : system_prompt = """你是一位专业的数据报告撰写专家。根据查询结果和指定的指标, 生成一份中文 Markdown 格式的专业商业报告。 报告必须包含以下部分: 1. **关键发现**(Top 3-5 Insights):最重要的结论,用数字支撑 2. **指标详细分析**:对每个指标进行深入解读,包含同比/环比 3. **趋势和洞察**:数据背后的原因分析和未来预测 4. **建议和行动方案**:基于数据的具体可执行建议 写作要求: - 使用专业但易懂的语言 - 所有结论必须有数据支撑(引用具体数值) - 使用 Markdown 格式(标题、表格、列表、加粗) - 适当使用 emoji 增强可读性(📈 📉 🎯 💡) - 总长度控制在 1000-2000 字""" user_prompt = f"""查询数据(来自用户的多次查询): {json.dumps(data, ensure_ascii=False , indent=2 )} 需要重点分析的指标:{', ' .join(metrics)} 请生成报告:""" return await self .generate(user_prompt, system_prompt=system_prompt)
🔄 六、完整调用链路总结 一次典型的对话请求处理流程
从 FastAPI 路由到 SQL 执行与前端渲染
性能基准
阶段
耗时
占比
上下文加载 + 语义模型获取
50-100ms
2%
LLM 调用(含网络传输)
2000-4000ms
85%
SQL 提取 + 验证
<1ms
<1%
MySQL 查询执行
50-200ms
5%
图表推荐
<1ms
<1%
数据库写入
10-20ms
1%
总计
2.5-4.5s
100%
瓶颈在 LLM 调用 (85% 时间)。后续可通过流式输出优化用户体验。
🎯 七、最佳实践总结 ✅ 我们做到了什么
真正的多轮对话 :维护 10 轮上下文,理解指代关系(”上面”、”之前”)
结构化输出 :强制 LLM 按”说明 + SQL + 分析”三段式输出
零配置可视化 :_suggest_chart() 根据特征自动选择 5 种图表类型
一键报告生成 :聚合多次查询结果,输出专业 Markdown 报告
完善的错误处理 :SQL 缺失/验证失败/执行异常均有友好提示
📚 最佳实践清单
🚀 下一步 下一篇文章将深入 数据大屏的两阶段分离架构 —— 这是整个项目最具创新性的设计!为什么大屏预览不需要调 LLM?如何实现”设计时解析,运行时执行”?配置驱动的哲学是什么?
敬请期待!🚀
相关代码文件 :
app/services/chat_engine.py(chat_engine.py) — 对话引擎核心逻辑
app/models/chat.py(models/chat.py) — 会话与消息数据模型
app/api/chat.py(api/chat.py) — RESTful API 接口
views/chat/ChatRoom.vue — 前端对话界面