beatenevo commented on issue #1534:
URL: https://github.com/apache/dubbo-admin/issues/1534#issuecomment-5356186478

   ### 1. Store 抽象
   
   基于 [#1502](https://github.com/apache/dubbo-admin/issues/1502),新增统一 Store 抽象:
   
   ```go
   type SessionStore interface {
       Create(ctx context.Context, session *Session) error
       Get(ctx context.Context, sessionID string) (*Session, error)
       List(ctx context.Context) ([]*Session, error)
       Touch(ctx context.Context, sessionID string, updatedAt time.Time) error
       Delete(ctx context.Context, sessionID string) error
       DeleteExpired(ctx context.Context, now time.Time) (int, error)
   }
   
   type MessageStore interface {
       AddHistory(ctx context.Context, sessionID string, messages 
...*ai.Message) error
       IsEmpty(ctx context.Context, sessionID string) (bool, error)
       WindowMemory(ctx context.Context, sessionID string) ([]*ai.Message, 
error)
       AllMemory(ctx context.Context, sessionID string) ([]*ai.Message, error)
       NextTurn(ctx context.Context, sessionID string) error
   }
   
   type Store interface {
       SessionStore
       MessageStore
   }
   ```
   
   新增统一领域错误:
   
   ```go
   var (
       ErrSessionNotFound  = errors.New("session not found")
       ErrSessionExpired   = errors.New("session expired")
       ErrNoActiveTurn     = errors.New("no active turn")
       ErrTurnLimitReached = errors.New("conversation turn limit reached")
   )
   ```
   
   Store 接口只负责存储和读取,不负责模型调用、工具调用和 HTTP 逻辑。
   
   Session Manager 只依赖 `SessionStore`,Agent 和 Memory Tool 只依赖 
`MessageStore`;三者使用同一个底层 `Store` 实例。
   
   ### 2. MemoryStore
   
   将当前 Session Manager 和 HistoryMemory 的内存逻辑迁移到 `MemoryStore`。
   
   - 保留现有 Session 字段、状态和 24 小时过期规则。
   - 保留现有 Turn 生命周期以及 system、user、model 分组和读取顺序。
   - 保留 nil Message 和未知 Role 被忽略的行为。
   - 保留现有 `TurnLimit` 边界;本 PR 不修改 `max_turns` 的业务语义。
   - 使用 Store 锁保护 Session、Turn 和 Message 状态。
   - Session 删除时同时删除对应历史。
   - 现有 `HistoryMemory` 不再由 Agent 或 Memory Tool 单独创建。
   - MemoryStore 使用统一领域错误;窗口满错误必须支持 `errors.Is(err, 
ErrTurnLimitReached)`,同时保持现有用户可见错误信息。
   
   MemoryStore 和 GormStore 必须通过同一套 Contract Test 验证。
   
   ### 3. Store 依赖注入
   
   Runtime 中只创建一个 Store 实例:
   
   ```text
   MemoryComponent
     -> MemoryStore 或 GormStore
         -> Session Manager 使用 SessionStore
         -> Agent 使用 MessageStore
         -> Memory Tool 使用 MessageStore
   ```
   
   `MemoryComponent` 使用完整配置构造:
   
   ```go
   func NewMemoryComponent(spec MemorySpec) (runtime.Component, error)
   ```
   
   - Session Manager 不再持有 Session map。
   - Agent 不再创建 `HistoryMemory`。
   - Agent 接收 `MessageStore`。
   - Memory Tool 接收同一个 `MessageStore`。
   - Router 接收 `SessionStore`。
   - Handler 通过 Session Manager 调用 `Touch`。
   - Handler 删除 Session 时直接调用 Store 的级联删除。
   - 移除 `Agent.GetMemory()`。
   - 移除生产代码中创建 mock Session 的副作用。
   - Store 错误不得只记录日志后忽略,必须返回到 Agent、Handler 或 SSE。
   - Gin 请求的 `context.Context` 必须传递到 Agent 和 Store,确保请求取消能够中止存储调用。
   
   ### 4. Gorm Model
   
   新增三张表:
   
   ```text
   ai_sessions
   ai_turns
   ai_messages
   ```
   
   Model 和 GormStore 放在 `ai/store/gorm`,package 名使用 `gormstore`,避免与 
`gorm.io/gorm` 导入名冲突。
   
   Session Model:
   
   ```go
   type SessionModel struct {
       ID        string    `gorm:"primaryKey;size:64"`
       CreatedAt time.Time `gorm:"not null"`
       UpdatedAt time.Time `gorm:"not null;index"`
       Status    string    `gorm:"size:16;not null;index"`
   }
   
   func (SessionModel) TableName() string {
       return "ai_sessions"
   }
   ```
   
   Turn Model:
   
   ```go
   type TurnModel struct {
       ID          uint64       `gorm:"primaryKey;autoIncrement"`
       SessionID   string       `gorm:"size:64;not null;index"`
       Session     SessionModel 
`gorm:"foreignKey:SessionID;references:ID;constraint:OnDelete:CASCADE"`
       CreatedAt   time.Time    `gorm:"not null;index"`
       CompletedAt *time.Time   `gorm:"index"`
   }
   
   func (TurnModel) TableName() string {
       return "ai_turns"
   }
   ```
   
   Message Model:
   
   ```go
   type MessageModel struct {
       ID        uint64    `gorm:"primaryKey;autoIncrement"`
       TurnID    uint64    `gorm:"not 
null;uniqueIndex:uidx_ai_message_turn_sequence"`
       Turn      TurnModel 
`gorm:"foreignKey:TurnID;references:ID;constraint:OnDelete:CASCADE"`
       Sequence  uint64    `gorm:"not 
null;uniqueIndex:uidx_ai_message_turn_sequence"`
       Payload   []byte    `gorm:"not null"`
       CreatedAt time.Time `gorm:"not null"`
   }
   
   func (MessageModel) TableName() string {
       return "ai_messages"
   }
   ```
   
   约束:
   
   ```text
   PRIMARY KEY(ai_sessions.id)
   FOREIGN KEY(ai_turns.session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE
   FOREIGN KEY(ai_messages.turn_id) REFERENCES ai_turns(id) ON DELETE CASCADE
   UNIQUE(ai_messages.turn_id, ai_messages.sequence)
   ```
   
   `Payload` 保存完整的 Genkit `ai.Message` JSON,不拆分为多个业务字段。
   
   JSON 编解码必须完整保留 Role、Content、Message Metadata,以及 Part 中的 
ToolRequest、ToolResponse、Resource、Custom 和 Metadata。解码失败时返回包含 Message ID 
的错误,不返回部分解码结果。
   
   ### 5. GormStore
   
   实现 `SessionStore`、`MessageStore` 和组合 `Store`。
   
   GormStore 提供接收已打开 `*gorm.DB` 的构造函数,用于 Contract Test、重启恢复和多实例测试;生产环境由 
`MemoryComponent` 根据配置创建数据库连接。Store 本身不使用全局数据库连接。
   
   #### AddHistory
   
   - 校验 Session 存在且未过期。
   - 在 MySQL/PostgreSQL 中锁定 Session 行,使单次 Store 写操作保持一致。
   - 查询或创建 active Turn。
   - 过滤 nil 和未知 Role。
   - 按顺序生成 Message `sequence`。
   - 在一个事务中完成 Turn 创建和 Message 插入。
   - 任意写入失败时整体回滚。
   - 消息不在模型推理或工具调用期间写入事务。
   
   #### WindowMemory
   
   - 读取 active Turn。
   - 按 `sequence ASC` 查询 Message。
   - JSON 解码为 `ai.Message`。
   - 按当前 system、user、model 顺序返回。
   
   #### AllMemory
   
   - 读取 active Turn 和已完成 Turn。
   - 保持当前 HistoryMemory 的兼容顺序。
   - Turn 内按 `sequence ASC` 排序。
   - 返回完整的 Genkit Message。
   
   #### NextTurn
   
   - 查询 active Turn。
   - 设置 `completed_at`。
   - 保留现有 `TurnLimit` 边界,不在本 PR 中重新定义 `max_turns`。
   - 无 active Turn 时返回 `ErrNoActiveTurn`。
   - 操作必须在事务中执行。
   
   #### Delete
   
   在同一个事务中删除:
   
   ```text
   Session 对应的 Message
   Session 对应的 Turn
   Session
   ```
   
   删除不存在的 Session 返回 `ErrSessionNotFound`。
   
   #### DeleteExpired
   
   删除超过 24 小时未更新的 Session,并同时删除关联 Turn 和 Message。
   
   ### 6. 后端配置和数据库连接
   
   在现有 `memory` Component 中增加 backend 配置:
   
   ```yaml
   type: memory
   spec:
     backend: memory
     history_key: chat_history
     max_turns: 100
   ```
   
   Gorm 配置示例:
   
   ```yaml
   type: memory
   spec:
     backend: gorm
     history_key: chat_history
     max_turns: 100
     database:
       driver: mysql
       dsn: ${AI_DB_DSN}
       max_open_conns: 50
       max_idle_conns: 10
   ```
   
   配置要求:
   
   - `backend` 支持 `memory` 和 `gorm`,默认值为 `memory`。
   - Runtime Gorm backend 支持 MySQL 和 PostgreSQL。
   - SQLite 仅由测试构造函数使用,不作为生产配置要求。
   - Gorm backend 必须配置 `database.driver` 和 `database.dsn`。
   - 连接池支持 `max_open_conns` 和 `max_idle_conns`。
   - 默认连接池参数与根项目保持一致:`max_open_conns=100`、`max_idle_conns=10`。
   - 连接池参数必须为正,且 `max_idle_conns <= max_open_conns`。
   - 初始化时执行数据库连接和 `AutoMigrate`。
   - 停止 Component 时关闭数据库连接。
   - 更新 JSON Schema 和配置字段说明文档。
   - Memory backend 不得创建数据库连接。
   
   AI 是独立 Go Module,`gorm.io/gorm`、MySQL、PostgreSQL 和测试用 SQLite driver 必须添加到 
`ai/go.mod`。AI 模块不直接依赖根模块的 `pkg/store/dbcommon`,只参考其连接池默认值、`AutoMigrate` 和关闭流程。
   
   ### 7. Migration、事务、顺序和删除
   
   - `ai_sessions`、`ai_turns`、`ai_messages` 表迁移。
   - Session、Turn、Message 写入事务。
   - Message 在同一 Turn 内的 sequence 唯一约束。
   - Session 到 Turn、Turn 到 Message 的外键关系。
   - Session 删除时关联数据级联删除。
   - Gorm 错误转换为统一 Store 错误。
   - MySQL/PostgreSQL 使用事务和行锁保证单次 Store 操作的数据一致性。
   - 事务只包含 Store 读写,不跨越模型推理、工具调用或 SSE 流式输出。
   - SQLite 不支持 `SELECT ... FOR UPDATE`,因此 SQLite 测试不作为同一 Session 并发写入保证。
   
   
   ### 8. 测试
   
   #### Contract Test
   
   MemoryStore 和 GormStore 共用一套测试,覆盖:
   
   - Session 创建、读取、列表、Touch、删除。
   - Session 过期判断。
   - AddHistory 的 Role 分组和顺序。
   - nil 和未知 Role 消息。
   - WindowMemory 和 AllMemory。
   - NextTurn。
   - 现有 `TurnLimit` 边界,以及失败时 active Turn 保持不变。
   - 无 active Turn 错误。
   - Session 删除后的历史清理。
   
   #### 重启恢复测试
   
   使用文件 SQLite:
   
   1. 创建 Store 并执行迁移。
   2. 创建 Session。
   3. 写入多个 Turn 和 Message。
   4. 关闭 Store。
   5. 使用同一个数据库文件重新创建 Store。
   6. 验证 Session 和完整历史仍然存在且顺序一致。
   
   #### 多实例共享测试
   
   创建两个连接同一 SQLite 文件的 GormStore:
   
   - 实例 A 写入数据。
   - 实例 B 读取数据。
   - 实例 B 能读取实例 A 创建的 Session、Turn 和 Message。
   - 一个实例删除 Session 后,另一个实例无法再读取相关数据。
   
   #### 其他测试
   
   - Gorm JSON 编解码测试。
   - 事务回滚测试。
   - `(turn_id, sequence)` 唯一约束测试。
   - MySQL/PostgreSQL 驱动编译和可选集成测试。
   - Agent 和 Memory Tool 共享同一个 Store 实例测试。
   - Agent、Handler、Memory Tool、SSE 回归测试。
   - MemoryStore `go test -race` 测试。
   
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to