🐛 fix(agent/schedulerefine): 修复复合微调分支链路问题,并将 MinContextSwitch 重构为固定坑位重排语义 - 🔧 修复 `schedulerefine` 复合路由中参数透传不完整、缺少 deterministic objective 时错误降级,以及“复合工具执行成功”与“终审通过”语义混淆的问题 - ✅ 保证新的独立复合分支能够正确执行、正确出站,并统一交由 `hard_check` 裁决最终结果 - 🔍 排查时发现 `MinContextSwitch` 上游 `context_tag` 存在整体退化为 `General` 的风险,影响MinContextSwitch - 🛡️ 为 `MinContextSwitch` 增加兜底策略:当标签整体退化时,按任务名关键词推断学科分组,避免分组能力失效 - ♻️ 将 `MinContextSwitch` 从“整周重新寻找新坑位”调整为“坑位不变,任务顺序改变” - 🎯 将落地方式从顺序 `BatchMove` 改为固定坑位原子重写,避免出现远距离跳位、跨天错迁、异常嵌入课位及循环换位冲突 - 🧹 修复 `hard_check` 在 `MinContextSwitch` 成功后仍执行 `origin_rank` 顺序归位、并导致逆序终审误判的问题 - 🚦 命中该分支后跳过顺序归位与顺序硬校验,避免 `summary` / `hard_check` 将有效重排结果误判为失败 📈 当前连续微调规划涉及的全部功能已可以稳定运行;下一步将继续扩展能力边界,并进一步优化 `schedule_plan` 流程 ♻️ refactor: 重整 agent2 架构,并迁移 quicknote/chat 新链路,目前还剩3个模块未迁移,后续迁移完成后会删除原agent并将此目录命名为agent - 🏗️ 明确 `agent2` 采用“统一分层目录 + 文件分层 + 依赖注入”的重构方案,不再沿用模块目录多层嵌套结构 - 🧩 完善 `agent2` 基础骨架,统一收口 `entrance` / `router` / `llm` / `stream` / `shared` / `model` / `prompt` / `node` / `graph` 等层级职责 - 🚚 将通用路由能力迁移至 `agent2/router`,沉淀统一的 `Action`、`RoutingDecision`、控制码解析,以及 `Dispatcher` / `Resolver` 抽象 - 💬 将普通聊天链路迁移至 `agent2/chat`,复用 `stream` 的 OpenAI 兼容输出协议与 LLM usage 聚合能力 - 📝 将 `quicknote` 链路迁移到 `agent2` 新结构,拆分为 `model` / `prompt` / `llm` / `node` / `graph` 多层实现,替换对旧 `agent/quicknote` 的直接依赖 - 🔌 调整 `agentsvc` 对 `agent2` 的引用,普通聊天、通用分流与 `quicknote` 全部切换到新链路 - ✂️ 去除 graph 内部 `runner` 转接层,改为由 node 层直接持有请求级依赖,并向 graph 暴露节点方法 - 🧹 合并 `graph/quicknote` 与 `graph/quicknote_run`,删除冗余骨架文件,收敛为单一 `quicknote graph` 文件 - 📚 新增 `agent2`《通用能力接入文档》,明确公共能力边界、接入方式以及 graph/node 协作约定 - 📝 更新 `AGENTS.md`,要求后续扩展 `agent2` 通用能力时必须同步维护接入文档 ♻️ refactor: 删除了现Agent目录内Chat模块的两条冗余Prompt
133 lines
4.9 KiB
Go
133 lines
4.9 KiB
Go
package agentnode
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
|
||
agentmodel "github.com/LoveLosita/smartflow/backend/agent2/model"
|
||
"github.com/cloudwego/eino-ext/components/model/ark"
|
||
"github.com/cloudwego/eino/components/tool"
|
||
"github.com/cloudwego/eino/compose"
|
||
)
|
||
|
||
const (
|
||
// QuickNoteGraphNodeIntent 是随口记图里的“意图识别”节点名。
|
||
// 这里把节点名下沉到 node 层,是为了让:
|
||
// 1. 节点自己的分支方法可以直接返回目标节点名;
|
||
// 2. graph 层只负责连线,不需要反向暴露常量给 node 层;
|
||
// 3. 后续若节点改名,只需要在这里统一收口。
|
||
QuickNoteGraphNodeIntent = "quick_note_intent"
|
||
// QuickNoteGraphNodeRank 是随口记图里的“优先级评估”节点名。
|
||
QuickNoteGraphNodeRank = "quick_note_priority"
|
||
// QuickNoteGraphNodePersist 是随口记图里的“持久化写库”节点名。
|
||
QuickNoteGraphNodePersist = "quick_note_persist"
|
||
// QuickNoteGraphNodeExit 是随口记图里的“提前退出”节点名。
|
||
QuickNoteGraphNodeExit = "quick_note_exit"
|
||
)
|
||
|
||
// QuickNoteGraphRunInput 描述一次“随口记图运行”所需的请求级依赖。
|
||
//
|
||
// 职责边界:
|
||
// 1. Model:当前请求实际使用的聊天模型;
|
||
// 2. State:本次图运行共享的状态对象;
|
||
// 3. Deps:工具层依赖,例如解析 user_id、执行写库;
|
||
// 4. SkipIntentVerification:若上游路由已高置信命中,可跳过二次意图判断;
|
||
// 5. EmitStage:向外层推送阶段消息的可选回调。
|
||
//
|
||
// 不负责什么:
|
||
// 1. 不负责真正的 graph 连线;
|
||
// 2. 不负责工具注册与提取;
|
||
// 3. 不负责节点内部业务流转。
|
||
type QuickNoteGraphRunInput struct {
|
||
Model *ark.ChatModel
|
||
State *agentmodel.QuickNoteState
|
||
Deps QuickNoteToolDeps
|
||
SkipIntentVerification bool
|
||
EmitStage func(stage, detail string)
|
||
}
|
||
|
||
// QuickNoteNodes 是“随口记”节点容器。
|
||
//
|
||
// 设计目的:
|
||
// 1. 把“请求级依赖”收口到 node 层,而不是继续堆在 graph 层;
|
||
// 2. 让 graph 层直接挂 `nodes.Intent / nodes.Priority / nodes.Persist` 这些方法;
|
||
// 3. 这样 graph 文件就只负责画图,不再负责依赖转接。
|
||
//
|
||
// 职责边界:
|
||
// 1. 负责提供可直接挂载到 graph 的节点方法;
|
||
// 2. 负责在节点执行时读取本次请求的 input / tool / stage emitter;
|
||
// 3. 不负责 graph 编译与运行,也不负责 service 层收尾持久化。
|
||
type QuickNoteNodes struct {
|
||
input QuickNoteGraphRunInput
|
||
createTaskTool tool.InvokableTool
|
||
emitStage func(stage, detail string)
|
||
}
|
||
|
||
// NewQuickNoteNodes 创建随口记节点容器。
|
||
//
|
||
// 说明:
|
||
// 1. 这里做的是“节点依赖注入”,不是 graph 连线;
|
||
// 2. emitStage 允许为空,内部会补成 no-op,避免节点里反复判空;
|
||
// 3. createTaskTool 为 persist 节点的硬依赖,缺失时直接报错,避免跑到写库节点再失败。
|
||
func NewQuickNoteNodes(input QuickNoteGraphRunInput, createTaskTool tool.InvokableTool) (*QuickNoteNodes, error) {
|
||
if createTaskTool == nil {
|
||
return nil, errors.New("quick note nodes: createTaskTool is nil")
|
||
}
|
||
|
||
emitStage := input.EmitStage
|
||
if emitStage == nil {
|
||
emitStage = func(stage, detail string) {}
|
||
}
|
||
|
||
return &QuickNoteNodes{
|
||
input: input,
|
||
createTaskTool: createTaskTool,
|
||
emitStage: emitStage,
|
||
}, nil
|
||
}
|
||
|
||
// Exit 是图里的显式退出节点。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责把当前 state 原样透传到 END;
|
||
// 2. 不负责追加业务逻辑;
|
||
// 3. 保留这个节点,是为了后续若要补统一埋点、日志、收尾逻辑时有稳定挂载点。
|
||
func (n *QuickNoteNodes) Exit(ctx context.Context, st *agentmodel.QuickNoteState) (*agentmodel.QuickNoteState, error) {
|
||
_ = ctx
|
||
return st, nil
|
||
}
|
||
|
||
// NextAfterIntent 负责根据意图识别结果决定 intent 后的分支走向。
|
||
func (n *QuickNoteNodes) NextAfterIntent(ctx context.Context, st *agentmodel.QuickNoteState) (string, error) {
|
||
_ = ctx
|
||
if st == nil || !st.IsQuickNoteIntent {
|
||
return QuickNoteGraphNodeExit, nil
|
||
}
|
||
if st.DeadlineValidationError != "" {
|
||
return QuickNoteGraphNodeExit, nil
|
||
}
|
||
return QuickNoteGraphNodeRank, nil
|
||
|
||
}
|
||
|
||
// NextAfterPersist 负责根据持久化结果决定 persist 后的分支走向。
|
||
func (n *QuickNoteNodes) NextAfterPersist(ctx context.Context, st *agentmodel.QuickNoteState) (string, error) {
|
||
_ = ctx
|
||
if st == nil {
|
||
return compose.END, nil
|
||
}
|
||
if st.Persisted {
|
||
return compose.END, nil
|
||
}
|
||
if st.CanRetryTool() {
|
||
return QuickNoteGraphNodePersist, nil
|
||
}
|
||
if st.AssistantReply == "" {
|
||
// 1. 重试次数耗尽且上游没有明确失败文案时,在这里补一条兜底回复;
|
||
// 2. 这样可以保证图结束后 service 层一定能拿到稳定可展示的失败信息;
|
||
// 3. 不在 graph 层处理,是因为这属于节点业务状态修正。
|
||
st.AssistantReply = "抱歉,我已经重试了多次,还是没能成功记录这条任务,请稍后再试。"
|
||
}
|
||
return compose.END, nil
|
||
}
|