Implement follow-up detection in dialog context to prevent context pollution in multi-topic conversations. Introduce is_likely_follow_up function to determine when to inject session history based on user input. Adjust _load_session_text2sql_context to conditionally include context based on follow-up status, enhancing SQL generation accuracy. Update relevant API endpoints to pass user text for improved context handling.
This commit is contained in:
@@ -42,6 +42,47 @@
|
||||
|
||||
---
|
||||
|
||||
# Impact Analysis Report — 方案A:新话题禁用 dialog_context(防上下文污染)
|
||||
|
||||
## 1. 改动概览
|
||||
|
||||
- **背景与目标**:同一会话内出现多个不同话题时,历史摘要(含上轮 SQL)注入 Text2SQL 易造成上下文污染,导致选表/条件串话。本次引入“续问判定”规则:仅在疑似续问时注入少量上文;新话题默认不注入上文。
|
||||
- **涉及模块**:`api_server.py`、`backend/utils/dialog_context.py`。
|
||||
- **改动类型**:缺陷修复 / 行为优化(仅影响带 `session_id` 且存在历史消息的请求)。
|
||||
|
||||
## 2. 方法级改动分析
|
||||
|
||||
| 位置 | 变更 |
|
||||
|------|------|
|
||||
| `backend/utils/dialog_context.py` `is_likely_follow_up` | 新增:基于用户文本的轻量续问判定(关键字/短句规则),用于决定是否需要注入上文。 |
|
||||
| `api_server.py` `_load_session_text2sql_context` | 调整:新增 `user_text` 入参;根据 `is_likely_follow_up(user_text)` 选择 `max_pairs=2` 或 `0`,并据此构造 `dialog_context`。 |
|
||||
| `api_server.py` 两个入口 | 调整:`/g3sb/api/nl/chat` 与 `/g3sb/api/nl/chat/stream` 调用 `_load_session_text2sql_context(request, text)`。 |
|
||||
|
||||
## 3. 调用方与影响范围分析
|
||||
|
||||
- **调用方**:`/g3sb/api/nl/chat`、`/g3sb/api/nl/chat/stream`(请求带 `session_id` 且历史不为空时生效)。
|
||||
- **行为变化**:
|
||||
- **新话题**(非续问):`dialog_context` 变为空(不再携带历史摘要),降低串话风险。
|
||||
- **续问**:仅携带最近 **2** 轮(原默认最多 8 轮),降低旧话题回流概率。
|
||||
- **破坏性变更**:否(对外 API 入参/出参未变;仅内部上下文组装策略变化)。
|
||||
|
||||
## 4. 风险与回滚
|
||||
|
||||
- **风险级别**:中(可能把少量“隐式续问”误判为新话题,导致指代消解能力下降;但能显著降低跨话题污染)。
|
||||
- **回滚**:回退本次提交(或将 `max_pairs` 固定恢复为 8 / 去掉续问判定)。
|
||||
|
||||
**回滚方式是否简单**:是。
|
||||
|
||||
## 5. 验证与测试
|
||||
|
||||
- 建议手工:
|
||||
- 同一 session:话题A生成 SQL → 话题B(无续问词)应不再引用 A 的表/条件。
|
||||
- 同一 session:首轮查询 → 次轮“再/按上面/沿用口径”续问,应仍能沿用上轮 SQL 的业务口径。
|
||||
|
||||
## 6. 配置变更
|
||||
|
||||
- 无。
|
||||
|
||||
# Impact Analysis Report — 库探针追问与交付分支(追加)
|
||||
|
||||
## 1. 改动概览
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
## 任务基本信息
|
||||
|
||||
- **任务**:方案A:同一聊天框多话题时避免上下文污染(新话题禁用 `dialog_context`)
|
||||
- **项目**:`backman-camel`(FastAPI Text2SQL API)
|
||||
- **范围**:后端接口 `/g3sb/api/nl/chat`、`/g3sb/api/nl/chat/stream` 的会话上文注入策略
|
||||
|
||||
## 改动说明
|
||||
|
||||
- **新增**:`backend/utils/dialog_context.py` 增加 `is_likely_follow_up(user_text)`,用于轻量判断是否为续问/沿用口径。
|
||||
- **调整**:`api_server.py` `_load_session_text2sql_context(request, user_text)` 在构造上文时:
|
||||
- **续问**:注入最近 2 轮会话摘要
|
||||
- **新话题**:不注入历史摘要(`max_pairs=0`)
|
||||
- **目的**:减少多话题情况下历史 SQL/条件回流导致的串话与错误 SQL。
|
||||
|
||||
## 影响与风险
|
||||
|
||||
- **破坏性变更**:否(对外 API 不变)
|
||||
- **风险等级**:中
|
||||
- 可能将少量“隐式续问”误判为新话题,导致指代消解能力下降
|
||||
- 但能显著降低跨话题上下文污染导致的 SQL 错误
|
||||
- **回滚**:回退本次改动即可恢复旧策略(或将 `max_pairs` 恢复为默认值 8)
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- 建议手工验证:
|
||||
- 同一 session:先做话题A数据查询,再提一个完全不同话题B(无“再/按上面”等续问词),应不再引用 A 的表/过滤条件。
|
||||
- 同一 session:首轮查询后,第二轮使用“再/按上面/沿用口径”等续问词,应仍能续用上轮口径生成 SQL。
|
||||
|
||||
## 后续事项
|
||||
|
||||
- 若误判率偏高,可迭代 `is_likely_follow_up` 规则(补充关键词/短语),或升级为轻量 LLM 判别器(方案B)。
|
||||
|
||||
Binary file not shown.
+7
-3
@@ -35,6 +35,7 @@ from utils.dialog_classifier import DialogIntent, classify_dialog
|
||||
from utils.dialog_context import (
|
||||
last_assistant_was_data_query,
|
||||
messages_to_text2sql_context,
|
||||
is_likely_follow_up,
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -246,6 +247,7 @@ def _conversation_nl_dict(reply: str) -> Dict[str, Any]:
|
||||
|
||||
async def _load_session_text2sql_context(
|
||||
request: NLChatRequest,
|
||||
user_text: str,
|
||||
) -> tuple[str, bool]:
|
||||
"""
|
||||
在写入本轮之前读取会话历史,构造 Text2SQL 上文,并判断上一轮助手是否为数据查询。
|
||||
@@ -266,7 +268,9 @@ async def _load_session_text2sql_context(
|
||||
if not items:
|
||||
return "", False
|
||||
last_data = last_assistant_was_data_query(items)
|
||||
block, _n = messages_to_text2sql_context(items)
|
||||
# 方案A:仅在“续问/沿用口径”时注入少量上文;新话题直接清空,避免上下文污染 SQL。
|
||||
max_pairs = 2 if is_likely_follow_up(user_text) else 0
|
||||
block, _n = messages_to_text2sql_context(items, max_pairs=max_pairs)
|
||||
return block, last_data
|
||||
|
||||
|
||||
@@ -377,7 +381,7 @@ async def _chat_stream_events(request: NLChatRequest) -> AsyncIterator[bytes]:
|
||||
return
|
||||
text = request.message.strip()
|
||||
orch = get_orchestrator()
|
||||
dialog_block, last_data = await _load_session_text2sql_context(request)
|
||||
dialog_block, last_data = await _load_session_text2sql_context(request, text)
|
||||
classified = await asyncio.to_thread(
|
||||
classify_dialog,
|
||||
text,
|
||||
@@ -461,7 +465,7 @@ async def nl_chat(request: NLChatRequest):
|
||||
text = request.message.strip()
|
||||
logger.info(f"[API] 问题: {text[:100]}...")
|
||||
orch = get_orchestrator()
|
||||
dialog_block, last_data = await _load_session_text2sql_context(request)
|
||||
dialog_block, last_data = await _load_session_text2sql_context(request, text)
|
||||
classified = await asyncio.to_thread(
|
||||
classify_dialog,
|
||||
text,
|
||||
|
||||
Binary file not shown.
@@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -14,6 +15,28 @@ _MAX_ASSISTANT_SQL_CHARS = 1400
|
||||
_MAX_ASSISTANT_TEXT_CHARS = 900
|
||||
_MAX_BLOCK_CHARS = 7500
|
||||
|
||||
# 续问/指代常见触发词:用于判定是否需要注入会话上文,避免多话题串扰。
|
||||
# 规则应偏保守:宁可把新话题当作“无上文”也不要带入无关上下文污染 SQL。
|
||||
_FOLLOW_UP_PATTERN = re.compile(
|
||||
r"(再|继续|同样|沿用|还是|照旧|刚才|上面|上一条|上一个|上次|上述|这个SQL|这条SQL|按上面|按刚才|在此基础上|同一口径|同口径|按之前|改成|改为|加上|加一下|筛一下|过滤一下|补充|补一下|追加|优化一下|调整一下|换成|换为)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def is_likely_follow_up(user_text: str) -> bool:
|
||||
"""
|
||||
仅基于用户本轮文本做“是否续问”的轻量判断。
|
||||
True -> 允许注入少量上文(最近 1~2 轮),用于指代消解/沿用口径
|
||||
False -> 新话题,禁用上文,避免上下文污染
|
||||
"""
|
||||
t = (user_text or "").strip()
|
||||
if not t:
|
||||
return False
|
||||
# 很短的“再来一个/同上/继续”等通常是续问
|
||||
if len(t) <= 10 and _FOLLOW_UP_PATTERN.search(t):
|
||||
return True
|
||||
return bool(_FOLLOW_UP_PATTERN.search(t))
|
||||
|
||||
|
||||
def _truncate(s: str, max_len: int) -> str:
|
||||
s = (s or "").strip()
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user