# 智能办事助手:产品定义、架构与决策 > 这份文档回答"我们在做什么、为什么这样做、接下来做什么"。里程碑和实测数据见 `PROGRESS.md`。 > 每次做出新的设计决策,在「已定决策」追加一条并写日期;改变方向时改这里,不要只改对话。 --- ## 1. 一句话定义 一个像 Codex 桌面端的办事 Agent:用户对着聊天框说一句"我要办某件事",助手识别业务、追问缺的资料、以对应角色登录学校的网页系统、自主找入口填表、在提交前逐项列出请用户确认,办完后记录结果,并把过程中学到的经验沉淀下来让下一次更快。 ## 2. 目标用户与角色 | 角色 | 用什么 | 做什么 | |---|---|---| | 学校里的办事人(学生、辅导员、管理员) | 桌面端 | 说需求、补资料、在关键节点确认。**不参与知识审核** | | 我们团队 | 后台管理(待做) | 写出厂 YAML、审核各校沉淀的经验、看运行监控、维护密码本、交付 | ## 3. 产品原则 1. 只在业务允许的域名内活动。 2. 登录凭据来自密码本或用户手动,模型永远看不到明文(browser-use `sensitive_data`)。 3. 任何正式提交前必须用户确认;用户取消就不提交。 4. 不猜学校没有明说的规则,宁可停下来问。 5. 全程可审计:每步截图、progress.md、run_logs。 6. 经验先成草稿再生效,用户端无感,审核由我们或规则完成。 ## 4. 架构 ### 4.1 现状(2026-09-17) ``` Electron 桌面端(renderer 聊天 + 右侧状态/截图/时间线) │ IPC Python FastAPI 后端(Electron 拉起并独占,端口 8788) ├─ nlu.py 意图识别 + 字段抽取(DeepSeek / 千问,一次调用,JSON) ├─ orchestrator.py 状态机、按业务路由、prompt 组装、暂停/续跑、沉淀触发 ├─ browser/runner.py browser-use Agent,独立 Chrome,会话保活,每步截图 ├─ learning.py 任务成功后生成 learned 草稿 └─ db/database.py psycopg 3,所有学校数据带 school_id PostgreSQL 17(Docker,127.0.0.1:15433) knowledge / tasks / run_logs / system_accounts / learned_entries / skills / settings ``` 桌面端直连 PostgreSQL,一台机器一所学校(`.env` 的 `SCHOOL_ID`)。 ### 4.2 目标形态 ``` 桌面端(编排 + 浏览器,必须在本机) ──HTTPS──▶ 服务端 API ◀── 后台管理(我们团队,跨学校) │ 中心 MySQL(按 school_id 隔离)+ 向量库(手册) ``` - 桌面端只保留必须在本机跑的部分:编排、browser-use、Chrome。知识、learned、账号、任务记录都走服务端接口。 - 后台管理和桌面端用同一套接口。 - 学校可以一校一库,也可以共库按 `school_id` 隔离,数据模型两种都支持。 ## 5. 知识的三层 | 层 | 内容 | 存在哪 | 谁维护 | 状态 | |---|---|---|---|---| | 出厂配置 | 系统入口、登录方式、业务定义(角色、必填项、步骤、规则) | 编写格式是 `knowledge/*.yaml`,启动时灌入 `knowledge` 表,运行时读表 | 我们,来源是学校手册 + 调研;将来后台直接改表,文件只做导入导出 | 已有 | | 经验 learned | 入口路径、控件技巧、前置阻断、踩过的坑 | `learned_entries` 表,draft → verified / rejected,带 school_id、类别、观察次数、来源任务;YAML 里的 `learned` 只是出厂基线 | 任务成功后自动生成草稿,按内容判重归并;控件技巧 / 入口路径类被 ≥2 个任务观察到自动生效,系统规则类由我们后台人审,用户不参与 | 已有;后台审核队列待做 | | 参考资料 | 学校手册、通知、FAQ | 向量库(可复用已有 Qdrant),按学校 / 系统打标 | 我们导入 | 待做 | **YAML 文件与数据库的关系**:文件是给人读写的格式,表是运行时副本。现阶段文件是源头;后台做出来之后反过来,表是源头,文件用于交付。 有后台之后(2026-09-17 确认):业务定义在后台维护(表单编辑 / YAML 文本编辑 / 上传导入 / 手册生成草稿),带学校、版本、发布状态;桌面端启动时从服务端拉本校已发布的定义并本地缓存,不再随安装包带文件;仓库里只保留模板(自家学工系统出厂模板、常见第三方系统骨架),给新学校开户时从模板复制进库再改;导出仍产出 YAML,用于备份、迁移和把 A 校审核过的 learned 打包给 B 校。过渡期加一个"知识来源 = 文件 / 服务端"的配置,服务端模式下启动不再用文件覆盖库,只在该校一条业务都没有时用模板初始化一次。 **有手册**:模型读手册生成 YAML 业务草稿(结构化,价值最高);手册切块进向量库,给 Agent 一个 `lookup_manual` 工具按需查"字段是什么意思、有什么前提"。 **没手册**:上线前按角色做一次建图扫描(枚举菜单、页面、表单字段),生成业务草稿;使用中靠 learned 沉淀;交付前预热多跑几遍,把慢的阶段藏起来。 ## 6. 执行模型 ``` 用户一句话 → nlu 识别业务、抽字段(今天/明天换算成日期) → 缺必填项:对话追问(模型生成追问语),用户下一句再抽取合并 → 角色准入:业务 role 与用户在该系统密码本账号的 role 不一致 → 直接告知办不了 → 浏览器 profile 按 人 × 系统 × 角色;任务涉及各系统的凭据按域名分组进 sensitive_data(占位符带系统后缀),没有账号的系统按 YAML fallback(测试环境快捷登录 / 请用户手动) → 业务的全部启用步骤按顺序在同一个浏览器会话里执行,前一步产物注入下一步 → 执行业务的启用步骤:prompt = 业务 + 目标 + 资料 + 登录方式 + 规则 + learned(YAML 基线 + 库里 verified)+ 步骤要求 → Agent 需要用户时调 ask_user:以 is_done 结束本轮,浏览器保活,状态 paused;用户回答后 add_new_task 续跑同一 Agent → 遇到系统提示「必须先办另一件事」时调 report_blocker:按 YAML 声明的 resolution 处理。self = 问用户要不要顺带办,同意则同一会话切去办前置业务再回来;other_role / manual = 说明要找谁怎么办,用户说已处理就重试,说取消就进 blocked(可稍后继续) → 提交类步骤:填完 ask_user 逐项确认 → 确认后提交,保存 submitted_record / result_message → 状态 submitted → 取消则保存 cancelled → 状态 completed(不算提交) → 成功后 learning.draft_learned 生成草稿 → submitted 任务按 next_check_at 由调度循环拉起只读回查步骤:approval_status 归类 → completed / rejected / 继续排期 ``` 判定规则:`success_artifacts_any` 覆盖 Agent 自评;`submitted_artifacts_any` 决定是否算提交;`cancelled` 优先。 ## 7. 已定决策(按时间) | 日期 | 决策 | 原因 / 备注 | |---|---|---| | 09-16 | 产品形态像 Codex 桌面端;一定是桌面端 | 浏览器和登录态在本机,隐私好 | | 09-16 | 账号由我们按角色从库里取,自动登录 | 用户不该反复输密码;用 sensitive_data 防泄露 | | 09-16 | 知识由我们维护交付学校;手册是来源 | 学校只管用 | | 09-16 | 模型 Key 先放 .env,不做用户级 Key | 阿里 / DeepSeek Key 由我们持有 | | 09-16 | 学工系统是自家产品,先拿它练 | 快捷登录等便利只在自家系统有,注意别依赖 | | 09-16 | skill / learned 全校受益;数据层用 MySQL(Docker) | 为中心化做准备 | | 09-16 | 提交后跟踪要做,但排在链路走通之后 | 提交不等于办完 | | 09-17 | 演示业务从留校申请换成辅导员查课日志填报 | 测试环境学生端没有留校申请模块 | | 09-17 | 学校之间数据隔离(school_id) | 不是每所学校都用我们的系统 | | 09-17 | 用户端不出现采纳 / 忽略;审核靠自动晋升 + 我们后台 | 办事人不该做知识审核 | | 09-17 | 后台管理和服务端层要做,但桌面端先夯实 | 顺序:真实提交 → 第二业务 → 后台 | | 09-17 | learned 判重按内容不按 key;自动晋升只对控件技巧 / 入口路径类,系统规则类必须人审 | 模型每次起的 key 不同;记错规则代价大 | | 09-17 | 有后台后 YAML 只做模板和导入导出,表是源头,桌面端从服务端拉配置 | 见第 5 节 | | 09-17 | 前置阻断做成标准机制:同角色能办的顺带办(同一会话切换目标再回来),别的角色 / 线下的说明处理方式后 blocked 等待 | 请假被返校核验拦住是真实样例 | | 09-17 | 桌面端登录走学校统一身份认证;业务系统登录配方要支持「统一认证跳转」,允许域名含认证中心域名 | 用户 09-17 确认 | | 09-17 | 密码本只服务未接统一认证的系统:学校配发账号由后台导入,个人账号才需要用户绑定。开发期手动维护密码本是权宜之计。「绑定账号界面」从演示版拿掉,换成登录配方支持 sso(登录态在 profile 里复用,缺时请用户完成一次) | 用户 09-17 指出 | | 09-17 | 前置阻断需要别的角色处理时,是另一个人在自己的端上用自己的账号办(如辅导员办「核实学生返校」),不是同一用户切角色;两端联动(协办请求 / 完成通知 / 自动续跑)走服务端,现阶段学生手动点「继续」 | 纠正此前「跨角色闭环」的说法 | | 09-17 | 目标系统自身的问题(如测试数据、缺功能)不纠结、不绕过:如实报告并 blocked,等系统方处理。我们会面对各种系统,链路跑通优先 | 学生请假被后端规则拦住是首例 | | 09-17 | 跨人联动(通知辅导员、协办请求、自动续跑)本阶段不做,归服务端层 | 用户 09-17 决定 | | 09-17 | 对照同事 PenguinUniOS 后的四项决定:桌面端按其形态重做并连打包链一起对齐;数据库切 PostgreSQL 17;继续独立仓库;执行路线保持 browser-use run() + learned | 用户 09-17 拍板,见 REFERENCE_PENGUINUNIOS.md | | 09-17 | 身份模型:一个人在一个系统里只有一个身份,跨系统身份可不同。密码本按「人 × 系统」存账号并记录该账号在该系统的角色;业务的 role 是准入条件而不是登录依据;跨系统任务用 browser-use 按域名分组的 sensitive_data,一个会话依次登录各系统 | 用户 09-17 指出;这是跨系统操作的前提 | | 10-28 | 学校工程整体移入 `browser_use/school-assistant/`,与独立获客项目 `browser_use/assistant/` 并列;各自保留独立 Git 仓库,外层只作工作区导航 | 用户要求分开两个项目,避免代码、配置和文档混在工作区根目录 | ## 8. 待做设计(按建议顺序) 1. ~~真实提交验证~~(09-17 完成:两条查课日志真实提交成功,状态 submitted,日期校验无问题)。 2. ~~第二个业务~~(09-17 完成:每周谈心谈话)。~~前置阻断机制~~(09-17 完成:report_blocker + self / other_role 两条路径 + blocked 状态)。学生请假在测试环境被「返校核验」拦住;09-17 以辅导员和系统管理员探索后确认界面上没有任何角色能解开(后端业务规则作用于历史未返校记录),需学工团队清测试数据或补功能。业务 `counselor_return_verify` 已建,等系统侧有入口再补填报步骤;学生请假填报步骤同样等待。 3. ~~防重复提交~~(09-17 完成:两个填报步骤提交前先查已有记录,重复时 ask_user)。 4. ~~learned 自动晋升~~(09-17 完成:按内容判重,≥2 任务观察且为控件技巧 / 入口路径类自动生效,系统规则类人审;OPERATOR_MODE 控制草稿面板)。失败驱动的自动降级待做。 5. **浏览器展开 / 收起**:~~CDP 停靠 + 最小化 / 恢复~~(09-17 完成,暂停确认时自动展开);下一步验证 Electron WebContentsView + cdp_url 真正内嵌。 6. **密码本升级(跨系统地基)**:~~本地部分 09-17 完成~~(role 列、角色准入、按系统分 profile、域名分组凭据)。目标形态是"用户登录桌面端 → 服务端按人返回他有账号的系统及角色、据此过滤可办业务、密码本条目清单(无密码)→ 任务开始时按需取该任务涉及系统的凭据,用完即丢并记审计"。密码本来源两条:学校配发账号由后台批量导入;个人账号由用户在桌面端「绑定账号」一次写入。当前阶段先做本地能做的部分:`system_accounts` 加 `role` 列;业务执行前做角色准入检查(当前用户在该系统的账号角色 ≠ 业务 role 就直接告知办不了);浏览器 profile 按「人 × 系统」而非「人 × 角色」;多系统凭据按域名分组装进 `sensitive_data`,占位符带系统后缀(`x_username_lib` / `x_password_lib`),登录说明按系统分段;每系统登录配方(表单 / 统一认证 / 扫码);生产环境放中心库并经服务端按任务取用、审计日志。 7. ~~多步串行执行器~~(09-17 完成:步骤顺序执行、每步可声明系统、产物跨步传递、同一会话 keep_session、允许域名取并集;跨系统登录待第二个系统接入后实测)。 8. ~~提交后跟踪~~(09-17 完成:YAML tracking、调度循环、track-now / track-stop、状态 rejected、桌面端提示;真实任务实测两次回查)。后续:回查时静默运行。 9. **手册导入 / 建图扫描**:手册 → YAML 草稿;手册 → 向量库 + lookup_manual 工具;无手册时按角色扫描菜单和表单生成草稿。 10. **后台管理 + 服务端层**(审核队列部分 09-17 完成,见 PROGRESS D17):已做——`server/`(FastAPI,独立库 school_assistant_hub)+ `console/`(React 审核台),各校桌面端带凭证上报草稿、中心按内容跨校合并、智能预审(规则层 + 模型层)、人工采纳 / 驳回 / 改写、按「仅来源学校 / 所有学校」发布、桌面端按版本号增量拉回并落成本地 verified / rejected;`backend/hub_sync.py` 全后台异步,没配 HUB_URL 就是单机模式。智能采纳(09-17,PROGRESS D18):类别硬门槛只放行控件技巧 / 入口路径,证据够硬或模型背书两条路,模型建议改写则留人,自动驳回可复活,先干跑再执行。待做——运行监控(成功率 / 步数 / 时长趋势、失败截图)、知识与密码本管理、按学校分组推送、后台多人账号;桌面端统一身份认证登录、按人过滤业务、按需取凭据;**跨人协办**:一个人的任务被别的角色的前置事项拦住时,向对应角色发协办请求,对方办完后通知并自动续跑。 11. ~~UI 往 Codex 形态靠~~(09-17 完成:三栏布局、任务列表与回放、确认 / 阻断 / 结果卡片、我来接管)。后续:嵌入式浏览器面板。 12. **登录配方支持统一认证**:`login.mode: sso | form | quick_login | manual`;允许域名含认证中心;有登录态直接过,没有则请用户完成一次登录后复用 profile 登录态。测试环境无 SSO,先做配置与提示词,接真实学校再验。绑定账号界面推迟到真遇到非 SSO 的个人账号系统。 13. ~~测试集~~(09-17 完成,见 PROGRESS D16):`npm test` 288 个用例 3.4 秒,覆盖 nlu 退路、状态判定、learned 规则与数据库生命周期、角色准入、凭据分组、阻断问句、执行结果判定、Key 兜底。`@pytest.mark.db` 的用例用一次性 `pytest_xxx` 学校隔离,PostgreSQL 不可达时自动跳过。写测试时挖出并修掉两个缺陷:关键词退路要求整段短语原样出现(模型不可用时真实说法全部误判为通用探索)、单字肯定词在句中任意位置命中(补充资料被当成同意先办前置事项)。 14. **桌面端对齐同事的 PenguinUniOS 形态(09-17 完成,见 PROGRESS D13 / D14)**(见 `docs/REFERENCE_PENGUINUNIOS.md`):已做——React/TS/Vite 渲染层、TS 主进程、`WebContentsView` 内嵌浏览器(browser-use 经 CDP 连 Electron 自带 Chromium,界面页走 `app://ui` 协议、主机名 ui 进白名单,不展示的视图停到窗口边缘外留 2px 在窗内而不能隐藏,否则截图挂死)、任务卡 / 确认卡 / 状态词汇对齐、Lucide 与其视觉规范、uv + PyInstaller + electron-builder 打包链(`npm run package:win` → `release/win-unpacked`,后端冻结成 exe 放 resources/backend,userData 放 .env / data / profiles)。待做——NSIS 安装器 / 图标 / 签名、Pydantic 契约生成 TS。已决:数据库已切 PostgreSQL(09-17 完成迁移);仓库独立;打包态只支持内嵌浏览器,不带 Chromium。 ## 9. 实测基线 | 场景 | 配置 | 结果 | |---|---|---| | 学生端只读探索 | 无 learned,3 次 | 1 成功 2 自评失败(产物齐全),31 步 / 181 秒 | | 学生端只读探索 | 修判定 + learned,2 次 | 2/2 成功,23 步 / 99 秒 | | 辅导员查课日志填报到确认 | 首次 | 47 步 / 372 秒 | | 辅导员查课日志填报到确认 | 控件技巧 + 每步 6 动作,干跑 2 次 | 2/2,18 步 / 158 秒 | | 暂停续跑 | 强制先 ask_user | 15 秒暂停,回答后 21 秒完成,同一会话 | | 辅导员谈心谈话填报到确认 | 首次即带 learned,干跑 1 次 | 15 步 / 81 秒;一句话四个必填全抽出,无追问 | | 辅导员谈心谈话填报到确认 | 第二次干跑 | 11 步 / 61 秒 | | 查课日志真实提交 | 用户操作,2 次 | 均「填报成功」,状态 submitted | | 前置阻断 other_role(学生请假) | 真实环境 | 25 秒识别拦截、标准化提问,取消后 blocked | | 前置阻断 self(模拟) | 临时业务 | 切子目标并回到原目标完成,约 35 秒 | | 两步串行(模拟,同系统) | 临时业务 | 54 秒,一个会话,第二步使用第一步产物 | | 提交后跟踪回查 | 真实提交的查课日志 | 立即回查 73 秒读到「未审批」+ 审批轨迹;调度自动回查 77 秒完成并排期 | ## 10. 目录与关键文件 学校项目根目录为 `browser_use/school-assistant/`(2026-10-28 从工作区根目录整体移入,含独立 Git 历史)。同级 `browser_use/assistant/` 为独立 AI 获客项目。以下路径均相对学校项目根目录。 ``` backend/nlu.py 意图识别与字段抽取 backend/orchestrator/orchestrator.py 状态机、路由、prompt、暂停续跑、沉淀触发 backend/browser/runner.py browser-use 执行器、会话保活、每步截图 backend/browser/tools.py ask_user / save_artifact backend/learning.py learned 草稿生成 backend/security.py 密码本加解密(Fernet,ACCOUNT_ENC_KEY) backend/db/schema.sql MySQL 表结构(含 school_id) backend/db/database.py 数据层与旧库迁移 knowledge/xuegong_demo.yaml 出厂业务定义 + 基线 learned scripts/eval_run.py 评测(默认干跑) electron/main.js 后端唯一所有者、旧后端接管 renderer/app.js 三栏 UI:任务列表与回放、对话与卡片、状态 / 截图 / 时间线 / 沉淀草稿;无 Electron 时回退 fetch 便于自测 docs/PROGRESS.md 里程碑与实测 ```