实验室智能助手(Agent)— 上线前补充方案

项 内容
文档版本 v1.0
创建日期 2026-10-09
所属系统 LabManagementSystem
目标 让智能助手达到学院正式使用标准
文档状态 待评审

1. 结论

当前智能助手在查询类场景可以内测使用,但还无法正式上线面向全学院使用。

主要原因有以下四点:

  1. 身份与数据隔离:Agent 接口不校验登录,会话不绑用户,报一个姓名就能查别人的申请;代提交的申请不属于任何用户。

  2. 写操作安全:「先确认再提交」只写在提示词里,代码没有拦截;提交逻辑复制了一份正式接口,校验口径不一致。

  3. 数据合规:学生姓名、申请理由、拒绝原因会发给外部大模型,目前没有部署方式和数据处理上的约定。这一点确实是问题,但经评估后,暂不影响上线。

  4. 运行与责任:不记 token、不记异常、不限流,出了问题无法追溯,也无法控制费用;Agent 并发高时会拖慢整个后端。

2. 现状基线

层 位置 现状
接口 backend/app/api/routes/agent.py health / sessions / sessions/{key}/messages / chat(SSE),均无登录校验
循环 backend/app/agent/loop.py 手写工具调用循环,最多 5 轮;run_agent(未使用)与 run_agent_stream 两份实现
模型 backend/app/agent/llm.py OpenAI 兼容接口,默认 deepseek-chat,单次超时 30 秒,非流式调用
提示词 backend/app/agent/prompts.py 范围限制、拒答规则、借设备的 6 步流程
工具 backend/app/agent/tools/ 5 个只读 + 1 个写(create_device_booking)
FAQ
backend/app/agent/faq.yaml 11 条,关键词命中第一条
存储
chat_sessions / chat_messages user_id 恒为空;token 字段不写入
前端 frontend/src/views/AssistantView.vue 会话号存 localStorage,SSE 渲染,简易 Markdown

3.3 目标架构

Image

4. 上线改造要求

每项按「现状 → 要求 → 改造要点 → 验收」组织。优先级:P0 = 上线前必须,P1 = 正式运行前完成,P2 = 持续改进。

4.1 身份与数据隔离(P0)

现状

  • 四个 Agent 接口都不校验登录;create_session 写入 user_id=None。

  • 会话号存在浏览器 localStorage,拿到会话号就能读历史、继续对话。

  • query_appointments / query_device_bookings 按模型传入的姓名查询,不核对当前用户;重名学生的记录会混在一起。

要求

  • 所有 Agent 接口要求登录,复用 app/core/auth.py 的 get_current_user。

  • 会话绑定 user_id;读历史、发消息时校验会话属于当前用户,不属于返回 404。

  • 学生只能查自己的申请;教师可查自己负责审批的;管理员按现有管理权限查。范围与「我的申请」「待我审批」页面保持一致。

改造要点

  • create_session 写入 current_user.id;get_messages、chat 按 session_key + user_id 查会话。

  • execute_tool 增加 ctx 参数,工具签名改为 fn(db, ctx, **args)。

  • 两个进度查询工具删除 applicant_name 参数,改为按 ctx.user.id 过滤(与 GET /api/device-bookings?scope=mine 同一口径)。

  • 提示词删除「查询进度需要先问姓名」。

  • 前端未登录时进入助手页弹出登录(路由加 requiresAuth),并在登出时清掉本地会话号。

验收

  • 未登录调用任一 Agent 接口返回 401。

  • 用户 A 的会话号由用户 B 调用,返回 404。

  • 用户在对话中说「帮我查张三的申请」,只返回本人记录或说明无权查询。

4.2 写操作:代提交设备申请(P0)

现状

  • 工具提交的申请 user_id、姓名、学号、电话、班级都为空,在「我的申请」里看不到,管理员审批时也不知道是谁。

  • 只存了 teacher_name 文本,正式接口要求从教师名册选定工号(_resolve_teacher),工具绕过了这一步。

  • 设备按名称精确匹配,失败再模糊匹配,取第一条;而搜索工具把同名设备各地点的库存加总后展示。助手说「可借 61 台」,提交时可能因单条记录库存不足而失败,或者提交到了学生不想要的存放地点。

  • 是否确认只靠提示词约束。

