feat:修复投顾agent功能
This commit is contained in:
@@ -49,6 +49,32 @@ def build_cache_key(
|
||||
return f"nl2sql:{digest}"
|
||||
|
||||
|
||||
def build_question_cache_key(
|
||||
question: str,
|
||||
*,
|
||||
permission: dict[str, Any],
|
||||
data_scope: dict[str, Any] | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 100,
|
||||
sort_by: str | None = None,
|
||||
sort_order: str = "asc",
|
||||
) -> str:
|
||||
"""生成自然语言查询结果缓存键,覆盖问题、权限和数据范围。"""
|
||||
payload = {
|
||||
"question": " ".join((question or "").split()),
|
||||
"permission": _permission_payload(permission),
|
||||
"data_scope": data_scope or {},
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"sort_by": sort_by or "",
|
||||
"sort_order": sort_order,
|
||||
}
|
||||
digest = hashlib.sha256(
|
||||
json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
|
||||
).hexdigest()
|
||||
return f"nl2sql:question:{digest}"
|
||||
|
||||
|
||||
async def cache_get(redis, key: str) -> dict[str, Any] | None:
|
||||
"""读取缓存,Redis 异常或内容损坏时返回空结果。"""
|
||||
try:
|
||||
|
||||
@@ -16,6 +16,11 @@ _SYSTEM_PROMPT = """你是基金平台 NL2SQL 的问题改写器。
|
||||
不能扩大权限范围,不能臆造查询结果;上下文不足时保留原问题。
|
||||
"""
|
||||
|
||||
_REFERENCE_TERMS = (
|
||||
"他", "她", "它", "他们", "她们", "它们", "这个客户", "该客户", "那个客户",
|
||||
"这个基金", "该基金", "那只基金", "这只基金", "这个结果", "上一轮", "刚才", "前面",
|
||||
)
|
||||
|
||||
|
||||
def _rewrite_explicit_customer_identity(question: str) -> str | None:
|
||||
normalized = re.sub(r"\s+", "", question)
|
||||
@@ -68,7 +73,7 @@ async def rewrite_query(
|
||||
contextual_identity = _rewrite_customer_name_follow_up(original, context)
|
||||
if contextual_identity:
|
||||
return contextual_identity
|
||||
if not context or llm_client is None:
|
||||
if not context or llm_client is None or not any(term in original for term in _REFERENCE_TERMS):
|
||||
return original
|
||||
|
||||
prompt = (
|
||||
|
||||
@@ -67,6 +67,7 @@ async def render_answer(
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0,
|
||||
max_tokens=256,
|
||||
)
|
||||
except Exception: # noqa: BLE001 摘要失败回退固定文本
|
||||
return fallback
|
||||
|
||||
+10
-3
@@ -1,6 +1,7 @@
|
||||
"""NL2SQL 元数据召回与授权过滤。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from typing import Any
|
||||
|
||||
@@ -70,15 +71,21 @@ async def retrieve_metadata(
|
||||
"is_valid",
|
||||
"is_deprecated",
|
||||
]
|
||||
results: list[dict[str, Any]] = []
|
||||
for chunk_type in ("table_meta", "field_meta"):
|
||||
hits = await milvus_client.search(
|
||||
async def search_chunk(chunk_type: str):
|
||||
return await milvus_client.search(
|
||||
collection_name=NL2SQL_COLLECTION,
|
||||
data=[vector],
|
||||
limit=top_k,
|
||||
filter=f'chunk_type == "{chunk_type}" and is_valid == true',
|
||||
output_fields=output_fields,
|
||||
)
|
||||
|
||||
table_hits, field_hits = await asyncio.gather(
|
||||
search_chunk("table_meta"),
|
||||
search_chunk("field_meta"),
|
||||
)
|
||||
results: list[dict[str, Any]] = []
|
||||
for hits in (table_hits, field_hits):
|
||||
for hit in _flatten_hits(hits):
|
||||
entity = hit.get("entity") or hit
|
||||
if not entity.get("is_valid", True) or entity.get("is_deprecated", False):
|
||||
|
||||
+31
-2
@@ -1,12 +1,23 @@
|
||||
"""从 MySQL information_schema 加载并校验 NL2SQL 权威 Schema。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import bindparam, text
|
||||
|
||||
from nl2sql.metadata import normalize_column_row, normalize_table_row
|
||||
|
||||
logger = logging.getLogger("nl2sql.schema")
|
||||
SCHEMA_CACHE_TTL = 60
|
||||
|
||||
|
||||
def _schema_cache_key(database: str, candidates: list[str]) -> str:
|
||||
payload = json.dumps([database, candidates], ensure_ascii=False, separators=(",", ":"))
|
||||
return "nl2sql:schema:" + hashlib.sha256(payload.encode()).hexdigest()
|
||||
|
||||
|
||||
TABLES_SQL = text(
|
||||
"""
|
||||
@@ -34,13 +45,25 @@ async def load_authoritative_schema(
|
||||
*,
|
||||
database: str,
|
||||
candidate_tables: set[str] | list[str],
|
||||
redis=None,
|
||||
cache_ttl: int = SCHEMA_CACHE_TTL,
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
"""从权威元数据源加载候选表,并剔除不存在的表和孤立字段。"""
|
||||
candidates = {str(name).strip() for name in candidate_tables if str(name).strip()}
|
||||
if not candidates:
|
||||
return {"tables": [], "columns": []}
|
||||
|
||||
params = {"database": database, "candidate_tables": sorted(candidates)}
|
||||
candidate_list = sorted(candidates)
|
||||
cache_key = _schema_cache_key(database, candidate_list)
|
||||
if redis is not None:
|
||||
try:
|
||||
cached = await redis.get(cache_key)
|
||||
if cached:
|
||||
return json.loads(cached)
|
||||
except Exception: # noqa: BLE001 缓存异常回退权威查询
|
||||
logger.warning("Schema 缓存读取失败", exc_info=True)
|
||||
|
||||
params = {"database": database, "candidate_tables": candidate_list}
|
||||
table_result = await session.execute(TABLES_SQL, params)
|
||||
column_result = await session.execute(COLUMNS_SQL, params)
|
||||
|
||||
@@ -56,4 +79,10 @@ async def load_authoritative_schema(
|
||||
normalized = normalize_column_row(dict(row))
|
||||
if normalized["table_name"] in valid_table_names and normalized["field_name"]:
|
||||
columns.append(normalized)
|
||||
return {"tables": tables, "columns": columns}
|
||||
schema = {"tables": tables, "columns": columns}
|
||||
if redis is not None:
|
||||
try:
|
||||
await redis.set(cache_key, json.dumps(schema, ensure_ascii=False), ex=cache_ttl)
|
||||
except Exception: # noqa: BLE001 缓存异常不阻断查询
|
||||
logger.warning("Schema 缓存写入失败", exc_info=True)
|
||||
return schema
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ async def generate_sql(
|
||||
},
|
||||
]
|
||||
try:
|
||||
output = await llm_client.chat(messages, temperature=0)
|
||||
output = await llm_client.chat(messages, temperature=0, max_tokens=256)
|
||||
except Exception as exc: # noqa: BLE001 统一收敛模型异常
|
||||
raise SqlGenerationError("SQL 模型调用失败") from exc
|
||||
sql = _clean_model_output(output)
|
||||
|
||||
Reference in New Issue
Block a user