🏠

conversation_history_aggregate_models 模块技术解析

1. 模块概述

conversation_history_aggregate_models 模块定义了系统中用于表示和处理对话历史的核心数据模型。该模块位于系统的领域层,为整个对话管理系统提供了数据结构基础。

1.1 问题空间与设计目标

在构建一个智能对话系统时,我们需要解决以下关键问题:

  • 上下文保持:如何有效存储和传递对话上下文,使系统能够理解用户的后续问题
  • 知识关联:如何记录系统在生成回答时引用的知识库内容,增强回答的可追溯性
  • 多角色消息:如何区分和处理不同角色(用户、助手、系统)的消息
  • 数据持久化:如何高效地将复杂的对话数据存储到数据库中,同时保持查询性能

1.2 核心设计洞察

该模块采用了聚合模型的设计思想,将对话历史的各个相关组件组织在一起,形成一个内聚的领域模型。主要设计原则包括:

  • 职责分离:不同数据结构负责不同的功能(如 History 用于历史记录,Message 用于详细消息)
  • 数据库友好:自定义类型实现了标准的数据库序列化接口
  • 可扩展性:使用 JSON 类型存储复杂结构,便于未来扩展
  • 数据完整性:通过 GORM 钩子确保数据创建时的一致性

2. 核心组件深度解析

2.1 History 结构体

type History struct {
	Query               string     // 用户查询文本
	Answer              string     // 系统响应文本
	CreateAt            time.Time  // 历史记录创建时间
	KnowledgeReferences References // 回答中使用的知识引用
}

设计意图History 结构体代表一个简化的对话历史条目,主要用于快速展示和上下文传递。它包含用户的查询和系统的回答,以及相关的知识引用。

使用场景

  • 在会话摘要中显示历史对话
  • 为 LLM 提供上下文信息
  • 快速检索历史问答对

2.2 Message 结构体

type Message struct {
	ID                  string         // 消息唯一标识符
	SessionID           string         // 所属会话ID
	RequestID           string         // API请求标识符
	Content             string         // 消息文本内容
	Role                string         // 消息角色:"user", "assistant", "system"
	KnowledgeReferences References     // 响应中使用的知识块引用
	AgentSteps          AgentSteps     // 代理执行步骤(仅用于助手消息)
	MentionedItems      MentionedItems // 用户消息中提到的知识库和文件
	IsCompleted         bool           // 消息生成是否完成
	CreatedAt           time.Time      // 消息创建时间戳
	UpdatedAt           time.Time      // 最后更新时间戳
	DeletedAt           gorm.DeletedAt // 软删除时间戳
}

设计意图Message 是该模块中最核心的数据结构,代表一个完整的对话消息。它包含了消息的所有元数据、内容以及相关的附加信息。

关键设计决策

  1. 角色区分:通过 Role 字段明确区分不同来源的消息,这对于构建上下文和生成响应至关重要。

  2. 知识引用分离KnowledgeReferences 字段专门用于存储系统在生成回答时引用的知识块,增强了回答的可解释性。

  3. 代理步骤存储AgentSteps 字段存储了代理的详细推理过程和工具调用,但设计注释明确指出这些信息"存储用于用户历史显示,但不包含在 LLM 上下文中以避免冗余"。这是一个重要的性能优化决策。

  4. 提及项记录MentionedItems 字段专门用于记录用户在消息中 @ 提及的知识库或文件,支持更精确的知识检索。

  5. 软删除支持:通过 gorm.DeletedAt 实现软删除,保留数据的同时不影响正常查询。

GORM 钩子

func (m *Message) BeforeCreate(tx *gorm.DB) (err error) {
	m.ID = uuid.New().String()
	if m.KnowledgeReferences == nil {
		m.KnowledgeReferences = make(References, 0)
	}
	if m.AgentSteps == nil {
		m.AgentSteps = make(AgentSteps, 0)
	}
	if m.MentionedItems == nil {
		m.MentionedItems = make(MentionedItems, 0)
	}
	return nil
}

这个钩子确保了在创建新消息记录时:

  • 自动生成 UUID 作为消息 ID
  • 初始化所有可能为 nil 的切片字段,避免后续操作中的空指针异常

2.3 MentionedItem 和 MentionedItems

type MentionedItem struct {
	ID     string // 项目ID
	Name   string // 项目名称
	Type   string // 类型:"kb" 表示知识库,"file" 表示文件
	KBType string // 知识库类型:"document" 或 "faq"(仅适用于 kb 类型)
}

