Files
Mutual_Fund/nl2sql/supervisor.py
T

65 lines
2.0 KiB
Python
Raw Normal View History

2026-09-13 16:19:24 +08:00
"""NL2SQL Supervisor 的意图和会话并发控制。"""
from __future__ import annotations
from uuid import uuid4
class UnsupportedIntent(ValueError):
"""当前版本不支持的查询意图。"""
class SessionBusyError(RuntimeError):
"""同一会话已有查询运行。"""
def detect_intent(question: str) -> str:
"""根据有限关键词识别当前版本的任务类型。"""
text = question.lower()
if "etl" in text or "数据任务" in text:
return "etl_build"
if "口径" in text or "caliber" in text:
return "caliber_maintain"
if "血缘" in text or "lineage" in text:
return "lineage_query"
return "query"
def ensure_query_intent(question: str) -> None:
"""M1 只允许 query 意图进入 SQL 主链路。"""
intent = detect_intent(question)
if intent != "query":
raise UnsupportedIntent(f"当前不支持 {intent} 意图")
class SessionLock:
"""基于 Redis NX 的同会话互斥锁。"""
def __init__(self, redis, session_id: str, *, ttl: int = 120):
self.redis = redis
self.key = f"nl2sql:session:{session_id}:lock"
self.token = uuid4().hex
self.ttl = ttl
self.acquired = False
async def acquire(self) -> None:
if not await self.redis.set(self.key, self.token, nx=True, ex=self.ttl):
raise SessionBusyError("同一会话已有查询运行")
self.acquired = True
async def release(self) -> None:
if not self.acquired:
return
release_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
"""
if hasattr(self.redis, "eval"):
await self.redis.eval(release_script, 1, self.key, self.token)
else:
current = await self.redis.get(self.key)
if current in {self.token, self.token.encode()}:
await self.redis.delete(self.key)
self.acquired = False