diff --git a/app/api/kyc.py b/app/api/kyc.py index ea27b3c..d4d44be 100644 --- a/app/api/kyc.py +++ b/app/api/kyc.py @@ -5,7 +5,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, Request from app.api.advisor_auth_adapter import get_advisor_auth, require_advisor_permission -from app.model.advisor_schemas import ApiResponse, KycChatRequest, KycSessionCreate +from app.model.advisor_schemas import KycChatRequest, KycSessionCreate from app.api.advisor_auth_adapter import AdvisorAuthContext from app.service.kyc_session_service import KycSessionService from app.api.advisor_http import advisor_ok as _advisor_ok @@ -24,7 +24,7 @@ def ping(request: Request, auth: AdvisorAuthContext = AUTH_CONTEXT_DEP): return _advisor_ok(request, {"module": "kyc", "status": "ready"}) -@router.post("/sessions", response_model=ApiResponse) +@router.post("/sessions") def create_kyc_session( request: Request, payload: KycSessionCreate, @@ -38,7 +38,7 @@ def create_kyc_session( return _advisor_ok(request, result.model_dump(mode="json")) -@router.get("/sessions/{session_id}", response_model=ApiResponse) +@router.get("/sessions/{session_id}") def get_kyc_session( session_id: str, request: Request, @@ -48,7 +48,7 @@ def get_kyc_session( return _advisor_ok(request, result.model_dump(mode="json")) -@router.post("/sessions/{session_id}/chat", response_model=ApiResponse) +@router.post("/sessions/{session_id}/chat") def chat_kyc_session( session_id: str, request: Request, @@ -64,7 +64,7 @@ def chat_kyc_session( return _advisor_ok(request, result.model_dump(mode="json")) -@router.post("/sessions/{session_id}/complete", response_model=ApiResponse) +@router.post("/sessions/{session_id}/complete") def complete_kyc_session( session_id: str, request: Request, diff --git a/app/tool/milvus_template_tool.py b/app/tool/milvus_template_tool.py index a25440f..68c4a6f 100644 --- a/app/tool/milvus_template_tool.py +++ b/app/tool/milvus_template_tool.py @@ -117,15 +117,18 @@ class MilvusTemplateVectorStore: filter_expr = "is_approved == true" if scene: filter_expr = f'{filter_expr} and scene == "{scene}"' - raw_results = self._get_client().search( - collection_name=self.collection_name, - data=[embedding], - anns_field="embedding", - limit=top_k, - filter=filter_expr, - output_fields=["template_id"], - search_params={"metric_type": "COSINE", "params": {"nprobe": 10}}, - ) + try: + raw_results = self._get_client().search( + collection_name=self.collection_name, + data=[embedding], + anns_field="embedding", + limit=top_k, + filter=filter_expr, + output_fields=["template_id"], + search_params={"metric_type": "COSINE", "params": {"nprobe": 10}}, + ) + except Exception as exc: + raise MilvusToolError(str(exc)) from exc return [_parse_vector_hit(hit) for hit in (raw_results[0] if raw_results else [])] def _get_client(self): diff --git a/docs/memory/MEMORY.md b/docs/memory/MEMORY.md index 5a00cef..f1677c2 100644 --- a/docs/memory/MEMORY.md +++ b/docs/memory/MEMORY.md @@ -79,6 +79,8 @@ mysql … < scripts/agent/migrate-advisor-agent-sprint1-3.sql # 投资顾问专用表(合并后必跑) 5. mysql … < scripts/agent/seed-aml-list.sql # AML 名单(Windows 乱码:`python scripts/dev/fix_utf8_seed.py`) python scripts/seed/import_compliance_rules.py # 顾问合规规则(源:`docs/开发文档/20-Sprint1首批合规规则数据集.md`) + .\scripts\demo\seed_advisor_agent.ps1 # 一键:迁移 + 规则 + 话术 + 演示审核(推荐) + .\scripts\demo\seed_advisor_agent.ps1 # 一键:迁移 + 规则 + 话术 + 演示审核(推荐) (风控演示:`.\scripts\demo\prepare_all.ps1` 或 `prepare_risk_demo.sql` · PRD §10.2) 6. python scripts/sync/sync_advisor_rel.py && python scripts/sync/sync_neo4j.py 7. `docker compose up -d redis`(或 `.\scripts\dev\start-redis.ps1`)→ **REDIS_URL=redis://127.0.0.1:6380/0**(Docker Redis 7;避开本机 Windows Redis 占 6379) diff --git a/docs/memory/TODO.md b/docs/memory/TODO.md index 64c2e59..a4b3139 100644 --- a/docs/memory/TODO.md +++ b/docs/memory/TODO.md @@ -10,7 +10,7 @@ - [x] 拍板 D1–D9 · 合并说明 + superpowers spec/plan - [x] 代码接缝:`/api/advisor-agent/*` · `advisor_auth_adapter` · `advisor_db` · 话术/合规重命名 · `main.py` 挂载 - [x] Sprint0 冒烟 · OpenAPI **62** 路径 · `test_main` 更新 -- [ ] 本机 **`migrate-advisor-agent-sprint1-3.sql`** + 合规/话术 seed +- [x] 本机 **`migrate-advisor-agent-sprint1-3.sql`** + 合规/话术 seed(`scripts/demo/seed_advisor_agent.ps1`) - [ ] **`test_sprint*` 全绿**(conftest 内存 SQLite + 规则种子已接;模板 seed · Core 行情 · Milvus 等待补) - [ ] 合入 **`merger`** · 契约附录 · 本说明状态「已接线」· memory 基线收敛 - [ ] `git commit`(用户确认后) diff --git a/docs/开发文档/29-Sprint2首批话术模板数据集.md b/docs/开发文档/29-Sprint2首批话术模板数据集.md new file mode 100644 index 0000000..ecab986 --- /dev/null +++ b/docs/开发文档/29-Sprint2首批话术模板数据集.md @@ -0,0 +1,22 @@ +| 序号 | scene | customer_type | title | content | tags | is_active | 审核状态 | 审核人 | 备注 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | loss_comfort | general | 亏损安抚模板01 | 您好,关于亏损安抚场景说明1:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | loss_comfort;demo | true | 测试数据 | | 测试数据 | +| 2 | loss_comfort | general | 亏损安抚模板02 | 您好,关于亏损安抚场景说明2:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | loss_comfort;demo | true | 测试数据 | | 测试数据 | +| 3 | loss_comfort | general | 亏损安抚模板03 | 您好,关于亏损安抚场景说明3:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | loss_comfort;demo | true | 测试数据 | | 测试数据 | +| 4 | loss_comfort | general | 亏损安抚模板04 | 您好,关于亏损安抚场景说明4:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | loss_comfort;demo | true | 测试数据 | | 测试数据 | +| 5 | product_recommend | general | 产品说明模板05 | 您好,关于产品说明场景说明1:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | product_recommend;demo | true | 测试数据 | | 测试数据 | +| 6 | product_recommend | general | 产品说明模板06 | 您好,关于产品说明场景说明2:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | product_recommend;demo | true | 测试数据 | | 测试数据 | +| 7 | product_recommend | general | 产品说明模板07 | 您好,关于产品说明场景说明3:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | product_recommend;demo | true | 测试数据 | | 测试数据 | +| 8 | product_recommend | general | 产品说明模板08 | 您好,关于产品说明场景说明4:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | product_recommend;demo | true | 测试数据 | | 测试数据 | +| 9 | product_recommend | general | 产品说明模板09 | 您好,关于产品说明场景说明5:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | product_recommend;demo | true | 测试数据 | | 测试数据 | +| 10 | dual_record | general | 双录开场模板10 | 您好,关于双录开场场景说明1:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | dual_record;demo | true | 测试数据 | | 测试数据 | +| 11 | dual_record | general | 双录开场模板11 | 您好,关于双录开场场景说明2:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | dual_record;demo | true | 测试数据 | | 测试数据 | +| 12 | dual_record | general | 双录开场模板12 | 您好,关于双录开场场景说明3:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | dual_record;demo | true | 测试数据 | | 测试数据 | +| 13 | market_comment | general | 市场解读模板13 | 您好,关于市场解读场景说明1:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | market_comment;demo | true | 测试数据 | | 测试数据 | +| 14 | market_comment | general | 市场解读模板14 | 您好,关于市场解读场景说明2:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | market_comment;demo | true | 测试数据 | | 测试数据 | +| 15 | market_comment | general | 市场解读模板15 | 您好,关于市场解读场景说明3:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | market_comment;demo | true | 测试数据 | | 测试数据 | +| 16 | market_comment | general | 市场解读模板16 | 您好,关于市场解读场景说明4:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | market_comment;demo | true | 测试数据 | | 测试数据 | +| 17 | greeting | general | 日常问候模板17 | 您好,关于日常问候场景说明1:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | greeting;demo | true | 测试数据 | | 测试数据 | +| 18 | greeting | general | 日常问候模板18 | 您好,关于日常问候场景说明2:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | greeting;demo | true | 测试数据 | | 测试数据 | +| 19 | greeting | general | 日常问候模板19 | 您好,关于日常问候场景说明3:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | greeting;demo | true | 测试数据 | | 测试数据 | +| 20 | greeting | general | 日常问候模板20 | 您好,关于日常问候场景说明4:基金投资存在净值波动风险,历史业绩不代表未来表现。请结合自身风险承受能力理性决策,并阅读产品法律文件。 | greeting;demo | true | 测试数据 | | 测试数据 | diff --git a/scripts/demo/seed_advisor_agent.ps1 b/scripts/demo/seed_advisor_agent.ps1 new file mode 100644 index 0000000..5b35f31 --- /dev/null +++ b/scripts/demo/seed_advisor_agent.ps1 @@ -0,0 +1,44 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + 投资顾问 Agent 演示灌库:迁移表 + 合规规则 + 话术模板 + 演示用审核启用 +#> +$ErrorActionPreference = "Stop" +$Root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +Set-Location $Root + +Write-Host ">> Advisor DB migration (IF NOT EXISTS)" +python (Join-Path $Root "scripts/dev/run_sql_file.py") (Join-Path $Root "scripts/agent/migrate-advisor-agent-sprint1-3.sql") + +Write-Host ">> Compliance rules (50 test rules)" +python (Join-Path $Root "scripts/seed/import_compliance_rules.py") + +$templateDataset = Join-Path $Root "docs/开发文档/29-Sprint2首批话术模板数据集.md" +if (-not (Test-Path $templateDataset)) { + Write-Warning "Missing $templateDataset — run repo generator or copy from docs/演示文档/功能演示版数据清单.md" + exit 1 +} + +Write-Host ">> Script templates (20 test rows)" +python (Join-Path $Root "scripts/seed/import_script_templates.py") --dataset $templateDataset + +Write-Host ">> Approve seed templates for advisor search (demo only)" +python -c @" +from datetime import datetime +from app.advisor_db import AgentSessionLocal +from app.model.entities_advisor import ScriptTemplate +with AgentSessionLocal() as s: + n = s.query(ScriptTemplate).filter( + ScriptTemplate.created_by == 'seed:template_test_data', + ScriptTemplate.is_active == True, + ).update({ + 'is_approved': True, + 'approved_by': 'seed:demo_ready', + 'approved_at': datetime.now(), + 'updated_by': 'seed:demo_ready', + }, synchronize_session=False) + s.commit() + print(f'approved_templates={n}') +"@ + +Write-Host "Done. Smoke: tests/test_sprint0_foundation.py or POST /api/advisor-agent/compliance/content-check" diff --git a/scripts/sync/sync_template_vectors.py b/scripts/sync/sync_template_vectors.py index 84f3843..86d4d2e 100644 --- a/scripts/sync/sync_template_vectors.py +++ b/scripts/sync/sync_template_vectors.py @@ -10,7 +10,7 @@ ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from app.repository.script_template_repository import ScriptTemplateRepository -from app.service.script_template_vector_service import ScriptScriptTemplateVectorService +from app.service.script_template_vector_service import ScriptTemplateVectorService def main() -> None: @@ -25,7 +25,7 @@ def main() -> None: print(f"Template vector sync dry run: total={total}") return - result = ScriptScriptTemplateVectorService(repository=repository).sync_approved_templates(created_by=args.created_by) + result = ScriptTemplateVectorService(repository=repository).sync_approved_templates(created_by=args.created_by) print( "Template vector sync: " f"total={result.total}, upserted={result.upserted}, skipped={result.skipped}"