From b309a32a98a9a0220f96ea26e793ecc381221b92 Mon Sep 17 00:00:00 2001 From: Losita <2810873701@qq.com> Date: Sun, 19 Apr 2026 23:54:48 +0800 Subject: [PATCH] =?UTF-8?q?Version:=200.9.33.dev.260419=20=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=EF=BC=9A=201.=20deliver=20=E6=94=B6=E5=8F=A3=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E9=87=8D=E6=9E=84=E2=80=94=E2=80=94=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E6=8A=98=E5=8F=A0=E5=88=B0=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=EF=BC=8C=E4=BB=85=E5=9F=BA=E4=BA=8E=E6=9C=AC=E8=BD=AE=20execut?= =?UTF-8?q?e=20=E7=AA=97=E5=8F=A3=E8=AF=9A=E5=AE=9E=E6=94=B6=E5=8F=A3=20?= =?UTF-8?q?=20=20-=20newAgent/prompt/deliver.go=EF=BC=9ABuildDeliverMessag?= =?UTF-8?q?es=20=E6=94=B9=E4=B8=BA=E5=90=91=20buildDeliverWorkspace=20?= =?UTF-8?q?=E9=80=8F=E4=BC=A0=20ConversationContext=20=20=20-=20newAgent/p?= =?UTF-8?q?rompt/deliver=5Fcontext.go=EF=BC=9Adeliver=20=E7=9A=84=20msg1?= =?UTF-8?q?=20=E6=94=B9=E4=B8=BA=E8=BD=BB=E9=87=8F=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=9B=9E=E7=81=8C=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=EF=BC=9Bmsg2=20=E8=BF=BD=E5=8A=A0=E6=9C=AC?= =?UTF-8?q?=E8=BD=AE=20execute=20=E7=AA=97=E5=8F=A3=E4=B8=8E=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E6=80=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: 2. 品牌命名统一切换为 SmartMate - index.html:页面标题从 SmartFlow 改为 SmartMate - package.json:前端包名改为 smartmate-frontend - App.vue:布局类名从 smartflow-* 统一改为 smartmate-* - stores/auth.ts:access/refresh token 与 last username 的 localStorage key 全部切到 smartmate_* - utils/idempotency.ts:默认幂等键前缀从 smartflow 改为 smartmate - DashboardView.vue:首页默认问候名从 SmartFlow 用户改为 SmartMate 用户 3. 助手页体验重做——默认空会话、排程卡片懒加载、上下文统计刷新时机收口 - components/dashboard/AssistantPanel.vue:进入页面不再自动打开最后一次会话,改为展示居中欢迎空态 - components/dashboard/AssistantPanel.vue:schedule_completed 改为先展示占位卡片,点击后再拉取 schedule preview,避免预览未落库时并发 404 - components/dashboard/AssistantPanel.vue:tool done、schedule card、SSE block done、[DONE] 与整轮流结束后统一刷新 context stats - components/dashboard/AssistantPanel.vue:重构聊天区布局、空态欢迎内容、底部交互区与内外边距,整体视觉切到更轻的阅读式界面 - views/AssistantView.vue:移除外层白底卡片壳,交由 AssistantPanel 自己承接容器视觉 4. 排程微调保存链路补幂等保护,并修正请求头口径 - api/schedule_agent.ts:正式应用接口请求头从 Idempotency-Key 改为 X-Idempotency-Key - components/assistant/ScheduleFineTuneModal.vue:同一预览会话复用稳定幂等键,保存成功后再刷新新 key,避免重试或延迟导致重复落库 - components/assistant/ScheduleResultCard.vue:结果卡片样式、hover 与进场动效整体升级 5. 任务类选择器与侧边导航细节调整 - components/assistant/TaskClassPlanningPicker.vue:popover、骨架屏、列表项、选中态与按钮视觉整体重绘 - components/common/MainSidebar.vue:移除“任务”占位入口,侧栏只保留总览 / 日程 / 助手 6. 登录页与首页展示风格重做 - views/AuthView.vue:品牌文案切到 SmartMate,登录/注册从 tabs 改为自定义双态切换,重做背景、玻璃卡片、表单与动效 - views/DashboardView.vue:首页主区改为 auto + 1fr 布局,锁定顶部栏高度,避免缩放时形变 仓库: 7. README 全量更新到当前版本能力边界 - README.md:重写项目定位、功能描述、业务闭环图、newAgent graph 流程、工具定义、前端衔接边界、页面展示、部署方案与监控说明 --- README.md | 722 +++++++++++------- backend/newAgent/prompt/deliver.go | 10 +- backend/newAgent/prompt/deliver_context.go | 24 +- backend/newAgent/prompt/deliver_window.go | 103 +++ docs/pics/AI-工具调用中.png | Bin 0 -> 316589 bytes docs/pics/AI-开启新对话.png | Bin 0 -> 284796 bytes docs/pics/AI-日程微调页面.png | Bin 0 -> 527007 bytes docs/pics/主页.png | Bin 0 -> 191939 bytes docs/pics/登录页.png | Bin 0 -> 2146525 bytes docs/pics/课程表页面.png | Bin 0 -> 301354 bytes frontend/index.html | 2 +- frontend/package.json | 2 +- frontend/src/App.vue | 8 +- frontend/src/api/schedule_agent.ts | 2 +- .../assistant/ScheduleFineTuneModal.vue | 10 +- .../assistant/ScheduleResultCard.vue | 61 +- .../assistant/TaskClassPlanningPicker.vue | 144 ++-- .../src/components/common/MainSidebar.vue | 1 - .../components/dashboard/AssistantPanel.vue | 281 +++++-- frontend/src/stores/auth.ts | 6 +- frontend/src/utils/idempotency.ts | 2 +- frontend/src/views/AssistantView.vue | 4 - frontend/src/views/AuthView.vue | 336 +++++--- frontend/src/views/DashboardView.vue | 7 +- 24 files changed, 1193 insertions(+), 532 deletions(-) create mode 100644 backend/newAgent/prompt/deliver_window.go create mode 100644 docs/pics/AI-工具调用中.png create mode 100644 docs/pics/AI-开启新对话.png create mode 100644 docs/pics/AI-日程微调页面.png create mode 100644 docs/pics/主页.png create mode 100644 docs/pics/登录页.png create mode 100644 docs/pics/课程表页面.png diff --git a/README.md b/README.md index 3ab77cf..4b2e59d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ > **问题3:** 那么我作为一个规划能力比较差的懒人,也能用这个项目来让自己变的充实吗? -**本项目带来的解决方案3:** 当然可以,这就是本项目接入AI的意义。聊天区域的AI将会被调教成一个日程安排的小助手,既能满足你简单的对日程的增删改查,又能协助你从0开始一点点制定属于你的计划。(**第一批次开发计划**只支持AI随口记这一"增"的功能,以及大多数能想到的"查"功能,暂时无让AI改和删的想法) +**本项目带来的解决方案3:** 当然可以,这就是本项目接入AI的意义。聊天区域的AI将会被调教成一个日程安排的小助手,既能满足你简单的任务记录、任务查询与部分日程调整需求,又能协助你从0开始一点点制定属于你的计划。(当前版本已支持AI随口记、任务查询,以及部分日程调整的确认流;更完整的自动规划与更复杂的读写能力仍在继续迭代) > **问题4:** 我平时会突然冒出来一个能让自己活的更舒服亦或是变得更好的小想法(例如把桌面理一下、给自己挑一件新衣服等),但是现在很忙,根本没时间做,然后等忙完了有时间了又忘记了。传统的日程软件确实能让我记录下来(比如将这个小想法记录在日程软件的四象限里面的"不重要不紧急"象限),就是太麻烦了。 > @@ -58,31 +58,31 @@ 1. **对用户目前时间尺度的适应。** - 如果用户是正在上学的大学生,时间尺度可以设置为以学校排课为主,通用时间为辅(以第1-2节,第3-4节这种的学校排课的时间方式为主,又能兼容某个特定时间的突发小事,做到对总体执行效率和事情安排效率的兼顾); + 当前版本以学校排课节次为主,采用第1-2节、第3-4节这种时间组织方式,同时兼容首页任务管理与周课表日程编排两套视图,方便把任务管理和日程安排放在同一系统中; - 如果用户既想要自定义时间,又想要一键编排任务,本项目还支持用户自定义时间尺度,例如设置9:00-11:00为第一节课等。 + 目前暂未开放用户自定义时间尺度配置,当前仍以固定节次模型为主。后续会有更新计划的! -2. **导入学校课表。** 如果用户选择以学校排课为主的时间尺度,本项目支持快速导入学校课表(只会尝试兼容CQUPT的课表格式),以便后续以课表为基底的日程安排。 +2. **导入学校课表。** 本项目后端已提供学校课表导入能力(当前主要尝试兼容CQUPT的课表格式),以便后续以课表为基底进行日程安排;前端完整导入流程入口仍在补齐。 -3. **"水课"任务嵌入。** 正如上方**问题2**所言,在导入课表后,支持设置某一门你想拿来干其它事情的课为"可嵌入任务"状态,此时这门课所占据的时间区域就是可以嵌入任务的了,但是仍然有区别于其它完全空白的时间区域,便于真正安排适合在嘈杂环境下做的事情。 +3. **"水课"任务嵌入。** 正如上方**问题2**所言,在已导入课表的前提下,支持设置某一门你想拿来干其它事情的课为"可嵌入任务"状态,此时这门课所占据的时间区域就是可以嵌入任务的了,但是仍然有区别于其它完全空白的时间区域,便于真正安排适合在嘈杂环境下做的事情。 -4. **设置某一任务类,并提前安排其执行路线。** 正如上方**问题1**所言,用户可以先设置一个大的任务类(例如概率论复习、算法进阶计划等等),再在这个任务类下方安排其在对应时间尺度下的执行计划(例如第1-2节干啥,第3-4节干啥),方便后续的日程编排。 +4. **设置某一任务类,并配置其编排参数。** 正如上方**问题1**所言,用户可以先设置一个大的任务类(例如概率论复习、算法进阶计划等等),再为这个任务类配置任务块内容、起止日期、总节数、编排策略等信息,方便后续的日程编排。 -5. **一键编排任务。** 结合算法、用户偏好以及AI的建议,将任务基于上方的时间尺度、导入的课表,排进日程中,并给出这样排的理由(如果动用了AI)。 +5. **一键编排任务。** 结合算法与用户配置,将任务基于导入的课表和任务类设置先生成预览结果;确认无误后,再正式应用到日程中。 -6. **AI随口记。** 正如问题4所言,就是支持通过AI随手记录一些大小事。 +6. **AI随口记与任务查询。** 正如问题4所言,当前版本支持通过AI随手记录一些大小事,也支持按象限、关键词、截止时间等维度查询任务;部分日程调整能力已接入确认流。 7. **多用户。** 本系统可支持多个用户同时使用,并且记录AI对话、编排任务的Token使用情况等,并进行限额。 -8. **动态任务和静态任务。** **动态任务**包括学校的课和排入日程中的任务类,这些任务随着时间往后会默认已经完成,无需手动勾选; +8. **四象限任务与日程编排并行管理。** 首页的四象限任务用于日常待办管理; - 而**静态任务**为四个任务队列中的任务,这些任务需要手动勾选为完成状态。 + 而周课表中的课程与排入日程的任务类则用于日程编排,两类信息分别展示、相互配合。 -9. **完成任务状态的撤回。** 无论是因为哪种情况,是误触给队列里面的任务打钩,还是水课翘课了被叫回去点名导致任务中断,都支持**撤回**这个"任务已完成"的状态。 +9. **完成任务状态的恢复。** 当前首页四象限任务支持将"已完成"恢复为"未完成"状态。 - 前者,用户只需要在队列的下沉列表中找到该任务然后点击一下灰色的勾即可(模仿了滴答清单的设计)。 + 用户只需要在队列中找到该任务,然后再次点击对应状态按钮即可完成恢复。 - 至于后者,由于后者为动态任务,所以用户需要手动去"最近已完成任务"的清单里面选择该任务然后恢复,此时任务会自动回到未安排状态。目前暂不支持课程的撤回,课程方面的改动目前仅支持删除,其它操作后续考虑开发。 + 至于日程侧,当前主要支持删除、解除安排与预览后再正式应用,其它更完整的恢复能力仍在后续迭代中。 10. **长期记忆积累。** 系统在对话中自动抽取用户相关事实与偏好,跨会话召回并注入对话上下文,实现"越用越懂你"的个性化体验。支持结构化检索 + 向量召回双路召回,全链路优雅降级。 @@ -90,23 +90,65 @@ ## 2.1 业务流程图 +当前版本主业务闭环如下,重点体现“首页任务管理 / AI 助手 / 日程编排 / 长期记忆”四条主线如何互相联动: +```mermaid +flowchart TD + A["用户进入系统"] --> B{"是否已登录"} + B -- "否" --> C["/auth 登录 / 注册"] + C --> D["进入主工作区"] + B -- "是" --> D -## 2.2 原型展示 + D --> E{"选择功能入口"} -![登录页](./docs/design/Pics/登录页.png) + E -- "首页 /dashboard" --> F["查看四象限任务 + 今日日程"] + F --> G["创建任务 / 完成任务 / 恢复任务"] + G --> H["首页持续展示最新待办状态"] -![平台首页_已登录](./docs/design/Pics/平台首页_已登录.png) + E -- "AI 助手 /assistant" --> I["输入自然语言需求"] + I --> J["newAgent 统一 graph"] + J --> K{"路由结果"} + K -- "随口记 / 任务查询" --> L["写入任务或返回查询结果"] + K -- "智能编排 / 日程调整" --> M["plan / confirm / execute / deliver"] + K -- "普通问答" --> N["直接回复用户"] -![日程查看&安排中心 多选后](./docs/design/Pics/日程查看&安排中心_多选后.png) + M --> O["返回排程结果卡片"] + O --> P["查看结构化预览并继续微调"] + P --> Q{"如何收口"} + Q -- "暂存" --> R["保存到 Redis 运行态"] + Q -- "正式应用" --> S["写入正式课表"] -![日程查看&安排中心 展开数据结构并排进去一个任务后](./docs/design/Pics/日程查看&安排中心_展开数据结构并排进去一个任务后.png) + L --> T["更新任务列表 / 对话历史"] + N --> U["沉淀对话历史"] -![日程查看&安排中心](./docs/design/Pics/日程查看&安排中心.png) + E -- "日程中心 /schedule" --> V["查看周课表与任务类"] + V --> W["新建任务类 / 删除任务块 / 单选或批量多选"] + W --> X["智能粗排 / 批量粗排"] + X --> Y["前端预览态拖拽调整"] + Y --> Z["正式应用到日程"] -![用户设置&杂项](./docs/design/Pics/用户设置&杂项.png) + S --> AA["周课表更新"] + Z --> AA + H --> AB["用户继续使用系统"] + T --> AB + U --> AB + AA --> AB -![注册页](./docs/design/Pics/注册页.png) + AB --> AC["异步事件:聊天持久化 / Outbox / Memory 抽取"] + AC --> AD["长期记忆写入与更新"] + AD --> AE["下一轮对话自动召回 memory_context"] + AE --> I +``` + +## 2.2 页面展示 + +![登录页](D:\SmartFlow-Agent\docs\pics\登录页.png) + +![平台首页_已登录](./docs/pics/主页.png) + +![课程表页面](D:\SmartFlow-Agent\docs\pics\课程表页面.png) + +![AI-工具调用中](D:\SmartFlow-Agent\docs\pics\AI-工具调用中.png) # 3 后端数据架构 @@ -278,6 +320,51 @@ CREATE TABLE `users` ## 4.2 Agent可调用的工具定义 +以下定义基于当前代码实现(`backend/newAgent/tools/registry.go` + `backend/cmd/start.go` 注入),不是规划态文档。 + +### 4.2.1 调用契约 + +1. `tool_call` 必须是单个对象,格式为: + +```json +{"tool_call":{"name":"工具名","arguments":{}}} +``` + +2. 日程写工具默认走 `confirm` 确认闸门(`always_execute=true` 时可跳过确认)。 +3. 非日程写工具(如 `quick_note_create`、`query_tasks`、`web_search`、`web_fetch`)走 `continue + tool_call`。 +4. 当前每轮只允许调用一个工具,不支持同轮批量工具数组。 + +### 4.2.2 工具清单(当前版本) + +| 工具名 | 类型 | 是否需确认 | 是否依赖 ScheduleState | 核心参数 | 作用与约束 | +| --- | --- | --- | --- | --- | --- | +| `get_overview` | 读 | 否 | 是 | 无 | 获取规划窗口总览(任务视角,全量返回) | +| `query_range` | 读 | 否 | 是 | `day`(必填), `slot_start`, `slot_end` | 查询某天/某时段占用详情 | +| `query_available_slots` | 读 | 否 | 是 | `span`, `duration`, `limit`, `day_scope`, `week_filter` 等 | 查询候选空位池(纯空位优先,不足再补可嵌入位) | +| `query_target_tasks` | 读 | 否 | 是 | `status`, `category`, `task_ids`, `enqueue` 等 | 过滤任务集合,可选自动入队供后续队列工具处理 | +| `queue_pop_head` | 读 | 否 | 是 | 无 | 取出/复用当前队首任务(一次只处理一个) | +| `queue_status` | 读 | 否 | 是 | 无 | 查看队列状态(pending/current/completed/skipped) | +| `get_task_info` | 读 | 否 | 是 | `task_id`(必填) | 查询单任务详细信息 | +| `place` | 写 | 是 | 是 | `task_id`, `day`, `slot_start`(均必填) | 将待安排任务预排到指定位置 | +| `move` | 写 | 是 | 是 | `task_id`, `new_day`, `new_slot_start`(均必填) | 仅允许移动 `suggested`;`existing` 不可 `move` | +| `swap` | 写 | 是 | 是 | `task_a`, `task_b`(均必填) | 交换两个已落位任务,要求时长一致 | +| `batch_move` | 写 | 是 | 是 | `moves[]`(必填) | 原子批量移动,当前最多 2 条,任一冲突整批回滚 | +| `queue_apply_head_move` | 写 | 是 | 是 | `new_day`, `new_slot_start`(均必填) | 移动当前队首并自动出队,不接受 `task_id` | +| `queue_skip_head` | 队列控制 | 否 | 是 | `reason` | 跳过当前队首并标记 `skipped`(不改日程) | +| `spread_even` | 写 | 是 | 是 | `task_ids`(必填,兼容 `task_id`) | 在任务集合内做均匀铺开,按筛选条件原子落地 | +| `min_context_switch` | 写 | 是 | 是 | `task_ids`(必填,兼容 `task_id`) | 减少上下文切换重排;仅在用户明确允许打乱顺序时可执行 | +| `unplace` | 写 | 是 | 是 | `task_id`(必填) | 取消任务落位并恢复待安排状态 | +| `quick_note_create` | 读写混合(业务写入) | 否 | 否 | `title`(必填), `deadline_at`, `priority_group` | 记录随口记任务,支持中文相对时间;优先级可自动推断 | +| `query_tasks` | 读 | 否 | 否 | `quadrant`, `keyword`, `deadline_before/after`, `limit` 等 | 按象限/关键词/时间边界查询任务 | +| `web_search` | 读 | 否 | 否 | `query`(必填), `top_k`, `domain_allow`, `recency_days` | Web 检索,返回结构化标题/摘要/URL;未启用时优雅返回错误 observation | +| `web_fetch` | 读 | 否 | 否 | `url`(必填), `max_chars` | 抓取并清洗网页正文;服务不可用时优雅返回错误 observation | + +### 4.2.3 当前实现中的关键规则 + +1. `min_context_switch` 有顺序护栏:未授权“允许打乱顺序”会被后端拦截并返回拒绝结果。 +2. `batch_move` 有安全上限:当前最多支持 2 条移动请求,超出建议走队列化逐项处理。 +3. `quick_note_create` 和 `query_tasks` 不依赖 `ScheduleState`,由执行层注入 `_user_id` 后可直接调用。 +4. `web_search`/`web_fetch` 失败不会打断主链路,都会回传结构化错误 observation 给模型继续决策。 # 5 后端实现 @@ -368,193 +455,170 @@ $$Gap = \frac{TotalAvailableSlots - (TaskCount \times 2)}{TaskCount + 1}$$ ## 5.4 Agent范式实现细节 -### 1) 总分流图(消息识别后的去向) +### 1) 统一入口与 graph 主链 ```mermaid flowchart TD - A["/api/v1/agent/chat
解析请求体 + 规范 conversation_id
Header 写入 X-Conversation-ID"] --> B["AgentService.AgentChat
创建 outChan / errChan"] - B --> C["规范 chat_id + 选择模型(worker/strategist)"] - C --> D["确保会话存在
先查 Redis 状态
未命中回源 DB + 必要时创建"] - D --> E["模型控制码路由
route.DecideActionRouting
action=chat/quick_note_create/task_query/schedule_plan_create/schedule_plan_refine"] - E --> F{"RouteFailed?"} + A["/api/v1/agent/chat
接收 user_message / thinkingMode / extra"] --> B["AgentService.runNewAgentGraph"] + B --> C["确保会话存在
加载或创建 RuntimeState"] + C --> D["恢复或构建 ConversationContext
并预取 memory pinned block"] + D --> E["先持久化本轮 user message"] + E --> F["组装 AgentGraphRunInput
Chat/Deliver 用 Pro
Plan/Execute 用 Max"] + F --> G["RunAgentGraph"] + G --> H["START -> chat"] - F -- "是" --> G["pushErrNonBlocking(errChan, RouteControlInternalError)
API 侧 SSE 输出 error + [DONE]"] - F -- "否" --> H{"action 类型"} + H --> I{"chat 决定下一步"} + I -- "简单回复 / 深答" --> J["chat 节点内直接完成"] + I -- "复杂规划" --> K["plan"] + I -- "直接执行" --> L["execute"] + I -- "execute 前需要粗排" --> M["rough_build"] + I -- "已有 pending interaction" --> N["chat 先做 resume"] - H -- "chat" --> I["runNormalChatFlow
Redis 取历史 -> miss 回源 DB + 回填
裁剪上下文窗口 -> StreamChat 流式输出"] - I --> I2["后置持久化收口
user/assistant 先写 Redis
再 PersistChatHistory(outbox 或同步DB)
异步尝试生成标题"] - - H -- "quick_note_create" --> J["发阶段块 request.accepted
tryHandleQuickNoteWithGraph"] - J --> J1{"graph 出错?"} - J1 -- "是" --> J2["记录日志 + 发 fallback 阶段块
回退 runNormalChatFlow"] - J1 -- "否" --> J3{"handled=true?"} - J3 -- "否" --> J2 - J3 -- "是" --> J4["buildQuickNoteFinalReply
emitSingleAssistantCompletion"] - J4 --> J5["persistChatAfterReply
统一后置持久化 + 异步标题"] - - H -- "task_query" --> K["runTaskQueryFlow -> TaskQueryGraph
plan/quadrant/time_anchor/tool_query/reflect"] - K --> K1{"查询链路报错?"} - K1 -- "是" --> K2["记录日志 + 发 fallback 阶段块
回退 runNormalChatFlow"] - K1 -- "否" --> K3["emitSingleAssistantCompletion
persistChatAfterReply + 异步标题"] - - H -- "schedule_plan_create" --> L["runSchedulePlanFlow -> SchedulePlanGraph
并写入排程预览缓存"] - L --> L1{"排程链路报错?"} - L1 -- "是" --> L2["记录日志 + 发 fallback 阶段块
回退 runNormalChatFlow"] - L1 -- "否" --> L3["emitSingleAssistantCompletion
persistChatAfterReply + 异步标题"] - - H -- "schedule_plan_refine" --> M["runScheduleRefineFlow -> ScheduleRefineGraph
读取上一版排程预览上下文"] - M --> M1{"连续微调链路报错?"} - M1 -- "是" --> M2["直接上报错误
不回退普通聊天"] - M1 -- "否" --> M3["emitSingleAssistantCompletion
persistChatAfterReply + 异步标题"] - - H -- "未知 action" --> N["兜底回退 runNormalChatFlow"] - - I2 --> Z["API c.Stream 转发 outChan/errChan
正常收尾或错误收尾"] - J2 --> Z - J5 --> Z - K2 --> Z - K3 --> Z - L2 --> Z - L3 --> Z - M2 --> Z - M3 --> Z - N --> Z - G --> Z -``` - -### 2) 命中“添加日程/随口记”后的业务流转 - -```mermaid -flowchart TD - A[用户消息进入 /agent/chat] --> B[规范会话ID + 选模型] - B --> C[确保会话存在
Redis会话状态检查
必要时回源DB创建] - C --> D[模型控制码路由
action=quick_note/chat] - D --> E{route是否命中quick_note} - E -- 否 --> X[普通聊天链路
StreamChat流式输出
或者其它分支] - E -- 是 --> F[quick_note.request.accepted
推送reasoning状态块] - F --> G[跳过二次意图判定
直接进入聚合规划] - G --> H[单请求聚合规划
生成title/deadline/priority/banter] - H --> I[时间校验
quick_note.deadline.validating] - I --> J{时间是否有效} - J -- 否 --> K[返回纠错文案
不写库
quick_note.failed] - J -- 是 --> L{优先级是否有效} - L -- 是 --> M[复用聚合优先级] - L -- 否 --> N[本地优先级兜底
不再二次调用模型] - M --> O[调用写库工具
quick_note.persisting] + K --> O["confirm / rough_build / execute / deliver / interrupt"] + L --> P["execute / confirm / order_guard / deliver / interrupt"] + M --> Q["execute / order_guard / deliver / interrupt"] N --> O - O --> P{task_id是否有效} - P -- 否 --> Q[按重试策略处理
最终返回失败文案] - P -- 是 --> R[quick_note.persisted] - R --> S[拼接最终正文
优先复用聚合banter
一次性content输出] - S --> T[后置持久化
user+assistant写Redis
并写outbox/DB] - X --> T + N --> P + + J --> R["deliver 或 END"] + O --> S["deliver"] + P --> S + Q --> S + S --> T["END"] ``` -### 3) 命中“随口问/任务查询”后的业务流转 +### 2) `chat` 节点路由与恢复 ```mermaid flowchart TD - A["用户消息进入 /agent/chat"] --> B["通用控制码分流
action=chat/quick_note_create/task_query/schedule_plan_create/schedule_plan_refine"] - B --> C{"action 是否为 task_query"} - C -- 否 --> D["走其它分支
普通聊天或随口记"] - C -- 是 --> E["进入 TaskQueryGraph"] - E --> F["节点1: plan
一次模型调用产出查询计划"] - F --> G["节点2: quadrant
归一化象限范围"] - G --> H["节点3: time_anchor
锁定时间过滤边界"] - H --> I["节点4: tool_query
调用 query_tasks 工具查询"] - I --> J{"首次结果是否为空"} - J -- 是 --> K["自动放宽一次
仅放宽关键词/完成状态/时间边界"] - K --> L["再次调用 query_tasks"] - J -- 否 --> M["进入反思节点"] + A["进入 chat 节点"] --> B{"存在 pending interaction?"} + + B -- "是" --> C["handleChatResume
不再调路由 LLM"] + C --> D{"pending 类型"} + D -- "ask_user" --> E["ResumeFromPending
发 resumed 状态"] + E --> F["恢复原 phase
继续 plan 或 execute"] + D -- "confirm + accept" --> G["恢复 PendingTool
Phase=executing"] + D -- "confirm + reject(计划)" --> H["RejectPlan
回 planning"] + D -- "confirm + reject(工具)" --> I["回 executing 改策略"] + D -- "interaction_id 不匹配" --> J["stale_resume
本轮结束"] + + B -- "否" --> K{"上一轮是否 completed?"} + K -- "是" --> L["写 execute_loop_closed marker
ResetForNextRun"] + K -- "否" --> M["直接构建路由消息"] L --> M - M --> N["节点5: reflect
模型判断结果是否满足用户诉求"] - N --> O{"need_retry 且未超上限"} - O -- 是 --> P["应用 retry_patch
重试次数+1"] - P --> I - O -- 否 --> Q["后端确定性渲染最终回复
严格按 limit 输出条数"] - Q --> R["后置持久化
user+assistant 写 Redis + outbox/DB"] + M --> N["一次快速路由 LLM
BuildChatRoutingMessages"] + N --> O{"route"} + + O -- "direct_reply" --> P["chat 内直接流式回复
Phase=chatting -> END"] + O -- "deep_answer" --> Q["二次 deep answer 调用
可开启 thinking -> END"] + O -- "plan" --> R["Phase=planning"] + O -- "execute" --> S["StartDirectExecute
写 AllowReorder / ExecuteThinking"] + + S --> T{"NeedsRoughBuild?"} + T -- "是" --> U["rough_build"] + T -- "否" --> V["execute"] + R --> W["plan"] ``` -### 4) 命中新建“智能排程”后的业务流转图 +### 3) `plan / confirm / rough_build` 链路 ```mermaid flowchart TD - A["命中 action=schedule_plan_create
发 request.accepted 阶段块"] --> B["runSchedulePlanFlow 入口"] - B --> B1{"依赖齐全?
model + 3个函数注入"} - B1 -- "否" --> B2["返回 error 给上层
上层回退普通聊天"] - B1 -- "是" --> C["清理旧预览缓存
DeleteSchedulePlanPreview
失败仅记日志"] - C --> D["加载对话历史
Redis 优先 -> miss 回源 DB
失败降级为空历史继续"] - D --> E["RunSchedulePlanGraph
注入并发度与预算配置"] + A["Phase=planning -> plan 节点"] --> B["LLM 生成 PlanDecision
可开启 thinking"] + B --> C{"action"} - E --> P1["plan 节点
合并 extra.task_class_ids + 模型提取约束/策略/标签
模型失败时可用 extra 兜底"] - P1 --> P1B{"FinalSummary 非空
或 task_class_ids 为空?"} - P1B -- "是" --> PX["exit 节点 -> END
直接返回已有失败文案"] - P1B -- "否" --> P2["rough_build 节点
HybridScheduleWithPlanMulti 构建 HybridEntries
可选解析全局窗口(起止周/天)"] + C -- "continue" --> A + C -- "ask_user" --> D["OpenAskUserInteraction
graph -> interrupt"] + C -- "done" --> E["FinishPlan
写 pinned blocks:
current_plan / current_step"] - P2 --> P2B{"HybridEntries 为空
或构建失败?"} - P2B -- "是" --> PX - P2B -- "否" --> P3{"len(task_class_ids) >= 2 ?"} + E --> F{"AlwaysExecute?"} + F -- "否" --> G["confirm"] + G --> H["EmitConfirmRequest
OpenConfirmInteraction(type=plan)"] + H --> I["interrupt 等用户"] + I --> J["用户下一轮回复 -> chat resume"] + J --> K{"accept / reject"} + K -- "accept" --> L{"NeedsRoughBuild?"} + K -- "reject" --> A - P3 -- "是" --> P4["daily_split
按周天拆 DayGroup + 注入 ContextTag
suggested<=2 标记 SkipRefine"] - P4 --> P5["daily_refine(并发)
按天并发 ReAct
单天失败回退原天结果"] - P5 --> P6["merge
合并 DailyResults
冲突则整体回退 merge 前快照"] - P6 --> P7["weekly_refine(并发按周)
有效周保底预算 + 负载加权分配"] + F -- "是" --> M["自动展示计划摘要
ConfirmPlan -> PhaseExecuting"] + M --> L - P3 -- "否" --> P7 - P7 --> P7A["单周 worker 循环
每轮只允许 1 个 Move/Swap 或 done
总预算(成功/失败都扣) + 有效预算(仅成功扣)
Move 受本周与全局窗口硬约束"] - - P7A --> P8["final_check
physicsCheck(冲突/节次越界/数量核对)
失败回退 MergeSnapshot
再生成自然语言总结"] - P8 --> P9["return_preview
回填 AllocatedItems 嵌入时间
生成 CandidatePlans + FinalSummary + Completed"] - P9 --> F1["saveSchedulePlanPreview
写 Redis 结构化快照
失败仅记日志"] - F1 --> F2["返回 FinalSummary 给 AgentChat"] - - F2 --> G1["emitSingleAssistantCompletion
SSE 输出终审文本"] - G1 --> G2["persistChatAfterReply
user/assistant 写 Redis + outbox/DB"] - G2 --> G3["ensureConversationTitleAsync"] - - F1 --> H1["结构化通道
GET /api/v1/agent/schedule-preview?conversation_id=..."] - H1 --> H2["GetSchedulePlanPreview
按 user_id + conversation_id 读 Redis 快照
未命中返回业务错误码"] - - B2 --> Z["上层发 fallback 阶段块
回退 runNormalChatFlow"] - PX --> F1 - G3 --> Z - H2 --> Z + L -- "否" --> N["execute"] + L -- "是" --> O["rough_build"] + O --> P["调用 RoughBuildFunc
写回 ScheduleState"] + P --> Q["写 rough_build_done pinned block
标记 HasScheduleChanges"] + Q --> R{"仍有真实 pending?"} + R -- "是" --> S["Abort -> deliver"] + R -- "否" --> T{"需要继续微调?"} + T -- "是" --> N + T -- "否" --> U["Done -> deliver"] ``` -### 5) 命中“排程连续微调”后的业务流转图 +### 4) `execute / confirm / order_guard` 链路 ```mermaid flowchart TD - A["命中 action=schedule_plan_refine
发 request.accepted 阶段块"] --> B["runScheduleRefineFlow 入口"] - B --> C{"selectedModel 非空?"} - C -- "否" --> C1["直接返回错误
不回退普通聊天"] - C -- "是" --> D["loadSchedulePreviewContext
Redis 预览优先 -> miss 回源 MySQL 快照"] - D --> E{"上一版预览存在?"} - E -- "否" --> E1["返回 SchedulePlanPreviewNotFound
直接上报错误"] - E -- "是" --> F["NewScheduleRefineState
注入 HybridEntries / AllocatedItems / CandidatePlans / OriginOrderMap"] - F --> G["RunScheduleRefineGraph"] + A["Phase=executing -> execute 节点"] --> U{"NextRound 成功?"} + U -- "否" --> V["Exhaust -> deliver"] + U -- "是" --> B{"有 PendingConfirmTool?"} + B -- "是" --> C["直接执行已确认写工具"] + B -- "否" --> D["BuildExecuteMessages
LLM 产出 ExecuteDecision"] - G --> H["contract
抽取 intent / strategy / hard_assertions
默认 keep_relative_order=true"] - H --> I["plan
生成 3~4 步执行计划
必要时注入复合工具硬条件"] - I --> J["slice
提取 week/source_days/target_days
编译 objective + workset"] - J --> K["route
命中 SpreadEven / MinContextSwitch 时先走复合路由
首次 + 最多2次重试"] - K --> K1{"CompositeRouteSucceeded?"} - K1 -- "是" --> L["react
检测到已收口,直接 skip"] - K1 -- "否" --> M["react
单任务微步循环
失败后禁复合,只用基础工具"] - L --> N["hard_check
先锁定业务目标
再按需顺序归位 / 一次修复"] - M --> N - N --> O["summary
回填 AllocatedItems + CandidatePlans
Completed 仅由终审是否通过决定"] + C --> E["写 observation / 更新 ScheduleState"] + D --> F{"action"} - O --> P{"shouldPersistScheduleRefinePreview?"} - P -- "是" --> Q["saveSchedulePlanPreview
覆盖 Redis + MySQL 快照"] - P -- "否" --> R["emit schedule_refine.preview.skipped
保留上一版预览基线"] - Q --> S["emitSingleAssistantCompletion
输出 FinalSummary"] + F -- "continue + tool_call" --> G["每轮只调用 1 个工具"] + G --> H{"写工具且需要确认?"} + H -- "是" --> I["PendingConfirmTool -> confirm"] + I --> J["EmitConfirmRequest
OpenConfirmInteraction(type=execute)"] + J --> K["interrupt 等用户"] + K --> L["用户 accept -> chat resume -> execute"] + H -- "否" --> M["写 observation
继续 execute 循环"] + + F -- "continue 无工具" --> M + F -- "ask_user" --> N["OpenAskUserInteraction
interrupt 等用户"] + F -- "next_plan" --> O["推进 current_step
写 execute_step_advanced marker"] + O --> P{"还有后续计划?"} + P -- "是" --> M + P -- "否" --> Q["Done -> deliver"] + + F -- "done" --> R{"AllowReorder?"} + R -- "false" --> S["order_guard
校验 suggested 相对顺序"] + S --> T["自动复原或保守放行"] + T --> Q + R -- "true" --> Q + + F -- "abort" --> Q +``` + +### 5) `interrupt / deliver / 状态持久化` 链路 + +```mermaid +flowchart TD + A["plan / execute / confirm 产生 pending"] --> B["interrupt"] + B --> C{"pending 类型"} + C -- "ask_user" --> D["把 DisplayText 当 assistant 文本输出"] + C -- "confirm" --> E["不重复发确认卡片
仅发 waiting status"] + D --> F["interrupt -> END"] + E --> F + F --> G["saveAgentState"] + G --> H["用户下一轮输入
重新从 chat resume"] + + I["任务正常完成 / abort / exhausted"] --> J["deliver"] + J --> K["GenerateDeliverSummary
LLM 失败则降级机械总结"] + K --> L{"completed 且有日程变更?"} + L -- "是" --> M["EmitScheduleCompleted"] + L -- "否" --> N["跳过排程完毕卡片"] + M --> O["输出最终总结"] + N --> O + + O --> P{"terminal_status=completed?"} + P -- "是" --> Q["WriteSchedulePreview
只写结果态工作区"] + P -- "否" --> R["跳过排程预览写入"] + Q --> S["deliver 后 saveAgentState"] R --> S - S --> T["persistChatAfterReply
统一后置持久化 + 异步标题"] - - C1 --> Z["错误直接返回前端"] - E1 --> Z - T --> Z + S --> T["graph 返回 service
发布 AgentStateSnapshot(outbox)"] + T --> U["EmitDone + 异步生成会话标题"] ``` ## 5.5 长期记忆系统 @@ -578,50 +642,58 @@ flowchart TD ### 读路径(同步) ``` -用户消息到达 → injectMemoryContext → 结构化检索(按用户/类别过滤) -+ 向量召回(Milvus) → 重排序 → 拼接为 pinned block 注入 Prompt → LLM 生成回复 +用户消息到达 → injectMemoryContext +→ 先读 Redis 预取缓存并注入 memory_context(上一轮检索结果,首字节零等待) +→ 后台完整检索:结构化检索(按用户/类别过滤) + 向量召回(Milvus) + 重排序 +→ 渲染后的最新结果经 channel 交给 Plan/Execute 节点消费,同时回写 Redis +→ 作为下一轮 Chat 节点的预取记忆继续接力 → LLM 生成回复 ``` 关键设计: -1. **双路召回**:结构化检索保证精确匹配,向量召回覆盖语义关联,两者合并后重排序取 Top-K。 -2. **优雅降级**:记忆检索失败时不阻断主对话链路,仅降级为无记忆模式,不影响正常功能。 -3. **访问时间刷新**:被召回的记忆会更新最近访问时间,热点记忆更不容易被淘汰。 +1. **Redis 接力预取**:本轮 Chat 先消费上一轮写入 Redis 的记忆预取缓存,保证首字节几乎不受完整检索耗时影响;后台再把最新检索结果通过 channel 交给 Plan/Execute,并顺手回写 Redis,形成“上一轮服务下一轮”的接力链路。 +2. **双路召回**:完整检索阶段仍采用结构化检索 + 向量召回的混合模式,先保证精确过滤,再补足语义相关性,最后统一重排序取 Top-K。 +3. **优雅降级**:Redis 未命中、后台检索失败、短应答跳过检索等情况都不会阻断主链路;最差也只是本轮不注入记忆,而不是把对话打挂。 # 6 前端实现 -PS:当前前端进度大幅度落后于后端,将在后端闭环跑通后开始维护。 +当前前端位于 `frontend/` 目录,已经形成一个可独立运行的 Vue 单页应用,并且前端入口目前呈现为“两条主线并存”: -## 6.1 当前前端技术栈与工程约定 +1. `/assistant`:承接 `newAgent` 对话、确认、时间线、排程结果卡片与微调链路。 +2. `/schedule`:承接传统课表中心、任务类侧栏、粗排预览、拖拽调整与正式应用。 -当前前端位于 `frontend/` 目录,已经落地为一个可独立运行的 Vue 单页应用。 +## 6.1 当前前端技术栈与整体结构 技术栈如下: | 分类 | 当前选型 | 说明 | | --- | --- | --- | -| 前端框架 | Vue 3 | 统一使用 Composition API 与 `