You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/AI/docs/observation-summary-design.md

14 KiB

结构化会话存储 - 设计文档

1. 文档信息

项目 说明
功能名称 结构化会话存储Structured Conversation Store
版本 3.0
对应需求 docs/observation-summary-requirements.mdv3.0

2. 总体设计

2.1 设计原则

  • 单一事实来源:会话级结构化存储Conversation Store是唯一真相源所有消息与卡片按时间顺序以结构化条目形式存在其中。
  • UI 与 Prompt 为视图:不直接使用「混入特殊消息的 ChatHistory」UI 列表与发给 LLM 的历史均由存储派生
  • LLM History 为派生视图ChatHistory 仅在调用 LLM 时,从会话存储按规则投影得到(仅文本或文本+短占位),不参与持久化真相、不承担「存卡片」职责。
  • YAML 承载载荷:特殊消息的载荷采用 YAML对 AI 与人可读性更好);存储整体持久化也可采用 YAML。

2.2 架构概览

                    ┌─────────────────────────────────────────┐
                    │  结构化会话存储Conversation Store      │
                    │  唯一事实来源:按序的条目列表               │
                    │  - TextEntry(Role, Content)               │
                    │  - SpecialEntry(Type, YamlPayload)        │
                    └───────────────────┬─────────────────────┘
                                        │
            ┌───────────────────────────┼───────────────────────────┐
            │                           │                           │
            ▼                           ▼                           ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│  UI 视图               │ │  Prompt 视图           │ │  持久化                │
│  遍历 Store →          │ │  遍历 Store →          │ │  Store → YAML/JSON     │
│  ChatMessages         │ │  ChatHistory           │ │  加载时反序列化 → Store │
│  (文本 + 卡片)         │ │  (仅文本或+短占位)      │ │                         │
└───────────────────────┘ └───────────────────────┘ └───────────────────────┘
            │                           │
            ▼                           ▼
┌───────────────────────┐ ┌───────────────────────┐
│  MainWindowViewModel  │ │  KernelService        │
│  LoadMessagesFromStore│ │  AskAsync 使用        │
│  绑定到界面            │ │  GetHistoryForLlm()   │
└───────────────────────┘ └───────────────────────┘

3. 会话存储的数据结构

3.1 条目类型(抽象)

每条记录为以下两种之一(或通过 type 区分的并集):

条目类型 含义 主要字段
TextEntry 纯文本消息 RoleUser/Assistant/System, Contentstring
SpecialEntry 特殊消息(卡片) Type见下, PayloadYAML 字符串)
  • 实现类型ConversationEntry 为抽象基类;具体为 TextConversationEntrySpecialConversationEntry。持久化 DTO 使用 Kind"Text" / "Special")、ConversationEntryMapperConversationEntryDto 互转。
  • 顺序由列表下标保证;新增条目一律追加(AppendText / AppendSpecial)。

3.2 存储的归属

  • 存储与会话一一对应:每个 ChatSession(或等价会话对象)持有一份「会话存储」实例(ConversationStore 类型,其内部持有 IList<ConversationEntry>)。即存储由 ChatSession 持有,每会话一份。
  • ChatSession.HistoryChatHistory)可保留用于兼容或过渡,但不再作为真相源;在「构建发给 LLM 的历史」时,由 GetHistoryForLlm() 从存储生成新的 ChatHistory 传入后端,而不是直接读 session.History

