Version: 0.8.3.dev.260328
后端: 1.彻底删除原agent文件夹,并将现agent2文件夹全量重命名为agent(包括全部涉及到的文件以及文档、注释),迁移工作完美结束 2.修复了重试消息的相关逻辑问题 前端: 1.改善了一些交互体验,修复了一些bug,现在只剩少的功能了,现存的bug基本都修复完毕 全仓库: 1.更新了决策记录和README文档
This commit is contained in:
17
backend/agent/model/common.go
Normal file
17
backend/agent/model/common.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package agentmodel
|
||||
|
||||
// AgentRequest 是 Agent 总入口接收的统一请求结构。
|
||||
type AgentRequest struct {
|
||||
UserID int
|
||||
ConversationID string
|
||||
UserMessage string
|
||||
ModelName string
|
||||
Extra map[string]any
|
||||
}
|
||||
|
||||
// AgentResponse 是 Agent 总入口返回的统一响应结构。
|
||||
type AgentResponse struct {
|
||||
Action AgentAction
|
||||
Reply string
|
||||
Meta map[string]any
|
||||
}
|
||||
102
backend/agent/model/quicknote.go
Normal file
102
backend/agent/model/quicknote.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package agentmodel
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
agentshared "github.com/LoveLosita/smartflow/backend/agent/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
// QuickNoteDatetimeMinuteLayout 是随口记链路统一使用的分钟级时间格式。
|
||||
QuickNoteDatetimeMinuteLayout = "2006-01-02 15:04"
|
||||
// QuickNoteTimezoneName 是随口记时间解析与展示优先使用的时区。
|
||||
QuickNoteTimezoneName = "Asia/Shanghai"
|
||||
|
||||
QuickNotePriorityImportantUrgent = TaskPriorityImportantUrgent
|
||||
QuickNotePriorityImportantNotUrgent = TaskPriorityImportantNotUrgent
|
||||
QuickNotePrioritySimpleNotImportant = TaskPrioritySimpleNotImportant
|
||||
QuickNotePriorityComplexNotImportant = TaskPriorityComplexNotImportant
|
||||
)
|
||||
|
||||
// QuickNoteState 是随口记图在节点间流转的完整状态。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责保存意图识别、任务提取、工具重试和最终回复所需状态。
|
||||
// 2. 不负责图编排,也不直接映射数据库任务实体。
|
||||
type QuickNoteState struct {
|
||||
TraceID string
|
||||
UserID int
|
||||
ConversationID string
|
||||
RequestNow time.Time
|
||||
RequestNowText string
|
||||
UserInput string
|
||||
|
||||
IsQuickNoteIntent bool
|
||||
IntentJudgeReason string
|
||||
|
||||
ExtractedTitle string
|
||||
ExtractedDeadline *time.Time
|
||||
ExtractedDeadlineText string
|
||||
ExtractedUrgencyThreshold *time.Time
|
||||
ExtractedPriority int
|
||||
ExtractedBanter string
|
||||
PlannedBySingleCall bool
|
||||
ExtractedPriorityReason string
|
||||
DeadlineValidationError string
|
||||
|
||||
ToolAttemptCount int
|
||||
MaxToolRetry int
|
||||
LastToolError string
|
||||
PersistedTaskID int
|
||||
Persisted bool
|
||||
AssistantReply string
|
||||
}
|
||||
|
||||
// NewQuickNoteState 负责创建随口记图的初始状态。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. RequestNow 与 RequestNowText 会在创建时同步写入,保证整条链路共用同一时间基准。
|
||||
// 2. MaxToolRetry 默认给 3,避免上层未配置时完全失去重试能力。
|
||||
func NewQuickNoteState(traceID string, userID int, conversationID, userInput string) *QuickNoteState {
|
||||
requestNow := agentshared.NowToMinute()
|
||||
return &QuickNoteState{
|
||||
TraceID: traceID,
|
||||
UserID: userID,
|
||||
ConversationID: conversationID,
|
||||
RequestNow: requestNow,
|
||||
RequestNowText: agentshared.FormatMinute(requestNow),
|
||||
UserInput: userInput,
|
||||
MaxToolRetry: 3,
|
||||
}
|
||||
}
|
||||
|
||||
// CanRetryTool 返回当前是否还允许再次调用持久化工具。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. true 表示“尚未达到最大重试次数”,调用方仍可继续重试。
|
||||
// 2. false 表示必须收口,避免无限重试。
|
||||
func (s *QuickNoteState) CanRetryTool() bool {
|
||||
return s.ToolAttemptCount < s.MaxToolRetry
|
||||
}
|
||||
|
||||
// RecordToolError 记录一次工具失败,并推进重试计数。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只更新与工具失败相关的状态。
|
||||
// 2. 不决定是否继续重试,是否重试由节点分支逻辑判断。
|
||||
func (s *QuickNoteState) RecordToolError(errMsg string) {
|
||||
s.ToolAttemptCount++
|
||||
s.LastToolError = errMsg
|
||||
}
|
||||
|
||||
// RecordToolSuccess 记录一次工具成功结果。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. taskID 必须是持久化后的真实任务 ID。
|
||||
// 2. 成功后会清空 LastToolError,表示当前链路已进入稳定态。
|
||||
func (s *QuickNoteState) RecordToolSuccess(taskID int) {
|
||||
s.ToolAttemptCount++
|
||||
s.PersistedTaskID = taskID
|
||||
s.Persisted = true
|
||||
s.LastToolError = ""
|
||||
}
|
||||
20
backend/agent/model/route.go
Normal file
20
backend/agent/model/route.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package agentmodel
|
||||
|
||||
// AgentAction 表示一级路由动作。
|
||||
type AgentAction string
|
||||
|
||||
const (
|
||||
ActionChat AgentAction = "chat"
|
||||
ActionQuickNoteCreate AgentAction = "quick_note_create"
|
||||
ActionTaskQuery AgentAction = "task_query"
|
||||
ActionSchedulePlanCreate AgentAction = "schedule_plan_create"
|
||||
ActionSchedulePlanRefine AgentAction = "schedule_plan_refine"
|
||||
)
|
||||
|
||||
// RouteDecision 是统一一级分流结果。
|
||||
type RouteDecision struct {
|
||||
Action AgentAction
|
||||
TrustRoute bool
|
||||
Detail string
|
||||
Confidence float64
|
||||
}
|
||||
200
backend/agent/model/schedule.go
Normal file
200
backend/agent/model/schedule.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package agentmodel
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LoveLosita/smartflow/backend/model"
|
||||
)
|
||||
|
||||
const (
|
||||
// SchedulePlanTimezoneName 是排程链路默认业务时区。
|
||||
// 与随口记保持一致,固定东八区,避免容器运行在 UTC 导致“明天/今晚”偏移。
|
||||
SchedulePlanTimezoneName = "Asia/Shanghai"
|
||||
|
||||
// SchedulePlanDatetimeLayout 是排程链路内部统一的分钟级时间格式。
|
||||
SchedulePlanDatetimeLayout = "2006-01-02 15:04"
|
||||
|
||||
// SchedulePlanDefaultDailyRefineConcurrency 是日内并发优化默认并发度。
|
||||
// 这里给一个保守默认值,避免未配置时直接把模型并发打满导致限流。
|
||||
SchedulePlanDefaultDailyRefineConcurrency = 3
|
||||
|
||||
// SchedulePlanDefaultWeeklyAdjustBudget 是周级配平默认调整额度。
|
||||
// 额度存在的目的:
|
||||
// 1. 防止周级 ReAct 过度调整导致震荡;
|
||||
// 2. 控制 token 与时延成本;
|
||||
// 3. 让方案改动更可解释。
|
||||
SchedulePlanDefaultWeeklyAdjustBudget = 5
|
||||
|
||||
// SchedulePlanDefaultWeeklyTotalBudget 是周级“总尝试次数”默认预算。
|
||||
//
|
||||
// 设计意图:
|
||||
// 1. 总预算统计“动作尝试次数”(成功/失败都记一次);
|
||||
// 2. 有效预算统计“成功动作次数”(仅成功时记一次);
|
||||
// 3. 通过双预算把“探索次数”和“有效改动次数”分离,降低模型无效空转成本。
|
||||
SchedulePlanDefaultWeeklyTotalBudget = 8
|
||||
|
||||
// SchedulePlanDefaultWeeklyRefineConcurrency 是周级“按周并发”默认并发度。
|
||||
// 说明:
|
||||
// 1. 周级输入规模通常比单天更大,默认并发度不宜过高,避免触发模型侧限流;
|
||||
// 2. 可在运行时按请求状态覆盖。
|
||||
SchedulePlanDefaultWeeklyRefineConcurrency = 2
|
||||
|
||||
// SchedulePlanAdjustmentScopeSmall 表示“小改动微调”。
|
||||
// 语义:优先走快速路径,只做轻量周级调整。
|
||||
SchedulePlanAdjustmentScopeSmall = "small"
|
||||
// SchedulePlanAdjustmentScopeMedium 表示“中等改动微调”。
|
||||
// 语义:跳过日内拆分,直接进入周级配平。
|
||||
SchedulePlanAdjustmentScopeMedium = "medium"
|
||||
// SchedulePlanAdjustmentScopeLarge 表示“大改动重排”。
|
||||
// 语义:必要时重新走全量路径(日内并发 + 周级配平)。
|
||||
SchedulePlanAdjustmentScopeLarge = "large"
|
||||
)
|
||||
|
||||
const (
|
||||
schedulePlanTimezoneName = SchedulePlanTimezoneName
|
||||
schedulePlanDatetimeLayout = SchedulePlanDatetimeLayout
|
||||
schedulePlanDefaultDailyRefineConcurrency = SchedulePlanDefaultDailyRefineConcurrency
|
||||
schedulePlanDefaultWeeklyAdjustBudget = SchedulePlanDefaultWeeklyAdjustBudget
|
||||
schedulePlanDefaultWeeklyTotalBudget = SchedulePlanDefaultWeeklyTotalBudget
|
||||
schedulePlanDefaultWeeklyRefineConcurrency = SchedulePlanDefaultWeeklyRefineConcurrency
|
||||
schedulePlanAdjustmentScopeSmall = SchedulePlanAdjustmentScopeSmall
|
||||
schedulePlanAdjustmentScopeMedium = SchedulePlanAdjustmentScopeMedium
|
||||
schedulePlanAdjustmentScopeLarge = SchedulePlanAdjustmentScopeLarge
|
||||
)
|
||||
|
||||
// DayGroup 是“按天拆分后”的最小优化单元。
|
||||
//
|
||||
// 设计目的:
|
||||
// 1. 把全量周视角数据拆成“单天小包”,降低日内 ReAct 输入规模;
|
||||
// 2. 支持并发优化不同天的数据,缩短整体等待;
|
||||
// 3. 通过 SkipRefine 让低收益天数直接跳过,节省模型调用成本。
|
||||
type DayGroup struct {
|
||||
Week int
|
||||
DayOfWeek int
|
||||
Entries []model.HybridScheduleEntry
|
||||
SkipRefine bool
|
||||
}
|
||||
|
||||
// SchedulePlanState 是“智能排程”链路在 graph 节点间传递的统一状态容器。
|
||||
//
|
||||
// 设计目标:
|
||||
// 1) 收拢排程请求全生命周期的上下文,降低节点间参数散落;
|
||||
// 2) 支持“粗排 -> 日内并发优化 -> 周级配平 -> 终审校验”的完整链路追踪;
|
||||
// 3) 支持连续对话微调:保留上版方案 + 本次约束变更,便于增量重排。
|
||||
type SchedulePlanState struct {
|
||||
// ── 基础上下文 ──
|
||||
TraceID string
|
||||
UserID int
|
||||
ConversationID string
|
||||
RequestNow time.Time
|
||||
RequestNowText string
|
||||
|
||||
// ── plan 节点输出 ──
|
||||
UserIntent string
|
||||
Constraints []string
|
||||
TaskClassIDs []int
|
||||
Strategy string
|
||||
TaskTags map[int]string
|
||||
TaskTagHintsByName map[string]string
|
||||
|
||||
// ── preview 节点输出 ──
|
||||
CandidatePlans []model.UserWeekSchedule
|
||||
AllocatedItems []model.TaskClassItem
|
||||
HasPlanningWindow bool
|
||||
PlanStartWeek int
|
||||
PlanStartDay int
|
||||
PlanEndWeek int
|
||||
PlanEndDay int
|
||||
|
||||
// ── 日内并发优化阶段 ──
|
||||
DailyGroups map[int]map[int]*DayGroup
|
||||
DailyResults map[int]map[int][]model.HybridScheduleEntry
|
||||
DailyRefineConcurrency int
|
||||
|
||||
// ── 周级 ReAct 精排阶段 ──
|
||||
HybridEntries []model.HybridScheduleEntry
|
||||
MergeSnapshot []model.HybridScheduleEntry
|
||||
ReactRound int
|
||||
ReactMaxRound int
|
||||
ReactSummary string
|
||||
ReactDone bool
|
||||
WeeklyAdjustBudget int
|
||||
WeeklyAdjustUsed int
|
||||
WeeklyTotalBudget int
|
||||
WeeklyTotalUsed int
|
||||
WeeklyRefineConcurrency int
|
||||
WeeklyActionLogs []string
|
||||
|
||||
// ── 连续对话微调 ──
|
||||
PreviousPlanJSON string
|
||||
IsAdjustment bool
|
||||
RestartRequested bool
|
||||
AdjustmentScope string
|
||||
AdjustmentReason string
|
||||
AdjustmentConfidence float64
|
||||
HasPreviousPreview bool
|
||||
PreviousTaskClassIDs []int
|
||||
PreviousHybridEntries []model.HybridScheduleEntry
|
||||
PreviousAllocatedItems []model.TaskClassItem
|
||||
PreviousCandidatePlans []model.UserWeekSchedule
|
||||
|
||||
// ── 最终输出 ──
|
||||
FinalSummary string
|
||||
Completed bool
|
||||
}
|
||||
|
||||
// NewSchedulePlanState 创建排程状态对象并初始化默认值。
|
||||
func NewSchedulePlanState(traceID string, userID int, conversationID string) *SchedulePlanState {
|
||||
now := schedulePlanNowToMinute()
|
||||
return &SchedulePlanState{
|
||||
TraceID: traceID,
|
||||
UserID: userID,
|
||||
ConversationID: conversationID,
|
||||
RequestNow: now,
|
||||
RequestNowText: now.In(schedulePlanLocation()).Format(schedulePlanDatetimeLayout),
|
||||
Strategy: "steady",
|
||||
TaskTags: make(map[int]string),
|
||||
TaskTagHintsByName: make(map[string]string),
|
||||
DailyRefineConcurrency: schedulePlanDefaultDailyRefineConcurrency,
|
||||
WeeklyRefineConcurrency: schedulePlanDefaultWeeklyRefineConcurrency,
|
||||
AdjustmentScope: schedulePlanAdjustmentScopeLarge,
|
||||
ReactMaxRound: 2,
|
||||
WeeklyAdjustBudget: schedulePlanDefaultWeeklyAdjustBudget,
|
||||
WeeklyTotalBudget: schedulePlanDefaultWeeklyTotalBudget,
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeSchedulePlanAdjustmentScope 归一化排程微调力度字段。
|
||||
//
|
||||
// 兜底策略:
|
||||
// 1. 只接受 small/medium/large;
|
||||
// 2. 任何未知值都回退为 large,保证不会误走“过轻”路径。
|
||||
func NormalizeSchedulePlanAdjustmentScope(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case schedulePlanAdjustmentScopeSmall:
|
||||
return schedulePlanAdjustmentScopeSmall
|
||||
case schedulePlanAdjustmentScopeMedium:
|
||||
return schedulePlanAdjustmentScopeMedium
|
||||
default:
|
||||
return schedulePlanAdjustmentScopeLarge
|
||||
}
|
||||
}
|
||||
|
||||
// schedulePlanLocation 返回排程链路使用的业务时区。
|
||||
func schedulePlanLocation() *time.Location {
|
||||
loc, err := time.LoadLocation(schedulePlanTimezoneName)
|
||||
if err != nil {
|
||||
return time.Local
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
// schedulePlanNowToMinute 返回当前时间并截断到分钟级。
|
||||
func schedulePlanNowToMinute() time.Time {
|
||||
return time.Now().In(schedulePlanLocation()).Truncate(time.Minute)
|
||||
}
|
||||
|
||||
func normalizeAdjustmentScope(raw string) string {
|
||||
return NormalizeSchedulePlanAdjustmentScope(raw)
|
||||
}
|
||||
344
backend/agent/model/schedule_refine.go
Normal file
344
backend/agent/model/schedule_refine.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package agentmodel
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
agentshared "github.com/LoveLosita/smartflow/backend/agent/shared"
|
||||
"github.com/LoveLosita/smartflow/backend/model"
|
||||
)
|
||||
|
||||
const (
|
||||
datetimeLayout = agentshared.MinuteLayout
|
||||
|
||||
ScheduleRefineDefaultPlanMax = 2
|
||||
ScheduleRefineDefaultExecuteMax = 24
|
||||
ScheduleRefineDefaultPerTaskBudget = 4
|
||||
ScheduleRefineDefaultReplanMax = 2
|
||||
ScheduleRefineDefaultCompositeRetry = 2
|
||||
ScheduleRefineDefaultRepairReserve = 1
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPlanMax = ScheduleRefineDefaultPlanMax
|
||||
defaultExecuteMax = ScheduleRefineDefaultExecuteMax
|
||||
defaultPerTaskBudget = ScheduleRefineDefaultPerTaskBudget
|
||||
defaultReplanMax = ScheduleRefineDefaultReplanMax
|
||||
defaultCompositeRetry = ScheduleRefineDefaultCompositeRetry
|
||||
defaultRepairReserve = ScheduleRefineDefaultRepairReserve
|
||||
)
|
||||
|
||||
// RefineContract 琛ㄧず鏈疆寰皟鎰忓浘濂戠害銆?
|
||||
type RefineContract struct {
|
||||
Intent string `json:"intent"`
|
||||
Strategy string `json:"strategy"`
|
||||
HardRequirements []string `json:"hard_requirements"`
|
||||
HardAssertions []RefineAssertion `json:"hard_assertions,omitempty"`
|
||||
KeepRelativeOrder bool `json:"keep_relative_order"`
|
||||
OrderScope string `json:"order_scope"`
|
||||
}
|
||||
|
||||
// RefineAssertion 琛ㄧず鍙敱鍚庣鐩存帴鍒ゅ畾鐨勭粨鏋勫寲纭柇瑷€銆?
|
||||
//
|
||||
// 瀛楁璇存槑锛?
|
||||
// 1. Metric锛氭柇瑷€鎸囨爣鍚嶏紝渚嬪 source_move_ratio_percent锛?
|
||||
// 2. Operator锛氭瘮杈冩搷浣滅锛屾敮鎸?== / <= / >= / between锛?
|
||||
// 3. Value/Min/Max锛氶槇鍊硷紱
|
||||
// 4. Week/TargetWeek锛氬彲閫夊懆娆′笂涓嬫枃銆?
|
||||
type RefineAssertion struct {
|
||||
Metric string `json:"metric"`
|
||||
Operator string `json:"operator"`
|
||||
Value int `json:"value,omitempty"`
|
||||
Min int `json:"min,omitempty"`
|
||||
Max int `json:"max,omitempty"`
|
||||
Week int `json:"week,omitempty"`
|
||||
TargetWeek int `json:"target_week,omitempty"`
|
||||
}
|
||||
|
||||
// HardCheckReport 琛ㄧず缁堝纭牎楠岀粨鏋溿€?
|
||||
type HardCheckReport struct {
|
||||
PhysicsPassed bool `json:"physics_passed"`
|
||||
PhysicsIssues []string `json:"physics_issues,omitempty"`
|
||||
|
||||
IntentPassed bool `json:"intent_passed"`
|
||||
IntentReason string `json:"intent_reason,omitempty"`
|
||||
IntentUnmet []string `json:"intent_unmet,omitempty"`
|
||||
|
||||
OrderPassed bool `json:"order_passed"`
|
||||
OrderIssues []string `json:"order_issues,omitempty"`
|
||||
|
||||
RepairTried bool `json:"repair_tried"`
|
||||
}
|
||||
|
||||
// ReactRoundObservation 璁板綍姣忚疆 ReAct 鐨勫叧閿瀵熴€?
|
||||
type ReactRoundObservation struct {
|
||||
Round int `json:"round"`
|
||||
GoalCheck string `json:"goal_check,omitempty"`
|
||||
Decision string `json:"decision,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolParams map[string]any `json:"tool_params,omitempty"`
|
||||
ToolSuccess bool `json:"tool_success"`
|
||||
ToolErrorCode string `json:"tool_error_code,omitempty"`
|
||||
ToolResult string `json:"tool_result,omitempty"`
|
||||
Reflect string `json:"reflect,omitempty"`
|
||||
}
|
||||
|
||||
// PlannerPlan 琛ㄧず Planner 鐢熸垚鐨勯樁娈垫墽琛岃鍒掋€?
|
||||
type PlannerPlan struct {
|
||||
Summary string `json:"summary"`
|
||||
Steps []string `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// RefineSlicePlan 琛ㄧず鍒囩墖鑺傜偣杈撳嚭銆?
|
||||
type RefineSlicePlan struct {
|
||||
WeekFilter []int `json:"week_filter,omitempty"`
|
||||
SourceDays []int `json:"source_days,omitempty"`
|
||||
TargetDays []int `json:"target_days,omitempty"`
|
||||
ExcludeSections []int `json:"exclude_sections,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// RefineObjective 琛ㄧず鈥滃彲鎵ц涓斿彲鏍¢獙鈥濈殑鐩爣绾︽潫銆?
|
||||
//
|
||||
// 璁捐璇存槑锛?
|
||||
// 1. 鐢?contract/slice 浠庤嚜鐒惰瑷€缂栬瘧寰楀埌锛?
|
||||
// 2. 鎵ц闃舵锛坉one 鏀跺彛锛変笌缁堝闃舵锛坔ard_check锛夊叡鐢ㄥ悓涓€浠界害鏉燂紱
|
||||
// 3. 閬垮厤鈥滄墽琛岄€昏緫涓庣粓瀹¢€昏緫鍚勮鍚勮瘽鈥濄€?
|
||||
type RefineObjective struct {
|
||||
Mode string `json:"mode,omitempty"` // none | move_all | move_ratio
|
||||
|
||||
SourceWeeks []int `json:"source_weeks,omitempty"`
|
||||
TargetWeeks []int `json:"target_weeks,omitempty"`
|
||||
SourceDays []int `json:"source_days,omitempty"`
|
||||
TargetDays []int `json:"target_days,omitempty"`
|
||||
|
||||
ExcludeSections []int `json:"exclude_sections,omitempty"`
|
||||
|
||||
BaselineSourceTaskCount int `json:"baseline_source_task_count,omitempty"`
|
||||
RequiredMoveMin int `json:"required_move_min,omitempty"`
|
||||
RequiredMoveMax int `json:"required_move_max,omitempty"`
|
||||
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduleRefineState 鏄繛缁井璋冨浘鐨勭粺涓€鐘舵€併€?
|
||||
type ScheduleRefineState struct {
|
||||
// 1) 璇锋眰涓婁笅鏂?
|
||||
TraceID string
|
||||
UserID int
|
||||
ConversationID string
|
||||
UserMessage string
|
||||
RequestNow time.Time
|
||||
RequestNowText string
|
||||
|
||||
// 2) 缁ф壙鑷瑙堝揩鐓х殑鏁版嵁
|
||||
TaskClassIDs []int
|
||||
Constraints []string
|
||||
// InitialHybridEntries 淇濆瓨鏈疆寰皟寮€濮嬪墠鐨勫熀绾匡紝鐢ㄤ簬缁堝鍋氣€滃墠鍚庡姣斺€濄€?
|
||||
// 璇存槑锛?
|
||||
// 1. 鍙璇箟锛屼笉鍙備笌鎵ц鏈熸敼鍐欙紱
|
||||
// 2. 缁堝鍙熀浜庡畠鍒ゆ柇鈥滄潵婧愪换鍔℃槸鍚︾湡姝h縼绉诲埌鐩爣鍖哄煙鈥濄€?
|
||||
InitialHybridEntries []model.HybridScheduleEntry
|
||||
HybridEntries []model.HybridScheduleEntry
|
||||
AllocatedItems []model.TaskClassItem
|
||||
CandidatePlans []model.UserWeekSchedule
|
||||
|
||||
// 3) 鏈疆鎵ц鐘舵€?
|
||||
UserIntent string
|
||||
Contract RefineContract
|
||||
|
||||
PlanMax int
|
||||
PerTaskBudget int
|
||||
ExecuteMax int
|
||||
ReplanMax int
|
||||
// CompositeRetryMax 琛ㄧず澶嶅悎璺敱澶辫触鍚庣殑鏈€澶ч噸璇曟鏁帮紙涓嶅惈棣栨灏濊瘯锛夈€?
|
||||
CompositeRetryMax int
|
||||
|
||||
PlanUsed int
|
||||
ReplanUsed int
|
||||
|
||||
MaxRounds int
|
||||
RepairReserve int
|
||||
RoundUsed int
|
||||
ActionLogs []string
|
||||
|
||||
ConsecutiveFailures int
|
||||
ThinkingBoostArmed bool
|
||||
ObservationHistory []ReactRoundObservation
|
||||
|
||||
CurrentPlan PlannerPlan
|
||||
BatchMoveAllowed bool
|
||||
// DisableCompositeTools=true 琛ㄧず宸茶繘鍏?ReAct 鍏滃簳锛岀姝㈠啀璋冪敤澶嶅悎宸ュ叿銆?
|
||||
DisableCompositeTools bool
|
||||
// CompositeRouteTried 鏍囪鏄惁灏濊瘯杩団€滃鍚堟壒澶勭悊璺敱鈥濄€?
|
||||
CompositeRouteTried bool
|
||||
// CompositeRouteSucceeded 鏍囪澶嶅悎鎵瑰鐞嗚矾鐢辨槸鍚﹀凡瀹屾垚鈥滃鍚堝垎鏀嚭绔欌€濄€?
|
||||
//
|
||||
// 璇存槑锛?
|
||||
// 1. true 琛ㄧず褰撳墠閾捐矾鍙互璺宠繃 ReAct 鍏滃簳锛岀洿鎺ヨ繘鍏?hard_check锛?
|
||||
// 2. 瀹冧笉绛変环浜庘€滅粓瀹″凡閫氳繃鈥濓紝缁堝鏄惁閫氳繃浠嶄互鍚庣画 HardCheck 缁撴灉涓哄噯锛?
|
||||
// 3. 杩欐牱鍖哄垎鏄负浜嗛伩鍏嶁€滃鍚堝伐鍏峰凡鎴愬姛鎵ц锛屼絾涓氬姟鐩爣瑕佺瓑缁堝瑁佸喅鈥濇椂琚鍒や负澶辫触銆?
|
||||
CompositeRouteSucceeded bool
|
||||
TaskActionUsed map[int]int
|
||||
EntriesVersion int
|
||||
SeenSlotQueries map[string]struct{}
|
||||
|
||||
// RequiredCompositeTool 琛ㄧず鏈疆绛栫暐瑕佹眰鈥滃繀椤昏嚦灏戞垚鍔熶竴娆♀€濈殑澶嶅悎宸ュ叿銆?
|
||||
// 鍙栧€肩害瀹氾細"" | "SpreadEven" | "MinContextSwitch"銆?
|
||||
RequiredCompositeTool string
|
||||
// CompositeToolCalled 璁板綍澶嶅悎宸ュ叿鏄惁鑷冲皯璋冪敤杩囦竴娆★紙涓嶅尯鍒嗘垚鍔熷け璐ワ級銆?
|
||||
CompositeToolCalled map[string]bool
|
||||
// CompositeToolSuccess 璁板綍澶嶅悎宸ュ叿鏄惁鑷冲皯鎴愬姛杩囦竴娆°€?
|
||||
CompositeToolSuccess map[string]bool
|
||||
|
||||
SlicePlan RefineSlicePlan
|
||||
Objective RefineObjective
|
||||
WorksetTaskIDs []int
|
||||
WorksetCursor int
|
||||
CurrentTaskID int
|
||||
CurrentTaskAttempt int
|
||||
|
||||
LastFailedCallSignature string
|
||||
OriginOrderMap map[int]int
|
||||
|
||||
// 4) 缁堝鐘舵€?
|
||||
HardCheck HardCheckReport
|
||||
|
||||
// 5) 鏈€缁堣緭鍑?
|
||||
FinalSummary string
|
||||
Completed bool
|
||||
}
|
||||
|
||||
// NewScheduleRefineState 鍩轰簬涓婁竴鐗堥瑙堝揩鐓у垵濮嬪寲鐘舵€併€?
|
||||
//
|
||||
// 鑱岃矗杈圭晫锛?
|
||||
// 1. 璐熻矗鍒濆鍖栭绠椼€佷笂涓嬫枃瀛楁涓庡彲鍙樼姸鎬佸鍣紱
|
||||
// 2. 璐熻矗鎷疯礉 preview 鏁版嵁锛岄伩鍏嶈法璇锋眰寮曠敤姹℃煋锛?
|
||||
// 3. 涓嶈礋璐e仛浠讳綍璋冨害鍔ㄤ綔銆?
|
||||
func NewScheduleRefineState(traceID string, userID int, conversationID string, userMessage string, preview *model.SchedulePlanPreviewCache) *ScheduleRefineState {
|
||||
now := nowToMinute()
|
||||
st := &ScheduleRefineState{
|
||||
TraceID: strings.TrimSpace(traceID),
|
||||
UserID: userID,
|
||||
ConversationID: strings.TrimSpace(conversationID),
|
||||
UserMessage: strings.TrimSpace(userMessage),
|
||||
RequestNow: now,
|
||||
RequestNowText: now.In(loadLocation()).Format(datetimeLayout),
|
||||
PlanMax: defaultPlanMax,
|
||||
PerTaskBudget: defaultPerTaskBudget,
|
||||
ExecuteMax: defaultExecuteMax,
|
||||
ReplanMax: defaultReplanMax,
|
||||
CompositeRetryMax: defaultCompositeRetry,
|
||||
RepairReserve: defaultRepairReserve,
|
||||
MaxRounds: defaultExecuteMax + defaultRepairReserve,
|
||||
ActionLogs: make([]string, 0, 32),
|
||||
ObservationHistory: make([]ReactRoundObservation, 0, 24),
|
||||
TaskActionUsed: make(map[int]int),
|
||||
SeenSlotQueries: make(map[string]struct{}),
|
||||
OriginOrderMap: make(map[int]int),
|
||||
CompositeToolCalled: map[string]bool{
|
||||
"SpreadEven": false,
|
||||
"MinContextSwitch": false,
|
||||
},
|
||||
CompositeToolSuccess: map[string]bool{
|
||||
"SpreadEven": false,
|
||||
"MinContextSwitch": false,
|
||||
},
|
||||
CurrentPlan: PlannerPlan{
|
||||
Summary: "initialized, waiting for planner output",
|
||||
},
|
||||
SlicePlan: RefineSlicePlan{
|
||||
Reason: "灏氭湭鍒囩墖",
|
||||
},
|
||||
}
|
||||
if preview == nil {
|
||||
return st
|
||||
}
|
||||
|
||||
st.TaskClassIDs = append([]int(nil), preview.TaskClassIDs...)
|
||||
st.InitialHybridEntries = cloneHybridEntries(preview.HybridEntries)
|
||||
st.HybridEntries = cloneHybridEntries(preview.HybridEntries)
|
||||
st.AllocatedItems = cloneTaskClassItems(preview.AllocatedItems)
|
||||
st.CandidatePlans = cloneWeekSchedules(preview.CandidatePlans)
|
||||
st.OriginOrderMap = buildOriginOrderMap(st.HybridEntries)
|
||||
return st
|
||||
}
|
||||
|
||||
func loadLocation() *time.Location {
|
||||
return agentshared.ShanghaiLocation()
|
||||
}
|
||||
|
||||
func nowToMinute() time.Time {
|
||||
return agentshared.NowToMinute()
|
||||
}
|
||||
|
||||
func cloneHybridEntries(src []model.HybridScheduleEntry) []model.HybridScheduleEntry {
|
||||
return agentshared.CloneHybridEntries(src)
|
||||
}
|
||||
|
||||
func cloneTaskClassItems(src []model.TaskClassItem) []model.TaskClassItem {
|
||||
return agentshared.CloneTaskClassItems(src)
|
||||
}
|
||||
|
||||
func cloneWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
|
||||
return agentshared.CloneWeekSchedules(src)
|
||||
}
|
||||
|
||||
// buildOriginOrderMap 鏋勫缓 suggested 浠诲姟鐨勫垵濮嬮『搴忓熀绾匡紙task_item_id -> rank锛夈€?
|
||||
func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
|
||||
orderMap := make(map[int]int)
|
||||
if len(entries) == 0 {
|
||||
return orderMap
|
||||
}
|
||||
suggested := make([]model.HybridScheduleEntry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if isMovableSuggestedTask(entry) {
|
||||
suggested = append(suggested, entry)
|
||||
}
|
||||
}
|
||||
sort.SliceStable(suggested, func(i, j int) bool {
|
||||
left := suggested[i]
|
||||
right := suggested[j]
|
||||
if left.Week != right.Week {
|
||||
return left.Week < right.Week
|
||||
}
|
||||
if left.DayOfWeek != right.DayOfWeek {
|
||||
return left.DayOfWeek < right.DayOfWeek
|
||||
}
|
||||
if left.SectionFrom != right.SectionFrom {
|
||||
return left.SectionFrom < right.SectionFrom
|
||||
}
|
||||
if left.SectionTo != right.SectionTo {
|
||||
return left.SectionTo < right.SectionTo
|
||||
}
|
||||
return left.TaskItemID < right.TaskItemID
|
||||
})
|
||||
for i, entry := range suggested {
|
||||
orderMap[entry.TaskItemID] = i + 1
|
||||
}
|
||||
return orderMap
|
||||
}
|
||||
|
||||
// FinalHardCheckPassed 鍒ゆ柇鈥滄渶缁堢粓瀹♀€濇槸鍚︽暣浣撻€氳繃銆?
|
||||
//
|
||||
// 鑱岃矗杈圭晫锛?
|
||||
// 1. 璐熻矗鑱氬悎 physics/order/intent 涓夌被纭牎楠岀粨鏋滐紝缁欐湇鍔″眰涓庢€荤粨闃舵缁熶竴澶嶇敤锛?
|
||||
// 2. 涓嶈礋璐hЕ鍙戠粓瀹★紝涔熶笉璐熻矗鎺ㄥ淇鍔ㄤ綔锛?
|
||||
// 3. nil state 瑙嗕负鏈€氳繃锛岄伩鍏嶄笂灞傛妸缂哄け缁撴灉璇垽涓烘垚鍔熴€?
|
||||
func FinalHardCheckPassed(st *ScheduleRefineState) bool {
|
||||
if st == nil {
|
||||
return false
|
||||
}
|
||||
return st.HardCheck.PhysicsPassed && st.HardCheck.OrderPassed && st.HardCheck.IntentPassed
|
||||
}
|
||||
|
||||
func isMovableSuggestedTask(entry model.HybridScheduleEntry) bool {
|
||||
if strings.TrimSpace(entry.Status) != "suggested" || entry.TaskItemID <= 0 {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(entry.Type), "course") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
37
backend/agent/model/task_priority.go
Normal file
37
backend/agent/model/task_priority.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package agentmodel
|
||||
|
||||
const (
|
||||
TaskPriorityImportantUrgent = 1
|
||||
TaskPriorityImportantNotUrgent = 2
|
||||
TaskPrioritySimpleNotImportant = 3
|
||||
TaskPriorityComplexNotImportant = 4
|
||||
)
|
||||
|
||||
// IsValidTaskPriority 用于校验任务优先级是否合法。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责判断 priority 是否落在系统支持的 1~4 范围内。
|
||||
// 2. 不负责把自然语言映射成优先级,也不负责做业务兜底推断。
|
||||
func IsValidTaskPriority(priority int) bool {
|
||||
return priority >= TaskPriorityImportantUrgent && priority <= TaskPriorityComplexNotImportant
|
||||
}
|
||||
|
||||
// PriorityLabelCN 返回任务优先级对应的中文标签。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责“优先级枚举 -> 中文展示文案”的稳定映射。
|
||||
// 2. 不负责国际化、多语言切换或业务规则解释。
|
||||
func PriorityLabelCN(priority int) string {
|
||||
switch priority {
|
||||
case TaskPriorityImportantUrgent:
|
||||
return "重要且紧急"
|
||||
case TaskPriorityImportantNotUrgent:
|
||||
return "重要不紧急"
|
||||
case TaskPrioritySimpleNotImportant:
|
||||
return "简单不重要"
|
||||
case TaskPriorityComplexNotImportant:
|
||||
return "复杂不重要"
|
||||
default:
|
||||
return "未知优先级"
|
||||
}
|
||||
}
|
||||
87
backend/agent/model/taskquery.go
Normal file
87
backend/agent/model/taskquery.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package agentmodel
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// DefaultTaskQueryLimit 是任务查询默认返回条数。
|
||||
DefaultTaskQueryLimit = 5
|
||||
// MaxTaskQueryLimit 是任务查询允许的最大返回条数,用于限制模型输出范围。
|
||||
MaxTaskQueryLimit = 20
|
||||
// DefaultTaskQueryReflectRetry 是任务查询反思节点的默认重试次数。
|
||||
DefaultTaskQueryReflectRetry = 2
|
||||
)
|
||||
|
||||
// TaskQueryItem 是任务查询链路最终展示给模型和用户的轻量任务视图。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只承载展示和反思所需字段,避免把底层数据库结构直接暴露给图层。
|
||||
// 2. 不负责描述完整任务实体,也不负责持久化。
|
||||
type TaskQueryItem struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
PriorityGroup int `json:"priority_group"`
|
||||
PriorityLabel string `json:"priority_label"`
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
DeadlineAt string `json:"deadline_at,omitempty"`
|
||||
UrgencyThresholdAt string `json:"urgency_threshold_at,omitempty"`
|
||||
}
|
||||
|
||||
// TaskQueryPlan 是计划节点产出的内部查询方案。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. DeadlineBeforeText / DeadlineAfterText 保留原始文本,便于继续透传给工具和日志。
|
||||
// 2. DeadlineBefore / DeadlineAfter 是归一化后的时间对象,仅供执行期使用。
|
||||
// 3. IncludeCompleted=true 表示允许把已完成任务纳入候选集。
|
||||
type TaskQueryPlan struct {
|
||||
Quadrants []int
|
||||
SortBy string
|
||||
Order string
|
||||
Limit int
|
||||
|
||||
IncludeCompleted bool
|
||||
Keyword string
|
||||
DeadlineBeforeText string
|
||||
DeadlineAfterText string
|
||||
DeadlineBefore *time.Time
|
||||
DeadlineAfter *time.Time
|
||||
}
|
||||
|
||||
// TaskQueryState 是任务查询图在各节点之间流转的完整状态。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责保存用户输入、结构化计划、工具结果和反思过程状态。
|
||||
// 2. 不负责图编排本身,也不直接绑定外部数据库实体。
|
||||
type TaskQueryState struct {
|
||||
UserMessage string
|
||||
RequestNowText string
|
||||
UserGoal string
|
||||
Plan TaskQueryPlan
|
||||
ExplicitLimit int
|
||||
|
||||
LastQueryItems []TaskQueryItem
|
||||
LastQueryTotal int
|
||||
AutoBroadenApplied bool
|
||||
RetryCount int
|
||||
MaxReflectRetry int
|
||||
NeedRetry bool
|
||||
ReflectReason string
|
||||
FinalReply string
|
||||
}
|
||||
|
||||
// NewTaskQueryState 负责创建任务查询图的初始状态。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. maxReflectRetry <= 0 时会自动回退到默认值,避免上层遗漏配置导致无法重试。
|
||||
// 2. 返回的状态对象已初始化空切片,可直接进入 graph 执行。
|
||||
func NewTaskQueryState(userMessage, requestNowText string, maxReflectRetry int) *TaskQueryState {
|
||||
if maxReflectRetry <= 0 {
|
||||
maxReflectRetry = DefaultTaskQueryReflectRetry
|
||||
}
|
||||
return &TaskQueryState{
|
||||
UserMessage: userMessage,
|
||||
RequestNowText: requestNowText,
|
||||
MaxReflectRetry: maxReflectRetry,
|
||||
LastQueryItems: make([]TaskQueryItem, 0),
|
||||
AutoBroadenApplied: false,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user