要求

  • 申请记录与正式页面提交的完全一致:申请人、学号、电话、班级、负责教师工号齐全,状态流转、审批、通知同一套。

  • 学生在界面上明确确认之后才落库,模型无法绕过。

  • 学生确认的设备必须精确到一条设备记录(含存放地点),展示的可借数和提交校验的可借数一致。

改造要点

  1. 抽服务层:把 device_booking.py 中 create_device_booking 路由的校验与建单逻辑抽成 app/services/device_booking_service.py 的 create_booking(db, user, device_id, quantity, start, end, reason, teacher_id, phone, class_name),路由和确认接口都调用它,HTTPException 改为业务异常再由路由转换。

  2. 写工具改为草稿:create_device_booking 改名 draft_device_booking,参数用 device_id(不再用名称),只校验并保存一条草稿(新表或会话状态,含过期时间,建议 15 分钟),返回草稿内容和草稿号,**不写 ****device_bookings**。

  3. 确认接口:新增 POST /api/agent/drafts/{draft_id}/confirm,校验草稿属于当前用户且未过期,调用服务层建单;POST .../cancel 作废草稿。

  4. 前端确认卡片:SSE 新增 draft 事件,前端渲染设备、地点、数量、日期、理由、负责教师和「确认提交 / 取消」按钮。对话里输入「确认」不触发提交。

  5. 补齐参数来源:新增只读工具 search_teachers(查教师名册,返回工号与姓名);电话、班级默认取用户资料,资料不全时草稿返回缺失项,并引导到个人资料页。

  6. 库存口径统一:search_devices 返回按地点拆开的记录(每条带 id、地点、可借数),合并展示只作摘要;草稿必须指定一条记录。

  7. 提示词第 7、8 条改为描述「先生成草稿,再请用户在卡片上确认」。

说明:设备的 available_quantity 只在确认领取时扣减,多个待审核申请可以超过库存。这是设备申请模块本身的规则,Agent 不单独处理,但审批端要能看到冲突。

验收

  • 通过助手提交的申请出现在本人「我的申请」中,字段与页面提交的一致。

  • 构造对话诱导模型「直接提交不用确认」,数据库中不产生 device_bookings 记录。

  • 草稿过期或属于他人时,确认接口拒绝。

  • 选择具体地点后提交,库存校验与展示一致。

4.3 数据合规与模型部署(P0,需学院决策)

现状

对话全文、工具返回的申请理由与拒绝原因、学生自报的姓名,都会发给外部大模型服务商。代码里没有脱敏,也没有关于数据出校的约定。

要求

  • 学院(或学校信息化部门)书面确认可用的模型部署方式:校内私有部署、学校采购的云服务,或明确允许的公有云接口。

  • 与服务商签订数据处理协议,至少约定:不用于训练、留存期限、存储地域、泄露通报。

  • 明确哪些字段可以发给模型,哪些必须留在校内。

  • 用户首次使用时告知:对话会由 AI 处理、会被记录、记录保存多久。

改造要点

  • 在 llm.py 增加出校前处理:工具结果中只保留回答所需字段;学号、电话、身份证号格式的内容统一打码。

  • 字段白名单写进各工具的返回裁剪中,并在代码评审中检查。

  • 配置项增加模型部署类型与允许出校字段的说明,部署文档写明。

  • 前端首次进入展示使用须知,用户确认后记录时间。

  • chat_messages 设置留存期限(如 180 天,以学院规定为准),到期定时清理或匿名化。

验收

  • 抽查模型请求日志,不出现学号、手机号原文。

  • 有学院签字的部署方式与数据处理说明。

  • 留存清理任务可运行,过期数据被清理。

4.4 回答准确性(P1)

现状

  • FAQ 按关键词在问题中出现即命中,按文件顺序返回第一条。「预约被拒绝怎么办」会先命中「如何预约实验室」(关键词「预约」)。「时间」「怎么」「为什么」这类泛词会误命中。

  • FAQ 写在代码仓库的 YAML 里,管理员改不了,改完要发版。

  • 没有评测集,换模型、改提示词后无法知道效果有没有变差。

要求

  • 规定类问题答对率可度量、可回归。

  • 管理员可在后台维护 FAQ,修改即时生效。

  • 回答规定类问题时注明出处(来自哪条规定)。