3.3 System 消息的处理

  • 若产品需要「每条会话有一条 System 引导词」,可在存储中存 System而在生成 Prompt 视图时在生成的 ChatHistory 头部插入一条 System(内容为 guidePrompt或约定存储中允许一条 Role=System 的 TextEntry仅一条、仅作引导。具体由实现决定原则是 System 不混入摘要、不存大段 YAML。

4. 特殊消息 YAML 载荷格式

4.1 通用字段

每条 SpecialEntry 的 PayloadYAML至少包含

  • typeForm | ParameterSet | Table | ColumnMatch | WorkflowStatus | KnowledgeBase | XyzLoadCard | GriddingParamCard(与 SpecialMessageTypes / ISpecialMessage 实现一致)
  • id:可选,实例唯一 id

4.2 各类型载荷约定

type 主要字段 说明
Form formId, title, status, fields, submitLabel formId 用于恢复时从 FormRegistry 取 Definition。
ParameterSet title, itemsname, valueText, description, fieldType, options 与 ParameterSetMessage 可序列化字段一致。
Table title, columnNames, rows/totalRowCount/maxPreviewRows 与 TableDataMessage 一致。
ColumnMatch title, requiredColumns, previewColumns, mappings 与 ColumnMatchMessage 一致。
WorkflowStatus title, steps 可选。
  • 所有字段可 YAML 序列化;长文本可截断。序列化/反序列化由统一工具(如 SpecialMessageSerializer)完成,与存储读写解耦。

4.3 与 ISpecialMessage 的对应

  • FormYAML 含 formId、title、status、fields含 id/label/type/currentValue 等、submitLabelFormRequestMessageDefinition + FieldsWithValues可互转恢复时经 IFormRegistry 根据 formId 取 FormDefinition。
  • ParameterSetYAML 含 title、itemsname、valueText、description、fieldType、optionsParameterSetMessage 的 LoadFromJson/LoadFromYaml 及序列化输出一致。
  • TableYAML 含 title、columnNames、rows或 totalRowCount/maxPreviewRows + 行数据);与 TableDataMessage 的 ColumnNames/Rows/TotalRowCount/MaxPreviewRows 一致。
  • ColumnMatchYAML 含 title、requiredColumns、previewColumns、mappingsrequiredColumn/matchedColumnColumnMatchMessage 一致。
  • WorkflowStatusYAML 含 title、stepsid、displayName、order、status 等);与 WorkflowStatusMessage 一致;可选实现。
  • 上述约定保证 SpecialMessageSerializer.Serialize(ISpecialMessage)SpecialMessageDeserializer.Deserialize(type, yaml, formRegistry?) 往返无损Form 在 formId 缺失时降级为占位)。

5. 两种视图的构建

5.1 UI 视图(从存储 → ChatMessages

  • 入口:如 LoadMessagesFromSession(session)LoadMessagesFromStore(store)
  • 步骤
    1. 清空当前 ChatMessages
    2. 遍历会话存储的每条条目:
      • TextEntry:按 Role 构造 ChatMessageModelUser/AIMessageType.TextContent = Content
      • SpecialEntry:用 SpecialMessageDeserializer.Deserialize(type, payloadYaml, formRegistry) 得到 ISpecialMessage,构造 ChatMessageModelAuthorType.AI对应 MessageTypeSpecialContent = 反序列化结果)。
    3. 跳过 Role=System 的 TextEntry或按产品决定是否展示最后一条消息标记等 UI 状态。
  • 数据来源:仅读会话存储,不读 ChatSession.History

5.2 Prompt 视图(从存储 → ChatHistory供 LLM 使用)

  • 入口:如 session.GetHistoryForLlm(guidePrompt?)ConversationStore.BuildHistoryForLlm(guidePrompt?),返回 ChatHistory
  • 步骤
    1. 创建空的 ChatHistory
    2. 若需要 System在开头插入一条 ChatMessageContent(AuthorRole.System, guidePrompt)
    3. 遍历会话存储:
      • TextEntryUser/Assistant原样追加 ChatMessageContent(Role, Content)
      • TextEntrySystem:若已在步骤 2 统一插入则跳过,或按需追加。
      • SpecialEntry追加原始 YAML可选a跳过b追加一条短文本 Assistant 占位(如「[已展示表单xxx]」)。
    4. 返回该 ChatHistory
  • 使用处KernelService.AskAsync / AskStreamAsync 调用时,传入「从存储生成的 ChatHistory」而不是 session.History;若仍保留 session.History,则不再将其作为发给 LLM 的输入。

6. 写入与持久化