type MentionedItems []MentionedItem

设计意图:这组类型用于表示用户在消息中提及的知识库或文件。MentionedItems 是一个自定义切片类型,实现了数据库序列化接口。

数据库序列化实现

func (m MentionedItems) Value() (driver.Value, error) {
	if m == nil {
		return json.Marshal([]MentionedItem{})
	}
	return json.Marshal(m)
}

func (m *MentionedItems) Scan(value interface{}) error {
	if value == nil {
		*m = make(MentionedItems, 0)
		return nil
	}
	b, ok := value.([]byte)
	if !ok {
		*m = make(MentionedItems, 0)
		return nil
	}
	return json.Unmarshal(b, m)
}

这些方法实现了 driver.Valuersql.Scanner 接口,使得 MentionedItems 类型可以直接与数据库交互,自动进行 JSON 序列化和反序列化。

2.4 AgentSteps

type AgentSteps []AgentStep

设计意图AgentSteps 是一个代理执行步骤的集合,用于存储代理的推理过程和工具调用。与 MentionedItems 类似,它也实现了数据库序列化接口。

func (a AgentSteps) Value() (driver.Value, error) {
	if a == nil {
		return json.Marshal([]AgentStep{})
	}
	return json.Marshal(a)
}

func (a *AgentSteps) Scan(value interface{}) error {
	if value == nil {
		*a = make(AgentSteps, 0)
		return nil
	}
	b, ok := value.([]byte)
	if !ok {
		*a = make(AgentSteps, 0)
		return nil
	}
	return json.Unmarshal(b, a)
}

3. 架构与数据流程

3.1 模块在系统中的位置

conversation_history_aggregate_models 模块位于系统的核心领域层,为多个上层模块提供数据模型支持:

┌─────────────────────────────────────────────────────────────────┐
│                     application_services_and_orchestration      │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │ conversation_context_and_memory_services                   │ │
│  │  ┌─────────────────────────────────────────────────────┐ │ │
│  │  │ message_history_service                              │ │ │
│  │  └─────────────────────────────────────────────────────┘ │ │
│  └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              ▲
                              │ uses
                              │
┌─────────────────────────────────────────────────────────────────┐
│              core_domain_types_and_interfaces                   │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │ agent_conversation_and_runtime_contracts                  │ │
│  │  ┌─────────────────────────────────────────────────────┐ │ │
│  │  │ message_history_and_mentions_contracts              │ │ │
│  │  │  ┌───────────────────────────────────────────────┐  │ │ │
│  │  │  │ conversation_history_aggregate_models         │  │ │ │
│  │  │  └───────────────────────────────────────────────┘  │ │ │
│  │  └─────────────────────────────────────────────────────┘ │ │
│  └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              ▲
                              │ uses
                              │
┌─────────────────────────────────────────────────────────────────┐
│              data_access_repositories                            │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │ content_and_knowledge_management_repositories              │ │
│  │  ┌─────────────────────────────────────────────────────┐ │ │
│  │  │ conversation_history_repositories                    │ │ │
│  │  └─────────────────────────────────────────────────────┘ │ │
│  └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

3.2 典型数据流程

以下是一个典型的对话消息处理流程:

  1. 消息创建:当用户发送消息时,系统创建一个 Message 对象,设置 Role 为 "user",并记录用户提及的项目到 MentionedItems

  2. 消息持久化:通过 conversation_history_repositories 模块将 Message 对象保存到数据库。

  3. 上下文加载:在生成响应前,message_history_service 从数据库加载相关的历史消息,构建对话上下文。

  4. 响应生成:系统生成响应,创建另一个 Message 对象,设置 Role 为 "assistant",记录使用的知识引用到 KnowledgeReferences,以及代理执行步骤到 AgentSteps

  5. 响应持久化:将助手的响应消息也保存到数据库。

  6. 历史展示:当用户查看对话历史时,系统可能会将 Message 对象转换为更简洁的 History 对象进行展示。

4. 设计决策与权衡

4.1 结构化数据 vs JSON 存储

决策:对于复杂的嵌套结构(如 KnowledgeReferencesAgentStepsMentionedItems),采用 JSON 类型存储在数据库中。

