实验室智能助手(Agent)— 上线前补充方案
实验室智能助手(Agent)— 上线前补充方案
| 项 | 内容 |
|---|---|
| 文档版本 | v1.0 |
| 创建日期 | 2026-10-09 |
| 所属系统 | LabManagementSystem |
| 目标 | 让智能助手达到学院正式使用标准 |
| 文档状态 | 待评审 |
1. 结论
当前智能助手在查询类场景可以内测使用,但还无法正式上线面向全学院使用。
主要原因有以下四点:
身份与数据隔离:Agent 接口不校验登录,会话不绑用户,报一个姓名就能查别人的申请;代提交的申请不属于任何用户。
写操作安全:「先确认再提交」只写在提示词里,代码没有拦截;提交逻辑复制了一份正式接口,校验口径不一致。
数据合规:学生姓名、申请理由、拒绝原因会发给外部大模型,目前没有部署方式和数据处理上的约定。这一点确实是问题,但经评估后,暂不影响上线。
运行与责任:不记 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 目标架构
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 台」,提交时可能因单条记录库存不足而失败,或者提交到了学生不想要的存放地点。
是否确认只靠提示词约束。
要求
申请记录与正式页面提交的完全一致:申请人、学号、电话、班级、负责教师工号齐全,状态流转、审批、通知同一套。
学生在界面上明确确认之后才落库,模型无法绕过。
学生确认的设备必须精确到一条设备记录(含存放地点),展示的可借数和提交校验的可借数一致。
改造要点
抽服务层:把
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改为业务异常再由路由转换。写工具改为草稿:
create_device_booking改名draft_device_booking,参数用device_id(不再用名称),只校验并保存一条草稿(新表或会话状态,含过期时间,建议 15 分钟),返回草稿内容和草稿号,**不写 ****device_bookings**。确认接口:新增
POST /api/agent/drafts/{draft_id}/confirm,校验草稿属于当前用户且未过期,调用服务层建单;POST .../cancel作废草稿。前端确认卡片:SSE 新增
draft事件,前端渲染设备、地点、数量、日期、理由、负责教师和「确认提交 / 取消」按钮。对话里输入「确认」不触发提交。补齐参数来源:新增只读工具
search_teachers(查教师名册,返回工号与姓名);电话、班级默认取用户资料,资料不全时草稿返回缺失项,并引导到个人资料页。库存口径统一:
search_devices返回按地点拆开的记录(每条带id、地点、可借数),合并展示只作摘要;草稿必须指定一条记录。提示词第 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. 待学院确认的事项
以下事项不是技术问题,需要学院或学校层面给出决定,技术方案按决定调整:
模型部署方式与服务商,以及是否允许学生数据发往校外。
对话记录留存期限、谁有权查看审计记录。
助手答错或代提交出错时的责任人与处理流程,前端展示的联系方式。
服务对象:仅学生,还是教师、管理员也使用。
每日费用预算上限。
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 |