Version: 0.9.15.dev.260412
后端: 1. 排程工具从 tools/ 根目录拆分为 tools/schedule 独立子包 - 12 个排程工具文件等价迁入 tools/schedule/,tools/ 根目录仅保留 registry.go 作为统一注册入口 - 所有依赖方(conv / model / node / prompt / service)import 统一切到 schedule 子包 2. Web 搜索工具链落地(tools/web 子包) - 新增 web_search(结构化检索)与 web_fetch(正文抓取)两个读工具,支持博查 API / mock 降级 - 启动流程按配置选择 provider,未识别类型自动降级为 mock,不阻断主流程 - 执行提示补齐 web 工具使用约束与返回值示例 - config.example.yaml 补齐 websearch 配置段 前端:无 仓库:无
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"log"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
)
|
||||
|
||||
// AgentNodes 是 newAgent 通用图的节点容器。
|
||||
@@ -185,7 +185,7 @@ func (n *AgentNodes) Execute(ctx context.Context, st *newagentmodel.AgentGraphSt
|
||||
}
|
||||
|
||||
// 按需加载 ScheduleState(首次执行时从 DB 加载,后续复用内存中的 state)。
|
||||
var scheduleState *newagenttools.ScheduleState
|
||||
var scheduleState *schedule.ScheduleState
|
||||
if ss, loadErr := st.EnsureScheduleState(ctx); loadErr != nil {
|
||||
return nil, fmt.Errorf("execute node: 加载日程状态失败: %w", loadErr)
|
||||
} else if ss != nil {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
newagentprompt "github.com/LoveLosita/smartflow/backend/newAgent/prompt"
|
||||
newagentstream "github.com/LoveLosita/smartflow/backend/newAgent/stream"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -52,10 +53,10 @@ type ExecuteNodeInput struct {
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
ResumeNode string
|
||||
ToolRegistry *newagenttools.ToolRegistry
|
||||
ScheduleState *newagenttools.ScheduleState
|
||||
ScheduleState *schedule.ScheduleState
|
||||
SchedulePersistor newagentmodel.SchedulePersistor
|
||||
WriteSchedulePreview newagentmodel.WriteSchedulePreviewFunc
|
||||
OriginalScheduleState *newagenttools.ScheduleState
|
||||
OriginalScheduleState *schedule.ScheduleState
|
||||
AlwaysExecute bool // true 时写工具跳过确认闸门直接执行
|
||||
}
|
||||
|
||||
@@ -127,7 +128,7 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error {
|
||||
// 1. RoundUsed==0 说明当前还未消耗执行预算;
|
||||
// 2. 此时清理不会影响断线恢复中的中间进度(恢复场景通常 RoundUsed>0)。
|
||||
if input.ScheduleState != nil && flowState.RoundUsed == 0 {
|
||||
newagenttools.ResetTaskProcessingQueue(input.ScheduleState)
|
||||
schedule.ResetTaskProcessingQueue(input.ScheduleState)
|
||||
}
|
||||
if !flowState.AllowReorder && len(flowState.SuggestedOrderBaseline) == 0 {
|
||||
flowState.SuggestedOrderBaseline = buildSuggestedOrderSnapshot(input.ScheduleState)
|
||||
@@ -978,7 +979,7 @@ func renderExecuteStepScope(scope *executeStepScope) string {
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func buildScopeDaySet(state *newagenttools.ScheduleState, scope *executeStepScope) map[int]struct{} {
|
||||
func buildScopeDaySet(state *schedule.ScheduleState, scope *executeStepScope) map[int]struct{} {
|
||||
result := make(map[int]struct{}, 16)
|
||||
if state == nil || scope == nil {
|
||||
return result
|
||||
@@ -991,7 +992,7 @@ func buildScopeDaySet(state *newagenttools.ScheduleState, scope *executeStepScop
|
||||
return result
|
||||
}
|
||||
|
||||
func dayMatchesScope(state *newagenttools.ScheduleState, scope *executeStepScope, day int) bool {
|
||||
func dayMatchesScope(state *schedule.ScheduleState, scope *executeStepScope, day int) bool {
|
||||
if state == nil || scope == nil {
|
||||
return true
|
||||
}
|
||||
@@ -1016,7 +1017,7 @@ func dayMatchesScope(state *newagenttools.ScheduleState, scope *executeStepScope
|
||||
return true
|
||||
}
|
||||
|
||||
func estimateCandidateDaysFromArgs(state *newagenttools.ScheduleState, args map[string]any) (map[int]struct{}, bool, error) {
|
||||
func estimateCandidateDaysFromArgs(state *schedule.ScheduleState, args map[string]any) (map[int]struct{}, bool, error) {
|
||||
result := make(map[int]struct{}, 16)
|
||||
if state == nil {
|
||||
return result, false, fmt.Errorf("日程状态为空")
|
||||
@@ -1328,7 +1329,7 @@ func executeToolCall(
|
||||
toolCall *newagentmodel.ToolCallIntent,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
registry *newagenttools.ToolRegistry,
|
||||
scheduleState *newagenttools.ScheduleState,
|
||||
scheduleState *schedule.ScheduleState,
|
||||
writePreview newagentmodel.WriteSchedulePreviewFunc,
|
||||
) error {
|
||||
if toolCall == nil {
|
||||
@@ -1454,9 +1455,9 @@ func executePendingTool(
|
||||
runtimeState *newagentmodel.AgentRuntimeState,
|
||||
conversationContext *newagentmodel.ConversationContext,
|
||||
registry *newagenttools.ToolRegistry,
|
||||
scheduleState *newagenttools.ScheduleState,
|
||||
scheduleState *schedule.ScheduleState,
|
||||
persistor newagentmodel.SchedulePersistor,
|
||||
originalState *newagenttools.ScheduleState,
|
||||
originalState *schedule.ScheduleState,
|
||||
writePreview newagentmodel.WriteSchedulePreviewFunc,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
) error {
|
||||
@@ -1539,7 +1540,7 @@ func executePendingTool(
|
||||
func tryWritePreviewAfterWriteTool(
|
||||
ctx context.Context,
|
||||
flowState *newagentmodel.CommonState,
|
||||
scheduleState *newagenttools.ScheduleState,
|
||||
scheduleState *schedule.ScheduleState,
|
||||
registry *newagenttools.ToolRegistry,
|
||||
toolName string,
|
||||
writePreview newagentmodel.WriteSchedulePreviewFunc,
|
||||
@@ -1601,7 +1602,7 @@ func truncateText(text string, maxLen int) string {
|
||||
}
|
||||
|
||||
// summarizeScheduleStateForDebug 返回内存日程状态的关键计数,用于判断工具是否真的修改了 state。
|
||||
func summarizeScheduleStateForDebug(state *newagenttools.ScheduleState) string {
|
||||
func summarizeScheduleStateForDebug(state *schedule.ScheduleState) string {
|
||||
if state == nil {
|
||||
return "state=nil"
|
||||
}
|
||||
@@ -1618,11 +1619,11 @@ func summarizeScheduleStateForDebug(state *newagenttools.ScheduleState) string {
|
||||
hasSlot := len(t.Slots) > 0
|
||||
|
||||
switch {
|
||||
case newagenttools.IsPendingTask(*t):
|
||||
case schedule.IsPendingTask(*t):
|
||||
pendingNoSlot++
|
||||
case newagenttools.IsSuggestedTask(*t):
|
||||
case schedule.IsSuggestedTask(*t):
|
||||
suggestedTotal++
|
||||
case newagenttools.IsExistingTask(*t):
|
||||
case schedule.IsExistingTask(*t):
|
||||
existingTotal++
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,7 +21,7 @@ type suggestedOrderItem struct {
|
||||
Day int
|
||||
SlotStart int
|
||||
SlotEnd int
|
||||
Slots []newagenttools.TaskSlot
|
||||
Slots []schedule.TaskSlot
|
||||
}
|
||||
|
||||
type orderRestoreResult struct {
|
||||
@@ -126,7 +126,7 @@ func RunOrderGuardNode(ctx context.Context, st *newagentmodel.AgentGraphState) e
|
||||
// 1. 这里只关心 suggested 任务,因为顺序守卫目标是约束“本轮建议层”的相对次序;
|
||||
// 2. 多 slot 任务取“最早 slot”作为排序锚点,保证排序键稳定;
|
||||
// 3. 返回值是 state_id 列表,便于写入 CommonState 做跨节点持久化。
|
||||
func buildSuggestedOrderSnapshot(state *newagenttools.ScheduleState) []int {
|
||||
func buildSuggestedOrderSnapshot(state *schedule.ScheduleState) []int {
|
||||
items := buildSuggestedOrderItems(state)
|
||||
order := make([]int, 0, len(items))
|
||||
for _, item := range items {
|
||||
@@ -141,7 +141,7 @@ func buildSuggestedOrderSnapshot(state *newagenttools.ScheduleState) []int {
|
||||
// 1. 统一封装顺序守卫和自动复原都需要的排序素材,避免两处逻辑口径漂移;
|
||||
// 2. 排序键保持与历史实现一致:day -> slot_start -> slot_end -> state_id;
|
||||
// 3. 每项附带完整 slots 快照,供“坑位复用式复原”直接使用。
|
||||
func buildSuggestedOrderItems(state *newagenttools.ScheduleState) []suggestedOrderItem {
|
||||
func buildSuggestedOrderItems(state *schedule.ScheduleState) []suggestedOrderItem {
|
||||
if state == nil || len(state.Tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func buildSuggestedOrderItems(state *newagenttools.ScheduleState) []suggestedOrd
|
||||
items := make([]suggestedOrderItem, 0, len(state.Tasks))
|
||||
for i := range state.Tasks {
|
||||
task := state.Tasks[i]
|
||||
if !newagenttools.IsSuggestedTask(task) || len(task.Slots) == 0 {
|
||||
if !schedule.IsSuggestedTask(task) || len(task.Slots) == 0 {
|
||||
continue
|
||||
}
|
||||
day, slotStart, slotEnd := earliestTaskSlot(task.Slots)
|
||||
@@ -178,7 +178,7 @@ func buildSuggestedOrderItems(state *newagenttools.ScheduleState) []suggestedOrd
|
||||
return items
|
||||
}
|
||||
|
||||
func earliestTaskSlot(slots []newagenttools.TaskSlot) (day int, slotStart int, slotEnd int) {
|
||||
func earliestTaskSlot(slots []schedule.TaskSlot) (day int, slotStart int, slotEnd int) {
|
||||
if len(slots) == 0 {
|
||||
return 0, 0, 0
|
||||
}
|
||||
@@ -250,7 +250,7 @@ func detectRelativeOrderViolation(baseline []int, current []int) (bool, string)
|
||||
// 2. 复用 current 的“坑位序列”(时段集合),按 baseline 顺序重新回填任务;
|
||||
// 3. 回填前校验时长兼容,避免把长任务塞进短坑位;
|
||||
// 4. 回填后再次校验顺序;若失败则回滚,保证状态不会半成功。
|
||||
func restoreSuggestedOrderByBaseline(state *newagenttools.ScheduleState, baseline []int) orderRestoreResult {
|
||||
func restoreSuggestedOrderByBaseline(state *schedule.ScheduleState, baseline []int) orderRestoreResult {
|
||||
if state == nil {
|
||||
return orderRestoreResult{Restored: false, Detail: "schedule_state=nil"}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ func restoreSuggestedOrderByBaseline(state *newagenttools.ScheduleState, baselin
|
||||
}
|
||||
|
||||
// 1. 先构建“当前坑位序列”。
|
||||
slotPool := make([][]newagenttools.TaskSlot, 0, len(filteredCurrent))
|
||||
slotPool := make([][]schedule.TaskSlot, 0, len(filteredCurrent))
|
||||
for _, currentID := range filteredCurrent {
|
||||
item, ok := itemByID[currentID]
|
||||
if !ok {
|
||||
@@ -343,7 +343,7 @@ func restoreSuggestedOrderByBaseline(state *newagenttools.ScheduleState, baselin
|
||||
}
|
||||
|
||||
// 3. 执行回填,并在失败时支持回滚。
|
||||
beforeSlots := make(map[int][]newagenttools.TaskSlot, len(baselineInScope))
|
||||
beforeSlots := make(map[int][]schedule.TaskSlot, len(baselineInScope))
|
||||
changed := 0
|
||||
for i, targetID := range baselineInScope {
|
||||
task := state.TaskByStateID(targetID)
|
||||
@@ -401,16 +401,16 @@ func sameIDOrder(left, right []int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func cloneTaskSlots(slots []newagenttools.TaskSlot) []newagenttools.TaskSlot {
|
||||
func cloneTaskSlots(slots []schedule.TaskSlot) []schedule.TaskSlot {
|
||||
if len(slots) == 0 {
|
||||
return nil
|
||||
}
|
||||
copied := make([]newagenttools.TaskSlot, len(slots))
|
||||
copied := make([]schedule.TaskSlot, len(slots))
|
||||
copy(copied, slots)
|
||||
return copied
|
||||
}
|
||||
|
||||
func equalTaskSlots(left, right []newagenttools.TaskSlot) bool {
|
||||
func equalTaskSlots(left, right []schedule.TaskSlot) bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
@@ -428,7 +428,7 @@ func equalTaskSlots(left, right []newagenttools.TaskSlot) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func expectedTaskDuration(task newagenttools.ScheduleTask) int {
|
||||
func expectedTaskDuration(task schedule.ScheduleTask) int {
|
||||
if task.Duration > 0 {
|
||||
return task.Duration
|
||||
}
|
||||
@@ -438,7 +438,7 @@ func expectedTaskDuration(task newagenttools.ScheduleTask) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func totalSlotDuration(slots []newagenttools.TaskSlot) int {
|
||||
func totalSlotDuration(slots []schedule.TaskSlot) int {
|
||||
total := 0
|
||||
for _, slot := range slots {
|
||||
total += slot.SlotEnd - slot.SlotStart + 1
|
||||
@@ -446,7 +446,7 @@ func totalSlotDuration(slots []newagenttools.TaskSlot) int {
|
||||
return total
|
||||
}
|
||||
|
||||
func isSlotsCompatibleWithTask(task newagenttools.ScheduleTask, slots []newagenttools.TaskSlot) bool {
|
||||
func isSlotsCompatibleWithTask(task schedule.ScheduleTask, slots []schedule.TaskSlot) bool {
|
||||
if len(slots) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -205,20 +205,20 @@ func RunRoughBuildNode(ctx context.Context, st *newagentmodel.AgentGraphState) e
|
||||
// 1. 第一轮修复后,粗排成功会把任务直接标记为 suggested;
|
||||
// 2. 为兼容旧快照,仍按“pending 且 Slots 为空”认定真正未覆盖;
|
||||
// 3. 只要这里仍大于 0,就应视为粗排异常,而不是交给 LLM 补排。
|
||||
func countPendingTasks(state *newagenttools.ScheduleState, taskClassIDs []int) int {
|
||||
func countPendingTasks(state *schedule.ScheduleState, taskClassIDs []int) int {
|
||||
if state == nil {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
for i := range state.Tasks {
|
||||
task := state.Tasks[i]
|
||||
if !newagenttools.IsPendingTask(task) {
|
||||
if !schedule.IsPendingTask(task) {
|
||||
continue
|
||||
}
|
||||
if len(taskClassIDs) > 0 && !newagenttools.IsTaskInRequestedClassScope(task, taskClassIDs) {
|
||||
if len(taskClassIDs) > 0 && !schedule.IsTaskInRequestedClassScope(task, taskClassIDs) {
|
||||
continue
|
||||
}
|
||||
if newagenttools.IsPendingTask(task) {
|
||||
if schedule.IsPendingTask(task) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func countPendingTasks(state *newagenttools.ScheduleState, taskClassIDs []int) i
|
||||
// 4. suggested 表示“粗排建议位”,后续可用 move/swap/unplace 微调;
|
||||
// 5. 转换失败的条目静默跳过,不中断整体流程。
|
||||
func applyRoughBuildPlacements(
|
||||
state *newagenttools.ScheduleState,
|
||||
state *schedule.ScheduleState,
|
||||
placements []newagentmodel.RoughBuildPlacement,
|
||||
) roughBuildApplyStats {
|
||||
stats := roughBuildApplyStats{}
|
||||
@@ -262,10 +262,10 @@ func applyRoughBuildPlacements(
|
||||
matched := false
|
||||
for _, index := range taskIndexByItemID[p.TaskItemID] {
|
||||
t := &state.Tasks[index]
|
||||
t.Slots = []newagenttools.TaskSlot{
|
||||
t.Slots = []schedule.TaskSlot{
|
||||
{Day: day, SlotStart: p.SectionFrom, SlotEnd: p.SectionTo},
|
||||
}
|
||||
t.Status = newagenttools.TaskStatusSuggested
|
||||
t.Status = schedule.TaskStatusSuggested
|
||||
stats.AppliedCount++
|
||||
matched = true
|
||||
break
|
||||
@@ -294,7 +294,7 @@ func appendPlacementSample(samples []string, placement newagentmodel.RoughBuildP
|
||||
}
|
||||
|
||||
// summarizeRoughBuildWindow 提供 DayMapping 的紧凑摘要,便于判断窗口是否退化到错误周。
|
||||
func summarizeRoughBuildWindow(state *newagenttools.ScheduleState) string {
|
||||
func summarizeRoughBuildWindow(state *schedule.ScheduleState) string {
|
||||
if state == nil || len(state.Window.DayMapping) == 0 {
|
||||
return "empty"
|
||||
}
|
||||
@@ -311,7 +311,7 @@ func summarizeRoughBuildWindow(state *newagenttools.ScheduleState) string {
|
||||
}
|
||||
|
||||
// collectScopedTaskSamples 提供当前 state 中可用于匹配的 task_item 样本,便于排查 ID 对不上。
|
||||
func collectScopedTaskSamples(state *newagenttools.ScheduleState, taskClassIDs []int) []string {
|
||||
func collectScopedTaskSamples(state *schedule.ScheduleState, taskClassIDs []int) []string {
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -321,7 +321,7 @@ func collectScopedTaskSamples(state *newagenttools.ScheduleState, taskClassIDs [
|
||||
if task.Source != "task_item" {
|
||||
continue
|
||||
}
|
||||
if len(taskClassIDs) > 0 && !newagenttools.IsTaskInRequestedClassScope(task, taskClassIDs) {
|
||||
if len(taskClassIDs) > 0 && !schedule.IsTaskInRequestedClassScope(task, taskClassIDs) {
|
||||
continue
|
||||
}
|
||||
samples = append(samples, fmt.Sprintf(
|
||||
|
||||
Reference in New Issue
Block a user