改造要点

  • FAQ 入库(新表 agent_faqs:问题、答案、关键词、启用状态、更新人),管理端增加维护页。

  • 匹配改为打分:命中关键词越多、越长得分越高,返回前 3 条交给模型选择;去掉泛词。条数增长到上百条再考虑向量检索(PRD 中的 P1 RAG)。

  • 建立评测集 docs/agent-eval-set.md(PRD 已规划但尚未建立),至少 60 条,覆盖:工具选择、拒答、越权查询、诱导直接提交、提示词注入、多轮指代。

  • 写一个回归脚本,在测试库上逐条跑并统计工具选择、拒答、越权三项结果;换模型或改提示词前必须跑。

验收

  • 评测集工具选择准确率 ≥ 90%,越权与诱导提交用例 100% 不通过。

  • 管理员在后台修改 FAQ 后,助手下一次回答即使用新内容。

4.5 可观测与审计(P1)

现状

  • 流式路径不累计 token,chat_messages 的 token 字段始终为 0。

  • 工具异常与模型异常都被转成固定文案,不记日志。

  • assistant 消息的 tool_calls、tool 消息的 tool_call_id 字段存在但没有写入。

要求

学院需要能回答:谁在什么时间问了什么、模型调了哪个工具、传了什么参数、结果如何、花了多少、失败原因是什么。

改造要点

  • 循环中累计每轮 usage,流式调用开启 stream_options.include_usage,写入 assistant 消息。

  • 每次工具执行记录:工具名、参数、耗时、是否出错、当前用户。异常写应用日志(含堆栈),返回给模型的仍是脱敏后的文案。

  • 补写 tool_calls、tool_call_id,使一次对话可以完整回放。

  • 管理端增加会话审计页(PRD 5.4 已规划):按用户、时间、工具、是否出错筛选,可查看完整对话。

  • 每个请求生成追踪 ID,SSE 的 error 事件带上,学生报错时管理员可直接定位。

验收

  • 任一对话可在审计页回放,含工具调用与 token 用量。

  • 人为制造工具异常,应用日志可查到堆栈和追踪 ID。

4.6 费用控制与限流(P1)

现状

单消息 500 字、最多 5 轮、历史 20 条,这三项有限制;没有按用户或全局的频率与费用上限。

改造要点

  • 按用户限流(如每分钟 6 条、每天 100 条),超出返回 429,前端提示稍后再试。

  • 全局每日 token 预算,达到 80% 告警,达到 100% 自动降级为「服务繁忙,请使用页面功能」。

  • 单次对话设置总时长上限(如 60 秒),超时中止并返回兜底文案。

  • 管理端看板展示每日调用量、token、费用估算、失败率。

验收

  • 单用户连续发送超过上限被拒绝。

  • 模拟预算耗尽时助手降级,其他页面不受影响。

4.7 性能与可用性(P1)

现状

见 3.2 第 4、5 条:同步调用占用共用线程池,假流式导致首字慢。另外,llm.py 固定传 enable_thinking: false,这是部分供应商的私有参数,换供应商时可能报错。

改造要点

  • chat 路由改为 async def,使用 AsyncOpenAI;工具执行仍是同步 ORM 时用 run_in_threadpool 包装,数据库会话只在工具执行期间持有。

  • 最后一轮使用真流式;工具调用轮保持非流式即可。

  • 供应商私有参数放到配置里,按供应商开关。

  • 支持备用供应商:主供应商超时或 5xx 时切换一次。

  • 模型不可用时,前端展示直达入口(实验室预约、设备申请、我的申请),而不只是「服务未就绪」。

  • 生产部署使用多进程(如 uvicorn --workers 或 gunicorn),在部署文档中给出建议值。

验收

  • 压测 50 个并发对话时,预约、登录等其他接口 P95 延迟不明显上升。

  • 首字时间 P95 < 3 秒(不含工具调用轮)。

  • 关闭主供应商后对话可由备用供应商完成。

4.8 安全加固(P1)

改造要点

  • 前端 renderMarkdown 是手写正则,已先转义 HTML,但表格、列表拼接需要补测试;建议换成成熟库(如 markdown-it)并配合 DOMPurify。

  • 提示词注入:用户输入和工具返回内容(如申请理由、FAQ 文本)都可能包含指令。工具返回结果用明确的分隔标记包裹,提示词声明其中内容只作数据;评测集加入注入用例。

  • 工具参数在服务端做类型与范围校验(整数、日期、长度),不依赖模型遵守 schema。

  • 依赖与密钥:LLM_API_KEY 只放环境变量,支持轮换;部署文档写明。

