# 许可证
本项目采用 **GNU Affero General Public License v3.0 (AGPL-3.0)** 进行许可。
详见根目录 [`LICENSE`](./LICENSE) 文件。
# 时伴 SmartMate
> 越用越懂你的成长型 AI 排程伙伴 · 面向大学生的陪伴式日程管理平台
# 1 项目概览
## 1.1 总体介绍
时伴(SmartMate)是面向大学生的 AI 排程伙伴。通过自然语言对话管理课表与任务,更能在一次次交流中记住你的习惯、偏好和生活节奏——**越用越懂你,越排越贴心。**
核心能力包括:大规模智能排程、基于课表的任务编排、AI 驱动的"小事随口记",以及跨会话的长期记忆积累——做到:**一个伙伴,包揽生活大小事。**
本项目采用前后端分离设计,并且有一个**完整的工业化设计链路**:写功能-墨刀画页面-根据页面写接口-写后端-写前端(AI)。
## 1.2 项目解决的痛点
> **问题1:** 传统的日程平台(类似于滴答清单等)要么设置重复任务(例如每日背单词、每日刷题等),要么就需要手动设置任务,非常不方便。一旦遇到需要大规模安排任务的场景,例如期末考前半个月突击(涉及的科目多,手头的空余时间也多,每门课的任务又都不一样),就需要先手动规划好任务,再手指点个不停给它排进日程软件里面,还得思考怎样排比较合理,一旦执行出问题需要调整,又像多米纳骨牌倒了一样,连着调整一大块。
**本项目带来的解决方案1:** 采用类似于老师备课-教务处排课的"备课-排课"模式。即假如你是学校上课的老师,你先在"备课区"把这门课程的"教学"大纲排好,然后再考虑后面安排上课的事情。
拿概率论举例子,你准备给它16节课时间复习,那你就在新增任务类的区域**创建新任务类**,并设置好在第x**任务块**看xx章节的速成课,然后第x任务块是真题练习。
设置完了之后,再通过我们的排课功能(会先设计一个保证能排的算法,第二批次开发还会考虑AI介入让课排的更好),你设置一些排课的倾向(更倾向于在哪个时间点学、不想在哪个时间点学、想每天均匀推进还是快速突击等等),点一下**智能一键编排**按钮,会将课直接以黄色打底的形式嵌入日程中,确认无误后你点击正式应用日程,课才会真正被排进去。
至于调整,本项目支持在课表区域直接拖拽调整时间。
> **问题2:** 期末周没课,确实可以按照上面一样操作。那我如果不是期末复习呢,我如果想安排一些别的事情呢,比如推进项目?我平时可是有课的,而且有不少水课。传统的日程软件可没法在水课处排课,要么忘记去上水课,要么忘记任务,十分恼火。
**本项目带来的解决方案2:** 本项目支持学校课表导入,甚至还支持水课嵌入任务。在导入课表之后,本项目支持勾选某些课程为"**可嵌入任务**"状态,此时就可以配合上面的排课系统,将水课作为可用的区域,排任务进去。
> **问题3:** 那么我作为一个规划能力比较差的懒人,也能用这个项目来让自己变的充实吗?
**本项目带来的解决方案3:** 当然可以,这就是本项目接入AI的意义。聊天区域的AI将会被调教成一个日程安排的小助手,既能满足你简单的任务记录、任务查询与部分日程调整需求,又能协助你从0开始一点点制定属于你的计划。(当前版本已支持AI随口记、任务查询,以及部分日程调整的确认流;更完整的自动规划与更复杂的读写能力仍在继续迭代)
> **问题4:** 我平时会突然冒出来一个能让自己活的更舒服亦或是变得更好的小想法(例如把桌面理一下、给自己挑一件新衣服等),但是现在很忙,根本没时间做,然后等忙完了有时间了又忘记了。传统的日程软件确实能让我记录下来(比如将这个小想法记录在日程软件的四象限里面的"不重要不紧急"象限),就是太麻烦了。
>
> 还有,平时上课时,接踵而至的实验报告、小组作业等,也面临着类似的情况,既容易忘记,又懒得记录。
**本项目带来的解决方案4:** 本项目支持AI驱动的"随口记"功能。
你可以和本项目的AI助手说:"提醒我**有空的时候**给自己挑一件新衣服"(**请注意标粗的关键词**),AI助手就会自动评估这件小事的难度以及执行所需花费的时间:如果这件事很简单或者不费时,会被加入"简单不重要"的队列中;如果比较费时或者困难,就会被加入"不简单不重要"队列中。
至于突发任务,也支持使用该"随口记"功能。你可以这么说:"提醒我**下周周日之前**完成xx课程的大作业"(**请注意标粗的关键词**),AI就会自动通过**截止时间和任务量**判断是否紧急,选择将其加入"重要并紧急"或者"重要不紧急"任务队列中(这里也是借鉴了四象限设计)。并且,系统会**每经过一个固定时间,就自动调整两个任务队列中未完成任务的位置**(比如:随着DDL临近,将重要不紧急队列中的未完成任务挪到重要并紧急队列中)。
当你在空闲时(做完你的大主线之后),亦或是休息时间打开本项目,一眼就能看到这几个队列的事情,然后你就可以看心情选择做哪个,然后做完之后一划就完事。
> **问题5:** 每次打开 AI 助手,都要重新告诉它"我周三有课"、"我更喜欢早上学数学"。传统工具对你没有记忆,每次都从零开始,永远是个陌生人。
**本项目带来的解决方案5:** 时伴内置长期记忆系统,会在对话中自动抽取并积累关于你的事实与偏好(课程、习惯、目标等)。下次对话时自动召回相关记忆注入上下文,跨会话延续对你的了解,且支持全链路优雅降级——记忆检索失败不阻断正常对话。
## 1.3 项目实现的功能
1. **对用户目前时间尺度的适应。**
当前版本以学校排课节次为主,采用第1-2节、第3-4节这种时间组织方式,同时兼容首页任务管理与周课表日程编排两套视图,方便把任务管理和日程安排放在同一系统中;
目前暂未开放用户自定义时间尺度配置,当前仍以固定节次模型为主。后续会有更新计划的!
2. **导入学校课表。** 本项目后端已提供学校课表导入能力(当前主要尝试兼容CQUPT的课表图片识别与导入格式),前端也已在 `/schedule` 页面接入完整导入流程,便于后续直接以课表为基底进行日程安排。
3. **"水课"任务嵌入。** 正如上方**问题2**所言,在已导入课表的前提下,支持设置某一门你想拿来干其它事情的课为"可嵌入任务"状态,此时这门课所占据的时间区域就是可以嵌入任务的了,但是仍然有区别于其它完全空白的时间区域,便于真正安排适合在嘈杂环境下做的事情。
4. **设置某一任务类,并配置其编排参数。** 正如上方**问题1**所言,用户可以先设置一个大的任务类(例如概率论复习、算法进阶计划等等),再为这个任务类配置任务块内容、起止日期、总节数、编排策略等信息,方便后续的日程编排。
5. **一键编排任务。** 结合算法与用户配置,将任务基于导入的课表和任务类设置先生成预览结果;确认无误后,再正式应用到日程中。
6. **AI 对话与排程辅助。** 当前版本支持通过 AI 进行对话、查看历史会话与思考过程,并结合结构化工具完成排程分析、日程微调与确认流。
7. **多用户。** 本系统可支持多个用户同时使用,并且记录AI对话、编排任务的Token使用情况等,并进行限额。
8. **四象限任务与日程编排并行管理。** 首页的四象限任务用于日常待办管理;
而周课表中的课程与排入日程的任务类则用于日程编排,两类信息分别展示、相互配合。
9. **完成任务状态的恢复。** 当前首页四象限任务支持将"已完成"恢复为"未完成"状态。
用户只需要在队列中找到该任务,然后再次点击对应状态按钮即可完成恢复。
至于日程侧,当前主要支持删除、解除安排与预览后再正式应用,其它更完整的恢复能力仍在后续迭代中。
10. **长期记忆积累。** 系统在对话中自动抽取用户相关事实与偏好,跨会话召回并注入对话上下文,实现"越用越懂你"的个性化体验。支持结构化检索 + 向量召回双路召回,全链路优雅降级。
# 2 产品逻辑与设计
## 2.1 业务流程图
当前版本主业务闭环如下,重点体现“首页任务管理 / AI 助手 / 日程编排 / 长期记忆”四条主线如何互相联动:
```mermaid
flowchart TD
A["用户进入系统"] --> B{"是否已登录"}
B -- "否" --> C["/auth 登录 / 注册"]
C --> D["进入主工作区"]
B -- "是" --> D
D --> E{"选择功能入口"}
E -- "首页 /dashboard" --> F["查看四象限任务 + 今日日程"]
F --> G["创建任务 / 完成任务 / 恢复任务"]
G --> H["首页持续展示最新待办状态"]
E -- "AI 助手 /assistant" --> I["输入自然语言需求"]
I --> J["newAgent 统一 graph"]
J --> K{"路由结果"}
K -- "随口记 / 任务查询" --> L["写入任务或返回查询结果"]
K -- "智能编排 / 日程调整" --> M["plan / confirm / execute / deliver"]
K -- "普通问答" --> N["直接回复用户"]
M --> O["返回排程结果卡片"]
O --> P["查看结构化预览并继续微调"]
P --> Q{"如何收口"}
Q -- "暂存" --> R["保存到 Redis 运行态"]
Q -- "正式应用" --> S["写入正式课表"]
L --> T["更新任务列表 / 对话历史"]
N --> U["沉淀对话历史"]
E -- "日程中心 /schedule" --> V["查看周课表与任务类"]
V --> W["新建任务类 / 删除任务块 / 单选或批量多选"]
W --> X["智能粗排 / 批量粗排"]
X --> Y["前端预览态拖拽调整"]
Y --> Z["正式应用到日程"]
S --> AA["周课表更新"]
Z --> AA
H --> AB["用户继续使用系统"]
T --> AB
U --> AB
AA --> AB
AB --> AC["异步事件:聊天持久化 / Outbox / Memory 抽取"]
AC --> AD["长期记忆写入与更新"]
AD --> AE["下一轮对话自动召回 memory_context"]
AE --> I
```
## 2.2 页面展示




