Version: 0.5.8.dev.260315

♻️ 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 注释强制规范

- 📚 追加“注释规范(强制)”与“注释风格示例”
- ✍️ 明确复杂逻辑必须使用步骤化注释、跨文件调用需写调用目的、注释需同步维护
This commit is contained in:
Losita
2026-03-15 18:08:33 +08:00
parent c689af56c8
commit 7603a7561a
22 changed files with 1009 additions and 429 deletions

View File

@@ -0,0 +1,91 @@
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)
}