Skip to content

fix(expense): reject unknown employees in quota query instead of returning a default quota - #258

Open
BlueX888 wants to merge 1 commit into
OpenBMB:mainfrom
BlueX888:fix/expense-quota-unknown-employee
Open

fix(expense): reject unknown employees in quota query instead of returning a default quota#258
BlueX888 wants to merge 1 commit into
OpenBMB:mainfrom
BlueX888:fix/expense-quota-unknown-employee

Conversation

@BlueX888

@BlueX888 BlueX888 commented Sep 2, 2026

Copy link
Copy Markdown

Fixes #257.

问题

expense.quota_query 之前指向外部演示服务 http://58.57.119.30:52008/api/mock/expense/quota_query,对任意 employee_id 都返回 HTTP 200 和 total_quota=20000。于是对不存在的人(如「张三 / 工号 666」)也能报出「额度 20,000 CNY,剩余 20,000 CNY」,T L 侧再拼上用户自己输入的姓名与工号,看起来就像查到了真实员工。

根因有三层:

  1. 工具结果没有「未命中」语义——output_schemafound/miss_reason,即便换成真实后端,对未知工号返回默认额度也会同样播报。
  2. SOP 把「工具成功」等价于「业务结果成立」,响应节点只在「异常/数据缺失」时才提示。
  3. 姓名→工号从未对照花名册核实;用户提供的标识符被当作已验证事实。

改动

  • 工具契约expense.quota_queryoutput_schema 增加 foundmiss_reason(以及 employee_name/department),并把 URL 指向仓库内 mock /api/mock/expense/quota_query,不再依赖外部演示地址。
  • 本地 mockbackend/app/api/mock.py 新增 /expense/quota_query,内置小规模花名册(E1001/E1002/E1003)。未知工号返回 found=false + miss_reason=employee_not_found,且不包含任何额度数字;与同文件 _order_miss/_product_miss 语义一致。
  • SOP 指令(seed fixture skills/skill_versions/agent_skill_branches/agent_skill_branch_versions 四处同步更新):
    • node_collect_info:姓名不能替代工号,仅提供姓名时必须追问工号,不得猜测或用示例工号代替。
    • node_call_quota_queryfound=false/miss_reason 表示系统没有该工号,直接进入最终回复,不用相同工号重试,也不改用猜测工号。
    • node_response_result:先检查 foundfound=false 时明确告知未找到该员工,不得展示任何额度数字,且不得把用户提供的姓名/工号当作已核实信息。
  • 全局规则backend/app/llm/prompts/harness_agent_prompt.md):工具结果中出现 found=false/miss_reason 表示未命中而非成功,不得把默认额度、占位标识符(UNKNOWN 等)或用户自行提供的标识符当作已核实事实对外展示。
  • 测试
    • tests/test_mock_expense_quota_query.py:命中、裸数字工号归一、未知工号 miss、缺工号 miss、月份默认。
    • tests/test_mock_auth.py:新增路由走内部服务鉴权;通过 HTTP 校验未知工号返回 found=false 且无额度字段。
    • tests/test_staffdeck_seed.py:seed 后工具 URL 指向本地 mock、schema 含 found/miss_reason、SOP 指令包含未命中处理。

复现验证

请求本地 mock(内部服务鉴权后):

POST /api/mock/expense/quota_query   {"employee_id":"666","month":"2026-09"}
→ {"found":false,"miss_reason":"employee_not_found", ...}   # 无 total_quota/used/remaining

POST /api/mock/expense/quota_query   {"employee_id":"E1001","month":"2026-09"}
→ {"found":true,"total_quota":20000.0,"used":6350.0,"remaining":13650.0,...}

注意:需要重启开发环境以重新 seed(scripts/dev_up.sh --detach),数据库会以 seed-managed 方式把工具 URL 与 SOP 内容更新到现有行。

测试

  • pytest tests/test_mock_auth.py tests/test_mock_expense_quota_query.py tests/test_staffdeck_seed.py tests/test_chat_trace.py ✅ 58 passed
  • 全量 pytest tests:2060 passed,11 failed —— 与本次无关的环境预存失败,全部集中在 test_channel_scope.py / test_feishu_schema_migration.py / test_feishu_durable_inbox.py / test_channel_team_binding.py,在干净 base(未含本次改动)上同样失败。
  • ruff check 新增/改动文件:通过(mock.py 现存 4 处 B008 为改动前即存在的告警)。

风险与说明

…rning a default quota

expense.quota_query previously hit an external demo server that returned a
20,000 CNY quota for any employee_id, so an unknown employee (e.g. 张三 /
工号 666) was reported back as a real, funded employee.

- Point the tool at the in-repo mock (/api/mock/expense/quota_query) and add
  found/miss_reason to its output schema; the mock returns found=false with no
  quota numbers for unknown IDs.
- Teach the skill instructions (collect / call / respond nodes) that found=false
  or a miss_reason means no such employee: never show quota digits, never take a
  user-supplied name or ID as verified, and ask for the ID instead of guessing.
- Add a harness tool-result rule generalizing the same contract beyond this SOP.
- Tests for the mock miss contract, internal-service auth on the new route, and
  seeded tool/skill assertions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

报销额度查询对不存在的员工返回"成功"结果:expense.quota_query 缺少存在性校验,导致 TL 播报虚构员工额度

1 participant