# 3 后端数据架构
## 3.1 ER图
PS:此图截至版本v0.3.3

## 3.2 核心表结构
以下结构以 **2026 年 5 月 7 日** 对运行中的 `smartflow-mysql` 容器执行 `SHOW TABLES`、`SHOW CREATE TABLE` 与 `information_schema.columns` 查询结果为准。
### 3.2.0 全量表简述总览
| 领域 | 表名 | 简述 |
| --- | --- | --- |
| 用户与账户 | `users` | 用户主表,保存账号、密码、手机号与 token 配额。 |
| 用户与账户 | `user_token_usage_adjustments` | 用户 token 用量调整流水,按事件记录增减量。 |
| 用户与账户 | `user_notification_channels` | 用户通知通道配置表,保存 webhook、鉴权方式与最近一次测试结果。 |
| 任务与课表 | `tasks` | 首页任务池主表,承载优先级、DDL、预计节数与紧迫阈值。 |
| 任务与课表 | `task_classes` | 任务类定义表,保存任务类模式、策略、节数与学习属性配置。 |
| 任务与课表 | `task_items` | 任务类下的任务块明细表,记录内容、顺序、嵌入时间与状态。 |
| 任务与课表 | `schedule_events` | 课程 / 任务事件元数据表,是正式日程块的事实来源。 |
| 任务与课表 | `schedules` | 节次级日程展开表,把事件落到周次、星期与节次坐标。 |
| Agent | `agent_chats` | AI 会话头表,保存会话标题、模型、状态、消息数与 token 汇总。 |
| Agent | `chat_histories` | AI 消息明细表,保存正文、角色、推理内容、重试分支与 token 消耗。 |
| Agent | `agent_timeline_events` | Agent 时间线事件表,给前端时间线、状态流与调试回放使用。 |
| Agent | `agent_schedule_states` | Agent 排程状态快照表,保存会话内的排程运行态与 revision。 |
| Agent | `agent_state_snapshot_records` | Agent 阶段性状态快照归档表,按 phase 留存排障用 snapshot。 |
| 主动调度 | `active_schedule_jobs` | 主动调度后台任务表,管理定时触发、扫描状态、去重键与错误信息。 |
| 主动调度 | `active_schedule_triggers` | 主动调度触发表,记录触发来源、目标对象、幂等键、处理状态与 trace。 |
| 主动调度 | `active_schedule_previews` | 主动调度预览结果表,保存候选方案、决策结果、风险说明与 apply 结果。 |
| 主动调度 | `active_schedule_sessions` | 主动调度会话表,串起 trigger、preview 与对话侧 session 状态。 |
| Memory / RAG | `memory_items` | 记忆条目主表,保存内容、置信度、重要度、敏感级别与向量状态。 |
| Memory / RAG | `memory_jobs` | 记忆任务表,保存提取 / 管理类任务的 payload、重试与错误状态。 |
| Memory / RAG | `memory_audit_logs` | 记忆审计日志表,记录 memory 条目的变更前后内容与操作原因。 |
| Memory / RAG | `memory_user_settings` | 用户级记忆配置表,控制 memory 总开关与隐式 / 敏感记忆开关。 |
| 通知 | `notification_records` | 通知发送记录表,保存触发源、摘要、兜底文案、重试状态与供应商响应。 |
| 社区 / 任务类分享 | `forum_posts` | 社区帖子主表,对应任务类分享内容及其点赞 / 评论 / 导入统计。 |
| 社区 / 任务类分享 | `forum_post_templates` | 帖子对应的任务类模板表,固化分享时的任务类配置快照。 |
| 社区 / 任务类分享 | `forum_post_template_items` | 帖子模板下的任务块明细表,保存来源 task_item 与排序。 |
| 社区 / 任务类分享 | `forum_likes` | 帖子点赞记录表,记录点赞人、作者、事件 ID 与取消状态。 |
| 社区 / 任务类分享 | `forum_comments` | 帖子评论表,支持父子评论结构、幂等键与软删除。 |
| 社区 / 任务类分享 | `forum_imports` | 社区模板导入记录表,记录导入目标任务类、状态与失败原因。 |
| Credit / 计费 | `credit_accounts` | 用户 Credit 账户表,维护余额与累计充值 / 奖励 / 消耗。 |
| Credit / 计费 | `credit_ledger` | Credit 台账流水表,记录每次变动的来源、方向、前后余额与描述。 |
| Credit / 计费 | `credit_orders` | Credit 购买订单表,保存商品快照、数量、金额、支付状态与入账状态。 |
| Credit / 计费 | `credit_products` | Credit 商品表,定义 SKU、额度、价格、排序与上下架状态。 |
| Credit / 计费 | `credit_price_rules` | 模型计费规则表,定义 provider / model 的价格与利润率映射。 |
| Credit / 计费 | `credit_reward_rules` | Credit 奖励规则表,定义奖励来源、额度、状态与配置。 |
| Outbox | `agent_outbox_messages` | Agent 域 Outbox 表,承接聊天持久化、时间线与状态事件投递。 |
| Outbox | `task_outbox_messages` | 任务域 Outbox 表,承接任务相关异步事件投递。 |
| Outbox | `memory_outbox_messages` | Memory 域 Outbox 表,承接记忆提取 / 管理类异步事件投递。 |
| Outbox | `active_scheduler_outbox_messages` | 主动调度域 Outbox 表,承接调度触发相关异步事件。 |
| Outbox | `notification_outbox_messages` | 通知域 Outbox 表,承接外部通知发送事件。 |
| Outbox | `taskclass_forum_outbox_messages` | 社区 / 任务类分享域 Outbox 表,承接点赞、导入等异步事件。 |
| Outbox | `llm_outbox_messages` | LLM 域 Outbox 表,承接模型计费 / 记账等异步事件。 |
| Outbox | `token_store_outbox_messages` | Token / Credit 域 Outbox 表,承接 token 或 credit 账务类异步事件。 |
当前库中与主业务链路最相关的核心表包括:
- `users`
- `tasks`
- `task_classes`
- `task_items`
- `schedule_events`
- `schedules`
- `agent_chats`
- `chat_histories`
### 3.2.1 `users`
```text
id bigint unsigned PK AUTO_INCREMENT
username varchar(255) NOT NULL UNIQUE
password varchar(255) NOT NULL
phone_number varchar(255) NULL
token_limit bigint DEFAULT 100000
token_usage bigint DEFAULT 0
last_reset_at datetime(3) NULL
```
### 3.2.2 `tasks`
```text
id bigint PK AUTO_INCREMENT
user_id bigint NULL
title varchar(255) NULL
priority bigint NOT NULL
is_completed tinyint(1) DEFAULT 0
deadline_at datetime(3) NULL
urgency_threshold_at datetime(3) NULL
estimated_sections bigint NOT NULL DEFAULT 1
索引:
- idx_tasks_user_id(user_id)
- idx_user_done_threshold_priority(user_id, is_completed, urgency_threshold_at, priority)
```
### 3.2.3 `task_classes`
```text
id bigint PK AUTO_INCREMENT
user_id bigint NULL
name varchar(255) NULL
mode enum('auto','manual') NULL
start_date datetime(3) NULL
end_date datetime(3) NULL
total_slots bigint NULL
allow_filler_course tinyint(1) DEFAULT 1
strategy enum('steady','rapid') NULL
excluded_slots json NULL
subject_type varchar(32) NULL
difficulty_level varchar(16) NULL
cognitive_intensity varchar(16) NULL
excluded_days_of_week json NULL
索引:
- idx_task_classes_user_id(user_id)
```
### 3.2.4 `task_items`
```text
id bigint PK AUTO_INCREMENT
category_id bigint NULL
order bigint NULL
content text NULL
embedded_time json NULL
status bigint NULL
索引 / 约束:
- fk_task_classes_items(category_id -> task_classes.id)
```
### 3.2.5 `schedule_events`
```text
id bigint PK AUTO_INCREMENT
user_id bigint NOT NULL
name varchar(255) NOT NULL
location varchar(255) DEFAULT ''
type enum('course','task') NOT NULL
rel_id bigint NULL
can_be_embedded tinyint(1) NOT NULL DEFAULT 0
start_time datetime(3) NULL
end_time datetime(3) NULL
task_source_type varchar(32) NOT NULL DEFAULT ''
makeup_for_event_id bigint NULL
active_preview_id varchar(64) NULL
索引:
- idx_user_events(user_id)
- idx_schedule_event_task_source(task_source_type)
- idx_schedule_event_makeup_for(makeup_for_event_id)
- idx_schedule_event_active_preview(active_preview_id)
```
### 3.2.6 `schedules`
```text
id bigint PK AUTO_INCREMENT
event_id bigint NOT NULL
user_id bigint NOT NULL
week bigint NOT NULL
day_of_week bigint NOT NULL
section bigint NOT NULL
embedded_task_id bigint NULL
status enum('normal','interrupted') DEFAULT 'normal'
索引 / 约束:
- UNIQUE idx_user_slot_atomic(user_id, week, day_of_week, section)
- idx_event_id(event_id)
- fk_schedules_embedded_task(embedded_task_id -> task_items.id)
- fk_schedules_event(event_id -> schedule_events.id)
```
### 3.2.7 `agent_chats`
```text
id bigint PK AUTO_INCREMENT
chat_id varchar(36) NOT NULL UNIQUE
user_id bigint NOT NULL
title varchar(255) NULL
system_prompt text NULL
model varchar(100) NULL
message_count bigint NOT NULL DEFAULT 0
tokens_total bigint NOT NULL DEFAULT 0
last_message_at datetime(3) NULL
status varchar(32) NOT NULL DEFAULT 'active'
created_at datetime(3) NULL
updated_at datetime(3) NULL
deleted_at datetime(3) NULL
compaction_summary text NULL
compaction_watermark bigint NOT NULL DEFAULT 0
context_token_stats json NULL
last_history_event_id varchar(64) NULL
last_token_adjust_event_id varchar(64) NULL
索引:
- idx_user_last(user_id)
- idx_user_status(user_id, status)
```
### 3.2.8 `chat_histories`
```text
id bigint PK AUTO_INCREMENT
chat_id varchar(36) NOT NULL
user_id bigint NOT NULL
message_content text NULL
role varchar(32) NULL
tokens_consumed bigint NOT NULL DEFAULT 0
created_at datetime(3) NULL
reasoning_content text NULL
reasoning_duration_seconds bigint NOT NULL DEFAULT 0
retry_group_id varchar(64) NULL
retry_index bigint NULL
retry_from_user_message_id bigint NULL
retry_from_assistant_message_id bigint NULL
source_event_id varchar(64) NULL UNIQUE
索引:
- idx_user_chat(user_id, chat_id)
- idx_chat_id(chat_id)
- idx_retry_group(retry_group_id)
```
补充说明:
1. 运行库里已经不存在 README 旧版那种“`agent_chats` 直接承载单条消息正文”的结构;现在是 `agent_chats` 管会话头,`chat_histories` 管消息明细。
2. `task_classes.start_date` / `end_date` 在运行库中是 `datetime(3)`,不是旧文档里的 `date`。
3. `tasks` 现在多了 `urgency_threshold_at` 与 `estimated_sections`,`task_classes` 现在多了 `subject_type`、`difficulty_level`、`cognitive_intensity`、`excluded_days_of_week`。
4. README 此处如果后续再更新,建议继续以运行中的 MySQL 查询结果为准,而不是只看代码结构体。
# 4 接口契约
## 4.1 核心API列表(ApiFox)
链接如下:https://oqg5uiubh0.apifox.cn
## 4.2 Agent可调用的工具定义
以下定义基于当前代码实现(`backend/services/agent/tools/registry.go` + `backend/cmd/start.go` 注入),不是规划态文档。
### 4.2.1 调用契约
1. `tool_call` 必须是单个对象,格式为:
```json
{"tool_call":{"name":"工具名","arguments":{}}}
```
2. 日程写工具默认走 `confirm` 确认闸门(`always_execute=true` 时可跳过确认)。
3. 非日程类工具(如 `context_tools_add`、`context_tools_remove`、`web_search`、`web_fetch`)走 `continue + tool_call`;其中 `upsert_task_class` 虽不依赖 `ScheduleState`,但仍属于真实写库入口。
4. 当前每轮只允许调用一个工具,不支持同轮批量工具数组。
### 4.2.2 工具清单(当前版本)
| 工具名 | 类型 | 是否需确认 | 是否依赖 ScheduleState | 核心参数 | 作用与约束 |
| --- | --- | --- | --- | --- | --- |
| `context_tools_add` | 上下文控制 | 否 | 否 | `domain`, `packs`, `mode` | 激活 `schedule` / `taskclass` 工具域;`schedule` 可按 pack 增量注入 `mutation / analyze / detail_read / deep_analyze / queue / web` |
| `context_tools_remove` | 上下文控制 | 否 | 否 | `domain`, `packs`, `all` | 移除指定工具域或 pack;`core` 固定包不允许 remove |
| `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`, `reset_queue` 等 | 过滤任务集合,可选自动入队供后续队列工具处理 |
| `queue_pop_head` | 队列读 | 否 | 是 | 无 | 取出 / 复用当前队首任务(一次只处理一个) |
| `queue_status` | 队列读 | 否 | 是 | 无 | 查看队列状态(pending / current / completed / skipped) |
| `get_task_info` | 读 | 否 | 是 | `task_id`(必填) | 查询单任务详细信息 |
| `analyze_rhythm` | 分析 | 否 | 是 | `category`, `include_pending`, `detail`, `hard_categories` | 分析学习节奏、连续同类任务与切换情况 |
| `analyze_health` | 分析 | 否 | 是 | `detail`, `dimensions`, `threshold` | 作为主动优化裁判入口,判断当前排程是否仍值得继续优化 |
| `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`(不改日程) |
| `unplace` | 写 | 是 | 是 | `task_id`(必填) | 取消任务落位并恢复待安排状态 |
| `upsert_task_class` | 任务类写入 | 是 | 否 | `id`, `task_class`, `items`, `source` | 统一创建 / 更新任务类入口;会写库,但不直接修改当前日程预览 |
| `web_search` | 读 | 否 | 否 | `query`(必填), `top_k`, `domain_allow`, `recency_days` | Web 检索,返回结构化标题 / 摘要 / URL;未启用时优雅返回错误 observation |
| `web_fetch` | 读 | 否 | 否 | `url`(必填), `max_chars` | 抓取并清洗网页正文;服务不可用时优雅返回错误 observation |
### 4.2.3 当前实现中的关键规则
1. `context_tools_add` / `context_tools_remove` 是动态区协议入口:`schedule` 支持按 pack 精细注入,`taskclass` 当前只有 `core` 固定包。
2. 日程写工具默认走 `confirm` 确认闸门(`always_execute=true` 时可跳过)。
3. `upsert_task_class` 虽不依赖 `ScheduleState`,但属于真实写库入口,仍要求 confirm。
4. `batch_move` 有安全上限:当前最多支持 2 条移动请求,超出建议走队列化逐项处理。
5. `web_search` / `web_fetch` 失败不会打断主链路,都会回传结构化错误 observation 给模型继续决策。
# 5 后端实现
## 5.1 技术栈
| **分类** | **选用技术** | **在时伴中的应用场景** |
| ----------------- | ---------------- | ------------------------------------------------------------ |
| **API 网关** | **Gin** | 负责统一 HTTP 入口、JWT 鉴权、限流、幂等控制与前端 API 聚合。 |
| **服务间通信** | **go-zero zrpc + gRPC** | `api` 网关通过 zrpc 调用 `userauth / task / schedule / agent / memory / llm` 等后端服务,支撑当前多服务拆分。 |
| **持久层数据库** | **MySQL 8.0** | 存储用户、任务、课表、Agent 会话、主动调度、社区、Credit、memory 等核心结构化数据。 |
| **ORM / 数据访问** | **GORM** | 负责 MySQL 映射、事务处理、索引约束落地,以及日程 / Agent / memory 等域的数据访问。 |
| **缓存与运行态存储** | **Redis** | 缓存周课表与会话状态,承接幂等键、限流、Agent 状态快照、日程预览与 memory 预取上下文。 |
| **异步事件总线** | **Outbox + Kafka(segmentio/kafka-go)** | 用于聊天持久化、memory 抽取、主动调度触发、通知投递、Credit 记账等异步事件解耦。 |
| **AI / Agent 编排** | **CloudWeGo Eino** | 承担对话规划、工具调用、确认流、排程执行与 deliver/interrupt 状态机编排。 |
| **模型接入** | **Volcengine Ark + OpenAI Compatible 接口** | `llm` / `course` / `memory` / `agent` 等服务通过统一模型层调用文本模型、推理模型与课表识别视觉模型。 |
| **向量检索** | **Milvus** | 为长期记忆与 RAG 检索提供向量索引、召回与相似度搜索能力。 |
| **向量依赖** | **MinIO + etcd** | 作为 Milvus 的对象存储与元数据依赖,随容器编排一起部署。 |
| **身份认证** | **JWT** | 实现无状态登录,并把 `user_id` 等身份信息透传到 API 与服务层。 |
| **配置管理** | **Viper** | 管理数据库、Redis、Kafka、RPC、模型、memory/RAG、通知等多环境配置。 |
| **容器化交付** | **Dockerfile + Docker Compose + Nginx** | 已支持基础设施容器化与整站容器化;前端镜像通过 Nginx 托管静态产物。 |
| **接口调试 / 文档** | **Apifox** | 用于维护接口协议、联调接口与沉淀阶段性 API 文档。 |
| **日志与观测** | **标准库 `log` + Gin Logger/Recovery** | 当前代码以标准库日志为主,HTTP 层使用 Gin 默认日志与恢复中间件;Kafka、RAG、memory、Agent 链路也会输出关键观测日志。 |
## 5.2 架构图
基于当前 `docker-compose.full.yml`、`backend/cmd/*` 与各服务 `dao/connect.go` 整理。实线表示同步 HTTP / RPC / 直连存储关系,虚线表示 Outbox + Kafka 异步事件链路;标注“迁移期”的连线表示该服务仍在直接读写其他域的表。
```mermaid
flowchart TB
FE["Frontend (Vue)"] --> API["api / Gin Gateway
JWT 鉴权 / 限流 / 幂等"]
subgraph SVC["服务层"]
direction LR
UA["userauth"]
TASK["task"]
COURSE["course"]
TCLASS["task-class"]
SCH["schedule"]
AGENT["agent"]
MEM["memory"]
AS["active-scheduler"]
NOTI["notification"]
FORUM["taskclassforum"]
TOKEN["tokenstore"]
LLM["llm"]
RAG["RAG / Milvus"]
end
API --> UA
API --> TASK
API --> COURSE
API --> TCLASS
API --> SCH
API --> AGENT
API --> MEM
API --> AS
API --> NOTI
API --> FORUM
API --> TOKEN
AGENT --> LLM
AGENT --> TASK
AGENT --> TCLASS
AGENT --> SCH
AGENT --> MEM
AGENT --> RAG
COURSE --> LLM
MEM --> LLM
MEM --> RAG
AS --> LLM
AS --> TASK
AS --> SCH
FORUM --> TCLASS
LLM --> TOKEN
subgraph REDIS["Redis"]
direction TB
R_API["限流 / 幂等响应缓存"]
R_AUTH["JWT 黑名单 / Token 配额快照"]
R_TASK["任务列表缓存 / 紧急性提升去重锁"]
R_SCH["今日日程 / 周视图 / 最近完成 / 当前进行中缓存"]
R_TCLASS["任务集列表缓存"]
R_AGENT["会话历史 / Timeline / Schedule Preview / Agent State / Memory Prefetch"]
R_FORUM["评论树缓存"]
R_CREDIT["Credit 余额快照 / Blocked 标记"]
end
API --> R_API
UA --> R_AUTH
TASK --> R_TASK
SCH --> R_SCH
TCLASS --> R_TCLASS
AGENT --> R_AGENT
FORUM --> R_FORUM
TOKEN --> R_CREDIT
LLM --> R_CREDIT
subgraph MYSQL["MySQL"]
direction TB
T_USERS["users
user_token_usage_adjustments"]
T_TASK["tasks"]
T_TCLASS["task_classes
task_items"]
T_SCH["schedules
schedule_events"]
T_AGENT["agent_chats
chat_histories
agent_timeline_events
agent_schedule_states
agent_state_snapshot_records"]
T_AS["active_schedule_jobs
active_schedule_triggers
active_schedule_previews
active_schedule_sessions"]
T_MEM["memory_items
memory_jobs
memory_audit_logs
memory_user_settings"]
T_NOTI["notification_records
user_notification_channels"]
T_FORUM["forum_posts
forum_post_templates
forum_post_template_items
forum_likes
forum_comments
forum_imports"]
T_CREDIT["credit_accounts
credit_ledger
credit_products
credit_orders
credit_price_rules
credit_reward_rules"]
OB_AGENT["agent_outbox_messages"]
OB_TASK["task_outbox_messages"]
OB_MEM["memory_outbox_messages"]
OB_AS["active_scheduler_outbox_messages"]
OB_NOTI["notification_outbox_messages"]
OB_TOKEN["token_store_outbox_messages"]
end
UA --> T_USERS
TASK --> T_TASK
TCLASS --> T_TCLASS
TCLASS -.->|迁移期直写| T_SCH
SCH --> T_SCH
SCH -.->|迁移期依赖| T_TASK
SCH -.->|迁移期依赖| T_TCLASS
COURSE -.->|课表导入写入| T_SCH
AGENT --> T_AGENT
AGENT -.->|读取任务| T_TASK
AGENT -.->|读取任务集| T_TCLASS
AGENT -.->|读取日程 / 预览| T_SCH
AS --> T_AS
AS -.->|读取会话与时间线| T_AGENT
MEM --> T_MEM
NOTI --> T_NOTI
FORUM --> T_FORUM
TOKEN --> T_CREDIT
LLM -.->|读取价格规则 / Credit 守卫| T_CREDIT
subgraph KAFKA["Outbox + Kafka"]
direction TB
K_AGENT["smartflow.agent.outbox"]
K_TASK["smartflow.task.outbox"]
K_MEM["smartflow.memory.outbox"]
K_AS["smartflow.active-scheduler.outbox"]
K_NOTI["smartflow.notification.outbox"]
K_TOKEN["smartflow.token-store.outbox"]
end
AGENT -.->|聊天持久化 / 状态快照 / Timeline 持久化| OB_AGENT
AGENT -.->|memory.extract.requested| OB_MEM
TASK -.->|task.urgency.promote.requested| OB_TASK
AS -.->|active_schedule.triggered| OB_AS
AS -.->|notification.feishu.requested| OB_NOTI
FORUM -.->|forum.post.liked / forum.post.imported| OB_TOKEN
LLM -.->|credit.charge.requested| OB_TOKEN
OB_AGENT -.->|relay| K_AGENT
OB_TASK -.->|relay| K_TASK
OB_MEM -.->|relay| K_MEM
OB_AS -.->|relay| K_AS
OB_NOTI -.->|relay| K_NOTI
OB_TOKEN -.->|relay| K_TOKEN
K_AGENT -.->|consume| AGENT
K_TASK -.->|consume| TASK
K_MEM -.->|consume| MEM
K_AS -.->|consume| AS
K_NOTI -.->|consume| NOTI
K_TOKEN -.->|consume| TOKEN
```
## 5.3 核心算法
### 5.3.1 智能排课算法
本系统采用 **“原子化时间网格(Atomic TimeGrid)”** 架构,实现了针对大学生复杂课表环境的智能任务填充。算法核心分为 **“沙盘模拟”、“边界感知探测”** 与 **“逻辑位移步进”** 三大模块。
**1. 原子化时间沙盘 (Grid Sandboxing)**
算法首先将物理时间窗口(StartDate 到 EndDate)抽象为一个三维矩阵 $Grid[Week][Day][Section]$。
- **多维状态标记**:每个格子(Slot)是携带 `Status` 和 `EventID` 的 `slotNode` 节点。
- **优先级注水(Hydration)**:
1. **Blocked(屏蔽区)**:根据用户配置的 `ExcludedSlots` 强制锁定,优先级最高。
2. **Filler(嵌入区)**:识别“水课”,标记为可利用资源。
3. **Occupied(占用区)**:映射既有硬核课程,确保调度不产生物理冲突。
**2. 边界感知探测 (Boundary-Sensing Detection)**
为了解决“任务块跨课分身”的 Bug,算法引入了 **EventID 校验机制**。
- **容器自适应长度**:当算法探测到一个 `Filler` 槽位时,会向后贪心扫描,只有当相邻槽位的 `EventID` 相同且同为 `Filler` 时,才允许任务块拉伸。
- **逻辑闭环**:这保证了任务块(TaskItem)要么完美嵌入单门水课,要么占据空地,绝不会出现一个任务横跨两门不同课程的情况。
**3. 稳扎稳打:逻辑位移步进 (Logical-Offset Skipping)**
在 `Steady`(稳扎稳打)模式下,为了实现负载均衡,算法弃用了传统的“物理时间跳跃”,改用 **“逻辑坑位跳跃”**。
$$Gap = \frac{TotalAvailableSlots - (TaskCount \times 2)}{TaskCount + 1}$$
- **物理跳跃(旧版/错误)**:直接 $Time + Gap$,容易因遇到屏蔽时段或硬核课而导致游标溢出,从而“吞掉”后续任务。
- **逻辑跳跃(现行/优化)**:调用 `skipAvailableSlots` 函数,在 Grid 中沿时间轴向后数出 $Gap$ 个**真正可用**的格子作为下一个起点。
- **价值**:确保了在有限的 **2C4G** 服务器资源下,任务能像“等距列队”一样均匀分布在学期空隙中。
------
**🛠️ 算法运行流程**
1. **Build**:调用 `buildTimeGrid`,将数据库的离散 `Schedules` 映射为内存状态网格。
2. **Count**:统计当前窗口内所有 `Free` 与 `Filler` 的原子位总数。
3. **Allocate**:
- 通过 `FindNextAvailable` 锁定首个合法坑位。
- 进行 **容器探测** 决定任务块长度。
- 执行 `skipAvailableSlots` 寻找下一个负载均衡点。
4. **Preview**:输出 DTO 到前端,标记 `status: "suggested"` 供用户预览高亮。
------
**⚡ 性能表现 (Optimization)**
- **时间复杂度**:$O(W \times D \times S)$,其中 $W$ 为任务类跨度周数。在处理典型的 16 周排程时,计算量仅在数千次操作级别,单机响应达毫秒级。
- **空间复杂度**:由于采用了按需创建周 Map 的策略,内存占用随任务跨度动态伸缩,极大地减轻了重庆邮电大学校园服务器环境下的 GC 压力。
------
**数据回填**
在执行完上述算法后,将任务块分成两类数据:
1. 需要新建`ScheduleEvent`的,插入纯空闲时段的数据;
2. 直接嵌入现有课程中的任务块;
然后分别调用不同的业务逻辑,开启大事务,批量插入,使得只需要连接2次数据库,并且若插入出错,支持批量回滚,不会存在任何脏数据。
## 5.4 Agent范式实现细节
### 1) 统一入口与 graph 主链
```mermaid
flowchart TD
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"]
H --> I{"chat 决定下一步"}
I -- "简单回复 / 深答" --> J["chat 节点内直接完成"]
I -- "复杂规划" --> K["plan"]
I -- "直接执行" --> L["execute"]
I -- "execute 前需要粗排" --> M["rough_build"]
I -- "已有 pending interaction" --> N["chat 先做 resume"]
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
N --> P
J --> R["deliver 或 END"]
O --> S["deliver"]
P --> S
Q --> S
S --> T["END"]
```
### 2) `chat` 节点路由与恢复
```mermaid
flowchart TD
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["一次快速路由 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"]
```
### 3) `plan / confirm / rough_build` 链路
```mermaid
flowchart TD
A["Phase=planning -> plan 节点"] --> B["LLM 生成 PlanDecision
可开启 thinking"]
B --> C{"action"}
C -- "continue" --> A
C -- "ask_user" --> D["OpenAskUserInteraction
graph -> interrupt"]
C -- "done" --> E["FinishPlan
写 pinned blocks:
current_plan / current_step"]
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
F -- "是" --> M["自动展示计划摘要
ConfirmPlan -> PhaseExecuting"]
M --> L
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"]
```
### 4) `execute / confirm / order_guard` 链路
```mermaid
flowchart TD
A["Phase=executing -> execute 节点"] --> U{"NextRound 成功?"}
U -- "否" --> V["Exhaust -> deliver"]
U -- "是" --> B{"有 PendingConfirmTool?"}
B -- "是" --> C["直接执行已确认写工具"]
B -- "否" --> D["BuildExecuteMessages
LLM 产出 ExecuteDecision"]
C --> E["写 observation / 更新 ScheduleState"]
D --> F{"action"}
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["graph 返回 service
发布 AgentStateSnapshot(outbox)"]
T --> U["EmitDone + 异步生成会话标题"]
```
## 5.5 长期记忆系统
时伴的长期记忆系统采用**同步读 + 异步写**架构,确保对话体验不被记忆写入拖慢。
### 写路径(异步)
```
用户消息 → 聊天落库(同事务写 Outbox) → Kafka 投递 memory.extract.requested 事件
→ 幂等入队 memory_jobs → Worker 抢占执行 → LLM 抽取事实
→ 去重决策(ADD/UPDATE/DELETE/NONE) + UUID 映射防幻觉 → 持久化 memory_items
```
关键设计:
1. **Outbox 保证不丢消息**:聊天持久化与事件投递在同一事务内,失败整体回滚,由 Outbox 重试。
2. **去重决策状态机**:LLM 对抽取的事实判断是新增、更新、删除还是跳过,避免重复记忆。
3. **UUID 映射**:为每条记忆分配唯一 ID,LLM 引用时必须使用该 ID,防止幻觉篡改。
### 读路径(同步)
```
用户消息到达 → injectMemoryContext
→ 先读 Redis 预取缓存并注入 memory_context(上一轮检索结果,首字节零等待)
→ 后台完整检索:结构化检索(按用户/类别过滤) + 向量召回(Milvus) + 重排序
→ 渲染后的最新结果经 channel 交给 Plan/Execute 节点消费,同时回写 Redis
→ 作为下一轮 Chat 节点的预取记忆继续接力 → LLM 生成回复
```
关键设计:
1. **Redis 接力预取**:本轮 Chat 先消费上一轮写入 Redis 的记忆预取缓存,保证首字节几乎不受完整检索耗时影响;后台再把最新检索结果通过 channel 交给 Plan/Execute,并顺手回写 Redis,形成“上一轮服务下一轮”的接力链路。
2. **双路召回**:完整检索阶段仍采用结构化检索 + 向量召回的混合模式,先保证精确过滤,再补足语义相关性,最后统一重排序取 Top-K。
3. **优雅降级**:Redis 未命中、后台检索失败、短应答跳过检索等情况都不会阻断主链路;最差也只是本轮不注入记忆,而不是把对话打挂。
# 6 前端实现
当前前端位于 `frontend/` 目录,已经形成一个可独立运行的 Vue 单页应用,并且前端入口目前呈现为“两条主线并存”:
1. `/assistant`:承接 `newAgent` 对话、确认、时间线、排程结果卡片与微调链路。
2. `/schedule`:承接传统课表中心、任务类侧栏、粗排预览、拖拽调整与正式应用。
## 6.1 当前前端技术栈与整体结构
技术栈如下:
| 分类 | 当前选型 | 说明 |
| --- | --- | --- |
| 前端框架 | Vue 3 | 统一使用 Composition API 与 `