docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""场外基金专用 NL2SQL 适配器。"""
|
||||
|
||||
from importlib import import_module
|
||||
from typing import cast
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.core.contracts import RequestContext
|
||||
|
||||
|
||||
class OffsiteNl2SqlAdapter:
|
||||
"""把场外基金上下文转换为 query_dict 的稳定参数并执行只读查询。"""
|
||||
|
||||
script_path = "nl2sql_yc.py"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db_engine: Engine | None = None,
|
||||
settings: Settings | None = None,
|
||||
) -> None:
|
||||
self._db_engine = db_engine
|
||||
self._settings = settings or get_settings()
|
||||
self._owns_engine = db_engine is None
|
||||
|
||||
def query(self, question: str, context: RequestContext) -> dict[str, object]:
|
||||
try:
|
||||
query_dict = getattr(import_module("nl2sql_yc"), "query_dict", None)
|
||||
if not callable(query_dict):
|
||||
return {"status": "error", "message": "NL2SQL入口query_dict不存在"}
|
||||
db_engine = self._get_db_engine()
|
||||
result = cast(dict[str, object], query_dict(
|
||||
question,
|
||||
self._auth_context(context),
|
||||
db_engine=db_engine,
|
||||
use_llm=False,
|
||||
persist_audit=False,
|
||||
))
|
||||
if isinstance(result, dict):
|
||||
return result
|
||||
return {"status": "error", "message": "NL2SQL返回格式不正确"}
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
return {"status": "error", "message": f"NL2SQL调用失败:{type(exc).__name__}"}
|
||||
|
||||
def close(self) -> None:
|
||||
"""释放适配器自行创建的同步连接池。"""
|
||||
if self._owns_engine and self._db_engine is not None:
|
||||
self._db_engine.dispose()
|
||||
self._db_engine = None
|
||||
|
||||
def _get_db_engine(self) -> Engine:
|
||||
if self._db_engine is not None:
|
||||
return self._db_engine
|
||||
dsn = self._settings.mysql_dsn
|
||||
if dsn.startswith("mysql+asyncmy://"):
|
||||
dsn = dsn.replace("mysql+asyncmy://", "mysql+pymysql://", 1)
|
||||
elif dsn.startswith("mysql://"):
|
||||
dsn = dsn.replace("mysql://", "mysql+pymysql://", 1)
|
||||
if not dsn:
|
||||
raise RuntimeError("未配置数据库连接")
|
||||
self._db_engine = create_engine(dsn, pool_pre_ping=True)
|
||||
return self._db_engine
|
||||
|
||||
@staticmethod
|
||||
def _auth_context(context: RequestContext) -> dict[str, object]:
|
||||
return {
|
||||
"user_id": int(context.user_id) if context.user_id.isdigit() else None,
|
||||
"roles": list(context.roles),
|
||||
"allowed_domains": ["market_nav", "trading_account", "product_fee"],
|
||||
"customer_scope": context.data_scope if context.data_scope in {
|
||||
"self", "own_customers", "all",
|
||||
} else "all",
|
||||
"max_rows": 100,
|
||||
"max_query_seconds": 10,
|
||||
}
|
||||
Reference in New Issue
Block a user