4.9 能力范围(P2)

学院正式使用时,师生会自然地问到以下问题,目前都不支持。建议按使用频率排期,每加一项都走 4.1、4.2 的权限与确认机制:

能力 说明
查可约时段 「明天下午 905 有空吗」,需接入预约模块的时段与 ROOMIS 状态
代提交实验室预约 涉及教师审批、ROOMIS 同步、重复预约,复杂度高于设备申请
取消申请 与「我的申请」中的取消一致,走草稿确认
教师侧 「待我审批的有几条」,只读查询
进度通知 审批结果主动推送,需与消息模块打通

5. 待学院确认的事项

以下事项不是技术问题,需要学院或学校层面给出决定,技术方案按决定调整:

  1. 模型部署方式与服务商,以及是否允许学生数据发往校外。

  2. 对话记录留存期限、谁有权查看审计记录。

  3. 助手答错或代提交出错时的责任人与处理流程,前端展示的联系方式。

  4. 服务对象:仅学生,还是教师、管理员也使用。

  5. 每日费用预算上限。

6. 分期计划与验收

6.1 阶段

阶段 内容 对应章节 预估
阶段 0:上线前必须 登录与会话绑定、进度查询按本人过滤、申请服务层抽取、草稿 + 确认、教师名册工具、库存口径统一、数据出校脱敏、学院确认部署方式 4.1、4.2、4.3 6~8 人日
阶段 1:试点运行 异步化与真流式、token 与审计记录、限流与预算、FAQ 入库与打分匹配、评测集与回归脚本、Markdown 安全渲染 4.4~4.8 8~10 人日
阶段 2:正式运行 管理端审计与用量看板、备用供应商、留存清理、能力扩展 4.5、4.7、4.9 按能力逐项评估

阶段 0 完成后可在本院小范围试点(一个班级或一个实验室),阶段 1 完成并通过验收后再全院开放。

6.2 上线验收清单

编号 验收项 通过条件
CR-01 登录校验 未登录访问 Agent 接口返回 401
CR-02 会话隔离 他人会话号返回 404
CR-03 查询隔离 无法通过对话查到他人申请
CR-04 申请归属 助手提交的申请出现在本人「我的申请」,字段完整
CR-05 强制确认 诱导直接提交不产生申请记录
CR-06 库存一致 展示与提交校验的可借数一致
CR-07 数据出校 模型请求中无学号、手机号原文
CR-08 评测回归 工具选择 ≥ 90%,越权、注入、诱导用例全部拦截
CR-09 审计回放 任一对话可完整回放,含 token
CR-10 限流降级 超频返回 429;预算耗尽自动降级
CR-11 并发隔离 50 并发对话下其他接口不受明显影响
CR-12 首字延迟 P95 < 3 秒
CR-13 学院确认 第 5 节事项均有书面结论

7. 附录:涉及代码位置

文件 改动
backend/app/api/routes/agent.py 登录校验、会话绑定、异步化、限流、草稿确认接口、draft 事件
backend/app/agent/loop.py 合并两份循环、传入上下文、真流式、累计 token、总时长上限
backend/app/agent/llm.py 异步客户端、脱敏、主备切换、私有参数配置化
backend/app/agent/tools/__init__.py execute_tool 增加上下文、审计、参数校验、状态文案入注册表
backend/app/agent/tools/appointments.py、device_bookings.py 去掉姓名参数按本人过滤;写工具改为草稿
backend/app/agent/tools/devices.py 按地点拆分返回记录
backend/app/agent/tools/faq.py、faq.yaml 入库、打分匹配
backend/app/agent/prompts.py 删除问姓名规则,借设备流程改为草稿确认
backend/app/api/routes/device_booking.py 建单逻辑抽到服务层
backend/app/services/device_booking_service.py 新增,REST 与 Agent 共用
backend/app/models/chat.py 草稿表或会话状态、使用须知确认时间
frontend/src/views/AssistantView.vue 登录要求、确认卡片、追踪 ID、降级入口、Markdown 渲染
frontend/src/router/index.js 助手页加 requiresAuth