Merge branch 'data-analysis-agent-work' into merger

This commit is contained in:
2026-09-09 20:53:59 +08:00
39 changed files with 3430 additions and 6 deletions
+39
View File
@@ -0,0 +1,39 @@
"""演示:用真实 LLM + 真实 MySQL 跑数据分析 Agent 的典型问答。"""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from app.service.analyst_agent import AnalystAgent
from app.utils.auth import AuthContext
def run(question, roles, subject):
agent = AnalystAgent()
ctx = AuthContext(subject_id=subject, token_type="staff", roles=roles, staff_type=roles[0])
resp = agent.run(question, ctx)
print(f"\n=== 问题:{question}(角色 {roles[0]})===")
print(f"状态:{resp.status} 缓存命中:{resp.meta.cache_hit}")
print(f"解读:{resp.answer}")
print(f"表格:{json.dumps(resp.table.model_dump(), ensure_ascii=False, default=str)}")
print(f"SQL:{resp.sql}")
if resp.error_code:
print(f"错误码:{resp.error_code} 建议:{resp.suggestions}")
return resp
if __name__ == "__main__":
# 先拿到一个顾问和他的非名下客户
agent = AnalystAgent()
advisor = agent.repo.execute_readonly(
"SELECT staff_id FROM core_staff WHERE staff_type='advisor' AND is_active=1 LIMIT 1"
)["rows"][0][0]
scope = agent.repo.resolve_advisor_scope(advisor)
out_customer = next(
c for c in agent.repo.execute_readonly("SELECT customer_id FROM core_customer LIMIT 100")["rows"]
if c[0] not in scope
)[0]
run("按产品类型统计总持仓规模", ["analyst"], "STAFF-20001")
run(f"查 {out_customer} 的持仓", ["advisor"], advisor)
+30
View File
@@ -0,0 +1,30 @@
"""诊断单个问题的 SQL 生成与执行(用法:python scripts/dev/diag_question.py "问题" 角色)。"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from app.service.analyst_agent import AnalystAgent
from app.utils.auth import AuthContext
def main():
question = sys.argv[1] if len(sys.argv) > 1 else "近30天申购金额总额"
role = sys.argv[2] if len(sys.argv) > 2 else "ops"
agent = AnalystAgent()
ctx = AuthContext(subject_id=f"STAFF-{role}", token_type="staff", roles=[role], staff_type=role)
domain = {"analyst": "full", "advisor": "assigned", "risk_officer": "risk", "ops": "aggregate"}[role]
scope = []
if domain == "assigned":
scope = agent.repo.resolve_advisor_scope(ctx.subject_id)
sql, _ = agent._generate_sql(question, domain, scope)
print("SQL:", sql)
try:
res = agent.repo.execute_readonly(sql)
print("OK cols=", res["columns"], "rows[:3]=", res["rows"][:3])
except Exception as exc: # noqa: BLE001
print("EXEC ERROR:", type(exc).__name__, exc)
if __name__ == "__main__":
main()
+55
View File
@@ -0,0 +1,55 @@
"""导出数据分析 Agent 可读表的列结构(用于 NL2SQL 的 schema 元数据注入)。"""
import pathlib
import pymysql
def load_env(path=".env"):
env = {}
for line in pathlib.Path(path).read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
return env
TABLES = [
("jinrong_core", "core_customer"),
("jinrong_core", "core_customer_risk"),
("jinrong_core", "core_customer_advisor"),
("jinrong_core", "core_holding"),
("jinrong_core", "core_trade"),
("jinrong_core", "core_cash_flow"),
("jinrong_core", "core_product"),
("jinrong_core", "core_product_nav"),
("jinrong_core", "core_staff"),
("jinrong_core", "core_risk_grade"),
("jinrong_agent", "risk_alert"),
]
def main():
env = load_env()
conn = pymysql.connect(
host=env.get("MYSQL_HOST", "127.0.0.1"),
port=int(env.get("MYSQL_PORT", "3306")),
user=env.get("MYSQL_USER", "root"),
password=env.get("MYSQL_PASSWORD", ""),
charset="utf8mb4",
)
with conn.cursor() as cur:
for db, tbl in TABLES:
cur.execute(
"SELECT column_name, data_type FROM information_schema.columns "
"WHERE table_schema=%s AND table_name=%s ORDER BY ordinal_position",
(db, tbl),
)
cols = [f"{c[0]}:{c[1]}" for c in cur.fetchall()]
print(f"{tbl} ({db}) -> {', '.join(cols)}")
conn.close()
if __name__ == "__main__":
main()
+24
View File
@@ -0,0 +1,24 @@
"""查看种子数据关键事实(用于集成测试与演示)。"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from app.service.analytics_repo import AnalyticsRepo
def main():
repo = AnalyticsRepo()
q = lambda sql: repo.execute_readonly(sql) # noqa: E731
print("advisor ids:", [r[0] for r in q("SELECT staff_id FROM core_staff WHERE staff_type='advisor' AND is_active=1 LIMIT 5")["rows"]])
print("risk_officer ids:", [r[0] for r in q("SELECT staff_id FROM core_staff WHERE staff_type='risk_officer' AND is_active=1 LIMIT 5")["rows"]])
print("ops ids:", [r[0] for r in q("SELECT staff_id FROM core_staff WHERE staff_type='ops' AND is_active=1 LIMIT 5")["rows"]])
print("analyst ids:", [r[0] for r in q("SELECT staff_id FROM core_staff WHERE staff_type='analyst' AND is_active=1 LIMIT 5")["rows"]])
print("risk codes:", q("SELECT risk_code, COUNT(*) c FROM core_customer_risk GROUP BY risk_code ORDER BY risk_code")["rows"])
print("product types:", q("SELECT product_type, COUNT(*) c FROM core_product GROUP BY product_type")["rows"])
print("alert status:", q("SELECT status, COUNT(*) c FROM jinrong_agent.risk_alert GROUP BY status")["rows"])
print("customers sample:", [r[0] for r in q("SELECT customer_id FROM core_customer LIMIT 8")["rows"]])
if __name__ == "__main__":
main()