feat:修复客服agent功能

This commit is contained in:
2026-09-14 17:47:25 +08:00
parent b989d78248
commit 4f296eb58c
10 changed files with 556 additions and 91 deletions
+54 -2
View File
@@ -24,12 +24,43 @@ from repositories.fin_holdings import FinHoldingsRepo
from repositories.fin_product import FinProductRepo
def enrich_customer_rows(
rows: list[dict[str, Any]],
columns: list[str],
name_by_id: dict[int, str],
) -> dict[str, Any]:
"""为查询结果补充客户姓名,并生成可直接展示的姓名摘要。"""
id_keys = ("customer_id", "客户ID", "客户编号", "客户_id")
enriched = [dict(row) for row in rows]
names: list[str] = []
for row in enriched:
customer_id = next((row.get(key) for key in id_keys if row.get(key) is not None), None)
try:
customer_id = int(customer_id)
except (TypeError, ValueError):
continue
name = name_by_id.get(customer_id)
if name:
row["客户姓名"] = name
names.append(f"客户{customer_id}({name})")
output_columns = list(columns)
if any("客户姓名" in row for row in enriched) and "客户姓名" not in output_columns:
insert_at = next(
(index + 1 for index, column in enumerate(output_columns) if column in id_keys),
len(output_columns),
)
output_columns.insert(insert_at, "客户姓名")
return {"rows": enriched, "columns": output_columns, "name_summary": "、".join(dict.fromkeys(names))}
def _is_current_holdings_query(question: str) -> bool:
text = "".join((question or "").split())
return any(term in text for term in ("当前持仓", "目前持仓", "现有持仓", "持仓明细", "持仓情况"))
async def _query_current_holdings(db, *, customer_id: int, trace_id: str) -> dict[str, Any]:
async def _query_current_holdings(
db, *, customer_id: int, trace_id: str, customer_name: str | None = None
) -> dict[str, Any]:
holdings = await FinHoldingsRepo(db).list_by_customer(customer_id, status="持有中")
product_repo = FinProductRepo(db)
rows: list[dict[str, Any]] = []
@@ -60,7 +91,8 @@ async def _query_current_holdings(db, *, customer_id: int, trace_id: str) -> dic
"truncated": False,
"summary": f"当前持仓共 {len(rows)} 条记录。",
"answer": (
f"当前持有 {len(rows)} 只基金,总市值约 {total_value:.2f} 元。"
(f"客户{customer_id}({customer_name})" if customer_name else f"客户{customer_id}")
+ f"当前持有 {len(rows)} 只基金,总市值约 {total_value:.2f} 元。"
+ (f"包括:{'、'.join(names[:6])}。" if names else "")
),
"sql": None,
@@ -108,11 +140,25 @@ async def execute_advisor_data_query(
await ensure_customer_access(db, advisor_id=advisor_id, customer_id=customer_id)
customer_ids = [customer_id]
name_by_id: dict[int, str] = {}
try:
relation_rows = await CustomerRelationRepo(db).list_customer_rows(
advisor_id=advisor_id, limit=max(len(customer_ids), 100)
)
name_by_id = {
int(account.id): account.real_name
for _relation, account, _profile in relation_rows
if account.real_name
}
except Exception: # noqa: BLE001 姓名增强失败不阻断数据查询
pass
if scope == "customer" and _is_current_holdings_query(question):
return await _query_current_holdings(
db,
customer_id=customer_id,
trace_id=trace_id,
customer_name=name_by_id.get(customer_id),
)
permission = await load_query_permission(db, advisor_id)
if not permission.get("can_query", False):
@@ -168,6 +214,12 @@ async def execute_advisor_data_query(
except (EmbeddingError, LLMFailError) as exc:
raise QueryServiceError("投顾 Agent 依赖服务不可用,请检查 LLM/Embedding 服务连接") from exc
payload = asdict(result)
enriched = enrich_customer_rows(payload.get("rows", []), payload.get("columns", []), name_by_id)
payload["rows"] = enriched["rows"]
payload["columns"] = enriched["columns"]
if enriched["name_summary"]:
existing_answer = payload.get("answer") or payload.get("summary") or ""
payload["answer"] = f"{existing_answer.rstrip('。')}。客户姓名:{enriched['name_summary']}。"
payload["sql"] = None
payload["customer_id"] = customer_id
return payload
@@ -71,6 +71,7 @@ async def generate_recommendation_draft(
memories: list[dict] | None = None,
llm_client=None,
llm_timeout: float = 5.0,
customer_context: dict | None = None,
):
draft_data = build_recommendation_draft(
customer_id=customer_id,
@@ -87,7 +88,10 @@ async def generate_recommendation_draft(
user_prompt=(
"请根据以下候选基金和客户记忆生成简洁推荐说明:"
+ json.dumps(
{"candidates": candidates, "memories": memories or []},
{
"candidates": candidates,
"customer_context": customer_context or {},
},
ensure_ascii=False,
)
),