♻️ refactor(agent): 拆分 agentsvc,并增强 quicknote/outbox 注释与可维护性 - 📦 将 Agent 服务实现从 `service` 根目录迁移到 `service/agentsvc`,包含 `agent.go`、`agent_quick_note.go` 及相关测试 - 🔌 新增 service 层兼容桥接 `agent_bridge.go`,保持 `service.NewAgentService` 与 `*service.AgentService` 现有调用方式不变 - 📝 为 `quicknote` 补充高密度中文步骤化注释,覆盖 `graph` / `runner` / `nodes` / `tool` / `state` / `prompt`,明确职责边界、分支条件、重试与兜底策略 - 🧭 为 `infra/outbox` 与 service agent 链路补充详细中文注释,覆盖状态机流转、幂等处理、失败回写与异步持久化语义 - ✅ 统一格式化相关文件,并通过全量后端测试:`go test ./...` 📝 chore(docs): 更新 AGENTS.md 注释强制规范 - 📚 追加“注释规范(强制)”与“注释风格示例” - ✍️ 明确复杂逻辑必须使用步骤化注释、跨文件调用需写调用目的、注释需同步维护
92 lines
3.4 KiB
Go
92 lines
3.4 KiB
Go
package outbox
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
|
||
kafkabus "github.com/LoveLosita/smartflow/backend/infra/kafka"
|
||
"github.com/LoveLosita/smartflow/backend/model"
|
||
)
|
||
|
||
// ChatHistoryAsync 是“聊天记录异步持久化”的业务适配器。
|
||
//
|
||
// 设计目的:
|
||
// 1) 让业务层只调用 EnqueueChatHistoryPersist,而不感知扫描/投递/消费细节;
|
||
// 2) 保持现有 Agent 代码调用习惯,降低改造面;
|
||
// 3) 把具体的 outbox+kafka 主流程彻底收敛到 infra。
|
||
type ChatHistoryAsync struct {
|
||
engine *Engine
|
||
}
|
||
|
||
// NewChatHistoryAsync 创建聊天记录异步适配器并注册处理器。
|
||
//
|
||
// 处理器职责:
|
||
// 1) 从 envelope payload 解析聊天载荷;
|
||
// 2) 调用仓储“落库并标记 consumed”;
|
||
// 3) 解析失败时标记 dead(不可恢复错误),避免无意义重试。
|
||
func NewChatHistoryAsync(repo *Repository, cfg kafkabus.Config) (*ChatHistoryAsync, error) {
|
||
// 1. 先创建通用引擎,内部会按 cfg.Enabled 决定是否启用。
|
||
engine, err := NewEngine(repo, cfg)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if engine == nil {
|
||
// 2. 异步开关关闭:返回 nil 交给上层走同步降级路径。
|
||
return nil, nil
|
||
}
|
||
|
||
// 3. 注册“聊天记录持久化”业务处理器。
|
||
// 该处理器只做三件事:
|
||
// 3.1 解析 payload;
|
||
// 3.2 调仓储落库并推进 consumed;
|
||
// 3.3 遇到不可恢复错误时标记 dead。
|
||
if err = engine.RegisterHandler(model.OutboxBizTypeChatHistoryPersist, func(ctx context.Context, envelope kafkabus.Envelope) error {
|
||
var payload model.ChatHistoryPersistPayload
|
||
if unmarshalErr := json.Unmarshal(envelope.Payload, &payload); unmarshalErr != nil {
|
||
_ = repo.MarkDead(ctx, envelope.OutboxID, "解析聊天持久化载荷失败: "+unmarshalErr.Error())
|
||
// 返回 nil:该错误已被标记为 dead,不需要再走重试。
|
||
return nil
|
||
}
|
||
// 返回 error:由 engine 统一标记为 retry 并提交 offset。
|
||
return repo.PersistChatHistoryAndMarkConsumed(ctx, envelope.OutboxID, payload)
|
||
}); err != nil {
|
||
// 4. 注册失败时回收已创建的引擎资源,防止泄漏。
|
||
engine.Close()
|
||
return nil, err
|
||
}
|
||
|
||
// 5. 返回业务适配器,对业务层暴露“更语义化”的调用入口。
|
||
return &ChatHistoryAsync{engine: engine}, nil
|
||
}
|
||
|
||
// Start 启动异步引擎(扫描 + 消费)。
|
||
func (a *ChatHistoryAsync) Start(ctx context.Context) {
|
||
// 允许在未初始化(例如异步关闭)时被安全调用。
|
||
if a == nil || a.engine == nil {
|
||
return
|
||
}
|
||
a.engine.Start(ctx)
|
||
}
|
||
|
||
// Close 关闭异步引擎资源。
|
||
func (a *ChatHistoryAsync) Close() {
|
||
// 允许在未初始化(例如异步关闭)时被安全调用。
|
||
if a == nil || a.engine == nil {
|
||
return
|
||
}
|
||
a.engine.Close()
|
||
}
|
||
|
||
// EnqueueChatHistoryPersist 将聊天记录持久化请求写入 outbox。
|
||
// 该方法是业务层唯一需要调用的入口。
|
||
func (a *ChatHistoryAsync) EnqueueChatHistoryPersist(ctx context.Context, payload model.ChatHistoryPersistPayload) error {
|
||
// 1. 若引擎未初始化,说明启动配置有问题或异步功能未启用。
|
||
// 这里显式返回错误,交由业务层按需降级/告警。
|
||
if a == nil || a.engine == nil {
|
||
return errors.New("chat history async is not initialized")
|
||
}
|
||
// 2. 以 conversation_id 作为 messageKey,尽量让同会话消息落在稳定分区。
|
||
return a.engine.Enqueue(ctx, model.OutboxBizTypeChatHistoryPersist, payload.ConversationID, payload)
|
||
}
|