1488 lines
37 KiB
Markdown
1488 lines
37 KiB
Markdown
# 上下文长期记忆 + 图片目标定位 + 资产图谱表结构设计
|
||
|
||
> 基于当前项目结构的可落地方案
|
||
> 适用仓库:`ai-chatbot-main`
|
||
> 文档日期:2026-03-06
|
||
|
||
---
|
||
|
||
## 1. 文档目标
|
||
|
||
本方案不是抽象讨论,而是面向当前项目现状,补出一套可以直接进入开发的能力设计:
|
||
|
||
1. 让对话具备更稳定的**上下文长期记忆**
|
||
2. 让系统能更精准地知道用户连续对话里**正在修改哪张图片**
|
||
3. 为无限画布、图片编辑、Agent 模式、视频九宫格、3D 资产提供统一的**资产图谱索引层**
|
||
4. 方案必须兼容当前项目已上线的:
|
||
- 聊天接口
|
||
- 无限画布
|
||
- Agent 自动模式
|
||
- 图片生成/编辑
|
||
- 视频九宫格技能
|
||
- 3D 资产库
|
||
|
||
---
|
||
|
||
## 2. 基于当前项目的现状判断
|
||
|
||
### 2.1 现有核心入口
|
||
|
||
当前与本方案最相关的代码入口已经比较明确:
|
||
|
||
- 聊天主入口:`app/(chat)/api/chat/route.ts`
|
||
- 聊天请求结构:`app/(chat)/api/chat/schema.ts`
|
||
- 智能分析器:`lib/ai/smart-analyzer.ts`
|
||
- 重型 Agent 链路:`lib/ai/auto-agent-graph.ts`
|
||
- 无限画布聊天面板:`components/canvas/CanvasChatPanel.tsx`
|
||
- 画布工作区:`components/canvas/CanvasWorkspace.tsx`
|
||
- 画布状态仓库:`store/canvas/index.ts`
|
||
- 画布节点类型:`types/canvas/index.ts`
|
||
- 画布持久化接口:`app/api/canvas/[id]/route.ts`
|
||
- 数据库定义:`lib/db/schema.ts`
|
||
- 数据库查询:`lib/db/queries.ts`
|
||
- 视频技能样例:`lib/video-agent/skills/*.ts`
|
||
- 视频任务持久化样例:`lib/video-agent/task-store.ts`
|
||
|
||
### 2.2 现有系统已经具备的能力
|
||
|
||
当前项目并不是“没有上下文”,而是已经有了若干前置能力:
|
||
|
||
1. **聊天历史复用**
|
||
- `app/(chat)/api/chat/route.ts` 已经存在“用户本轮没传图时自动回查历史图片”的逻辑
|
||
- 现在主要通过扫描 `Message_v2.parts`、解析文本中的 Markdown 图片 URL 来回找最近结果图/原图
|
||
|
||
2. **画布选中上下文**
|
||
- `components/canvas/CanvasChatPanel.tsx` 已经会基于 `selectedNodeIds` 构造 `autoContext`
|
||
- 目前是把“用户选中了几张图、这些图大概在左/右/中位置”拼成一段字符串发给后端
|
||
|
||
3. **画布状态持久化**
|
||
- `app/api/canvas/[id]/route.ts` 当前以 `canvasProject.canvasData` 为主保存整张画布
|
||
- `CanvasNode` 表虽然已经存在,但目前实际上没有被系统当作规范化索引层使用
|
||
|
||
4. **图片结果入库**
|
||
- `app/(chat)/api/chat/route.ts` 内部 `saveGeneratedImages()` 会将生图结果写入 `GeneratedImage`
|
||
- 相册接口 `app/api/gallery/route.ts` 也是基于 `GeneratedImage` 返回
|
||
|
||
5. **技能化执行样板**
|
||
- 视频九宫格方案已经把复杂流程拆成:
|
||
- 脚本归一技能
|
||
- 九宫格检测技能
|
||
- 分镜映射技能
|
||
- 顺序执行编排
|
||
- 说明当前项目已经适合继续新增“记忆/目标定位技能”
|
||
|
||
### 2.3 当前真正的短板
|
||
|
||
当前系统最核心的问题不是“没有模型分析能力”,而是**上下文工程没有完全结构化**:
|
||
|
||
1. **历史图片复用依赖 URL 扫描和文本解析**
|
||
- 容易受 Markdown 格式变化、URL 替换、图片代理地址变化影响
|
||
|
||
2. **画布选择上下文还是文本提示,不是机器可判定结构**
|
||
- 当前 `autoContext` 更像“给 LLM 看的说明”
|
||
- 但“这轮到底改哪张图”这种问题不能只靠自然语言推断
|
||
|
||
3. **现有业务表是分散的,不是统一资产索引**
|
||
- `GeneratedImage`
|
||
- `Model3DAsset`
|
||
- `VideoStoryboardTask / Shot`
|
||
- `canvasProject.canvasData`
|
||
- `Message_v2.parts`
|
||
- 它们都是业务存储,不是统一的“可检索资产图谱”
|
||
|
||
4. **缺少一层持久化的 Working Memory**
|
||
- 当前很多“上一轮结果、最近焦点图、原图/结果图关系”是临时逻辑判断,不是可靠状态
|
||
|
||
5. **缺少对“本轮目标资产解析过程”的审计**
|
||
- 现在若判断错图,很难准确复盘到底是:
|
||
- 选中上下文丢了
|
||
- 历史图片回查错了
|
||
- LLM 判错了
|
||
- 还是画布位置推断错了
|
||
|
||
---
|
||
|
||
## 3. 方案总原则
|
||
|
||
### 3.1 不做“模型魔法记忆”
|
||
|
||
这套能力不依赖某个模型“天然有长期记忆”,而是采用工程化四层结构:
|
||
|
||
1. **会话工作记忆**
|
||
2. **会话摘要记忆**
|
||
3. **用户长期偏好记忆**
|
||
4. **资产图谱记忆**
|
||
|
||
### 3.2 目标资产定位采用“规则优先,模型兜底”
|
||
|
||
优先级必须固定:
|
||
|
||
1. 当前明确选中的资产
|
||
2. 本轮新上传资产
|
||
3. 用户文本里的显式指代
|
||
4. 当前会话焦点资产
|
||
5. 最近生成结果资产
|
||
6. 只有仍然歧义时,才让模型做候选集判别
|
||
|
||
### 3.3 尽量复用现有项目结构
|
||
|
||
不建议一上来大重构,而是按当前目录自然扩展:
|
||
|
||
- 聊天链路仍然从 `app/(chat)/api/chat/route.ts` 进入
|
||
- 分析器仍然沿用 `lib/ai/*`
|
||
- 技能化拆分继续参考 `lib/video-agent/skills/*`
|
||
- 数据库存储继续沿用 `drizzle + SQL migration`
|
||
|
||
### 3.4 先做“可追踪”,再做“更聪明”
|
||
|
||
先确保:
|
||
|
||
- 每一轮目标图解析都有记录
|
||
- 每一张图都有统一资产 ID
|
||
- 每一个画布节点都能绑定资产
|
||
- 每一次生成都能形成版本链
|
||
|
||
在此基础上再引入更复杂的模型判断,否则只会让错误更难排查。
|
||
|
||
---
|
||
|
||
## 4. 推荐总架构
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["前端发送消息<br/>text + files + contextPayload + autoContext"] --> B["resolveChatMediaInput()"]
|
||
B --> C["loadConversationWorkingState()"]
|
||
C --> D["resolveTargetAssetsSkill()"]
|
||
D --> E["analyzeIntentStructured()"]
|
||
E --> F{"是否进入重型 Agent?"}
|
||
F -- 否 --> G["轻量执行链路<br/>直接图片编辑/生图/问图"]
|
||
F -- 是 --> H["auto-agent-graph / 视频 / 多技能任务"]
|
||
G --> I["registerResultAssets()"]
|
||
H --> I
|
||
I --> J["updateConversationWorkingState()"]
|
||
J --> K["appendMessageTargetResolution()"]
|
||
K --> L["compactConversationMemory()"]
|
||
```
|
||
|
||
这条链路里最关键的新层是:
|
||
|
||
1. **结构化上下文载荷 `contextPayload`**
|
||
2. **统一媒体解析层 `resolveChatMediaInput()`**
|
||
3. **目标资产解析技能 `resolveTargetAssetsSkill()`**
|
||
4. **统一资产注册层 `registerResultAssets()`**
|
||
5. **持久化工作记忆 `ConversationWorkingState`**
|
||
|
||
---
|
||
|
||
## 5. 基于当前项目的模块落点
|
||
|
||
## 5.1 后端新增目录建议
|
||
|
||
建议新增:
|
||
|
||
```txt
|
||
lib/ai/context-memory/
|
||
types.ts
|
||
media-input.ts
|
||
working-memory.ts
|
||
asset-registry.ts
|
||
asset-summary.ts
|
||
target-resolution.ts
|
||
prompt-memory.ts
|
||
compact.ts
|
||
skills/
|
||
resolve-target-assets.ts
|
||
summarize-asset.ts
|
||
write-user-memory.ts
|
||
```
|
||
|
||
说明:
|
||
|
||
- `lib/ai/context-memory/*` 负责聊天连续记忆、图片目标定位、资产图谱
|
||
- `skills/` 设计方式参考 `lib/video-agent/skills/*`
|
||
- `auto-agent-graph`、`smart-analyzer` 不要再各自重复扫描 message parts,而是统一消费这里的解析结果
|
||
|
||
## 5.2 前端改造落点
|
||
|
||
### A. `components/canvas/CanvasChatPanel.tsx`
|
||
|
||
当前已有 `autoContext`,建议升级为双通道:
|
||
|
||
1. **保留 `autoContext`**
|
||
- 继续给模型看
|
||
- 作用是让模型更自然地理解“左上、右边、最近几张”
|
||
|
||
2. **新增 `contextPayload`**
|
||
- 这是给后端规则引擎用的机器结构
|
||
- 不再依赖自然语言字符串解析
|
||
|
||
### B. `store/canvas/index.ts`
|
||
|
||
当前已经持有:
|
||
|
||
- `nodes`
|
||
- `selectedNodeIds`
|
||
- `projectId`
|
||
|
||
这些数据足够构造一份结构化上下文快照,不需要新建前端全局状态。
|
||
|
||
### C. `app/canvas/[id]/page.tsx`
|
||
|
||
后续需要在画布保存或图片节点生成完成时,增加:
|
||
|
||
- 资产绑定上报
|
||
- 画布节点镜像同步
|
||
|
||
---
|
||
|
||
## 6. 请求结构升级设计
|
||
|
||
## 6.1 当前问题
|
||
|
||
现在 `app/(chat)/api/chat/schema.ts` 中请求体只有:
|
||
|
||
- `message`
|
||
- `hiddenPrompt`
|
||
- `autoContext`
|
||
- `selectedChatModel`
|
||
- `videoParams`
|
||
- `imageParams`
|
||
|
||
这意味着:
|
||
|
||
- `autoContext` 是非结构化文本
|
||
- 后端无法可靠知道“选中了哪几个 nodeId”
|
||
- 也无法知道它们是否对应同一轮结果资产
|
||
|
||
## 6.2 新增 `contextPayload`
|
||
|
||
建议在 `app/(chat)/api/chat/schema.ts` 增加:
|
||
|
||
```ts
|
||
const contextPayloadSchema = z.object({
|
||
source: z.enum(["chat", "canvas", "realtime-canvas"]).default("chat"),
|
||
projectId: z.string().uuid().optional(),
|
||
selectedNodeIds: z.array(z.string()).max(20).optional(),
|
||
selectedAssetIds: z.array(z.string().uuid()).max(20).optional(),
|
||
focusPolicy: z.enum(["auto", "selected", "latest_result", "original"]).optional(),
|
||
canvasImageNodes: z
|
||
.array(
|
||
z.object({
|
||
nodeId: z.string(),
|
||
url: z.string().url(),
|
||
title: z.string().optional(),
|
||
prompt: z.string().optional(),
|
||
x: z.number(),
|
||
y: z.number(),
|
||
width: z.number().optional(),
|
||
height: z.number().optional(),
|
||
isSelected: z.boolean().optional(),
|
||
})
|
||
)
|
||
.max(20)
|
||
.optional(),
|
||
}).optional();
|
||
```
|
||
|
||
然后将它挂入 `postRequestBodySchema`。
|
||
|
||
## 6.3 为什么必须要有 `contextPayload`
|
||
|
||
因为当前系统中“图片定位”存在三种来源:
|
||
|
||
1. 本轮消息上传图片
|
||
2. 画布当前选中图片
|
||
3. 历史消息里最近结果图
|
||
|
||
只靠 `autoContext` 文本,服务端无法稳定地区分:
|
||
|
||
- 当前选中图
|
||
- 最近结果图
|
||
- 原始上传图
|
||
- 画布上左上角那张图
|
||
|
||
结构化字段可以直接让服务端先走规则,再把自然语言上下文交给模型。
|
||
|
||
---
|
||
|
||
## 7. 新增统一资产索引层:为什么不能只用现有业务表
|
||
|
||
## 7.1 现有表的局限
|
||
|
||
### `GeneratedImage`
|
||
|
||
优点:
|
||
|
||
- 适合相册、历史统计、精选榜
|
||
|
||
问题:
|
||
|
||
- 只覆盖图片结果
|
||
- 不覆盖上传图、画布图、视频结果、3D 预览
|
||
- 不表达“这张图由哪张图编辑而来”
|
||
|
||
### `canvasProject.canvasData`
|
||
|
||
优点:
|
||
|
||
- 是画布完整状态快照
|
||
|
||
问题:
|
||
|
||
- 更适合渲染,不适合高效检索
|
||
- 不适合做“连续对话目标图片定位”的服务端索引
|
||
|
||
### `CanvasNode`
|
||
|
||
优点:
|
||
|
||
- 已经存在,天然适合做画布节点镜像
|
||
|
||
问题:
|
||
|
||
- 当前还没接入主流程
|
||
- 现在不能直接拿来做可靠资产定位
|
||
|
||
### `Model3DAsset` / `VideoStoryboardTask`
|
||
|
||
优点:
|
||
|
||
- 适合各自业务模块持久化
|
||
|
||
问题:
|
||
|
||
- 是垂直业务表,不是统一资产层
|
||
|
||
## 7.2 结论
|
||
|
||
应该保留现有业务表不动,同时新增一层**统一媒体资产索引层**,专门服务:
|
||
|
||
- 上下文记忆
|
||
- 目标图片定位
|
||
- 版本链追踪
|
||
- 资产关系检索
|
||
- 连续多轮编辑
|
||
|
||
---
|
||
|
||
## 8. 数据库表结构设计
|
||
|
||
以下表结构是基于当前 `lib/db/schema.ts` 风格设计的,建议新增迁移文件:
|
||
|
||
- `lib/db/migrations/0050_add_context_memory_asset_graph.sql`
|
||
|
||
## 8.1 `MediaAsset`:统一媒体资产表
|
||
|
||
用途:
|
||
|
||
- 将上传图、生成图、画布图、视频封面、视频结果、3D 预览统一注册成资产
|
||
|
||
推荐字段:
|
||
|
||
```ts
|
||
export const mediaAsset = pgTable("MediaAsset", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
|
||
projectId: uuid("projectId").references(() => canvasProject.id, { onDelete: "cascade" }),
|
||
|
||
mediaType: varchar("mediaType", { length: 32 }).notNull(), // image | video | model3d | file
|
||
sourceType: varchar("sourceType", { length: 32 }).notNull(), // upload | generated | canvas | storyboard_tile | storyboard_video | model3d_preview
|
||
status: varchar("status", { length: 32 }).notNull().default("ready"),
|
||
|
||
originalUrl: text("originalUrl").notNull(),
|
||
previewUrl: text("previewUrl"),
|
||
thumbnailUrl: text("thumbnailUrl"),
|
||
mimeType: varchar("mimeType", { length: 128 }),
|
||
|
||
width: integer("width"),
|
||
height: integer("height"),
|
||
durationMs: integer("durationMs"),
|
||
|
||
displayName: varchar("displayName", { length: 256 }),
|
||
prompt: text("prompt"),
|
||
modelId: varchar("modelId", { length: 128 }),
|
||
|
||
sourceMessageId: uuid("sourceMessageId").references(() => message.id, { onDelete: "set null" }),
|
||
sourceTable: varchar("sourceTable", { length: 64 }),
|
||
sourceRowId: varchar("sourceRowId", { length: 128 }),
|
||
sourceNodeId: varchar("sourceNodeId", { length: 128 }),
|
||
|
||
rootAssetId: uuid("rootAssetId"),
|
||
primaryParentAssetId: uuid("primaryParentAssetId"),
|
||
|
||
summary: jsonb("summary"),
|
||
metadata: jsonb("metadata"),
|
||
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
### 为什么需要 `MediaAsset`
|
||
|
||
因为以后所有定位逻辑都应该先查统一资产层,而不是到处扫:
|
||
|
||
- `GeneratedImage`
|
||
- `Message_v2.parts`
|
||
- `canvasProject.canvasData`
|
||
- `Model3DAsset`
|
||
- `VideoStoryboardShotTask`
|
||
|
||
## 8.2 `MediaAssetRelation`:资产关系表
|
||
|
||
用途:
|
||
|
||
- 建立版本链、原图/结果图、画布绑定、引用关系
|
||
|
||
推荐字段:
|
||
|
||
```ts
|
||
export const mediaAssetRelation = pgTable("MediaAssetRelation", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
|
||
projectId: uuid("projectId").references(() => canvasProject.id, { onDelete: "cascade" }),
|
||
|
||
fromAssetId: uuid("fromAssetId").notNull(),
|
||
toAssetId: uuid("toAssetId").notNull(),
|
||
relationType: varchar("relationType", { length: 64 }).notNull(),
|
||
// edited_from | generated_from | reference_of | selected_in_turn | latest_result_of | located_on_canvas | derived_preview | upscaled_from
|
||
|
||
sourceMessageId: uuid("sourceMessageId").references(() => message.id, { onDelete: "set null" }),
|
||
sourceNodeId: varchar("sourceNodeId", { length: 128 }),
|
||
confidence: integer("confidence").default(100),
|
||
metadata: jsonb("metadata"),
|
||
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
### 这张表主要解决什么
|
||
|
||
例如系统以后要知道:
|
||
|
||
- 这张图是不是原图
|
||
- 这张图是不是上一轮结果
|
||
- 这张图是不是由“抠图后产品图”裂变出来的
|
||
- 这张图是否被放到了画布某个节点上
|
||
|
||
都不应该再靠 prompt 猜,而应该查关系表。
|
||
|
||
## 8.3 `ConversationWorkingState`:会话工作记忆表
|
||
|
||
用途:
|
||
|
||
- 保存当前会话的短期状态
|
||
- 专门服务“连续修改这张图”
|
||
|
||
推荐字段:
|
||
|
||
```ts
|
||
export const conversationWorkingState = pgTable("ConversationWorkingState", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
chatId: uuid("chatId").notNull().references(() => chat.id, { onDelete: "cascade" }),
|
||
projectId: uuid("projectId").references(() => canvasProject.id, { onDelete: "cascade" }),
|
||
|
||
currentIntent: varchar("currentIntent", { length: 64 }),
|
||
currentSubIntent: varchar("currentSubIntent", { length: 64 }),
|
||
|
||
currentFocusAssetIds: jsonb("currentFocusAssetIds").$type<string[]>().notNull().default([]),
|
||
selectedAssetIds: jsonb("selectedAssetIds").$type<string[]>().notNull().default([]),
|
||
lastUploadedAssetIds: jsonb("lastUploadedAssetIds").$type<string[]>().notNull().default([]),
|
||
lastGeneratedAssetIds: jsonb("lastGeneratedAssetIds").$type<string[]>().notNull().default([]),
|
||
lastReferencedAssetIds: jsonb("lastReferencedAssetIds").$type<string[]>().notNull().default([]),
|
||
lastResolvedTargetAssetIds: jsonb("lastResolvedTargetAssetIds").$type<string[]>().notNull().default([]),
|
||
|
||
activeCanvasNodeIds: jsonb("activeCanvasNodeIds").$type<string[]>().notNull().default([]),
|
||
lastCanvasSnapshot: jsonb("lastCanvasSnapshot"),
|
||
|
||
lastTurnSummary: jsonb("lastTurnSummary"),
|
||
lastMessageId: uuid("lastMessageId"),
|
||
turnCount: integer("turnCount").notNull().default(0),
|
||
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
### 为什么不建议塞进 `Chat.lastContext`
|
||
|
||
当前 `Chat.lastContext` 的语义更接近 usage / 上下文结果缓存,不适合承载复杂的记忆状态。
|
||
|
||
工作记忆应该独立成表,便于:
|
||
|
||
- 单独更新
|
||
- 单独调试
|
||
- 单独回滚
|
||
- 单独做 TTL / 压缩
|
||
|
||
## 8.4 `ConversationSummaryMemory`:会话摘要记忆表
|
||
|
||
用途:
|
||
|
||
- 长对话压缩
|
||
- 记录某个阶段已达成的结果
|
||
|
||
```ts
|
||
export const conversationSummaryMemory = pgTable("ConversationSummaryMemory", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
chatId: uuid("chatId").notNull().references(() => chat.id, { onDelete: "cascade" }),
|
||
projectId: uuid("projectId").references(() => canvasProject.id, { onDelete: "cascade" }),
|
||
|
||
summaryType: varchar("summaryType", { length: 32 }).notNull().default("turn"),
|
||
// turn | compact | milestone
|
||
|
||
sourceMessageIds: jsonb("sourceMessageIds").$type<string[]>().notNull().default([]),
|
||
relatedAssetIds: jsonb("relatedAssetIds").$type<string[]>().notNull().default([]),
|
||
|
||
summaryText: text("summaryText").notNull(),
|
||
structuredSummary: jsonb("structuredSummary"),
|
||
extractedPreferences: jsonb("extractedPreferences"),
|
||
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
## 8.5 `UserMemoryFact`:用户长期偏好记忆表
|
||
|
||
用途:
|
||
|
||
- 保存稳定偏好,不保存一时性指令
|
||
|
||
```ts
|
||
export const userMemoryFact = pgTable("UserMemoryFact", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
|
||
scope: varchar("scope", { length: 32 }).notNull().default("global"),
|
||
// global | image | video | canvas | detail
|
||
|
||
factType: varchar("factType", { length: 32 }).notNull(),
|
||
// preference | constraint | style | workflow | domain
|
||
|
||
memoryKey: varchar("memoryKey", { length: 128 }).notNull(),
|
||
memoryValue: jsonb("memoryValue").notNull(),
|
||
|
||
confidence: integer("confidence").notNull().default(60),
|
||
sourceType: varchar("sourceType", { length: 32 }).notNull().default("inferred"),
|
||
// inferred | manual | imported
|
||
|
||
sourceChatId: uuid("sourceChatId").references(() => chat.id, { onDelete: "set null" }),
|
||
sourceMessageId: uuid("sourceMessageId").references(() => message.id, { onDelete: "set null" }),
|
||
|
||
isPinned: boolean("isPinned").notNull().default(false),
|
||
expiresAt: timestamp("expiresAt"),
|
||
lastConfirmedAt: timestamp("lastConfirmedAt"),
|
||
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
## 8.6 `MessageTargetResolution`:每轮目标定位审计表
|
||
|
||
用途:
|
||
|
||
- 记录“这一轮系统到底把目标判成了哪张图”
|
||
- 是后续排障和持续优化的关键
|
||
|
||
```ts
|
||
export const messageTargetResolution = pgTable("MessageTargetResolution", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId").notNull().references(() => user.id, { onDelete: "cascade" }),
|
||
chatId: uuid("chatId").notNull().references(() => chat.id, { onDelete: "cascade" }),
|
||
messageId: uuid("messageId").references(() => message.id, { onDelete: "set null" }),
|
||
projectId: uuid("projectId").references(() => canvasProject.id, { onDelete: "set null" }),
|
||
|
||
rawText: text("rawText").notNull(),
|
||
uploadedAssetIds: jsonb("uploadedAssetIds").$type<string[]>().notNull().default([]),
|
||
selectedAssetIds: jsonb("selectedAssetIds").$type<string[]>().notNull().default([]),
|
||
candidateAssetIds: jsonb("candidateAssetIds").$type<string[]>().notNull().default([]),
|
||
resolvedAssetIds: jsonb("resolvedAssetIds").$type<string[]>().notNull().default([]),
|
||
|
||
resolutionMode: varchar("resolutionMode", { length: 64 }).notNull(),
|
||
// explicit_selection | current_upload | text_reference | working_memory | latest_result | llm_disambiguation | clarification
|
||
|
||
confidence: integer("confidence").notNull().default(0),
|
||
needsClarification: boolean("needsClarification").notNull().default(false),
|
||
reasoning: jsonb("reasoning"),
|
||
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 与现有表的关系设计
|
||
|
||
### 9.1 现有表继续保留
|
||
|
||
以下表继续作为业务源表存在:
|
||
|
||
- `GeneratedImage`
|
||
- `CanvasProject`
|
||
- `CanvasNode`
|
||
- `Model3DAsset`
|
||
- `VideoStoryboardTask`
|
||
- `VideoStoryboardShotTask`
|
||
|
||
### 9.2 新表的角色
|
||
|
||
新增记忆表不是替代现有表,而是充当:
|
||
|
||
- **上下文索引层**
|
||
- **版本链关系层**
|
||
- **连续编辑状态层**
|
||
|
||
### 9.3 推荐的对应关系
|
||
|
||
| 现有业务来源 | 新索引写入 |
|
||
| --- | --- |
|
||
| 用户上传图片 | `MediaAsset(sourceType=upload)` |
|
||
| 生图成功写入 `GeneratedImage` | 同时写 `MediaAsset(sourceType=generated)` |
|
||
| 画布图片节点生成/落位 | 写 `CanvasNode` 镜像 + `MediaAssetRelation(located_on_canvas)` |
|
||
| 3D 预览图/模型 | 写 `MediaAsset(sourceType=model3d_preview / model3d)` |
|
||
| 九宫格切图 | 写 `MediaAsset(sourceType=storyboard_tile)` |
|
||
| 视频结果 | 写 `MediaAsset(sourceType=storyboard_video)` |
|
||
|
||
### 9.4 建议激活 `CanvasNode` 表
|
||
|
||
当前 `CanvasNode` 表已经存在但未被使用,建议正式启用:
|
||
|
||
1. `app/api/canvas/[id]/route.ts` 在保存 `canvasData` 后,同步镜像主要节点到 `CanvasNode`
|
||
2. 重点同步:
|
||
- `image`
|
||
- `video`
|
||
- `model3d`
|
||
3. `CanvasNode.data` 中新增:
|
||
- `assetId`
|
||
- `originalSrc`
|
||
- `prompt`
|
||
- `title`
|
||
|
||
这样服务端才能快速知道:
|
||
|
||
- 当前画布有哪些规范化节点
|
||
- 哪个 nodeId 对应哪个 assetId
|
||
- 左上/右下到底是哪张图
|
||
|
||
---
|
||
|
||
## 10. 资产摘要设计
|
||
|
||
## 10.1 为什么每张图都需要摘要
|
||
|
||
用户会说:
|
||
|
||
- “把刚才那张带模特的图背景换白”
|
||
- “不要改原图,改上一个结果”
|
||
- “左上角那个红色椅子再清晰一点”
|
||
|
||
这时只靠 URL 完全不够,系统需要每张资产都有一份结构化摘要。
|
||
|
||
## 10.2 摘要结构建议
|
||
|
||
`MediaAsset.summary` 建议存:
|
||
|
||
```ts
|
||
type AssetSummary = {
|
||
subject?: string;
|
||
productName?: string;
|
||
category?: string;
|
||
scene?: string;
|
||
style?: string;
|
||
viewAngle?: string;
|
||
backgroundType?: string;
|
||
dominantColors?: string[];
|
||
containsHuman?: boolean;
|
||
containsText?: boolean;
|
||
isCutoutLike?: boolean;
|
||
isLatestResult?: boolean;
|
||
shortCaption?: string;
|
||
};
|
||
```
|
||
|
||
## 10.3 摘要生成策略
|
||
|
||
### 第一层:规则补全
|
||
|
||
从已有信息快速写入:
|
||
|
||
- prompt
|
||
- modelId
|
||
- node title
|
||
- metadata
|
||
- 画布位置
|
||
|
||
### 第二层:多模态模型摘要
|
||
|
||
若项目后台已经配置多模态模型,则调用该模型生成结构化摘要。
|
||
|
||
这里建议直接复用当前项目已有的多模态模型选择体系,参考:
|
||
|
||
- `lib/ai/smart-analyzer.ts`
|
||
- `lib/db/ai-model-flags.ts`
|
||
|
||
要求:
|
||
|
||
- 优先使用已启用的多模态模型
|
||
- 仅生成结构化摘要,不让模型自由发挥长文本
|
||
- 超时则降级到规则摘要
|
||
|
||
### 第三层:延迟异步补全
|
||
|
||
对于非关键时刻,不要阻塞主链路:
|
||
|
||
- 先写基础摘要
|
||
- 再异步补 richer summary
|
||
|
||
---
|
||
|
||
## 11. 目标图片定位算法设计
|
||
|
||
## 11.1 统一输出结构
|
||
|
||
建议新增:
|
||
|
||
```ts
|
||
type ResolvedTargetAssets = {
|
||
targetAssetIds: string[];
|
||
candidateAssetIds: string[];
|
||
resolutionMode:
|
||
| "explicit_selection"
|
||
| "current_upload"
|
||
| "text_reference"
|
||
| "working_memory"
|
||
| "latest_result"
|
||
| "llm_disambiguation"
|
||
| "clarification";
|
||
confidence: number;
|
||
needsClarification: boolean;
|
||
reason: string;
|
||
};
|
||
```
|
||
|
||
## 11.2 优先级规则
|
||
|
||
### 规则 1:前端明确选中优先
|
||
|
||
若 `contextPayload.selectedAssetIds` 非空:
|
||
|
||
- 直接作为第一目标
|
||
- 除非用户文本明确说“不要改选中的图,改原图/上一轮结果”
|
||
|
||
### 规则 2:本轮上传图片优先
|
||
|
||
若本轮消息有图片上传:
|
||
|
||
- 默认优先处理这些上传图
|
||
|
||
### 规则 3:文本显式指代优先
|
||
|
||
解析:
|
||
|
||
- “左边那张 / 右上角那张 / 中间那张”
|
||
- “第二张 / 第三张”
|
||
- “刚生成的那张 / 上一轮结果”
|
||
- “原图 / 最初那张 / 参考图”
|
||
|
||
此时在候选集内通过规则命中:
|
||
|
||
- 画布位置
|
||
- 版本链
|
||
- 最近结果
|
||
- 原始上传链
|
||
|
||
### 规则 4:Working Memory 回退
|
||
|
||
若没有新上传,也没有显式指代:
|
||
|
||
按顺序回退:
|
||
|
||
1. `currentFocusAssetIds`
|
||
2. `lastResolvedTargetAssetIds`
|
||
3. `lastGeneratedAssetIds`
|
||
4. `lastUploadedAssetIds`
|
||
|
||
### 规则 5:模型仅做歧义消解
|
||
|
||
只有下面情况才调用模型:
|
||
|
||
- 候选图有多张且规则打平
|
||
- 用户说“它 / 这个版本 / 那个更自然的”
|
||
- 文本指代不足但候选摘要足够清晰
|
||
|
||
模型输入不应该是全量原始上下文,而应该是:
|
||
|
||
- 用户本轮文本
|
||
- 候选资产摘要列表
|
||
- 候选图位置
|
||
- 与当前轮次距离
|
||
- 是否为原图 / 结果图 / 选中图
|
||
|
||
输出只允许是结构化结果:
|
||
|
||
```json
|
||
{
|
||
"assetId": "uuid",
|
||
"confidence": 0.86,
|
||
"needsClarification": false
|
||
}
|
||
```
|
||
|
||
## 11.3 必须支持的指代表达
|
||
|
||
建议首批覆盖:
|
||
|
||
- 方位:左 / 右 / 上 / 下 / 中间 / 左上 / 右下
|
||
- 序号:第一张 / 第二张 / 第三个
|
||
- 结果链:原图 / 上一轮 / 最新生成 / 这个结果
|
||
- 选择态:选中的 / 当前这张 / 我选的这几张
|
||
|
||
## 11.4 多图模式
|
||
|
||
如果用户明确是批量操作:
|
||
|
||
- “这三张都去背景”
|
||
- “选中的几张统一换白底”
|
||
|
||
则 `targetAssetIds` 可以返回多个。
|
||
|
||
这将直接服务你们现有:
|
||
|
||
- 批量处理
|
||
- 产品裂变
|
||
- 多产品替换
|
||
- 多图统一编辑
|
||
|
||
---
|
||
|
||
## 12. 与当前聊天链路的集成方式
|
||
|
||
## 12.1 推荐新增统一媒体解析层
|
||
|
||
建议新增:
|
||
|
||
```ts
|
||
resolveChatMediaInput(params: {
|
||
messageParts: any[];
|
||
historyMessages: DBMessage[];
|
||
contextPayload?: ContextPayload;
|
||
requestOrigin?: string;
|
||
}): Promise<ResolvedChatMediaInput>
|
||
```
|
||
|
||
输出示例:
|
||
|
||
```ts
|
||
type ResolvedMediaRef = {
|
||
url: string;
|
||
assetId?: string;
|
||
source:
|
||
| "current_message"
|
||
| "canvas_selection"
|
||
| "history_user_upload"
|
||
| "history_assistant_result";
|
||
role: "upload" | "selected" | "reference" | "history_result";
|
||
nodeId?: string;
|
||
messageId?: string;
|
||
};
|
||
|
||
type ResolvedChatMediaInput = {
|
||
mediaRefs: ResolvedMediaRef[];
|
||
hasImage: boolean;
|
||
uploadedAssetIds: string[];
|
||
selectedAssetIds: string[];
|
||
historyCandidateAssetIds: string[];
|
||
};
|
||
```
|
||
|
||
## 12.2 在 `app/(chat)/api/chat/route.ts` 中的落点
|
||
|
||
建议将当前分散逻辑收口为以下顺序:
|
||
|
||
1. 取历史消息
|
||
2. `resolveChatMediaInput()`
|
||
3. `loadConversationWorkingState()`
|
||
4. `resolveTargetAssetsSkill()`
|
||
5. `analyzeIntentStructured()`
|
||
6. 决定:
|
||
- 轻量链路
|
||
- 重型 Agent 链路
|
||
7. 执行成功后:
|
||
- `registerResultAssets()`
|
||
- `updateConversationWorkingState()`
|
||
- `appendMessageTargetResolution()`
|
||
|
||
## 12.3 不要再让 fallback 重复各自扫图
|
||
|
||
当前项目已有多个 fallback / rescue / summary 路径。
|
||
|
||
升级后要求统一:
|
||
|
||
- 所有链路一律消费 `ResolvedChatMediaInput`
|
||
- 不允许某条链路再自己从 `message.parts` 重新扫描图片
|
||
|
||
否则就会再次出现:
|
||
|
||
- 主链路认定 A 图
|
||
- fallback 链路又重新认定成 B 图
|
||
|
||
---
|
||
|
||
## 13. 与 `smart-analyzer` 的关系
|
||
|
||
## 13.1 当前问题
|
||
|
||
当前 `lib/ai/smart-analyzer.ts` 同时承担:
|
||
|
||
- 意图分析
|
||
- 连续对话判断
|
||
- 自然语言回复
|
||
- 视觉图像分析
|
||
- fallback 逻辑
|
||
|
||
这会导致“目标资产定位”和“意图判断”耦合过深。
|
||
|
||
## 13.2 推荐拆分
|
||
|
||
建议拆为三层:
|
||
|
||
### A. `resolveTargetAssetsSkill()`
|
||
|
||
只负责回答:
|
||
|
||
- 这轮改哪张图
|
||
- 是原图还是上一轮结果
|
||
- 是单图还是多图
|
||
|
||
### B. `analyzeIntentStructured()`
|
||
|
||
只负责回答:
|
||
|
||
- image_edit / image_gen / video_gen / vision / chat
|
||
- subIntent
|
||
- confidence
|
||
|
||
### C. `buildFriendlyExecutionReply()`
|
||
|
||
只负责用户可见回复:
|
||
|
||
- “收到,我先按你选中的图继续优化”
|
||
- “收到,我按上一轮结果继续处理”
|
||
|
||
这样以后如果定位错图,可以独立检查:
|
||
|
||
- 是资产定位错了
|
||
- 还是意图分析错了
|
||
|
||
---
|
||
|
||
## 14. 记忆写入时机设计
|
||
|
||
## 14.1 用户上传图片成功后
|
||
|
||
触发位置:
|
||
|
||
- `app/(chat)/api/files/upload/route.ts`
|
||
|
||
建议:
|
||
|
||
- 上传成功后即注册 `MediaAsset(sourceType=upload)`
|
||
- 后续用户在聊天消息中引用该 URL 时,可直接回查为资产
|
||
|
||
## 14.2 生图/编辑结果成功后
|
||
|
||
触发位置:
|
||
|
||
- `app/(chat)/api/chat/route.ts` 的 `saveGeneratedImages()`
|
||
- `app/api/enhance/route.ts`
|
||
- `app/api/scene-fusion/route.ts`
|
||
- 其他图片处理接口
|
||
|
||
建议:
|
||
|
||
- 现有业务写 `GeneratedImage` 保持不变
|
||
- 同时写 `MediaAsset`
|
||
- 同时写 `MediaAssetRelation`
|
||
- `edited_from`
|
||
- `generated_from`
|
||
- `latest_result_of`
|
||
|
||
## 14.3 画布节点落位后
|
||
|
||
触发位置:
|
||
|
||
- `app/api/canvas/[id]/route.ts`
|
||
- 或新增专门绑定接口
|
||
|
||
建议:
|
||
|
||
- 同步镜像到 `CanvasNode`
|
||
- 对应资产写 `MediaAssetRelation(located_on_canvas)`
|
||
|
||
## 14.4 每轮聊天处理结束后
|
||
|
||
触发位置:
|
||
|
||
- `app/(chat)/api/chat/route.ts`
|
||
|
||
建议写:
|
||
|
||
- `ConversationWorkingState`
|
||
- `MessageTargetResolution`
|
||
|
||
## 14.5 对话过长时
|
||
|
||
触发位置:
|
||
|
||
- 每 N 轮
|
||
- 或 token 接近阈值
|
||
- 或任务完成时
|
||
|
||
建议写:
|
||
|
||
- `ConversationSummaryMemory`
|
||
- 高置信稳定偏好写 `UserMemoryFact`
|
||
|
||
---
|
||
|
||
## 15. 长期记忆写入规则
|
||
|
||
## 15.1 哪些内容应该进入长期记忆
|
||
|
||
适合写入 `UserMemoryFact`:
|
||
|
||
- 用户长期偏好白底商品图
|
||
- 用户常用 1:1 或 4:5
|
||
- 用户偏好高清/写实/自然光
|
||
- 用户常做产品裂变、详情图、场景融合
|
||
|
||
## 15.2 哪些内容不应该进入长期记忆
|
||
|
||
不要写入长期记忆:
|
||
|
||
- “把这张图背景换蓝”
|
||
- “这一轮不要加文字”
|
||
- “这张先试一下”
|
||
|
||
这些是当前任务指令,不是长期偏好。
|
||
|
||
## 15.3 写入条件
|
||
|
||
推荐至少满足一项:
|
||
|
||
1. 同类偏好连续出现 >= 3 次
|
||
2. 用户明确说“以后都按这个来”
|
||
3. 管理后台人工确认/置顶
|
||
|
||
---
|
||
|
||
## 16. 结合无限画布的特殊设计
|
||
|
||
## 16.1 当前画布上下文的优点与不足
|
||
|
||
当前 `CanvasChatPanel` 已经能知道:
|
||
|
||
- 当前画布有哪些图
|
||
- 哪几张图被选中
|
||
- 大概的上下位置
|
||
|
||
但它现在主要是转成自然语言文本给模型。
|
||
|
||
## 16.2 建议升级方式
|
||
|
||
### 前端继续生成自然语言 `autoContext`
|
||
|
||
用途:
|
||
|
||
- 给模型理解
|
||
- 提升对“左上角那张”这类表达的自然语言能力
|
||
|
||
### 同时新增结构化 `contextPayload`
|
||
|
||
用途:
|
||
|
||
- 服务端规则定位
|
||
- 稳定知道哪个 `nodeId` 正在被处理
|
||
|
||
### 资产与节点绑定
|
||
|
||
建议在画布图片节点数据里加入:
|
||
|
||
```ts
|
||
type ImageNodeData = {
|
||
src: string;
|
||
assetId?: string;
|
||
originalSrc?: string;
|
||
title?: string;
|
||
prompt?: string;
|
||
...
|
||
}
|
||
```
|
||
|
||
这样:
|
||
|
||
- 前端选中 node 时可直接把 `assetId` 传给后端
|
||
- 不需要只靠 URL 反查
|
||
|
||
## 16.3 与当前类型文件的关系
|
||
|
||
对应改动点:
|
||
|
||
- `types/canvas/index.ts`
|
||
- `components/canvas/CanvasChatPanel.tsx`
|
||
- `components/canvas/nodes/ImageNode.tsx`
|
||
|
||
---
|
||
|
||
## 17. 建议封装成技能
|
||
|
||
你之前希望这类复杂能力像“大龙虾 / 技能系统”一样可拆可扩展,这个方向是对的。
|
||
|
||
推荐把记忆与定位拆为以下技能:
|
||
|
||
## 17.1 `resolve-target-assets` 技能
|
||
|
||
职责:
|
||
|
||
- 输入:本轮文本 + 结构化上下文 + Working Memory + 候选资产
|
||
- 输出:本轮目标资产列表
|
||
|
||
## 17.2 `summarize-asset` 技能
|
||
|
||
职责:
|
||
|
||
- 输入:图片 URL / 资产 ID
|
||
- 输出:结构化摘要
|
||
|
||
## 17.3 `write-user-memory` 技能
|
||
|
||
职责:
|
||
|
||
- 从多轮摘要中抽取长期偏好
|
||
|
||
## 17.4 `compact-conversation-memory` 技能
|
||
|
||
职责:
|
||
|
||
- 将长对话压缩成摘要记忆
|
||
|
||
## 17.5 为什么要技能化
|
||
|
||
因为后续你们还会继续增加:
|
||
|
||
- 产品裂变 Agent
|
||
- 九宫格视频 Agent
|
||
- 3D Agent
|
||
- AI 详情 Agent
|
||
|
||
把“记忆”“目标定位”“摘要”封装成可复用技能,才能让这些能力被不同 Agent 共享。
|
||
|
||
---
|
||
|
||
## 18. 具体到当前仓库的实施文件清单
|
||
|
||
以下是推荐的首批改造文件清单。
|
||
|
||
## 18.1 必改文件
|
||
|
||
### `app/(chat)/api/chat/schema.ts`
|
||
|
||
新增:
|
||
|
||
- `contextPayload`
|
||
|
||
### `components/canvas/CanvasChatPanel.tsx`
|
||
|
||
新增:
|
||
|
||
- `buildContextPayload()`
|
||
- 发送 `contextPayload`
|
||
- 选中节点优先携带 `assetId`
|
||
|
||
### `app/(chat)/api/chat/route.ts`
|
||
|
||
新增:
|
||
|
||
- `resolveChatMediaInput()`
|
||
- `loadConversationWorkingState()`
|
||
- `resolveTargetAssetsSkill()`
|
||
- `updateConversationWorkingState()`
|
||
- `appendMessageTargetResolution()`
|
||
|
||
### `lib/ai/smart-analyzer.ts`
|
||
|
||
调整:
|
||
|
||
- 不再单独承担目标资产定位
|
||
- 接收已解析好的目标资产上下文
|
||
|
||
### `types/canvas/index.ts`
|
||
|
||
新增:
|
||
|
||
- `assetId?: string` 到图片/视频/3D 节点数据
|
||
|
||
### `app/api/canvas/[id]/route.ts`
|
||
|
||
增强:
|
||
|
||
- 保存画布时同步镜像 `CanvasNode`
|
||
|
||
### `lib/db/schema.ts`
|
||
|
||
新增:
|
||
|
||
- `MediaAsset`
|
||
- `MediaAssetRelation`
|
||
- `ConversationWorkingState`
|
||
- `ConversationSummaryMemory`
|
||
- `UserMemoryFact`
|
||
- `MessageTargetResolution`
|
||
|
||
### `lib/db/queries.ts`
|
||
|
||
新增:
|
||
|
||
- 资产注册
|
||
- 关系写入
|
||
- Working Memory 读写
|
||
- Resolution 审计写入
|
||
- Long-term Memory 检索
|
||
|
||
## 18.2 建议新增文件
|
||
|
||
```txt
|
||
lib/ai/context-memory/types.ts
|
||
lib/ai/context-memory/media-input.ts
|
||
lib/ai/context-memory/working-memory.ts
|
||
lib/ai/context-memory/asset-registry.ts
|
||
lib/ai/context-memory/asset-summary.ts
|
||
lib/ai/context-memory/target-resolution.ts
|
||
lib/ai/context-memory/compact.ts
|
||
lib/ai/context-memory/skills/resolve-target-assets.ts
|
||
lib/ai/context-memory/skills/summarize-asset.ts
|
||
lib/ai/context-memory/skills/write-user-memory.ts
|
||
```
|
||
|
||
---
|
||
|
||
## 19. 回填与迁移策略
|
||
|
||
## 19.1 为什么必须做回填
|
||
|
||
如果新系统上线后只记录新数据,那么历史聊天里的连续编辑仍然会不稳定。
|
||
|
||
因此建议做一次回填任务。
|
||
|
||
## 19.2 回填顺序
|
||
|
||
### 第一步:回填 `GeneratedImage`
|
||
|
||
把现有图库全部注册到 `MediaAsset`。
|
||
|
||
### 第二步:回填近期聊天消息
|
||
|
||
从 `Message_v2.parts` 和 assistant 文本 Markdown 中抽取历史图片 URL,建立初步映射。
|
||
|
||
### 第三步:回填 `Model3DAsset`
|
||
|
||
把 3D 资产预览图 / 模型地址写为统一资产。
|
||
|
||
### 第四步:回填 `VideoStoryboardShotTask`
|
||
|
||
把切片图、结果视频写为统一资产。
|
||
|
||
### 第五步:回填近期 `canvasProject.canvasData`
|
||
|
||
将近期画布的节点镜像同步到 `CanvasNode`。
|
||
|
||
## 19.3 回填任务建议
|
||
|
||
建议新增 Job:
|
||
|
||
```txt
|
||
lib/jobs/backfill-media-assets-job.ts
|
||
app/api/jobs/backfill-media-assets/route.ts
|
||
```
|
||
|
||
---
|
||
|
||
## 20. 性能与可用性设计
|
||
|
||
## 20.1 不建议首期引入新基础设施
|
||
|
||
当前项目已经依赖 PostgreSQL,首期不建议强依赖:
|
||
|
||
- 新向量数据库
|
||
- 新搜索服务
|
||
- 新消息总线
|
||
|
||
首期完全可以基于 Postgres + JSONB + 现有服务完成。
|
||
|
||
## 20.2 检索策略建议
|
||
|
||
首期:
|
||
|
||
- 精确 ID
|
||
- URL 归一化
|
||
- 时间倒序
|
||
- 关系链回查
|
||
- 位置规则匹配
|
||
|
||
二期可选:
|
||
|
||
- `pg_trgm`
|
||
- `tsvector`
|
||
- `pgvector`
|
||
|
||
## 20.3 不阻塞主请求
|
||
|
||
主链路中同步执行的仅限:
|
||
|
||
- 当前轮目标资产定位
|
||
- Working Memory 更新
|
||
- Resolution 审计写入
|
||
|
||
异步执行:
|
||
|
||
- 富摘要生成
|
||
- 对话压缩
|
||
- 长期偏好抽取
|
||
- 历史回填
|
||
|
||
---
|
||
|
||
## 21. 监控指标与验收口径
|
||
|
||
## 21.1 必加日志
|
||
|
||
建议在聊天主链路打印:
|
||
|
||
- `chatId`
|
||
- `messageId`
|
||
- `hasCurrentUpload`
|
||
- `selectedAssetCount`
|
||
- `candidateAssetCount`
|
||
- `resolvedAssetCount`
|
||
- `resolutionMode`
|
||
- `resolutionConfidence`
|
||
- `needsClarification`
|
||
|
||
## 21.2 建议的核心指标
|
||
|
||
1. 目标资产一次命中率
|
||
2. “上一轮结果 / 原图”区分正确率
|
||
3. 多图批量操作正确率
|
||
4. 需澄清率
|
||
5. 错图返工率
|
||
6. 平均候选资产数量
|
||
|
||
## 21.3 首阶段验收标准
|
||
|
||
做到以下即可判定首阶段成功:
|
||
|
||
1. 用户连续 3~5 轮修改同一张图时,稳定命中同一资产链
|
||
2. 用户说“改原图 / 改上一轮结果”时可以区分
|
||
3. 用户在画布选中图片后,系统优先命中选中图
|
||
4. 批量选中多图时,支持多目标输出
|
||
5. 后台可追踪每轮到底命中了哪张图
|
||
|
||
---
|
||
|
||
## 22. 分阶段实施建议
|
||
|
||
## 22.1 P0:先止血
|
||
|
||
目标:
|
||
|
||
- 让连续编辑先稳定下来
|
||
|
||
范围:
|
||
|
||
1. 新增 `contextPayload`
|
||
2. 新增 `resolveChatMediaInput()`
|
||
3. 新增 `ConversationWorkingState`
|
||
4. 新增 `MessageTargetResolution`
|
||
5. 聊天主链路改为统一消费结构化媒体输入
|
||
|
||
## 22.2 P1:补统一资产层
|
||
|
||
目标:
|
||
|
||
- 让所有图都可被追踪
|
||
|
||
范围:
|
||
|
||
1. 新增 `MediaAsset`
|
||
2. 新增 `MediaAssetRelation`
|
||
3. 上传图、生图、画布图统一注册资产
|
||
4. 启用 `CanvasNode` 镜像
|
||
|
||
## 22.3 P2:补长期记忆与摘要
|
||
|
||
目标:
|
||
|
||
- 让系统不只是知道“改哪张图”,还知道“用户长期偏好什么”
|
||
|
||
范围:
|
||
|
||
1. 新增 `ConversationSummaryMemory`
|
||
2. 新增 `UserMemoryFact`
|
||
3. 新增资产摘要异步生成
|
||
4. 新增对话压缩
|
||
|
||
## 22.4 P3:补高级召回
|
||
|
||
目标:
|
||
|
||
- 让长期项目、多轮大画布也稳定
|
||
|
||
范围:
|
||
|
||
1. 关键词 / 全文检索增强
|
||
2. 可选 `pgvector`
|
||
3. 后台调试页
|
||
|
||
---
|
||
|
||
## 23. 推荐的开发顺序
|
||
|
||
建议严格按下面顺序实施:
|
||
|
||
1. `schema.ts` 新增表
|
||
2. `queries.ts` 新增记忆/资产 CRUD
|
||
3. `chat/schema.ts` 新增 `contextPayload`
|
||
4. `CanvasChatPanel.tsx` 发送结构化上下文
|
||
5. `resolveChatMediaInput()`
|
||
6. `resolveTargetAssetsSkill()`
|
||
7. `chat/route.ts` 主链路接入
|
||
8. 上传图/生图结果注册 `MediaAsset`
|
||
9. `CanvasNode` 镜像同步
|
||
10. 长期记忆与摘要异步化
|
||
|
||
---
|
||
|
||
## 24. 最终结论
|
||
|
||
对当前项目来说,要做到接近 Gemini 官方生图那种“连续对话不丢目标图”的体验,不能只继续堆模型提示词,必须补齐下面三层基础设施:
|
||
|
||
1. **结构化上下文载荷 `contextPayload`**
|
||
2. **统一资产索引层 `MediaAsset + MediaAssetRelation`**
|
||
3. **持久化短期记忆 `ConversationWorkingState`**
|
||
|
||
再往上,才是:
|
||
|
||
4. **会话摘要记忆**
|
||
5. **长期用户偏好记忆**
|
||
6. **模型歧义消解**
|
||
|
||
一句话总结:
|
||
|
||
> 你们当前已经有“聊天能力”“画布能力”“Agent 能力”“技能化样板”,现在缺的不是更大的模型,而是一层统一的上下文记忆与资产图谱基础设施。
|
||
> 这层一旦补齐,图片连续编辑、批量处理、九宫格视频、3D 资产、AI 详情都能共用同一套上下文引擎。
|
||
|