lzf_0626
|
f09ea9e988
|
Merge origin/NL_develop:客服画像出口、知识管理三端点、合规语境与知识向量链路
NL 线(含其并入的袁聪场外/推广域)。唯一冲突是 .gitignore —— 双方都往同一区域加了
.workdir/,取对方版本(他的更完整,含 .tmp/ 与说明),顺带修掉我之前用
Add-Content -Encoding utf8 造成的编码混合(read 工具当时报 invalid UTF-8)。
合并后修的问题 —— 都不是"改别人业务逻辑",是让门禁能绿:
1. 缺运行依赖 python-docx。document_parser.py 解析 .docx 用它,但 requirements.txt 与
pyproject.toml 都没声明 —— 别人环境跑知识入库会直接
ModuleNotFoundError: No module named 'docx'。已补声明。
2. ruff 7 项:其中 tests/conftest.py 的 F821 Undefined name 'Path'(他的 tmp_path 修复
写了字符串注解 "Path" 却漏 import,运行时不求值所以没炸,但 mypy/ruff 会抓)、
tools/publish_customer_service_config.py 的 F841 inherited_keys 死变量(他改同 key
覆盖、换成 inherited_only 后忘删旧的)、3 处 E501,另 2 项 ruff --fix 自动修复。
3. 合规基线种子未跑:integration 的 test_compliance_seed_mysql 4 个用例要求
agent_negative_word 有 7 条 active 且已复核、agent_reply_template 覆盖 6 场景。
跑 tools/seed_compliance_baseline.py(11 条 active 规则 / 6 个场景模板)后 80 passed。
验证:ruff 干净 / mypy 180 文件 0 错 / unit+contract 1140 passed /
integration 80 passed / 表数 68(alembic 已在 20260911_merge_risk_heads)。
唯一失败 tests/unit/repository/test_fund_readonly_contract.py 是双方一致的既有缺陷:
它断言 Base.metadata 里的 fin_* 表集合,而实测为空集 —— 即该测试依赖别的测试先导入模型的
副作用,单独跑必失败。NL 方也明确"不修不报",此处照办,仅记录。
|
2026-09-11 20:22:59 +08:00 |
|
zhangshy
|
beb67f0a26
|
优化奶龙字段语义理解
|
2026-09-11 19:37:26 +08:00 |
|
qyqy
|
7677aeaee1
|
merge: 并入同事的场外申购/推广/行情/NL2SQL 线(11 提交、334 文件)
冲突仅 3 个文件,全部取并集(双方都没有需要丢弃的改动):
- app/main.py:import 双方路由(我方 knowledge_management + 同事的 offsite_fund/
promotion_material);include_router 段本已自动合并
- app/service/agent/bootstrap.py:import 与工具注册均取并集
(query_customer_profile + query_financial_data 都注册)
- tests/integration/test_config_release_mysql.py:outbox 清理同时保留
架构师的 event_type 限定(防误删其它域 outbox 行)与同事新增的 peer_release_id
同事这轮带入:11 个 alembic 迁移(建 offsite_* / promotion_* 等表)、
场外申购与推广素材 Agent、financial NL2SQL 工具。
注意:本库尚无 offsite_*/promotion_* 表,跑相关测试前需要执行 alembic upgrade。
边界核对:同事的场外代码未写入场内交易表(fin_sim_order/fin_capital_flow/fin_cash_ledger),
符合 AGENTS.md 规则 8。
|
2026-09-11 18:56:26 +08:00 |
|
lzf_0626
|
fcd3eaadd6
|
Merge origin/qyqy_develop:风控时间口径补齐 + Agent 截断提示
组员的 4 个提交(相对 c11e56c):
- 4b0c148 把我这边的风控 P3 收尾与适当性豁免额度合并进 qyqy_develop(PR #4)
- 2ebe438 docs: 增加风控 Agent 工具白名单与意图配置
- 5102eaa Merge origin/qyqy_develop into RM2_develop
- d2cdbba 修复风控时间口径与 Agent 截断提示
自动合并零冲突,两侧的改动是互补而非重叠:
- 时区:我在 list_alerts 做了 from_local,他补的是 list_evidence 与通知列表;
- 截断:我加了 data_truncated / evidence_truncated,他在 risk_agent 的 system prompt
里加了第 10 条 —— 这些标记出现时不得按全量证据下结论。方向是一致的。
验证:ruff 干净 / mypy 138 文件 / 703 unit+contract(+6,组员新增的用例)/
33 integration,均在合并后的工作树上跑过。
|
2026-09-11 16:33:55 +08:00 |
|
qyqy
|
cbd6de2721
|
Merge remote-tracking branch 'origin/qyqy_develop' into nl-merge-latest
# Conflicts:
# app/service/model_gateway.py
|
2026-09-11 15:38:11 +08:00 |
|
qyqy
|
395bad25b7
|
fix(knowledge): 检索服务适配现库集合 schema、发布对齐的工具白名单、免责声明只由治理层注入
合并暴露的三个真机问题(单测全绿但线上必挂):
1. 检索服务字段名与现库集合不符 -> 静默零召回
架构师那套按 load_knowledge_milvus.py 的 schema 读 doc_id/content/chapter/section/
visibility,而现库三集合的真实字段是 knowledge_id/title/snippet/tags/version/intent。
Milvus 对不存在的字段直接报错 -> 三集合全失败 -> degraded -> 客服一律转人工。
改法:只改读取侧,用 _FIELD_ALIASES 映射;KnowledgeHit 对外形状不变(下游与测试不动)。
代价已注明:没有 visibility 字段 -> 检索层内部资料硬隔离失效(现库 356 行均为对外知识)。
2. 发布白名单与代码上限不匹配 -> AGENT_PERMISSION_DENIED
active 版本白名单是 query_knowledge,而合并后代码上限是 search_knowledge/check_suitability/
query_customer_profile -> 交集为空 -> 所有知识问题 failed。
改法:publish_customer_service_config.py 补 PROFILE_TOOL 进 faq 白名单,并让同 key 的
继承项被本次定义覆盖(旧值原样继承会被子集校验 422 拒掉整次发布)。已激活版本 216。
3. 免责声明重复出现
Agent 自己拼一句 + 治理层追加权威话术 -> 客户看到两条。改为只由治理层注入
(话术属发布配置,改文案不该改代码)。风控等内部 Agent 不注入(结构化输出不被污染)。
测试:934 passed / 1 failed(test_fund_readonly_contract 既有空集缺陷,与本线无关)
真机:知识问答两问 succeeded 且只带一条声明;画像问答 succeeded;知识库三端点全绿
|
2026-09-11 15:27:16 +08:00 |
|
zhangshy
|
d2cdbbac01
|
修复风控时间口径与Agent截断提示
|
2026-09-11 15:25:45 +08:00 |
|
qyqy
|
57c4add7d8
|
merge: 客服Agent+RAG+画像 与 架构师最新 qyqy_develop 合并
- customer_service.py 以架构师实现为骨架(三档置信/适当性/会话记忆/话题矩阵),嫁接本人画像出口
- 知识检索契约合并两条链路:架构师 search_knowledge(KnowledgeSearchInput) + 本线
query_knowledge 链路所需常量(ALLOWED_CONSTANTS/VECTOR_DIM/intent_for_qa_id)
- bootstrap 保留架构师 6 工具/3 Agent,补回 query_customer_profile 与 get_milvus_knowledge_writer
- model_gateway 能力映射修正 intent_classification→chat,保留空集回退兜底
- governance 免责声明限定面向客户 Agent(agent_type 由定义透传),风控结构化输出不再被追加
- 修 JWT 密钥路径(config/jwt/dev)、文档 21 号撞号→25
- 测试基线 934 passed / 1 failed(既有空集缺陷)
|
2026-09-11 15:06:44 +08:00 |
|
lzf_0626
|
575b4c2baa
|
客服适当性改按第十二条匹配矩阵(更正我上一轮的复核结论)
你说得对:C ≥ R 是风控的口径,客服要走政策原文的矩阵。
上一轮我复核后说"两边一致、无需改动"是错的 —— 我只对比了政策文本与
SuitabilityService,漏了第三个东西:客服自己的知识库。POL-AST-012 就是第十二条矩阵
原文,而客服为 C1-C5 补的「能买什么产品」问答答案直接取自该矩阵(docs/24 第二节)。
于是同一个客服 Agent 对同一个问题给出相反答案:
问"C1 能买什么产品" → 知识检索答 R1、R2 可买(矩阵口径)
问"C1 能买这只 R2 吗" → 适当性出口答不能购买(严格 C ≥ R)
政策冲突的精确位置也不是"矩阵 vs 硬匹配",而是第十四条**第 1 款与它自己的第 2、3 款**
矛盾:第 2 款说"低于一个等级以上"才拒绝(C1→R2 只低 1 级,够不上"以上"),第 3 款禁止
C1 买 R3+、C2 买 R4+(R2 不在禁止列表)。矩阵与第 2、3 款三方一致,孤立的是第 1 款。
改动:
- suitability_service.py 新增 MATRIX_ALLOWED / MATRIX_NEEDS_DISCLOSURE,_decide 由
"C < R 即拒绝"改为按矩阵:C1→R2、C2→R3 直接可买;C3→R4、C4→R5 走第十五条豁免档
(reason_code=SUITABLE_WITH_DISCLOSURE,强制揭示 + 确认 + 录音);低两级及以上仍拒绝。
- 客服话术分档:越级档不再说"在您的风险承受能力范围内" —— 那句只对 C ≥ R 成立,
用在 C1 买 R2 上会让客户以为自己的测评本来就覆盖这只产品。
- 风控侧不动,保留 C ≥ R:它要发现的是"越级成交且留痕不全",这个差异是有意保留的。
测试:新增逐格对照政策原文的 25 格矩阵用例、豁免档用例、话术分档用例(+29)。
docs/25 第七节 #1 与 docs/24 第七节同步更正,包括写明我上一轮那个结论错在哪。
门禁:ruff 干净 / mypy 137 文件 / 693 unit+contract / 33 integration。
|
2026-09-11 14:47:08 +08:00 |
|
wangjianlong_0626
|
e4c4099aaa
|
wip: 客服Agent + RAG + 画像收尾(基于 6516ccb)
|
2026-09-11 14:46:40 +08:00 |
|
lzf_0626
|
4e2e42c896
|
test(base): 验证最后一种工具拒绝(角色不符),四种分支全部实测通过
分支 4 是最难构造的一种,两个前提缺一不可:
1. **工具的角色集合必须比 Agent 的更窄**。Agent 层的 validate_access(base.py:101)会先按
AgentDefinition.allowed_roles 拦截,两者一致时永远进不到工具层的角色校验。所以把
probe_alt 收窄为 ("risk_operator",),而 Agent 仍允许 admin。
2. **调用者必须有工具要求的权限**,否则会先命中权限分支。所以脚本临时给 admin 授
probe:read,验证后撤销。
过程中又修掉一处自己写错的地方:探针的 handle 原先硬编码调用 PROBE_TOOL,导致分支 4
(需要调 probe_alt)与分支 2(需要调白名单之外的那一个)互相干扰——第一次跑出来的结果
是"工具不在当前意图白名单"。改为按消息里的 "alt" 选择要调的工具。
四种分支的实测结果,message 各自独立、指向不同处置动作:
- 白名单为空 → 该意图未配置工具白名单
- 工具不在白名单 → 工具不在当前意图白名单
- 缺少工具权限 → 缺少工具权限
- 角色不符 → 角色不能使用工具
目标的另一半也验证了:审计里是完整细节(reason = "角色 ['admin'] 与工具允许的角色
['risk_operator'] 无交集",并带 tool_name / intent / trace_id),而异常 message 只有
"角色不能使用工具"、不含角色集合。**内部配置只进审计,不进客户可见响应。**
环境复原:生效配置 9 条(与起点一致);sys_permission / sys_role_permission 中
probe:read 的行数为 0。
|
2026-09-11 13:12:56 +08:00 |
|
lzf_0626
|
1eb8946552
|
test(base): 端到端验证工具拒绝的分支 2 与 3,探针加第二个工具
接上一轮(分支 1 已验证)。本轮用只读探针触发另外两种拒绝:
- 分支 2「工具不在当前意图白名单」:发布白名单 ["probe_alt"] 但 Agent 调 probe_echo。
这一步能做,正是因为给探针加了第二个工具 —— governance.resolve 取的是
「发布白名单 ∩ 代码声明的 allowed_tools」,配置**只能缩小不能放大**,所以单个工具的
Agent 永远构造不出"有白名单但不含该工具"的场景。这是做端到端时才撞到的结构性约束,
platform_probe.py 里已注明。
- 分支 3「缺少工具权限」:发布白名单 ["probe_echo"],工具可用了,但 admin 角色并没有
probe:read 这条权限,天然命中权限分支,不需要动 RBAC。
实测:两种拒绝的 stderr 分别为「工具不在当前意图白名单」与「缺少工具权限」,
运行状态均为 failed / AGENT_PERMISSION_DENIED;生效配置已恢复(终点 9 条,与起点一致)。
顺带发现分支 4 的结构性障碍(下一步处理):Agent 层的 validate_access 会先按
AgentDefinition.allowed_roles 拦截,所以要在**工具层**触发"角色不能使用工具",
必须让工具的角色集合比 Agent 的更窄 —— 探针目前两者的角色集合相同,触发不到。
|
2026-09-11 13:11:30 +08:00 |
|
lzf_0626
|
edc0c43245
|
test(base): 造只读探针端到端验证工具拒绝,并修掉它暴露的一个死分支
**为什么造探针**:ToolExecutor 的四种拒绝在真实链路上很难安全触发——要么改客服、风控的
生效配置,要么动 RBAC,两条路都会影响正在工作的 Agent。platform_probe 是个只读、无副作用
的探针:它只声明 probe 一个意图(所以意图分类只可能返回它)、没有发布工具白名单
(天然处于"未配置"状态)、工具只回显参数不碰业务数据。
**它立刻查出一个死分支**:探针报的是「工具不在当前意图白名单」,而不是我新加的
「该意图未配置工具白名单」。原因是 governance.resolve 会为每个 supported_intents
**预填条目**(governance.py:55-61),未配置时得到的是**空元组**——所以
intent not in configured_tools 在运行期**永远不成立**,那个分支是死代码。
单元测试没能发现它,因为我在测试里手工构造了 configured={},而真实链路不产生这个形状。
**这正是端到端测试的价值**:单元测试验证的是我设想的形状,端到端验证的是真实形状。
修法:改判"白名单为空"而非"缺键",文案改为"该意图的工具白名单为空",并注明经过 governance
装配后"完全没配"与"配了空列表"无法区分、也不假装能区分(两者运维动作相同)。新增一条按
**真实形状**({"faq": ()})构造的用例把它锁住。
实测:探针调用 → failed / AGENT_PERMISSION_DENIED,stderr 为
ForbiddenAgentError: 该意图未配置工具白名单(tool_executor.py:108)。
ruff / mypy(136 文件) / 611 unit+contract 全绿。
|
2026-09-11 13:10:04 +08:00 |
|
lzf_0626
|
478b64e4d7
|
Merge remote-tracking branch 'origin/qyqy_develop' into qyqy_develop_1
# Conflicts:
# app/service/agent/bootstrap.py
|
2026-09-11 10:43:08 +08:00 |
|
lzf_0626
|
c369a919c6
|
test(customer-service): 用格式矩阵把 _topic_of 的第四、五口咬痕也堵上
方案 A:把每种出口的**真实产出**都过一遍 _topic_of。谁再改回答格式或加出口,测试立刻红,
而不是等客户实测先撞上——它已经咬了三次,三次都是客户先发现的。
写这个矩阵当场又抓出两个真 bug:
1. 整节块的主语带着章节号("2.1 南方季季盈90天")。下游 _product_risk_level 要求产品名
是该块标题或正文的**子串**,带编号就永远匹配不上——适当性查询会静默失败、退化成转人工。
这是真故障,不是洁癖。
2. 政策条款的主语是"第十二条 投资者与产品匹配矩阵";整节块去掉标题行后剩下的表格行
("| C1保守型 | ✅ 可购买 |")也会被当成主语。
修法:剥掉开头的章节号;排除以"|"开头的表格行;排除"第X条"开头的条款标题。
矩阵覆盖全部四个出口:知识直返(行级块/整节块/FAQ/政策四种形状)、适当性裁决(四种话术,
真调 _suitability_text 而非手写样例)、引导人工(兜底话术取不出主语是对的)、
闲聊(无产品实体,取不出也是对的)。
|
2026-09-10 22:53:29 +08:00 |
|
lzf_0626
|
8b1ed70916
|
fix(customer-service): 自述等级的说明行不能挡住产品名
实测连问「季季盈90天起投多少」→「C3 客户能买它吗」→「那我能买它吗」,第三问转人工。
原因是上一步刚加的客户自述等级说明("您提到自己是 C3。…以您在公司留存的测评结果为准")
占了回答首行,而 _topic_of 只看首行,产品名被挤到第二行,追问于是丢掉了指代对象。
这是 _topic_of 第三次因为"只认固定格式"而咬人(前两次:FAQ 的"问"字、适当性回答没有
冒号)。改成遍历回答前四行、逐行尝试解析,并把单行解析抽成 _topic_in。
同时补上自述等级时的依据说明:客户说"我是 C3"而系统答"您没有测评结果",在他眼里是
矛盾的,必须点明判断以档案为准、不以自述为准。自述等级只用于这一句说明,**绝不**参与
裁决。拒绝后的指引也从"联系客户经理或拨打热线"改成"请先完成风险测评"。
新增三条单测:自述等级识别(含小写)、自述说明行占首行时仍能取到主语、
"为 R2"开头说明前面没有产品名时返回空。
|
2026-09-10 22:48:56 +08:00 |
|
lzf_0626
|
c564494f84
|
fix(customer-service): 追问适当性结论时不能丢掉产品名
实测三连问:「季季盈90天起投多少」→「C1 客户能买它吗」→「那我能买它吗」,
第三问转人工。原因是 _topic_of 不认识适当性回答的格式——它那一行是
"南方季季盈90天为 R2(中低风险),与您当前的风险测评结果不匹配…",
既没有冒号也不是标题行,于是被判成"取不出主语",第三问就失去了指代对象。
补上这种格式:产品名在"为 R{1-5}"之前,且必须在冒号判断**之前**匹配(那一行没有冒号)。
以"为 R2"开头的行说明前面没有产品名,返回空串,而不是把等级本身当成主语。
修好后第三问给出与第二问一致的结论,这是对的:裁决是确定性的,同一个客户问同一只
产品,结论就该一样。新增两条单测钉住这两种情形。
|
2026-09-10 22:46:31 +08:00 |
|
lzf_0626
|
33fbb0eb01
|
feat(customer-service): 接入适当性裁决,回答"我这个等级能不能买它"
客户实测反馈:「c1客户能买它吗」答的是 C1 的通用规则,没回答"能不能买季季盈90天"。
根因是这个问题需要**组合两个事实**——产品的风险等级(R2)与客户档案等级能不能匹配——
而检索只能给出"最像的那段原文",给不出结论。基座早就有了 check_suitability 裁决工具,
接口留好了但客服没接线(这个项目里第三次遇到同一类情况)。
改动三处:
1. 新增 suitability_check 意图,意图码三处对齐(AgentDefinition.supported_intents、
agent_intent_config 的 active 行、发布版 agent_tools 白名单)。
2. 新出口 _answer_suitability:产品风险等级**从知识库查出来**(不猜、也不采信问句里
出现的"R2"字样),产品名只取上一轮回答里的主语(来自知识块字段,可信),客户等级
交给 check_suitability 按档案解析——**不采信客户自称**。任何一步拿不到确定值就转人工:
这个出口会给出"能不能买"的结论,宁可答不了也不能答错。
3. 发布脚本加意图注册与工具白名单,并做成幂等(重跑不会因为"已经审过了"而 409)。
实测:意图正确路由到新出口,裁决链路走通。9001 因为在 fin_risk_assessment 里没有测评
记录,系统给出"暂时无法购买 + 您目前没有在有效期内的风险测评结果"——这正是适当性管理
要求的行为,不是故障:不能卖给一个没有有效测评结果的客户。措辞也据此改过,不写
"您的等级为未记录"这种客户看不懂的句子。
顺带修了前端一处误导标记:它用"是否含客服热线"判断"已引导人工",而正常的适当性回答
里也会建议拨打客服热线,于是"已经给出结论"被误报成"已引导人工"。
|
2026-09-10 22:42:17 +08:00 |
|
lzf_0626
|
ee9de1b520
|
fix(customer-service): FAQ 型回答不能把"问"字当成追问的主语
给 C1-C5 补 FAQ 之后暴露的连带问题:FAQ 型回答的首行是"问:C1 客户能买什么产品?",
_topic_of 取冒号前的内容会拿到一个孤零零的"问"字,客户追问时检索词被污染成
"问 那它能买基金吗"。
改为"问"/"答"这类纯标签直接判为取不出主语,退化成只查当前这一句。
另附一条实测结论(**没有改代码**):在 FAQ 型回答之后追问「那它能买基金吗」仍会转人工,
这是安全且合理的——上一轮回答里没有可指代的产品实体,而"C1 能不能买基金"本身取决于
那只基金的风险等级,知识库没有、也不该有这种一概而论的答案。按金融场景的原则,
"答不了"好过"答错"。
|
2026-09-10 22:35:17 +08:00 |
|
lzf_0626
|
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 全绿。
|
2026-09-10 22:29:23 +08:00 |
|
lzf_0626
|
a6c09fa3c4
|
fix(customer-service): 检索问句只在客户这一句说不清楚时才带上文
实测的答非所问:同一会话先问「季季盈90天的起投金额是多少」,再问「基金赎回几天到账」,
第二问答出的是季季盈的产品介绍。原因是 _search_query 无条件把上一轮客户问题拼进检索词,
客户换话题时旧话题的检索结果被带了回来。在金融场景里这比"引导转人工"糟得多:客户问 A
得到 B 的答案会直接失去对客服的信任,而"答不了"至少是诚实的。
改为只在两种真正需要上文的情况下拼接:句子里有明确指代词("这个产品""该基金"),
或短到不构成完整意图("那它风险高吗"只有 6 个字)。指代词刻意不收单字"它/他"——中文里
"其他产品"会被误判,而这类短句已经由长度规则覆盖。
验证:换话题场景第二问回到 FAQ-0016 的到账时间,指代追问仍命中季季盈产品块;
ruff / mypy(113 文件) / 457 unit+contract 全绿。
|
2026-09-10 22:17:34 +08:00 |
|
lzf_0626
|
dbe7285c1c
|
feat: 短期会话记忆(多轮指代可解析)
一、此前的缺口
方案 §2.2 要求会话短期记忆,但底座**没有任何加载历史消息的代码**:conversation_message
存了全部消息、svc_conversation_session 只在计数,而 run 执行时只拿到当前这一条消息。
后果是客户问"那它风险高吗"时"它"无从对应,向量检索落到无关内容、整条回答走兜底——
多轮对话事实上不可用。
二、实现
1. 契约:AgentRequest 新增 `history: tuple[ConversationTurn, ...] = ()`(默认空元组,
既有构造点无需改动)。ConversationTurn 只保留 role 与正文,不把意图/置信度等内部字段
喂给模型——既减少噪声,也收窄"模型看到不该看的东西"的面。
2. 加载:WorkerRuntime._execute_claimed 构造 AgentRequest 时加载本会话此前的对话
(上限 10 轮,按 id 正序)。`before_message_id` 排除本轮请求消息本身,否则模型会在
上下文里看到自己的问题被重复一遍。
3. 使用:客服 Agent 构造检索查询时,把最近两轮**客户**消息与当前问题拼接。只取客户的
话、不取 Agent 自己的回答——把后者拼进来会让检索偏向自己上一轮的说法,而客户的真实
意图可能已经在下一句里被修正。
三、两个刻意的取舍
· **不引入 Redis 双写**:方案 §2.2 设想用 Redis 列表,但消息在受理时已落库,再同步一份
只会带来不一致与 TTL 管理成本,换来的仅是一次索引查询的节省。这里取等价语义
(同样"最近若干轮、超出即截断")而不复制存储。
· **按条数截断而非 token**:没有与模型一致的分词器,按 token 截断只能估算、边界会随实现
漂移;按条数是确定性的,宁可少给几轮,也不给一个不稳定的边界。
四、实测(同一会话两轮)
· 第 1 轮"南方季季盈90天的起投金额是多少" → 正确返回该产品表格(R2、起投 1 万元等);
· 第 2 轮只说"那它风险高吗"(不含任何产品名)→ 仍正确检索到同一产品并答出风险等级 R2、
业绩比较基准与投资范围;此前这类提问必然走兜底;
· ruff 通过、mypy 113 文件无错、unit+contract 447 passed。
|
2026-09-10 22:01:43 +08:00 |
|
zhangshy
|
a94d5c754d
|
feat: 迁移奶龙风控业务模块与演示文档
|
2026-09-10 21:03:44 +08:00 |
|
lzf_0626
|
1fa5fc7d03
|
refactor: 客服回答正文只保留固定免责声明
业务方要求客户侧只看到一句固定话术(不构成投资建议),因此从回答正文移除:
1. 中置信的「(以上信息可能不完整,具体以产品说明书与公司制度为准)」提示;
2. 「(依据:…)」出处行——原先它显示的是知识块标题,FAQ 的标题就是问题本身,
展示为「(依据:公司什么时候成立的?)」并没有可读价值。
可追溯性不受影响:本次命中哪个知识块仍由审计(agent.tool_executed 的工具调用记录)
与消息表留痕,source_references(tool 类型)也照常返回,只是不再面向客户展示。
取舍已在代码注释中标明:中置信回答此后不再向客户标注不确定性。
若将来要把出处展示给客户,应当走 source_references 的 knowledge 类型
(前提是让 ToolExecutor 把工具返回的 doc_id 登记为本次可引用来源),
而不是继续往正文里拼字符串。
同时移除因此不再使用的 INCOMPLETE_NOTICE 常量、_source_note 方法,
以及提问工具里那句"正文没有依据行"的提示。
验证:ruff 通过、mypy 107 文件无错;customer_service_check 9/9 通过;
ask_customer_service 实测回答正文为「答案 + 免责声明」两行。
|
2026-09-10 20:33:13 +08:00 |
|
lzf_0626
|
66595ce080
|
fix: 客服置信判定改为「绝对阈值 + 相对间隙」混合判定
问题(业务方实测发现):问「我们公司叫什么名字」被引导人工,但公司名称就在知识库里
——FAQ-0001「公司全称是什么?」与 COMP-001 都稳定命中 top1。所以根因不是检索不准,
而是判定规则不完整:客服方案 §2.3 要求的是「绝对阈值 AND(相对间隙 OR 分布优势)」,
实现里只做了绝对阈值 0.60。
实测校准(qwen3.7-text-embedding-flash,COSINE):
库内问法 top1 top1-top2
我们公司叫什么名字 0.592 0.100
你们公司名称是什么 0.579 0.090
公司全称是什么 0.671 0.098
你们公司总部在哪 0.764 0.245
南方科技的全称 0.855 —
库外 / 越界 top1 top1-top2
你们公司什么时候上市 0.500 0.046
推荐明天肯定涨的基金 0.488 0.023
我要投诉 0.492 0.025
今天天气怎么样 0.416 0.035
量子计算机退相干 0.416 0.044
两条结论:一是同为正确命中,口语问法的相似度天然偏低(0.592 vs 0.855),单用绝对阈值
必然误判;二是库内命中的 top1 领先幅度(≥0.09)显著大于库外(≤0.046),间隙是有效判别信号。
新规则:≥0.75 直接答(不再要求间隙);≥0.55 且间隙 ≥0.07 则回答并附「信息可能不完整」
提示;其余一律引导客户致电人工客服。两侧余量:库内最低 0.579、库外最高 0.500。
验证:ruff 通过、mypy 107 文件无错;ask_customer_service 对「我们公司叫什么名字」
正确返回南方科技有限公司;customer_service_check 由 8 项扩为 9 项,全部通过,
其中「推荐明天肯定涨的基金 / 我要投诉 / 量子计算机退相干」三条必须引导人工的用例
未被放宽后的阈值误答。
|
2026-09-10 20:29:12 +08:00 |
|
lzf_0626
|
13bab7c3d0
|
feat: 客服 Agent 端到端跑通(知识直返 + 答不了引导人工客服)
按业务方确定的取向实现:金融场景确定性优先,能溯源到公司资料的才答,答不了就
引导客户拨打客服热线,绝不用模型猜答案。端到端验收 8/8 通过。
新增:
- app/service/knowledge_search_service.py:知识检索。未复用记忆的 VectorMemoryAdapter
是因为它只返回 (memory_uuid, score),会丢掉知识块的标题与正文,而客服回答必须能把
原文与出处一起交付。检索失败一律返回 degraded 而不抛异常,由 Agent 走兜底。
- app/service/knowledge_tool.py + app/core/knowledge_contracts.py:只读工具 search_knowledge。
走 ToolExecutor 而不是让 Agent 直接持有检索服务,是为了让白名单、权限、审计、超时
都归基座统一管理;工具只读也符合 ToolRegistry 的硬约束。复用既有权限码
knowledge:reference:read(customer 角色已具备),不新增权限点。
- app/service/agent/implementations/customer_service.py:Agent 本体,刻意保持薄——
意图分发 + 四条出口(faq/产品/政策直返、闲聊走模型、其余与异常引导人工)。
直接返回知识原文而不经模型改写,答案的字面内容全部来自公司已发布资料。
- tools/publish_customer_service_config.py:发布意图工具白名单。
- tools/customer_service_check.py:端到端验收(8 个用例,含越界请求与知识库外问题)。
装配:
- bootstrap 新增 get_knowledge_search_service 工厂,注册 search_knowledge 工具与
customer_service Agent。
- runtime_config_service 新增 load_active_prompt:提示词绑定 release_id,按当前生效
版本读取,未发布时回落代码默认值。闲聊话术因此可审核、可回滚,不必改代码发版。
过程中发现并处理的三个问题:
1. 自造 source_references 被基座合规闸门拒绝。governance.review_output 只接受
「本次召回的记忆」与「本次成功调用的工具」两类引用(用于防止伪造来源),
knowledge 类型会被判非法并使整个 run 失败。处理方式是**不放开那道校验**,
而把知识出处(文件标题与内部编号)写进正文,source_references 交给基座自动附加。
2. 发布配置是整版本替换语义:新版本会清空旧版本的全部配置项。若只发客服白名单,
示例 Agent 的 fund_query_demo:fund_quote 会被静默清空。故发布脚本先读取当前生效
版本的全部配置项并原样继承,再追加新增项。
3. 验收脚本自身两处自伤:打印 emoji 触发 GBK UnicodeEncodeError、以及读错结果字段
(RunQueryService 返回的答案键是 content 不是 text)。
已知缺口(未修,已记录):
- CoreResult.transfer_required 未持久化:conversation_message 不存该标记,
API 读不到"本次是否引导了人工"。当前靠正文里的固定话术判断。
- 知识块引用(source_type=knowledge)尚未启用,需先让 ToolExecutor 把工具返回的
doc_id 登记为本次可引用来源。
验证:ruff 通过、mypy 107 文件无错、unit+contract 447 passed;
tools/customer_service_check.py 8/8 通过(含越界请求、投诉、知识库外问题三类
必须引导人工的场景,以及 7 个零容忍负面词零命中)。
|
2026-09-10 20:22:42 +08:00 |
|
lzf_0626
|
6516ccb385
|
feat: 第二版——接口契约对齐 docs/05,修复静默故障与数据库基线
相对第一版 46fc976 的完整变更。组员迁移对照表见 docs/20。
一、对外契约对齐 docs/05(破坏性,共 4 处,组员需按 docs/20 调整)
1) 配置发布端点改为文档规定的复数资源名:submit→validations、
approve→reviews(需 body decision)、activate→activations、
rollback→rollbacks;第一版这 4 个动词式路径 docs/05 从未定义过。
2) 错误码由 8 个笼统码改为 15 个具体语义码(FORBIDDEN→AGENT_PERMISSION_DENIED、
UNAUTHORIZED→AUTHENTICATION_REQUIRED、CONFLICT→RESOURCE_VERSION_CONFLICT、
RESOURCE_NOT_FOUND→RUN_NOT_FOUND/SESSION_NOT_FOUND 等),
输入类错误状态码 400→422。
3) POST /api/v1/agent-runs 与 GET /api/v1/agent-runs/{run_id} 统一为
{data, meta} 信封(data 内字段名与语义未变)。
4) 错误响应体统一为 {error:{code,message,retryable,field_errors}, meta:{trace_id}},
不再返回 FastAPI 默认的 {"detail": ...}。
二、数据库基线与约束
新增 39 张表的基线迁移(链根)与联合唯一键纠偏(4 张表、删 8 增 4,幂等收敛);
撤下 config_release 的双人复核 CHECK(应用层已允许自审,审核节点保留,
自审如实写入 reviewer_id);记忆 active key 生成列与唯一键;
activate 开始记录 supersedes_release_id 使版本链可追溯。
docs/00 基线未修改,未重命名或删除任何表与字段。
三、修复会静默出错或无报错的缺陷
- 跑完集成测试后平台会静默失去生效配置:清理只删自己创建的版本,却没有恢复被它
顶成 superseded 的原生效版本,且审计一并删除因而完全无痕,表现为所有工具被拒
但没有任何报错。已修清理逻辑并加恢复。
- Worker 单轮异常导致进程退出;记忆抽取调用方的“事务已开始”异常;
召回缓存丢失 degraded 标记;连接时区未生效导致 created_at/updated_at 差 8 小时;
.env 与 os.getenv 密钥来源分裂导致“没有可用的已批准模型端点”。
- 记忆信号识别漏判与跨键误命中;SSE 未带 Accept 的协商行为。
四、功能补齐
记忆链路 P1/P2/P3(抽取、受控词表、召回与缓存、生命周期级联及投影事件)、
fin_* 场内交易只读 ORM 层、agent_intent_config 状态流转并在运行期真正生效、
限流(Redis 固定窗口、故障一律放行)、游标校验、trace_id 中间件、
示例业务 Agent fund_query_demo 与一键端到端验证脚本,以及审计/指纹/迁移状态工具。
五、文档与验证
新增 docs/19(业务 Agent 接入实操)、docs/20(第一版迁移指南)与 docs/evidence 证据;
docs/01/02/06/08/09/17 同步实现现状。
验证结果:ruff 通过、mypy 103 文件无错、unit+contract 447 passed、
integration 29 passed、acceptance_check --production 7 PASS、
demo_agent_e2e 9/9 PASS(含失败关闭反证)。
|
2026-09-10 15:55:54 +08:00 |
|