Files
group_xinghuo_jinrong/tests/test_sprint2_template_hybrid_search.py
T
zhanghongyu_0626 70aa861983 feat(advisor-agent): Introduce advisor agent functionalities with compliance, KYC, and script templates
- Added new modules for advisor compliance, KYC sessions, and script templates, enhancing the advisor agent's capabilities.
- Implemented a comprehensive API structure under the `/api/advisor-agent` prefix, ensuring clear organization and access to new features.
- Established database models and repositories for compliance rules and KYC sessions, facilitating robust data management.
- Integrated exception handling and response models to improve error management and user feedback.
- Updated settings to include new configurations for compliance and KYC features, ensuring flexibility and adaptability.

This update significantly expands the advisor agent's functionality, providing essential tools for compliance and customer interaction while maintaining a structured API design.
2026-09-12 16:33:07 +08:00

131 lines
4.6 KiB
Python

from types import SimpleNamespace
from uuid import uuid4
from fastapi.testclient import TestClient
import app.api.advisor_script_templates as templates_api
from app.advisor_db import AgentSessionLocal
from app.main import app
from app.model.entities_advisor import ScriptTemplate
from app.model.advisor_schemas import AuthContext
from app.repository.script_template_repository import ScriptTemplateRepository
from app.service.script_template_service import ScriptTemplateService
from app.service.script_template_vector_service import TemplateVectorError
client = TestClient(app)
def advisor_auth() -> AuthContext:
return AuthContext(
user_id="advisor_test",
display_name="Advisor Test",
roles=["advisor"],
permissions=["template:read"],
trace_id=f"trace-hybrid-{uuid4().hex}",
advisor_id="ADV-TEST-001",
)
def token_for(username: str, password: str) -> str:
response = client.post(
"/api/v1/auth/login",
json={"username": username, "password": password},
)
assert response.status_code == 200
return response.json()["data"]["access_token"]
def create_approved_template(
*,
title: str,
content: str,
usage_count: int = 0,
scene: str = "loss_comfort",
created_by: str | None = None,
) -> ScriptTemplate:
actor = created_by or f"test:hybrid_search:{uuid4().hex}"
with AgentSessionLocal() as session:
template = ScriptTemplate(
scene=scene,
customer_type="C3",
title=title,
content=content,
tags=["安抚", "市场波动"],
embedding_id=f"tpl_test_{uuid4().hex[:8]}",
is_approved=True,
approved_by="compliance_test",
is_active=True,
version=1,
usage_count=usage_count,
created_by=actor,
updated_by=actor,
)
session.add(template)
session.commit()
session.refresh(template)
session.expunge(template)
return template
class FakeHybridVectorService:
def __init__(self, hits: list[SimpleNamespace]) -> None:
self.hits = hits
self.calls: list[dict] = []
def search_templates(self, *, query: str, scene: str | None, top_k: int):
self.calls.append({"query": query, "scene": scene, "top_k": top_k})
return self.hits
class FailingVectorService:
def search_templates(self, *, query: str, scene: str | None, top_k: int):
raise TemplateVectorError("milvus unavailable")
def test_template_search_merges_keyword_and_vector_hits_with_score_order():
keyword = create_approved_template(
title="客户情绪安抚话术",
content="您好,短期波动不代表长期趋势,请先看风险揭示材料。",
usage_count=1,
)
semantic = create_approved_template(
title="市场波动沟通话术",
content="您好,近期净值回撤来自市场波动,可以先复盘持仓结构。",
usage_count=0,
)
vector_service = FakeHybridVectorService(
[
SimpleNamespace(template_id=semantic.id, score=0.95),
SimpleNamespace(template_id=keyword.id, score=0.80),
]
)
service = ScriptTemplateService(repository=ScriptTemplateRepository(), vector_service=vector_service)
result = service.search_templates(auth=advisor_auth(), q="客户情绪安抚", top_k=3)
ids = [item.id for item in result.items]
assert ids[0] == semantic.id
assert keyword.id in ids
assert result.items[0].match_type == "semantic"
assert all(item.score <= 1.0 for item in result.items)
assert vector_service.calls == [{"query": "客户情绪安抚", "scene": None, "top_k": 3}]
def test_template_search_api_falls_back_to_keyword_when_vector_unavailable(monkeypatch):
suffix = uuid4().hex[:8]
query = f"跌幅沟通关键词{suffix}"
template = create_approved_template(
title=f"{query}模板",
content="您好,产品净值短期有波动,请结合自身风险承受能力理性看待。",
)
monkeypatch.setattr(
templates_api,
"template_service",
ScriptTemplateService(repository=ScriptTemplateRepository(), vector_service=FailingVectorService()),
)
token = token_for("advisor_test", "advisor_test")
response = client.get(
"/api/advisor-agent/script-templates/search",
params={"q": query},
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 200
items = response.json()["data"]["items"]
assert items[0]["id"] == template.id
assert items[0]["match_type"] == "keyword"