Files
smartmate/backend/agent2/node/quicknote.go
LoveLosita f4ef6fb256 Version: 0.7.5.dev.260324
🐛 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
2026-03-24 21:35:22 +08:00

133 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}