From 927a6fecc06d3de641d392d72c99502bdac8d479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=BF=E4=BA=91=E7=A7=8B=E6=9C=88?= <15273589815@163.com> Date: Fri, 11 Sep 2026 16:51:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=84=E5=AE=A1=20NL=5Fdevelop=20=E4=BA=A4?= =?UTF-8?q?=E4=BB=98=E8=AF=B4=E6=98=8E=EF=BC=9A=E4=B8=89=E4=B8=AA=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=89=8D=E6=8F=90=E5=BF=85=E9=A1=BB=E5=85=88=E5=AF=B9?= =?UTF-8?q?=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 产出 docs/NL_develop交付说明-评审意见.md(可直接转给组员),以及一个只读探查工具 tools/probe_knowledge_collections.py(Milvus 集合 schema 与行数)。 核查中发现的硬矛盾,都有可复核的证据: 1. 配置库不是同一个。对方说 active=216、合并前生效版本是 186;我这边实测 active=201, 最近 5 条 id 就是 201/198/197/196/195 —— release.id 自增,同库不可能一边 216 一边 201。 所以他以为已发布的 customer_service:faq=[search_knowledge, query_customer_profile] 在这边并不存在,而他的画像出口复用的正是这个 key ⇒ 合并后被 ToolExecutor 失败关闭。 同理他说的 fund_query_demo:fund_quote 缺失,我这边是存在的。 2. Milvus 也不是同一个。对方说现库字段是 knowledge_id/snippet、无 visibility、行数 106/177/73;我这边实测是 doc_id/content/chapter/section/.../visibility 共 15 个字段、 行数 125/297/214,且与 knowledge_search_service.py 的 OUTPUT_FIELDS 逐字一致。 他这次的字段映射改动(doc_id→knowledge_id)在这边会直接报 field knowledge_id not exist,把客服知识检索整条打挂 —— 比他自述的"关闭 visibility 隔离"严重得多。 建议不是 A/B/C 三选一,而是第四种:运行时探测字段名,两套 schema 都能跑。 3. 解释器不同。他用 .venv(项目里不存在,那是他机器上的 gitignore 目录),约定是 D:\conda\envs\jr_py313。所以"mypy 151→181"跑不到本基线 —— 这边是 138 文件 0 错。 另指出:docs/26-JWT密钥管理与轮换.md 会与已存在的 docs/21-JWT密钥管理与轮换.md 重复, 建议并入 21;驳回删除 docs/04/06/10/13/99。 四项待裁决的答复:同意 agent_type 方案(要求补审计);字段映射改为运行时探测; 驳回删除编号文档;26 并入 21。 --- docs/NL_develop交付说明-评审意见.md | 208 ++++++++++++++++ docs/evidence/knowledge-collections.json | 304 +++++++++++++++++++++++ docs/evidence/release-state.json | 49 +++- tools/probe_knowledge_collections.py | 79 ++++++ tools/probe_release_state.py | 22 ++ 5 files changed, 661 insertions(+), 1 deletion(-) create mode 100644 docs/NL_develop交付说明-评审意见.md create mode 100644 docs/evidence/knowledge-collections.json create mode 100644 tools/probe_knowledge_collections.py diff --git a/docs/NL_develop交付说明-评审意见.md b/docs/NL_develop交付说明-评审意见.md new file mode 100644 index 0000000..8f18200 --- /dev/null +++ b/docs/NL_develop交付说明-评审意见.md @@ -0,0 +1,208 @@ +# 评审意见 · `NL_develop` 交付说明(给架构师的回复) + +> **评审人**:`qyqy_develop` 维护者 +> **被评审**:`交付说明-NL_develop-给架构师.md`,来源分支 `NL_develop` @ `96a6e01`(远端当前 `e342670`) +> **基线核对时间**:2026-09-11 +> **结论**:**代码质量认可,但合并前有三个环境前提必须先对齐,否则合并即故障。** + +--- + +## 0. 先说结论 + +这份交付说明写得很好:每项改动都给了理由、真机证据、以及对"动了别人什么"的逐文件说明, +还主动标注了代价(`_HAS_VISIBILITY=False` 会让内部资料隔离失效)。§4.1 的"确认式白名单 + +空串不注入"、§4.2 的"只改读取侧不改检索逻辑"、§4.5 的"话术只由治理层注入",这几个判断我 +都同意,理由见 §3。 + +**但是**:说明里有三处前提与我这边的实际环境不符,而且它们不是细节,是"合并会当场打挂"级别的: + +| # | 问题 | 后果 | +|---|---|---| +| 1 | 你说的生效版本 **216** 在我这里**不存在**(我这边 active 是 **201**) | 你以为已发布的修复,在我这边没发布 → 画像出口会 `AGENT_PERMISSION_DENIED` | +| 2 | 你说的 Milvus schema(`knowledge_id`/`snippet`/无 `visibility`)与我这边**完全不同** | 你这次改的字段映射,**在我这边会把检索打挂** | +| 3 | 你用 `.venv`,项目约定是 `D:\conda\envs\jr_py313` | "mypy 151→181" 这类结论与我这边不可比(我这边是 0 错) | + +下面逐条给证据和修法。 + +--- + +## 1. 环境不一致(最要紧,先对齐这个) + +### 1.1 配置发布:你说的 216 不在这个库里 + +我这边实测(`python tools/probe_release_state.py`,证据 `docs/evidence/release-state.json`): + +``` +active release = 201 (cs-prompt-d918ca507fc6, "客服闲聊提示词", 2026-09-11 05:25 activated) +最近 5 条 id:201 / 198 / 197 / 196 / 195 ← 不存在 216 +active 配置项:agent_tools 9 条 + prompt 1 条 +``` + +`release.id` 是自增主键,同一个库不可能一边看到 216、一边最高只有 201。 +所以"`config_release 216` 已在**共享库**生效"这句,**对这台环境不成立**。 + +同样地,你说的"合并前生效版本是 186、白名单是 `query_knowledge`"、"`fund_query_demo:fund_quote` +已经不存在",在我这边也不成立 —— 我这边 201 的内容是: + +| config_key | allowed_tools(我这边 201 实测) | +|---|---| +| `customer_service:faq` | `["search_knowledge"]` | +| `customer_service:policy_explain` | `["search_knowledge"]` | +| `customer_service:product_inquiry` | `["search_knowledge"]` | +| `customer_service:suitability_check` | `["search_knowledge", "check_suitability"]` | +| `fund_query_demo:fund_quote` | `["query_fund_quote"]` ← **存在** | +| `risk:general` | `[]` | +| `risk:risk_evidence` | `["get_alert_evidence"]` | +| `risk:risk_overview` | `["get_risk_overview"]` | +| `risk:risk_search` | `["search_risk_alerts"]` | + +### 1.2 ⚠️ 合并后画像出口会立刻断链 + +你在 §4.3 写"`PROFILE_WHITELIST_INTENT = INTENT_FAQ`:画像工具调用复用**已发布**的 `faq` +意图 key" —— 那是基于你的 216(`faq = [search_knowledge, query_customer_profile]`)。 + +**我这边 201 的 `faq` 只有 `["search_knowledge"]`,没有 `query_customer_profile`。** +`ToolExecutor` 取交集、失败关闭 ⇒ 画像出口一调用就被拒。 + +**修法(合并时必须同时做)**:补发一个版本,把 `customer_service:faq` 改成 +`["search_knowledge", "query_customer_profile"]`,其余 8 条原样继承(`config_release` 是整版本 +替换,漏带会清空别人的白名单)。这一步我这边可以出脚本。 + +### 1.3 ⚠️ Milvus 也不是同一个,字段映射改动会打挂检索 + +我这边实测三个集合(`python tools/probe_knowledge_collections.py`,证据 +`docs/evidence/knowledge-collections.json`): + +| 集合 | 行数 | 字段 | +|---|---|---| +| `fin_faq_collection` | **125** | `doc_id`(主键) `title` `content` `chapter` `section` `tags` `doc_no` `version` `effective_date` `expire_date` `source_url` `reviewer` `source_file` `visibility` `embedding` | +| `fin_policy_collection` | **297** | 同上 | +| `fin_product_collection` | **214** | 同上 | + +你说的是 `knowledge_id | title | snippet | tags | version | intent | embedding`、**无** `visibility`、 +行数 **106/177/73** —— **字段名、`visibility` 有无、行数三处全不一样。** + +而我这边 `app/service/knowledge_search_service.py:23-25` 的 + +```python +OUTPUT_FIELDS = ("doc_id", "title", "content", "chapter", "section", + "tags", "doc_no", "version", "source_file", "visibility") +``` + +与我的集合**逐字一致**,`:152` 的 `visibility == "public"` 过滤也在正常工作。 + +**所以 §4.2 不是"A/B/C 三选一"的问题。** 你的 A 方案在这边会: + +1. `doc_id` → `knowledge_id` ⇒ Milvus 报 `field knowledge_id not exist`,**整条检索失败**; +2. `content` → `snippet` ⇒ 同样失败; +3. `_HAS_VISIBILITY = False` ⇒ 把**本来正常工作的**内部资料隔离关掉。 + +**我要的修法不是 A/B/C,而是第四种:把字段映射改成运行时探测。** + +```python +# 启动时 describe_collection 拿真实字段名,建立 逻辑名 → 实际字段名 的映射; +# visibility 有就过滤、没有就跳过;缺哪个逻辑字段就明确报出来,不要硬编码。 +``` + +这样两套 schema **都能跑**,不需要迁移、不需要重灌数据,也不需要改检索逻辑 —— 你在 §4.2 里 +"只改读取侧、不动 `KnowledgeHit` 对外形状"的克制我完全同意,只是落点应该从"硬编码另一套字段名" +换成"探测"。这也顺带解决你担心的"改回去很麻烦":探测之后没有需要改回去的东西。 + +> 顺带:你那边行数 106/177/73 vs 我这边 125/297/214,说明**演示数据也不是同一份**。演示前要统一。 + +### 1.4 解释器不统一,数字不可比 + +你用 `.venv\Scripts\python.exe` —— 这个目录**在项目里不存在**(`.venv` 被 gitignore,那是你机器上的)。 +项目约定与文档用的是 `D:\conda\envs\jr_py313\python.exe`。 + +结果就是 §5 那张"mypy 181 个错"的表跑不到我的基线上:**我这边 `mypy app` 是 `138 source files, +no issues found`,0 个错**。你列的"`app/model/fund.py` 105 个错"在我这里一个都没有。 + +**请先统一解释器再讨论 mypy 数字**,否则每次合并都要重新吵一遍。 + +--- + +## 2. 文档编号:`docs/26` 会和已有的 `docs/21` 重复 + +你说"`docs/26-JWT密钥管理与轮换.md` 是新文件,原 21→25→26 两次让号"。 + +**但我这边 `docs/21-JWT密钥管理与轮换.md` 已经存在。** 也就是说合并后会出现**两份同主题文档** +(21 与 26),这比编号冲突更糟 —— 读者不知道该信哪一份。 + +**建议**:不要新增 26,把内容合并进已有的 `docs/21`。如果你手上那份是旧 21 的修订版, +请在 PR 里说明"以 26 的内容覆盖 21"并**删除 21**,二选一,别并存。 + +--- + +## 3. §6 四项待裁决的答复 + +### 3.1 `AgentGovernance.review()` 新增 `agent_type` —— ✅ 同意 + +风控 Agent 的输出是给风控专员看的字段化摘要(预警编号、级别、建议动作),给它追加一句面向 +投资者的免责声明**本身就是错的** —— 你实测 `tests/contract/test_risk_agent_contract.py` 因此 +失败,那是真实症状,不是测试太严。 + +判定口径选"**确认式白名单**"(只有 `customer_service` / `fund_query_demo` 注入,空串不注入) +比"默认注入"安全,理由(测试替身不连库,默认注入会污染每个替身测试)我认同。 +"带默认值所以旧调用点不会 TypeError"也处理得对。 + +**补充一个要求**:请确认 `agent_type` 同时写进了治理环节的审计记录。治理层改写了对外输出, +事后必须能追溯到是哪个 Agent 触发的。 + +### 3.2 字段映射 —— ⚠️ 不是 A/B/C,见 §1.3(改为运行时探测) + +### 3.3 删除 `docs/04`/`06`/`10`/`13`/`99` —— ❌ 驳回,请保留 + +我这五个文件**全都在**(`docs/04-开发文档评审报告.md`、`docs/06-底座代码测试报告.md`、 +`docs/10-业务域接入评估.md`、`docs/13-基金行情工具业务接入清单.md`、 +`docs/99-已废弃-公共Agent平台接口规范.md`)。 + +删除它们收益为零(你说"无人引用",但保留的成本同样为零),代价是:PR diff 里出现 5 个删除、 +还要同步改 `AGENTS.md` 里"已删除"的说明、并且丢掉了 `docs/10` 这份业务域接入评估。 +**请把这一项从 PR 里去掉。** + +### 3.4 `docs/26` 编号 —— ⚠️ 见 §2,建议并入 `docs/21` + +--- + +## 4. 关于你 §7.1 的提醒(`memory_sync_outbox`) + +你提醒"你最近那批提交里有'记忆→画像→图全自动触发',`memory_sync_outbox` 没有消费者, +这两处可能重叠,请先确认再动手"。 + +我 grep 过了:`MemorySyncOutbox` 在全仓**只有模型定义**,**既没有生产者、也没有消费者**。 +所以你说的"事件写入了、没人消费"在我这边不成立(根本没写进去)。**不重叠。** + +但你的观察方向是对的,而且比你想的更严重:我另外发现 `app/worker/graph_projection_worker.py` +的 `GraphProjectionWorker` **没有任何实例化点**。**这两件事大概率是同一个问题** —— 组件写好了、 +线没接。这条链路整体未接线,麻烦你那边也确认下你的分支上是什么状态。 + +--- + +## 5. 合并顺序建议 + +1. **先对齐环境**:确认你我连的是不是同一个 MySQL 与同一个 Milvus。**所有"配置已发布""数据是 + 某某 schema"的结论都建立在这上面**,不对齐的话下面每一步都要返工; +2. 统一解释器(`D:\conda\envs\jr_py313\python.exe`); +3. `§1.3` 的字段映射改成运行时探测,`§3.3` 的删除项撤回,`§2` 的编号并入 21; +4. 合代码; +5. **合并后立刻补发配置**(`customer_service:faq` 加 `query_customer_profile`,§1.2),否则画像 + 出口不可用; +6. 统一演示数据(行数差异见 §1.3)。 + +--- + +## 6. 我这边的状态(供你参考) + +- 分支 `qyqy_develop_1` 已包含 `origin/qyqy_develop` 的 `d2cdbba`,并且**多 8 个提交**(含组员 + RM2 线并入的 4 个),尚未推送; +- 门禁:`ruff` 干净 / `mypy app` 138 文件 0 错 / **703** unit+contract / **33** integration; +- 我这一轮改的公共部分(供你判断冲突面):`ApiTransactionService.execute_in`(幂等,复用调用方 + 事务)、`app/api/views/envelope.py`(§3.3 信封,风控+会话+知识引用三处共用)、 + `app/api/dependencies/negotiation.py`(SSE `Accept` 协商)、`risk_cursor.py` 的游标绑定、 + `OutboxHandlerError`(worker 失败原因可诊断且不泄露异常消息); +- 你 `§4.2` 说的"公共 SSE 协商""公共鉴权基座"这类问题,**确实归我这边修**,已经在做了 —— + 分工没有空档。 + +有异议随时提,尤其是 §1.3:如果你那边确认过现库字段就是 `knowledge_id`/`snippet`, +那说明我们连的不是同一个 Milvus,那就先把这个对齐了再谈代码。 diff --git a/docs/evidence/knowledge-collections.json b/docs/evidence/knowledge-collections.json new file mode 100644 index 0000000..4b4c592 --- /dev/null +++ b/docs/evidence/knowledge-collections.json @@ -0,0 +1,304 @@ +{ + "collections": [ + { + "collection": "fin_faq_collection", + "row_count": 125, + "field_names": [ + "doc_id", + "title", + "content", + "chapter", + "section", + "tags", + "doc_no", + "version", + "effective_date", + "expire_date", + "source_url", + "reviewer", + "source_file", + "visibility", + "embedding" + ], + "fields": [ + { + "name": "doc_id", + "type": "21", + "is_primary": true + }, + { + "name": "title", + "type": "21", + "is_primary": false + }, + { + "name": "content", + "type": "21", + "is_primary": false + }, + { + "name": "chapter", + "type": "21", + "is_primary": false + }, + { + "name": "section", + "type": "21", + "is_primary": false + }, + { + "name": "tags", + "type": "21", + "is_primary": false + }, + { + "name": "doc_no", + "type": "21", + "is_primary": false + }, + { + "name": "version", + "type": "21", + "is_primary": false + }, + { + "name": "effective_date", + "type": "21", + "is_primary": false + }, + { + "name": "expire_date", + "type": "21", + "is_primary": false + }, + { + "name": "source_url", + "type": "21", + "is_primary": false + }, + { + "name": "reviewer", + "type": "21", + "is_primary": false + }, + { + "name": "source_file", + "type": "21", + "is_primary": false + }, + { + "name": "visibility", + "type": "21", + "is_primary": false + }, + { + "name": "embedding", + "type": "101", + "is_primary": false + } + ], + "has_visibility": true, + "looks_like_legacy_schema": true + }, + { + "collection": "fin_policy_collection", + "row_count": 297, + "field_names": [ + "doc_id", + "title", + "content", + "chapter", + "section", + "tags", + "doc_no", + "version", + "effective_date", + "expire_date", + "source_url", + "reviewer", + "source_file", + "visibility", + "embedding" + ], + "fields": [ + { + "name": "doc_id", + "type": "21", + "is_primary": true + }, + { + "name": "title", + "type": "21", + "is_primary": false + }, + { + "name": "content", + "type": "21", + "is_primary": false + }, + { + "name": "chapter", + "type": "21", + "is_primary": false + }, + { + "name": "section", + "type": "21", + "is_primary": false + }, + { + "name": "tags", + "type": "21", + "is_primary": false + }, + { + "name": "doc_no", + "type": "21", + "is_primary": false + }, + { + "name": "version", + "type": "21", + "is_primary": false + }, + { + "name": "effective_date", + "type": "21", + "is_primary": false + }, + { + "name": "expire_date", + "type": "21", + "is_primary": false + }, + { + "name": "source_url", + "type": "21", + "is_primary": false + }, + { + "name": "reviewer", + "type": "21", + "is_primary": false + }, + { + "name": "source_file", + "type": "21", + "is_primary": false + }, + { + "name": "visibility", + "type": "21", + "is_primary": false + }, + { + "name": "embedding", + "type": "101", + "is_primary": false + } + ], + "has_visibility": true, + "looks_like_legacy_schema": true + }, + { + "collection": "fin_product_collection", + "row_count": 214, + "field_names": [ + "doc_id", + "title", + "content", + "chapter", + "section", + "tags", + "doc_no", + "version", + "effective_date", + "expire_date", + "source_url", + "reviewer", + "source_file", + "visibility", + "embedding" + ], + "fields": [ + { + "name": "doc_id", + "type": "21", + "is_primary": true + }, + { + "name": "title", + "type": "21", + "is_primary": false + }, + { + "name": "content", + "type": "21", + "is_primary": false + }, + { + "name": "chapter", + "type": "21", + "is_primary": false + }, + { + "name": "section", + "type": "21", + "is_primary": false + }, + { + "name": "tags", + "type": "21", + "is_primary": false + }, + { + "name": "doc_no", + "type": "21", + "is_primary": false + }, + { + "name": "version", + "type": "21", + "is_primary": false + }, + { + "name": "effective_date", + "type": "21", + "is_primary": false + }, + { + "name": "expire_date", + "type": "21", + "is_primary": false + }, + { + "name": "source_url", + "type": "21", + "is_primary": false + }, + { + "name": "reviewer", + "type": "21", + "is_primary": false + }, + { + "name": "source_file", + "type": "21", + "is_primary": false + }, + { + "name": "visibility", + "type": "21", + "is_primary": false + }, + { + "name": "embedding", + "type": "101", + "is_primary": false + } + ], + "has_visibility": true, + "looks_like_legacy_schema": true + } + ] +} \ No newline at end of file diff --git a/docs/evidence/release-state.json b/docs/evidence/release-state.json index 9e3ee0b..850c8aa 100644 --- a/docs/evidence/release-state.json +++ b/docs/evidence/release-state.json @@ -47,5 +47,52 @@ "items": 9 } ], - "active_prompt_versions": 1 + "active_prompt_versions": 1, + "active_agent_tools": [ + { + "config_key": "customer_service:faq", + "value_json": "{\"allowed_tools\": [\"search_knowledge\"]}", + "schema_version": "1" + }, + { + "config_key": "customer_service:policy_explain", + "value_json": "{\"allowed_tools\": [\"search_knowledge\"]}", + "schema_version": "1" + }, + { + "config_key": "customer_service:product_inquiry", + "value_json": "{\"allowed_tools\": [\"search_knowledge\"]}", + "schema_version": "1" + }, + { + "config_key": "customer_service:suitability_check", + "value_json": "{\"allowed_tools\": [\"search_knowledge\", \"check_suitability\"]}", + "schema_version": "1" + }, + { + "config_key": "fund_query_demo:fund_quote", + "value_json": "{\"allowed_tools\": [\"query_fund_quote\"]}", + "schema_version": "1" + }, + { + "config_key": "risk:general", + "value_json": "{\"allowed_tools\": []}", + "schema_version": "1" + }, + { + "config_key": "risk:risk_evidence", + "value_json": "{\"allowed_tools\": [\"get_alert_evidence\"]}", + "schema_version": "1" + }, + { + "config_key": "risk:risk_overview", + "value_json": "{\"allowed_tools\": [\"get_risk_overview\"]}", + "schema_version": "1" + }, + { + "config_key": "risk:risk_search", + "value_json": "{\"allowed_tools\": [\"search_risk_alerts\"]}", + "schema_version": "1" + } + ] } \ No newline at end of file diff --git a/tools/probe_knowledge_collections.py b/tools/probe_knowledge_collections.py new file mode 100644 index 0000000..9e488b8 --- /dev/null +++ b/tools/probe_knowledge_collections.py @@ -0,0 +1,79 @@ +"""只读探查:Milvus 知识集合的真实 schema(用于评审 NL_develop 的字段映射改动)。 + +只做 describe / query,不写入。结果写 `docs/evidence/knowledge-collections.json`: + + python tools/probe_knowledge_collections.py + +要回答的问题(对应交付说明 §4.2): +1. 现库集合的**实际字段名**是什么 —— 是 `doc_id`/`content`/`visibility` 那套, + 还是 `knowledge_id`/`snippet` 那套?他选的字段映射(A 方案)建立在这个前提上; +2. 每个集合有多少行; +3. 有没有 `visibility` 字段 —— 没有的话,检索层的内部资料硬隔离就是关着的。 +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from app.core.config import get_settings + +OUTPUT = Path("docs/evidence/knowledge-collections.json") + + +def _client() -> Any: + from pymilvus import MilvusClient # type: ignore[import-untyped] + + settings = get_settings() + return MilvusClient(uri=settings.milvus_uri, token=settings.milvus_token or None) + + +def collect() -> dict[str, Any]: + client = _client() + report: dict[str, Any] = {"collections": []} + for name in sorted(client.list_collections()): + try: + described = client.describe_collection(name) + fields = [ + { + "name": str(field.get("name")), + "type": str(field.get("type")), + "is_primary": bool(field.get("is_primary", False)), + } + for field in described.get("fields", []) + ] + stats = client.get_collection_stats(name) + row_count = stats.get("row_count") if isinstance(stats, dict) else None + except Exception as exc: # 单个集合探测失败不影响其余 + report["collections"].append( + {"collection": name, "error": f"{type(exc).__name__}: {exc}"} + ) + continue + report["collections"].append( + { + "collection": name, + "row_count": row_count, + "field_names": [field["name"] for field in fields], + "fields": fields, + "has_visibility": any(field["name"] == "visibility" for field in fields), + "looks_like_legacy_schema": any( + field["name"] in {"doc_id", "chapter", "doc_no"} for field in fields + ), + } + ) + return report + + +def main() -> None: + report = collect() + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + OUTPUT.write_text( + json.dumps(report, ensure_ascii=False, indent=2, default=str), + encoding="utf-8", + ) + print(f"wrote {OUTPUT}") + + +if __name__ == "__main__": + main() diff --git a/tools/probe_release_state.py b/tools/probe_release_state.py index 290ea86..d65be7e 100644 --- a/tools/probe_release_state.py +++ b/tools/probe_release_state.py @@ -73,6 +73,28 @@ async def collect() -> dict[str, Any]: ) ) ).scalar_one() + # 工具白名单的**具体内容**:它必须与代码里的 allowed_tools 交集非空, + # 否则 ToolExecutor 失败关闭,Agent 任何工具调用都被拒。 + report["active_agent_tools"] = [ + dict(row) + for row in ( + ( + await session.execute( + text( + """ + SELECT i.config_key, i.value_json, i.schema_version + FROM platform_config_item i + JOIN config_release r ON r.id = i.release_id + WHERE r.status = 'active' AND i.namespace = 'agent_tools' + ORDER BY i.config_key + """ + ) + ) + ) + .mappings() + .all() + ) + ] return report