qyqy_develop
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94ce44a851 |
feat(W24): docs/43 场内基金手册入库 + B-02 展示候选集收口
补历史欠账:D3.4 B-02(决 12)与 D4.1 §B-02 早已承诺把 docs/43 纳入 SOURCES, 从未执行;W21 首次把它暴露成实测缺口(702 块里「科创债」0 处)。 语料与切片 - docs/43-场内基金产品手册(知识库入库版).md 纳入 SOURCES:20 只场内基金 (13 ETF + 7 LOF),fin_product_collection / public / prefix=ETF;切片件 702 -> 755 块 - 新增三个「按源开启」的切片开关(默认关,其它 8 个源零字节变化): strip_editorial_marks / qualify_table_rows / exclude_sections - 费率表表头补单位(%/年);docs/43 与 knowledge 镜像逐字一致 - docs/45 裁定不入库(与 FAQ-0018 / POL-AST-011/012 重复,会抢 top1) 修复两个真实缺陷 - W24-A 切片器行标签取错列:多列表格第 0 列是代码(159700[:2]='15'), 上游 _prefer_section 判不出重合 => 每一问都被换成整节块 (实测「科创债ETF南方怎么样」返回 20 只产品的整张表);改取表头「名称」列 - W24-B text-embedding-v3 单请求上限 10 条:load_knowledge_milvus.embed() 只在灌库路径分批,自检路径超 10 条即在数据写完后崩;分批下沉进 embed() - B-02 的 M-4 回归:_exit_partial 两条调用路径候选集不同(主路径 TopK 全量 vs 证据路径被 E4_MAX_EVIDENCE 截断)=> 同一句问句的答复质量取决于 E4 有没有 调用模型;_answer_from_evidence 新增 display_hits,展示候选一律用 TopK 全量, 送模型的证据包保持 6 块不变 产品名识别 - _PRODUCT_NAME_SHAPE 后缀集补 定期开放混合 / 股票(LOF)A / (LOF) / 原油A - 新增 _BRANDLESS_PRODUCT_NAMES(沪深300ETF,唯一无厂商字样的产品,只能枚举) - _strip_product_name_lead 增「先切掉前一只基金」 验收 - 金标 46 条 M-1 46/46、M-4 46/46、M-6 5/46、M-7/8/9/10 = 0、 M-2 28/31、M-2b 15/18、M-3 4/4(与 w23 基线逐项一致) - 两次独立复跑 result_w24d / result_w24e 指标完全相同 - Milvus 自检 15/15;全量回归 1996 passed / 3 skipped;ruff 零新增 - 真 HTTP 11 条全绿 判据变更(必须知情,不适用「零回归」表述) - B-01(访客)expected_evidence 补 ETF - B-05(客户,同一句问句)expected_exits [E3] -> [E3, E4] - load_knowledge_milvus 自检 BAS-CON-006 -> ETF-005 文档 - 客服agent/D2.1 升 v6.39;D2.4 升 v1.8(语料 755 块、手册切片实测 53); D2.8 新增 §3.6 与 §11 复测;D2.9 升 v1.3 - 开发文档/D1.1 升 v1.15(新增 §31);D1.6 升 v1.2(新增 §11、§10.4 销账); D4.8 升 v1.2(新增 §10) 诚实留痕 - _exit_partial 仍按分数挑块(不接收问句),本轮只统一候选集口径 - E5b 展示层净化仍不覆盖「收益 + 数字%」形态,继续挂账 - 语料里 2 个零容忍地雷块(POL-SPM-016 / POL-SPM-022-01)是禁令条款,故意保留 |
||
|
|
dd5e9f8435 |
fix(W21): 智能度体检 → 3 类真实缺陷修复(C-8/C-9/C-10)+ 1 类待裁登记(C-11)
甲方反馈「客服 agent 还是不太智能」。本轮不新增出口、不改需求, 把"不智能"拆成可复现的实测条目:81 条真实口语问法 + 8 组多轮追问链 (真 HTTP:API → 队列 → Worker → 治理层),逐条定位根因并修复。 已修复(每条都有真机 HTML 证据,见 开发文档/D4.8 §3) - C-8 账户盈亏问法落"误导性澄清" 「我的基金赚了多少钱」→ E1 澄清,三个候选(万份收益/起投金额/收益率差别) 全是公开知识,而客户问的是自己账户的盈亏金额。 修法:P1_PATTERNS 补「第一人称 + 盈亏动词 + 金额疑问词」骨架, 金额疑问词是必要条件 —— 「我买的基金亏了怎么办」照旧走知识检索(库里有解)。 - C-9 多轮指代断链,把"已经说过的"又问一遍 「我想买个债基」→「它适合我吗」落 E2d「请告诉我具体的基金名称或代码」, 而客户刚刚才被告知是那只产品;「赎回费怎么算」→「持有 8 个月呢」同因。 两个根因:① _topic_of 只在答复前 4 行的固定形状里取主语,FAQ 型答复 (首行「问:…」)反解为空;② 意图分类把短追问标成 suitability_check, 绕开了知识出口里已做好的追问继承逻辑。 修法:_answer_suitability 建成四级降级链(严格主语 → 形状反解 _product_name_in_history → 有上文交回检索 → 首轮才澄清)+ 纯参数追问前置闸门。 安全边界三条不放宽:不给访客"能不能买"结论;推测出的名字查不到风险等级时 回落检索而非回"给不出结论";首轮指代保留澄清(金标 E-01 口径)。 - C-10 「你们投诉电话是多少」被强制转人工 根因:P2_WRITE_DISPUTE_KEYWORDS 里的裸词「投诉」子串命中,把"问投诉渠道" 和"提交投诉"撞在同一判据上;而答案就在库里(POL-SPM-036-01 / FAQ-0054)。 修法:新增 _is_p2_contact_inquiry() 作为第二类 P2 豁免(渠道词 + 疑问词, 且不带明确投诉意图)。「我要投诉,让你们经理来找我」照旧建单。 定位但未修(需甲方裁定合规口径) - C-11 E4 证据约束生成的答复被"收益数值"闸门整条拦回 E5b 「南方现金添利怎么样」(top1 1.0000) / 「买基金要手续费吗」(0.7328) / 「债基和货基哪个收益高」(0.5457) 三条同因:生成稿出现 YIELD_METRIC_TERMS → hits_zero_tolerance → _exit_partial → E5b 兜底。 试过"先净化再判合规",实测更差(drop_yield_claims 整行丢弃 + 生成稿常是 单行长段 ⇒ 整条被删空),已回退并把原因写进代码注释留痕。 建议方案:改 E4 提示词禁止输出收益数值(源头消除,不动红线代码)。 待判物 - _chunks_report.txt 判定可删:无代码引用 / 内容可从 knowledge/_chunks.jsonl 复算 / 从未入库 / 统计口径已写进 D2.4 §6 与 D2.8 §2。已删 + 加 .gitignore。 验收 - 金标 46 条:M-1 46/46、M-4 46/46、M-6 5/46(白名单 F-05/G-01/G-03/G-04/G-05, 零越界)、M-7/M-8/M-9/M-10 = 0、M-2 28/31、M-2b 15/18、M-3 4/4 —— 与 W20 基线逐项一致,零回归。 - 全量回归 1985 passed / 3 skipped(W20 基线 1969 passed / 3 skipped)。 - 新增守卫单测 16 条(含 C-8 两条 / C-9 四条 / C-10 两条)。 - ruff 仅剩 4 条既有告警,未顺手改,避免混入无关 diff。 文档 - 新增 开发文档/D4.8-客服Agent智能度体检与整改报告-W21-2026-09-21.md - 客服agent/D2.1 升 v6.37;开发文档/D1.1 升 v1.13;D1.6 增 §9 上下文提取 |
||
|
|
5d0becb67d |
客服 Agent 重构收口:五出口决策链 + 知识库档位隔离 + 前端入参边界(答辩演示版本)
一、客服 Agent 智能增强(正面回应"不智能、动不动就转人工")
- 决策链由 2 个出口扩到 5 个:E1 澄清 / E2 计算型 / E3 知识直返 / E4 证据约束生成 / E5 分级回退
- 转人工从"默认动作"降为最后一档 E5c,只保留 4 类白名单:
P0 反诈 / P1 账户与个人数据 / P2 写操作与争议 / 用户明确要求人工
- 46 条金标实测(修复前 → 修复后):
转人工率 43.5% → 10.9%;出口准确率 45.7% → 100%;事实正确率 69.6% → 100%
禁忌违反 1 → 0;档位越权 / 无出处数字 / 误拒 四项零容忍全 0
- 安全不变量 INV-1~INV-5;零容忍规则未删,改的是挂载点
(输出侧字面黑名单 → 检索层档位隔离 + 判定层合规词表 + 输出守护)
二、知识库:档位单点化与物理隔离
- 新增 app/core/knowledge_tier.py 作为档位规则唯一落点(G-03),
knowledge_contracts.py 原定义块改为显式再导出(X as X,非副本)
- 档位过滤由 bool 默认值(fail-open)改为 tiers 必填集合(缺参即 TypeError)
- Milvus 侧四集合按 visibility 分区键物理隔离;双 schema 收敛为一套
- 新增 app/core/actor.py:访客三元组与匿名判定的唯一构造/判定点(G-01/G-01b)
- 新增 app/core/fund_fee_rules.py:费率计算纯函数
三、前端入参边界对齐(本轮 W11 新修,4 处"校验宽于存储")
- message 加 max_length=8000(与浮窗 widget.js 的 maxlength 一致)
- session_id 加 1—64;idempotency_key 上限 128 → 64(对齐列宽 String(64))
- feedback_type 加 max_length=32(对齐列宽 String(32))
- 8 条路径参数补 min_length=1 + max_length=64 + 字符集正则
({session_id} / {run_id} / {handover_id})
- 改前超限值会落到 MySQL 才失败(500);改后一律 422 AGENT_INPUT_INVALID + 字段级定位
- 新增 tests/unit/api/test_frontend_boundaries.py(33 例),含"端点表 ↔ OpenAPI 全量对照"
四、投顾模块整体清除(D4.4 / D4.5)
- 删除投顾相关 controller / schema / model / repository / service 及门户页面
- tools/portal_api_check.py 同步作废 AD003/AD005/AD011/A047 四条用例与 advisor_t 登录
(端点与账号均已不存在,此前稳定报 3 条假红)
五、验证(提交前实测)
- pytest -q:1856 passed / 2 skipped / 0 failed
- ruff check app tools tests:19(= 基线);mypy app:2(= 基线)
- 前端接口契约体检 portal_api_check.py:38 项,通过 34,失败 0,跳过 4
- 全链路冒烟 e2e_smoke_test.py --read-only:31/31
- HTTP 全链路探针 http_probe.py:11/11 succeeded
- 跨文档一致性 _consistency.py:GATE PASS
- 真机边界复验 12 条:12/12 符合预期
六、纪律与文档
- 可改文件白名单 A-09(docs/46)与底座会签申请单 A-10(docs/47,组 1—组 4 全部受理)
- 零 DDL:未新增/修改任何表结构,89 张业务表与基线一致
- 证据留痕:docs/evidence/**(含 46 条金标 score、快照、清除与重建记录)
- 未提交(刻意排除,见提交说明):仓库内 客服agent/ 与 开发文档/ 是 2026-09-16 前的
过期副本(Todolist 440 行 vs 权威 D2.1 1167 行),权威正本在仓库外;
_chunks_report.txt 是 tools/build_knowledge_chunks.py 生成的本地产物
|
||
|
|
06d0f9f231 |
知识库检索质量修复:「风险评估问卷怎么评分」从必然转人工 → 直接答
## 根因(两个问题叠加) 1. 灌库脚本 tools/build_knowledge_chunks.py 的 expand_table_rows() 用「第一个非分隔行」 当表头且 header 从不重置 → 同一节里第 2 张表格起**表头被当成数据行**。 《个人投资者适当性管理指南》第九条下有 16 张问卷表格 → 15 条正文逐字相同的 19 字零信息量碎片;同类共 19 条。 2. 检索层去重只按 doc_id,接不住「同一父块的兄弟子块」(POL-AST-009-07 与 -12 是 不同 doc_id、同一父块)→ 15 条碎片全留在候选里互相打平,把 top1/次优差压到 0.002, 而客服判定要求中置信必须领先 ≥0.07(MIN_GAP)→ 恒判并列转人工。 ## 为什么没有重灌知识库 用 Milvus 现成向量离线复算四种做法(同一问句、同一批向量): 现状 gap 0.002 转人工 只修 bug(=重灌全部收益) gap 0.001 仍转人工,且更糟 只加父块归并 gap 0.093 可答,但答案是 19 字碎片 两处都改 gap 0.076 可答且有内容 原因:第九条被切成 94 块,删掉 15 条表头碎片后,剩下 79 条数据行碎片依然互相打平。 重灌解决不了,却要停机 3–10 分钟并丢掉上传路径的内容(含 R1–R5 那条)。 ## 改了什么 ① app/service/knowledge_search_service.py:新增 _merge_sibling_subblocks() 同一父块的兄弟子块只保留最高分那条;父块自身与 FAQ/政策这类本身就是细粒度答案 的块一律不合并(它们之间打平是真的多个候选)。6 个单测守着。 被丢掉的只是同节其它细节,本节完整内容由 _parent_hits 带回的父块兜底。 ② tools/build_knowledge_chunks.py:修表头识别(markdown 表格只有紧邻 |---| 之前的 那一行才是表头)+ 新增 assert_no_duplicate_contents() 自带守卫 —— 正文完全相同的块必须为 0,否则中止且不写 jsonl。该脚本是一次性灌库脚本、 原来没有单测覆盖,这正是该 bug 活下来的原因,所以守卫放在它自己的执行路径上。 块数 636 → 617;正文完全相同的组 1 组 15 块 → 0 组。 负向验证:换回旧逻辑跑,退出码 1 并报出那 15 份碎片,且未覆盖 jsonl。 ③ tools/drop_table_header_vectors.py:清掉已灌进 Milvus 的 19 条历史碎片。 判定可复现:语义 id(非纯数字)且正文不在修正后产物里。只动语义 id 是因为 纯数字 id 来自上传路径、本来就不在 jsonl 里(实测冒烟 A 线的 top1 就是数字 id 179); 不按长度判是因为短块本身是设计的一部分(「评审标准:管理人资质 15%」13 字是有效答案)。 实删 policy 16 + product 3,faq 0;dry-run 逐条核对过,全部是「标签 + 表头词」形态。 这是唯一一次绕过 Outbox 的删除:事件消费侧要回读 MySQL 行,而这批是无元数据种子向量。 ## 验证 真实链路 10 问句回归 10/10 与预期一致,0 个变坏: 风险评估问卷怎么评分 0.7156 gap 0.1473 → 答(原 0.002 转人工),top1 变成真实答案行 场内基金的管理费率 0.8114 gap 0.0977 → 直接答(冒烟 A 线) 南方季季盈90天起投金额 0.8631 gap 0.3387 → 直接答(行级子块精确命中能力未受影响) 今天天气怎么样 仍正确转人工 端到端:ask_customer_service.py「风险评估问卷怎么评分」→ 直接答,返回完整评分标准; e2e_smoke_test.py 44/44(含 A4 知识库覆盖未转人工); pytest tests/unit tests/contract 1506 passed / 0 failed; 对账 重复正文 15 → 0,孤儿/死向量/缺向量仍全为 0。 (冒烟首跑 42/43 的唯一失败 B6 买入下单 503 是行情过期,补刷后 44/44,与本次无关。) ## 顺带查明 Milvus 的 delete() 是标记删除,约 1 秒后才在 query/search 中不可见(隔离临时集合实测: 删完立即查仍看得到,+1s 起消失)。清理工具第一版"删完立刻复核"因此谎报 19 条残留, 已改为轮询复核并把文案改成实测依据。get_collection_stats().row_count 在删除后仍显示旧值, 这也是对账工具坚持用 query 实际行数的原因。 ## 文档 新增 docs/演示用/知识库检索质量修复-2026-09-15.md(根因、四方案对比、改动、全部证据); 并对 docs/演示用/知识库向量对账与清理-2026-09-15.md 做更正 —— 451 条短块里 429 条是 设计内的有效数据行、只有 19 条是 bug 产物;"按长度合并短块 + 重建重灌"的建议已被实测否决。 ## 仍未解决(记录在案) 「高净值客户有什么权益」gap 0.0075 仍转人工:根因是不同父块之间同族内容打平 (金卡 vs 白金权益),父块归并救不了也不该救,要从内容侧或业务口径入手。 knowledge/_chunks.jsonl(新 617 块、编号连续)与 Milvus(旧编号、含 19 个空洞)目前不一致; 不重灌无影响,但下次重灌必须 drop 集合重建而不是 upsert,否则两套编号会共存。 |
||
|
|
7b3a72860c |
feat(knowledge): 为 C1-C5 各补一条「能买什么产品」的问答
客户实测反馈:「C1 客户能买什么」被引导到人工客服,而知识库里其实有答案。 实测分数:这条问句 top1 只有 0.5633、与次优差 0.0236(低于 0.07 门槛)→ 转人工; 而「C1 保守型客户可以买哪些风险等级的产品」是 0.7794,过了 0.75 硬门槛、能答。 根因是客户与知识库的用词鸿沟:客户说「C1 客户」,知识块标题写的是「C1 保守型」。 短问法少了"保守型"这个锚点就差 0.19 分——而客户不知道 C1 就等于保守型,这正是他要问的。 按 A 方案(数据问题用数据解决)为 C1-C5 各补一条 FAQ,答案全部取自 《个人投资者适当性管理指南》原文,不自行编写: - 第十二条投资者与产品匹配矩阵(各级别可购买的产品风险等级) - 第十四条硬匹配规则的跨级禁止要求 - 第十五条豁免规则(C3 买 R4、C4 买 R5 的签署揭示书与持仓上限) 知识块 631 → 636。同时修正 load 脚本自检里过时的期望:这句话现在命中 FAQ 而非 POL-AST(两者是同一份内容,只是 FAQ 的问句措辞更接近客户口语)。 验证:C1-C5 六个等级的「能买什么」问法全部直接回答、无一转人工; ruff / mypy / 468 unit+contract 全绿。 |
||
|
|
4c2b147793 |
feat(knowledge): 产品知识拆到表格行级,并按问句选粒度
问题(客户实测反馈):同一会话里问「季季盈90天起投多少」和「那它风险高吗」,两次回答 **一模一样**——都是整个产品小节的表格。客户问的是风险,收到的是整张说明书,看起来像 客服没听懂问题。 根因是切分粒度:原来"一个叶子标题 = 一块",产品手册里就是整个产品小节(表格 + 说明) 成一块。这既让两个不同的问题命中同一块,也让整节几百字的向量成了"整节的混合语义", 与"起投多少"这种具体小问题相似度天然偏低(实测该问句向量 top1 仅 0.6291,够不到 0.75 硬门槛,只能靠与次优的差值勉强通过)。 改动三处: 1. 切分:Markdown 表格的每一行额外生成一个**自解释**的小块("南方季季盈90天:起投金额 1万元"),挂在父块 doc_id 下(PROD-007-04),父块照旧保留。知识块 160 → 631。 效果:该问句的命中分从 0.6291 升到 0.869,命中的正是"起投金额"那一行。 2. 检索:命中行级子块时把它的整节父块一并带回(分数按 0.9 折算),供调用方按问句选粒度。 整节块保底占最后一个名额,且不参与 top1/top2 判定——实测它挤到第 2 位会把 gap 从 0.090 压到 0.076,几乎跌破 0.07 的转人工门槛。 3. 客服:命中的是行级子块时,看问句与子块标签是否真的对得上——「起投多少」对「起投金额」 对得上,用那一行;「介绍一下」对不上,换成整节。 过程中两次判据写错并已修正(都固化进了测试):用"含连字符"认子块时,整节块自己的编号 PROD-901 被误判成子块;用"不含两位数字后缀"认整节块时,FAQ 块全被误判成整节块排到后面, 把正确答案挤出 top1、害得「基金赎回几天到账」转人工。 验证:起投/管理费等字段问法给出聚焦的单行答案;"介绍一下"给出整节;FAQ 与政策问法不受 影响(换话题、指代追问等此前修好的场景复测通过); ruff / mypy(113 文件) / 468 unit+contract / 29 integration 全绿。 |
||
|
|
d2aff7c129 |
feat: 建客服知识库(Milvus 三集合)并修复模型端点筛选缺陷
一、知识库建设 - 新增 tools/build_knowledge_chunks.py:把 knowledge/ 下文档切成可检索知识块。 采用「叶子标题」策略(其后没有更深标题的标题即切分点),同时覆盖三种真实结构: 带子条款的按子条款切、无子条款的条款单独成块、无小节的章整章成块。 第一版按固定标题级别切是失败的——适当性指南的条款是 ### 而没有 ####,产品手册的 ### 1.1 又不匹配「第X条」,两条规则互相打架,导致 4 个文件一块都没切出来。 - 新增 tools/load_knowledge_milvus.py:向量化并写入 Milvus,用 upsert 保证幂等。 schema 按方案 §4.2 统一字段,另加 chapter/section/source_file/doc_no/visibility 五个 检索与合规必需字段;索引 IVF_FLAT + COSINE + nlist=128;向量输入取「标题+正文」, 标题含条款号与章节名,是比正文更干净的检索信号。 - 知识内容按业务范围裁剪:反洗钱合规操作手册不入客服知识库(业务只做公募基金、 不涉及资金划付,且该手册标注内部机密、禁止向客户透露可疑交易信息),留给后续风控; 高净值客户服务规范只保留「客户分层标准」与「各层级专属权益」两章, 家族信托、资产配置流程、客户经理考核、隐私应急预案等内部管理章节不入库。 - 入库现状:fin_faq_collection 61 块、fin_product_collection 26 块、 fin_policy_collection 73 块,合计 160 块。检索自检 5/6——未命中的一条分数 0.660 落在中置信区间,按三档兜底策略本应提示信息可能不完整,属于预期行为。 二、embedding 端点 - 新增 tools/configure_embedding_endpoint.py:走管理 API(draft→approved→active) 配置并激活 qwen-embedding 端点,而不是直接写库。理由是状态机与审计都要留痕, 且 DatabaseModelGateway 只认 status='active',手工写错状态会报成与病因无关的 「模型端点未注册或未激活」。脚本先查 endpoint_code 是否已存在,幂等可重跑。 三、修复模型端点筛选缺陷(app/service/model_gateway.py) - 原 DatabaseModelEndpointResolver 忽略 agent_type 与 task_type、直接返回全部 active 端点,而 ModelDispatchService 只按顺序尝试前 max_attempts(默认 2)个。两者叠加使 「能否选到支持该任务的端点」取决于端点表顺序:实测每次 embedding 都先拿文本生成 端点失败一次再落到向量端点(0.61s,修复后 0.42s)。 - 新增 TASK_CAPABILITY 显式映射后按能力筛选。用映射而不是同名筛选是必需的: memory_extraction 并不是任何端点的能力名(deepseek 声明的是 text_generation 等), 按同名筛会得到空集、把记忆抽取打成失败关闭——这是本次修复最容易引入的回归。 - 保守兜底:未映射的 task_type、以及没有任何端点声明该能力时,都退回全部端点, 让配置缺口表现为调用失败,而不是让上层收到「解析为空」这种与病因无关的报错。 - 验证结果:embedding→[qwen-embedding]、intent_classification→[deepseek-flash]、 memory_extraction→[deepseek-flash]、未映射 task_type→全部;ruff 通过、 mypy 103 文件无错、unit+contract 447 passed。 四、需求文档提取物 - 新增 _flows/:三份流程文档(智能客服 Agent 专项设计方案、投资顾问流程、基金运营流程) 的纯文本提取,供开发期对照。原始 .docx/.html 保留在业务方目录侧。 说明:本次仅本地提交,未推送远程仓库。knowledge/ 内含公司内部制度与产品资料, 是否入远程库待确认。 |