6.1 写入存储(不入 History

  • 用户发送文本:向会话存储追加一条 TextEntry(User, content);同时若需更新 UI可基于存储重新构建 UI 视图或仅追加一条到当前 ChatMessages(与「仅从存储构建」策略二选一或混合)。
  • 助手回复文本:向会话存储追加一条 TextEntry(Assistant, content)
  • 出现特殊消息(表单/参数集/表格/列匹配等):将 ISpecialMessage 序列化为 YAML向会话存储追加一条 SpecialEntry(type, yamlPayload)
  • 不再ChatSession.History 追加「带前缀的 Assistant」History 仅由 Prompt 视图在需要时生成。

6.2 持久化格式

  • 实现SessionStorage 按会话保存到 {Sessions}/{sessionId}.yaml;内容为 SessionFileDtosessionId, title, createdAt, updatedAt, workflowMode, entries。entries 来自 session.Store,经 ConversationEntryMapper.ToDtoList 得到 ConversationEntryDto 列表;加载时 store.LoadFromEntries(dto.Entries) 还原 Store再构造 ChatSession(..., store)
  • 条目格式:每项为 { kind: "Text", role, content }{ kind: "Special", type, payload }payload 为 YAML 字符串。
  • 格式:整体 YAML与特殊消息载荷一致。

7. 模块职责

模块 职责
ConversationStore 持有 IReadOnlyList<ConversationEntry>(内部 List);提供 AppendText / AppendSpecial / RemoveLast / Clear提供 ToYaml()、LoadFromYaml()、LoadFromEntries();触发 StoreChanged。提供 BuildHistoryForLlm由 ChatSession 提供)。
ChatSession 持有 ConversationStore提供 GetHistoryForLlm(guidePrompt) 从 Store 生成 ChatHistory保留 History 仅作兼容AddUserMessage/AddAssistantMessage/AppendSpecialEntry 写入 Store并可选同步 History
SpecialMessageSerializer ISpecialMessage ↔ YAML 字符串(仅载荷);不关心存储格式。
SpecialMessageDeserializer 根据 type + YAML 还原 ISpecialMessageForm 需 IFormRegistry提供 GetShortDescriptionForLlm 供 Prompt 视图占位。
SessionStorage 会话级持久化Save(session) 将 session.Store 写入 {sessionId}.yamlLoad/LoadFromYaml 反序列化为 ConversationStore 后构造 ChatSession。
MainWindowViewModel 添加消息时写入会话存储(经 ChatSession加载会话时 LoadMessagesFromSession(session) 从 session.Store 遍历构建 ChatMessages不依赖 History。
KernelService AskAsync/AskStreamAsync 使用 session.GetHistoryForLlm(guidePrompt),不使用 session.History 作为请求体。

8. 与现有代码的衔接

现有 变更方向
ChatSession.History 不再作为唯一事实来源;改为由「会话存储」在调用 LLM 时生成 ChatHistory。可保留属性用于过渡或只读兼容。
EnsureFormsYamlInSystem 仅作兼容(可能只更新 History发给 LLM 的 System 由 GetHistoryForLlm(guidePrompt) 在生成 ChatHistory 时插入,调用方传入 guidePrompt 即可;不再写入 formsYaml、不再维护摘要。
AddUserMessage / AddAssistantMessage 语义改为「追加到会话存储」的 TextEntry若仍需同步到旧 History 可保留一份兼容写入,但真相源为存储。
AddFormMessage / AddParameterSetMessage / SubmitForm 中添加 Table 在向 UI 添加卡片的同时,向会话存储追加 SpecialEntry不向 History 追加带前缀的 Content。
LoadMessagesFromSession 改为从会话存储遍历构建 ChatMessages不再解析 History 中的 Content 前缀。

9. 错误与边界

  • 反序列化失败SpecialEntry 的 payload 无效或 type 未知时,该条在 UI 视图中可显示为占位文本或跳过,并打日志。
  • Form 的 Definition 缺失:恢复时 formId 在 FormRegistry 中不存在则降级为占位或只读文本。
  • 顺序:严格按「写入顺序 = 存储顺序」;单线程 UI 下追加即保证顺序。

10. 测试建议

  • 单元:存储的序列化/反序列化ConversationStore.ToYaml/LoadFromEntries、ConversationEntryMapperSpecialMessage 的 YAML 往返;GetHistoryForLlm 仅含文本或短占位、不含原始 YAML。
  • 集成:写入若干 TextEntry + SpecialEntry 后UI 视图与 Prompt 视图结果符合预期;切换会话后从持久化恢复存储再构建 UI卡片与顺序正确。
  • 回归LLM 请求体中无大段 YAML会话列表与进入会话后行为一致。