Version: 0.7.9.dev.260326

后端:
1.把最后一块拼图:schedule_refine也搬迁到了agent2,此时agent已经完全解耦。但是它没融入新架构,Codex只尝试把它调整了一部分,回退了一些错误的更改,保持着现在的可运行状态。下次继续改。
2.agent目录先保留,直到refine彻底融入新架构。
3.改善Codex主导的新史山结构:node文件夹里面大量文件,转而改成了module.go+module_tool.go的双文件格局,极大提升架构整洁度和代码可读性。
前端:
1.新开了日历界面,正在保持往前推进。做了很多更改,感觉越来越好了。
This commit is contained in:
Losita
2026-03-26 00:38:17 +08:00
parent aa04bfb452
commit a243154e23
32 changed files with 11481 additions and 1239 deletions

View File

@@ -6,20 +6,21 @@ import (
"log"
"strings"
"github.com/LoveLosita/smartflow/backend/agent/scheduleplan"
"github.com/LoveLosita/smartflow/backend/agent/schedulerefine"
agentgraph "github.com/LoveLosita/smartflow/backend/agent2/graph"
agentmodel "github.com/LoveLosita/smartflow/backend/agent2/model"
agentnode "github.com/LoveLosita/smartflow/backend/agent2/node"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/cloudwego/eino-ext/components/model/ark"
)
// runScheduleRefineFlow 执行“连续对话微调排程”分支。
// runScheduleRefineFlow 鎵ц鈥滆繛缁璇濆井璋冩帓绋嬧€濆垎鏀€?
//
// 职责边界:
// 1. 负责读取“上一版排程预览快照”(优先 Redis缺失再回源 MySQL
// 2. 负责调用独立 schedulerefine 图链路完成本轮微调;
// 3. 负责把微调结果回写预览缓存与状态快照,供后续继续微调;
// 4. 不负责聊天消息持久化(消息持久化由 AgentChat 主链路统一处理)。
// 鑱岃矗杈圭晫锛?
// 1. 璐熻矗璇诲彇鈥滀笂涓€鐗堟帓绋嬮瑙堝揩鐓р€濓紙浼樺厛 Redis锛岀己澶卞啀鍥炴簮 MySQL锛夛紱
// 2. 璐熻矗璋冪敤鐙珛 schedulerefine 鍥鹃摼璺畬鎴愭湰杞井璋冿紱
// 3. 璐熻矗鎶婂井璋冪粨鏋滃洖鍐欓瑙堢紦瀛樹笌鐘舵€佸揩鐓э紝渚涘悗缁户缁井璋冿紱
// 4. 涓嶈礋璐h亰澶╂秷鎭寔涔呭寲锛堟秷鎭寔涔呭寲鐢?AgentChat 涓婚摼璺粺涓€澶勭悊锛夈€?
func (s *AgentService) runScheduleRefineFlow(
ctx context.Context,
selectedModel *ark.ChatModel,
@@ -34,24 +35,24 @@ func (s *AgentService) runScheduleRefineFlow(
_ = outChan
_ = modelName
// 1. 依赖预检:模型为空时无法执行任何节点,直接失败避免空指针。
// 1. 渚濊禆棰勬锛氭ā鍨嬩负绌烘椂鏃犳硶鎵ц浠讳綍鑺傜偣锛岀洿鎺ュけ璐ラ伩鍏嶇┖鎸囬拡銆?
if selectedModel == nil {
return "", errors.New("schedule refine model is nil")
}
emitStage("schedule_refine.context.loading", "正在加载上一版排程上下文。")
// 2. 先查 Redis 预览快照,保证热路径低延迟。
// 2.1 如果 Redis 未命中,再回源 MySQL 快照兜底;
// 2.2 如果两者都没有,说明当前会话没有可微调基础,直接返回业务错误。
// 2. 鍏堟煡 Redis 棰勮蹇収锛屼繚璇佺儹璺緞浣庡欢杩熴€?
// 2.1 濡傛灉 Redis 鏈懡涓紝鍐嶅洖婧?MySQL 蹇収鍏滃簳锛?
// 2.2 濡傛灉涓よ€呴兘娌℃湁锛岃鏄庡綋鍓嶄細璇濇病鏈夊彲寰皟鍩虹锛岀洿鎺ヨ繑鍥炰笟鍔¢敊璇€?
preview := s.loadSchedulePreviewContext(ctx, userID, chatID)
if preview == nil {
return "", respond.SchedulePlanPreviewNotFound
}
// 3. 初始化微调状态并运行独立图。
state := schedulerefine.NewScheduleRefineState(traceID, userID, chatID, userMessage, preview)
finalState, runErr := schedulerefine.RunScheduleRefineGraph(ctx, schedulerefine.ScheduleRefineGraphRunInput{
// 3. 鍒濆鍖栧井璋冪姸鎬佸苟杩愯鐙珛鍥俱€?
state := agentnode.NewScheduleRefineState(traceID, userID, chatID, userMessage, preview)
finalState, runErr := agentgraph.RunScheduleRefineGraph(ctx, agentnode.ScheduleRefineGraphRunInput{
Model: selectedModel,
State: state,
EmitStage: emitStage,
@@ -63,12 +64,12 @@ func (s *AgentService) runScheduleRefineFlow(
return "", errors.New("schedule refine graph returned nil state")
}
// 4. 调用目的:
// 4.1 saveSchedulePlanPreview 目前是“预览缓存 + MySQL 快照”的统一写入口;
// 4.2 这里把 refine state 映射为 scheduleplan state,复用已有落盘链路;
// 4.3 但若是“独立复合分支已出站、终审仍失败”,则不覆盖上一版预览,避免外部误以为新方案已验证通过。
// 4. 璋冪敤鐩殑锛?
// 4.1 saveSchedulePlanPreview 鐩墠鏄€滈瑙堢紦瀛?+ MySQL 蹇収鈥濈殑缁熶竴鍐欏叆鍙o紱
// 4.2 杩欓噷鎶?refine state 鏄犲皠涓?scheduleplan state锛屽鐢ㄥ凡鏈夎惤鐩橀摼璺紱
// 4.3 浣嗚嫢鏄€滅嫭绔嬪鍚堝垎鏀凡鍑虹珯銆佺粓瀹′粛澶辫触鈥濓紝鍒欎笉瑕嗙洊涓婁竴鐗堥瑙堬紝閬垮厤澶栭儴璇互涓烘柊鏂规宸查獙璇侀€氳繃銆?
if shouldPersistScheduleRefinePreview(finalState) {
s.saveSchedulePlanPreview(ctx, userID, chatID, convertRefineStateToPlanState(finalState))
s.saveSchedulePlanPreviewAgent2(ctx, userID, chatID, convertRefineStateToPlanState(finalState))
} else {
emitStage("schedule_refine.preview.skipped", "复合分支终审未通过,本轮结果不覆盖上一版预览。")
}
@@ -80,13 +81,13 @@ func (s *AgentService) runScheduleRefineFlow(
return reply, nil
}
// loadSchedulePreviewContext 读取“可用于连续微调”的排程上下文快照。
// loadSchedulePreviewContext 璇诲彇鈥滃彲鐢ㄤ簬杩炵画寰皟鈥濈殑鎺掔▼涓婁笅鏂囧揩鐓с€?
//
// 步骤化说明:
// 1. 先查 Redis:命中则直接返回,时延最小;
// 2. Redis miss 再查 MySQL:保证缓存过期后仍可继续微调;
// 3. MySQL 命中且 Redis 可用,顺便回填 Redis提升后续命中率
// 4. 任一步失败仅打日志,不 panic由上层根据返回 nil 做统一处理。
// 姝ラ鍖栬鏄庯細
// 1. 鍏堟煡 Redis锛氬懡涓垯鐩存帴杩斿洖锛屾椂寤舵渶灏忥紱
// 2. Redis miss 鍐嶆煡 MySQL锛氫繚璇佺紦瀛樿繃鏈熷悗浠嶅彲缁х画寰皟锛?
// 3. 鑻?MySQL 鍛戒腑涓?Redis 鍙敤锛岄『渚垮洖濉?Redis锛屾彁鍗囧悗缁懡涓巼锛?
// 4. 浠讳竴姝ュけ璐ヤ粎鎵撴棩蹇楋紝涓?panic锛岀敱涓婂眰鏍规嵁杩斿洖 nil 鍋氱粺涓€澶勭悊銆?
func (s *AgentService) loadSchedulePreviewContext(ctx context.Context, userID int, chatID string) *model.SchedulePlanPreviewCache {
normalizedChatID := strings.TrimSpace(chatID)
if normalizedChatID == "" || userID <= 0 {
@@ -96,7 +97,7 @@ func (s *AgentService) loadSchedulePreviewContext(ctx context.Context, userID in
if s.cacheDAO != nil {
preview, err := s.cacheDAO.GetSchedulePlanPreviewFromCache(ctx, userID, normalizedChatID)
if err != nil {
log.Printf("读取排程预览缓存失败 chat_id=%s: %v", normalizedChatID, err)
log.Printf("璇诲彇鎺掔▼棰勮缂撳瓨澶辫触 chat_id=%s: %v", normalizedChatID, err)
} else if preview != nil {
return preview
}
@@ -107,7 +108,7 @@ func (s *AgentService) loadSchedulePreviewContext(ctx context.Context, userID in
}
snapshot, err := s.repo.GetScheduleStateSnapshot(ctx, userID, normalizedChatID)
if err != nil {
log.Printf("读取排程状态快照失败 chat_id=%s: %v", normalizedChatID, err)
log.Printf("璇诲彇鎺掔▼鐘舵€佸揩鐓уけ璐?chat_id=%s: %v", normalizedChatID, err)
return nil
}
if snapshot == nil {
@@ -117,19 +118,19 @@ func (s *AgentService) loadSchedulePreviewContext(ctx context.Context, userID in
preview := snapshotToSchedulePlanPreviewCache(snapshot)
if preview != nil && s.cacheDAO != nil {
if setErr := s.cacheDAO.SetSchedulePlanPreviewToCache(ctx, userID, normalizedChatID, preview); setErr != nil {
log.Printf("回填排程预览缓存失败 chat_id=%s: %v", normalizedChatID, setErr)
log.Printf("鍥炲~鎺掔▼棰勮缂撳瓨澶辫触 chat_id=%s: %v", normalizedChatID, setErr)
}
}
return preview
}
// convertRefineStateToPlanState schedulerefine 状态映射为 scheduleplan 状态。
// convertRefineStateToPlanState 鎶?schedulerefine 鐘舵€佹槧灏勪负 scheduleplan 鐘舵€併€?
//
// 设计意图:
// 1. 复用现有 saveSchedulePlanPreview 写入链路,减少重复落盘代码;
// 2. 仅映射“预览持久化必须字段”,避免把 refine 运行期临时字段带入存储层;
// 3. 后续如要扩展 refine 专属快照字段,可在该映射处集中演进。
func convertRefineStateToPlanState(st *schedulerefine.ScheduleRefineState) *scheduleplan.SchedulePlanState {
// 璁捐鎰忓浘锛?
// 1. 澶嶇敤鐜版湁 saveSchedulePlanPreview 鍐欏叆閾捐矾锛屽噺灏戦噸澶嶈惤鐩樹唬鐮侊紱
// 2. 浠呮槧灏勨€滈瑙堟寔涔呭寲蹇呴』瀛楁鈥濓紝閬垮厤鎶?refine 杩愯鏈熶复鏃跺瓧娈靛甫鍏ュ瓨鍌ㄥ眰锛?
// 3. 鍚庣画濡傝鎵╁睍 refine 涓撳睘蹇収瀛楁锛屽彲鍦ㄨ鏄犲皠澶勯泦涓紨杩涖€?
func convertRefineStateToPlanState(st *agentnode.ScheduleRefineState) *agentmodel.SchedulePlanState {
if st == nil {
return nil
}
@@ -137,7 +138,7 @@ func convertRefineStateToPlanState(st *schedulerefine.ScheduleRefineState) *sche
if st.Contract.Strategy == "keep" {
adjustmentScope = "small"
}
return &scheduleplan.SchedulePlanState{
return &agentmodel.SchedulePlanState{
TraceID: strings.TrimSpace(st.TraceID),
UserID: st.UserID,
ConversationID: strings.TrimSpace(st.ConversationID),
@@ -157,17 +158,17 @@ func convertRefineStateToPlanState(st *schedulerefine.ScheduleRefineState) *sche
}
}
// shouldPersistScheduleRefinePreview 判断“本轮微调结果是否应覆盖上一版预览”。
// shouldPersistScheduleRefinePreview 鍒ゆ柇鈥滄湰杞井璋冪粨鏋滄槸鍚﹀簲瑕嗙洊涓婁竴鐗堥瑙堚€濄€?
//
// 职责边界:
// 1. 默认沿用原有 refine 持久化策略,保证普通 ReAct 微调链路不受影响;
// 2. 仅当“独立复合分支已直接出站,但终审未通过”时,拒绝覆盖上一版预览;
// 3. 这样可以避免外层把未经验证的复合结果当成新的基线继续滚动微调。
func shouldPersistScheduleRefinePreview(st *schedulerefine.ScheduleRefineState) bool {
// 鑱岃矗杈圭晫锛?
// 1. 榛樿娌跨敤鍘熸湁 refine 鎸佷箙鍖栫瓥鐣ワ紝淇濊瘉鏅€?ReAct 寰皟閾捐矾涓嶅彈褰卞搷锛?
// 2. 浠呭綋鈥滅嫭绔嬪鍚堝垎鏀凡鐩存帴鍑虹珯锛屼絾缁堝鏈€氳繃鈥濇椂锛屾嫆缁濊鐩栦笂涓€鐗堥瑙堬紱
// 3. 杩欐牱鍙互閬垮厤澶栧眰鎶婃湭缁忛獙璇佺殑澶嶅悎缁撴灉褰撴垚鏂扮殑鍩虹嚎缁х画婊氬姩寰皟銆?
func shouldPersistScheduleRefinePreview(st *agentnode.ScheduleRefineState) bool {
if st == nil {
return false
}
if st.CompositeRouteSucceeded && !schedulerefine.FinalHardCheckPassed(st) {
if st.CompositeRouteSucceeded && !agentnode.FinalHardCheckPassed(st) {
return false
}
return true