权衡分析

  • 优点
    • 灵活性:可以轻松修改这些结构的定义,无需数据库迁移
    • 简化查询:在大多数情况下,我们需要完整的对象,而不是单独的字段
    • 减少表连接:避免了为每个嵌套结构创建单独的表
  • 缺点
    • 难以进行复杂的嵌套查询
    • 没有数据库级别的类型检查
    • 可能占用更多存储空间

适用场景:这种设计适用于这些嵌套结构主要作为一个整体进行读写,不需要经常对其内部字段进行单独查询的情况。

4.2 History vs Message 分离

决策:提供两种不同的数据结构,History 用于简化的历史记录,Message 用于完整的消息表示。

权衡分析

  • 优点
    • 关注点分离:History 专注于问答对的基本信息,而 Message 包含所有元数据
    • 性能优化:在只需要基本历史信息的场景下,使用 History 可以减少数据传输量
    • 灵活性:可以根据不同的使用场景选择合适的数据结构
  • 缺点
    • 数据重复:两种结构可能包含相同的信息
    • 转换开销:在两种结构之间转换时需要额外的处理
    • 一致性维护:需要确保两种结构中的相关信息保持一致

4.3 AgentSteps 的存储策略

决策:存储 AgentSteps 但在 LLM 上下文中不包含它们。

权衡分析

  • 优点
    • 用户体验:用户可以查看系统的推理过程,增加透明度
    • 调试支持:开发人员可以通过查看这些步骤调试问题
    • 上下文效率:避免在每次请求时向 LLM 发送大量冗余信息
  • 缺点
    • 存储开销:需要额外的存储空间来保存这些步骤
    • 数据同步:需要确保这些步骤与实际的推理过程保持同步

5. 使用指南与注意事项

5.1 基本使用示例

创建用户消息

userMessage := &types.Message{
    SessionID:      sessionID,
    RequestID:      requestID,
    Content:        "你好,我想了解关于产品的信息",
    Role:           "user",
    MentionedItems: types.MentionedItems{
        {
            ID:     "kb-123",
            Name:   "产品知识库",
            Type:   "kb",
            KBType: "document",
        },
    },
    IsCompleted:    true,
}

// 通过 GORM 保存到数据库
db.Create(userMessage)

创建助手消息

assistantMessage := &types.Message{
    SessionID:           sessionID,
    RequestID:           requestID,
    Content:             "根据产品知识库,我们的产品具有以下特点...",
    Role:                "assistant",
    KnowledgeReferences: knowledgeRefs, // 假设这是之前准备好的 References
    AgentSteps:          agentSteps,     // 假设这是之前记录的 AgentSteps
    IsCompleted:         true,
}

// 通过 GORM 保存到数据库
db.Create(assistantMessage)

5.2 注意事项与陷阱

  1. 不要手动设置 IDMessage 的 ID 会在 BeforeCreate 钩子中自动生成,手动设置可能会被覆盖。

  2. 初始化切片字段:虽然 BeforeCreate 钩子会初始化 nil 切片,但在其他场景下创建 Message 对象时,最好手动初始化这些切片,以避免空指针异常。

  3. 正确处理角色:确保正确设置 Role 字段,系统的很多功能依赖于这个字段来区分消息来源。

  4. 注意 JSON 字段的查询限制:由于 KnowledgeReferencesAgentStepsMentionedItems 存储为 JSON,对它们内部字段的查询会比较困难,需要使用数据库特定的 JSON 查询语法。

  5. AgentSteps 的大小管理AgentSteps 可能会变得很大,特别是在复杂的推理场景下。需要考虑实现某种机制来限制其大小,或者在不需要时不存储这些信息。

6. 模块关系与依赖

该模块与以下模块有紧密的依赖关系:

  1. session_lifecycle_api:使用该模块的数据结构管理会话的生命周期。

  2. message_history_service:核心服务,使用这些数据结构提供消息历史管理功能。

  3. conversation_history_repositories:负责持久化该模块定义的数据结构。

7. 总结

conversation_history_aggregate_models 模块是系统中对话管理功能的核心数据模型层。它通过精心设计的数据结构,解决了对话上下文保持、知识关联、多角色消息处理等关键问题。

该模块的主要价值在于:

  • 提供了清晰、内聚的领域模型
  • 平衡了灵活性和性能的需求
  • 为上层服务提供了可靠的数据基础

通过理解该模块的设计思想和实现细节,开发者可以更好地使用和扩展系统的对话管理功能。

On this page