Version: 0.9.28.dev.260418
后端: 1. 查任务功能(query_tasks)从旧 Agent 链路迁移为新 execute 工具 - 新增 newAgent/tools/taskquery.go:自包含 TaskQueryToolHandler,零引用旧 agent 包;参数校验(象限1~4、排序白名单、limit上限20)、时间边界解析(四种格式自动补齐)、结构化 JSON 结果 - newAgent/tools/registry.go:DefaultRegistryDeps 新增 TaskQuery 字段;scheduleFreeTools 新增 query_tasks;注册 query_tasks 读工具(无需 confirm,不依赖 ScheduleState) - newAgent/prompt/execute.go:有 plan / ReAct 两套系统 prompt 执行规则新增 query_tasks 读操作说明,支持按象限、关键词、截止时间筛选排序 - service/agentsvc/agent_task_query.go:queryTasksForAgent 导出为 QueryTasksForTool,供启动层闭包调用;内部调用同步改为 QueryTasksForTool - cmd/start.go:NewDefaultRegistryWithDeps 注入 TaskQuery 闭包,桥接新工具参数到旧 service 层查询能力,复用已有过滤/排序/紧急度提升逻辑;旧链路全部保留不动 2. order_guard 条件触发——仅日程写操作后走守卫节点 - newAgent/model/common_state.go:新增 HasScheduleWriteOps 标记字段;ResetForNextRun 追加清理 - newAgent/node/execute.go:executeToolCall / executePendingTool 两处写工具执行后,通过 registry.IsWriteTool 判断并置 HasScheduleWriteOps=true - newAgent/graph/common_graph.go:branchAfterExecute 分支条件新增 HasScheduleWriteOps 判断,非日程操作(query_tasks / quick_note_create / web_search 等)直接 deliver 跳过 order_guard;branchAfterRoughBuild 不变,粗排天然是写操作 前端: 1. 助手面板新增 SSE 流式请求停止按钮 - AssistantPanel.vue:新增 streamAbortController ref 和 stopStreaming 方法;fetchChatStream / streamAssistantReply 透传 AbortSignal;sendMessage 创建 AbortController,catch 区分用户主动中断与异常;流式期间显示红色停止按钮替代发送按钮 2. 象限卡片任务列表取消硬截断,改为滚动查看 - TaskQuadrantCard.vue:visibleTasks 不再 slice(0,4),全部展示;quadrant-list 新增 max-height + overflow-y + 自定义滚动条样式 仓库:无
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
agentnode "github.com/LoveLosita/smartflow/backend/agent/node"
|
||||
"github.com/LoveLosita/smartflow/backend/api"
|
||||
"github.com/LoveLosita/smartflow/backend/dao"
|
||||
kafkabus "github.com/LoveLosita/smartflow/backend/infra/kafka"
|
||||
@@ -197,6 +198,41 @@ func Start() {
|
||||
return created.ID, nil
|
||||
},
|
||||
},
|
||||
TaskQuery: newagenttools.TaskQueryDeps{
|
||||
// 调用目的:桥接新工具参数到旧 service 层查询能力,复用已有的过滤/排序/紧急度提升逻辑。
|
||||
QueryTasks: func(ctx context.Context, userID int, params newagenttools.TaskQueryParams) ([]newagenttools.TaskQueryResult, error) {
|
||||
req := agentnode.TaskQueryRequest{
|
||||
UserID: userID,
|
||||
Quadrant: params.Quadrant,
|
||||
SortBy: params.SortBy,
|
||||
Order: params.Order,
|
||||
Limit: params.Limit,
|
||||
IncludeCompleted: params.IncludeCompleted,
|
||||
Keyword: params.Keyword,
|
||||
DeadlineBefore: params.DeadlineBefore,
|
||||
DeadlineAfter: params.DeadlineAfter,
|
||||
}
|
||||
records, err := agentService.QueryTasksForTool(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]newagenttools.TaskQueryResult, 0, len(records))
|
||||
for _, r := range records {
|
||||
deadlineStr := ""
|
||||
if r.DeadlineAt != nil {
|
||||
deadlineStr = r.DeadlineAt.In(time.Local).Format("2006-01-02 15:04")
|
||||
}
|
||||
results = append(results, newagenttools.TaskQueryResult{
|
||||
ID: r.ID,
|
||||
Title: r.Title,
|
||||
PriorityGroup: r.PriorityGroup,
|
||||
IsCompleted: r.IsCompleted,
|
||||
DeadlineAt: deadlineStr,
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
},
|
||||
},
|
||||
}))
|
||||
agentService.SetScheduleProvider(newagentconv.NewScheduleProvider(scheduleRepo, taskClassRepo))
|
||||
agentService.SetSchedulePersistor(newagentconv.NewSchedulePersistorAdapter(manager))
|
||||
|
||||
@@ -298,7 +298,7 @@ func branchAfterExecute(_ context.Context, st *newagentmodel.AgentGraphState) (s
|
||||
// 3. 若此处直接按 RoundUsed>=MaxRounds 跳 Deliver,会绕过 Execute 内的 Exhaust 写入,
|
||||
// 导致 deliver 收口和后续预览落盘语义不一致。
|
||||
if flowState.Phase == newagentmodel.PhaseDone {
|
||||
if flowState.TerminalStatus() == newagentmodel.FlowTerminalStatusCompleted && !flowState.AllowReorder {
|
||||
if flowState.TerminalStatus() == newagentmodel.FlowTerminalStatusCompleted && !flowState.AllowReorder && flowState.HasScheduleWriteOps {
|
||||
return NodeOrderGuard, nil
|
||||
}
|
||||
return NodeDeliver, nil
|
||||
|
||||
@@ -109,6 +109,9 @@ type CommonState struct {
|
||||
// SuggestedOrderBaseline 保存"本轮 execute 启动前"的 suggested 任务相对顺序基线。
|
||||
// OrderGuard 节点会基于该基线判断微调是否破坏顺序约束。
|
||||
SuggestedOrderBaseline []int `json:"suggested_order_baseline,omitempty"`
|
||||
// HasScheduleWriteOps 标记本轮 execute 循环是否执行过日程写工具。
|
||||
// 调用目的:graph 分支函数据此判断是否需要走 order_guard,非日程操作跳过守卫。
|
||||
HasScheduleWriteOps bool `json:"has_schedule_write_ops,omitempty"`
|
||||
|
||||
// ExecuteThinking 由 Chat 路由决策传入,表示 Execute 节点是否应开启深度思考。
|
||||
// 预埋字段,当前阶段 Execute 节点可自行决定是否读取。
|
||||
@@ -218,6 +221,7 @@ func (s *CommonState) ResetForNextRun() {
|
||||
|
||||
// 5. 重置顺序约束临时态与终止结果,避免上一轮 completed/aborted/exhausted 语义串到下一轮。
|
||||
s.AllowReorder = false
|
||||
s.HasScheduleWriteOps = false
|
||||
s.SuggestedOrderBaseline = nil
|
||||
s.ClearTerminalOutcome()
|
||||
}
|
||||
|
||||
@@ -1452,6 +1452,11 @@ func executeToolCall(
|
||||
// 3. 以标准 assistant+tool 消息对写回历史,避免消息链断裂。
|
||||
appendToolCallResultHistory(conversationContext, toolName, toolCall.Arguments, result)
|
||||
|
||||
// 3.1 标记本轮执行过日程写工具,graph 分支据此决定是否走 order_guard。
|
||||
if registry.IsWriteTool(toolName) {
|
||||
flowState.HasScheduleWriteOps = true
|
||||
}
|
||||
|
||||
// 4. 写工具实时预览:每次写工具执行后都尝试刷新 Redis 预览,确保前端可见“最新操作结果”。
|
||||
//
|
||||
// 步骤化说明:
|
||||
@@ -1563,6 +1568,11 @@ func executePendingTool(
|
||||
// 5. 将工具调用和结果写回历史,维持标准 tool_call 配对格式。
|
||||
appendToolCallResultHistory(conversationContext, pending.ToolName, args, result)
|
||||
|
||||
// 5.1 标记本轮执行过日程写工具,graph 分支据此决定是否走 order_guard。
|
||||
if registry.IsWriteTool(pending.ToolName) {
|
||||
flowState.HasScheduleWriteOps = true
|
||||
}
|
||||
|
||||
// 5. 写工具实时预览:confirm accept 后真实执行写工具时,立即刷新一次预览缓存。
|
||||
tryWritePreviewAfterWriteTool(ctx, flowState, scheduleState, registry, pending.ToolName, writePreview)
|
||||
|
||||
|
||||
@@ -40,10 +40,11 @@ const executeSystemPromptWithPlan = `
|
||||
2. 读操作:action=continue + tool_call。
|
||||
3. 写操作(日程变更,如 place/move/swap/batch_move/unplace/spread_even/min_context_switch):action=confirm + tool_call。
|
||||
4. quick_note_create(记录任务/提醒):若信息足够,action=continue + tool_call,并显式填写 priority_group;若信息不足且无法可靠推断,action=ask_user 先追问。quick_note_create 调用时和调用后 speak 必须留空,收口由 deliver 阶段统一完成;调用成功后可继续(done/next_plan/continue)处理其他任务,但不要为 quick_note_create 本身补充说明。
|
||||
5. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
6. 仅当当前步骤完成时输出 action=next_plan,并在 goal_check 对照 done_when 给出证据。
|
||||
7. 仅当整体任务完成时输出 action=done,并在 goal_check 总结完成证据。
|
||||
8. 流程应正式终止时输出 action=abort。`
|
||||
5. query_tasks(查看/筛选任务列表):读操作,action=continue + tool_call。用于回答"我有什么任务""最近有什么急事"等问题,支持按象限、关键词、截止时间范围筛选和排序。
|
||||
6. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
7. 仅当当前步骤完成时输出 action=next_plan,并在 goal_check 对照 done_when 给出证据。
|
||||
8. 仅当整体任务完成时输出 action=done,并在 goal_check 总结完成证据。
|
||||
9. 流程应正式终止时输出 action=abort。`
|
||||
|
||||
const executeSystemPromptReAct = `
|
||||
你是 SmartMate 的执行器,当前处于自由执行模式(无预定义 plan 步骤)。
|
||||
@@ -82,9 +83,10 @@ const executeSystemPromptReAct = `
|
||||
2. 读操作:action=continue + tool_call。
|
||||
3. 写操作(日程变更,如 place/move/swap/batch_move/unplace/spread_even/min_context_switch):action=confirm + tool_call。
|
||||
4. quick_note_create(记录任务/提醒):若信息足够,action=continue + tool_call,并显式填写 priority_group;若信息不足且无法可靠推断,action=ask_user 先追问。quick_note_create 调用时和调用后 speak 必须留空,收口由 deliver 阶段统一完成;调用成功后可继续(done/next_plan/continue)处理其他任务,但不要为 quick_note_create 本身补充说明。
|
||||
5. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
6. 任务完成:action=done,并在 goal_check 总结完成证据。
|
||||
7. 流程应正式终止:action=abort。`
|
||||
5. query_tasks(查看/筛选任务列表):读操作,action=continue + tool_call。用于回答"我有什么任务""最近有什么急事"等问题,支持按象限、关键词、截止时间范围筛选和排序。
|
||||
6. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
7. 任务完成:action=done,并在 goal_check 总结完成证据。
|
||||
8. 流程应正式终止:action=abort。`
|
||||
|
||||
// BuildExecuteSystemPrompt 返回执行阶段系统提示词(有 plan 模式)。
|
||||
func BuildExecuteSystemPrompt() string {
|
||||
|
||||
@@ -34,6 +34,9 @@ type DefaultRegistryDeps struct {
|
||||
|
||||
// QuickNote 随口记工具依赖。CreateTask 为 nil 时 quick_note_create 返回错误提示,不阻断主流程。
|
||||
QuickNote QuickNoteDeps
|
||||
|
||||
// TaskQuery 任务查询工具依赖。QueryTasks 为 nil 时 query_tasks 不注册,不影响其他工具。
|
||||
TaskQuery TaskQueryDeps
|
||||
}
|
||||
|
||||
// ToolRegistry 管理工具注册、查找与执行。
|
||||
@@ -127,6 +130,7 @@ var writeTools = map[string]bool{
|
||||
|
||||
var scheduleFreeTools = map[string]bool{
|
||||
"quick_note_create": true,
|
||||
"query_tasks": true,
|
||||
"web_search": true,
|
||||
"web_fetch": true,
|
||||
}
|
||||
@@ -340,6 +344,18 @@ func NewDefaultRegistryWithDeps(deps DefaultRegistryDeps) *ToolRegistry {
|
||||
)
|
||||
}
|
||||
|
||||
// --- 任务查询读工具 ---
|
||||
// 调用目的:将"帮我看看有什么任务""最近有什么急事"等查询请求直接查库返回结构化结果,无需 ScheduleState。
|
||||
// 不加入 writeTools:查询是只读操作,不需要 confirm 节点二次确认。
|
||||
if deps.TaskQuery.QueryTasks != nil {
|
||||
taskQueryHandler := NewTaskQueryToolHandler(deps.TaskQuery)
|
||||
r.Register("query_tasks",
|
||||
"按象限、关键词、截止时间筛选并排序任务列表,返回结构化结果。所有参数均为可选。",
|
||||
`{"name":"query_tasks","parameters":{"quadrant":{"type":"int","description":"可选象限筛选(1~4)"},"keyword":{"type":"string","description":"可选标题关键词,模糊匹配"},"deadline_before":{"type":"string","description":"可选截止时间上界,支持 yyyy-MM-dd HH:mm 或 yyyy-MM-dd"},"deadline_after":{"type":"string","description":"可选截止时间下界,支持 yyyy-MM-dd HH:mm 或 yyyy-MM-dd"},"sort_by":{"type":"string","description":"排序字段(deadline|priority|id),默认deadline"},"order":{"type":"string","description":"排序方向(asc|desc),默认asc"},"limit":{"type":"int","description":"返回条数,默认5,上限20"},"include_completed":{"type":"bool","description":"是否包含已完成任务,默认false"}}}`,
|
||||
taskQueryHandler,
|
||||
)
|
||||
}
|
||||
|
||||
// --- Web 搜索读工具 ---
|
||||
// 1. provider 为 nil 时 handler 返回"暂未启用"的 observation,不会阻断主流程;
|
||||
// 2. 两个工具均为读操作,走 action=continue + tool_call 模式。
|
||||
|
||||
320
backend/newAgent/tools/taskquery.go
Normal file
320
backend/newAgent/tools/taskquery.go
Normal file
@@ -0,0 +1,320 @@
|
||||
package newagenttools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
|
||||
)
|
||||
|
||||
// ==================== 常量 ====================
|
||||
|
||||
const (
|
||||
// defaultTaskQueryLimit 是任务查询默认返回条数。
|
||||
defaultTaskQueryLimit = 5
|
||||
// maxTaskQueryLimit 是任务查询允许的最大返回条数,用于限制 LLM 输出范围。
|
||||
maxTaskQueryLimit = 20
|
||||
)
|
||||
|
||||
// ==================== 优先级中文映射 ====================
|
||||
|
||||
// taskQueryPriorityLabelCN 将象限编号转为中文标签。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责 1~4 的合法映射,超出范围返回"未知"。
|
||||
// 2. 不依赖旧链路 agentmodel.PriorityLabelCN,保持新工具自包含。
|
||||
func taskQueryPriorityLabelCN(priority int) string {
|
||||
switch priority {
|
||||
case 1:
|
||||
return "重要且紧急"
|
||||
case 2:
|
||||
return "重要不紧急"
|
||||
case 3:
|
||||
return "简单不重要"
|
||||
case 4:
|
||||
return "复杂不重要"
|
||||
default:
|
||||
return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
// TaskQueryDeps 描述任务查询工具所需的外部依赖。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. QueryTasks 负责真正查库,工具层不直接依赖 DAO;
|
||||
// 2. UserID 由 execute 节点通过 args["_user_id"] 注入,工具层不自行解析会话身份。
|
||||
type TaskQueryDeps struct {
|
||||
// QueryTasks 将解析后的查询参数传入业务层,返回匹配的任务列表。
|
||||
// 调用目的:解耦工具层与 DAO 层,方便测试和替换。
|
||||
QueryTasks func(ctx context.Context, userID int, params TaskQueryParams) ([]TaskQueryResult, error)
|
||||
}
|
||||
|
||||
// TaskQueryParams 描述任务查询工具传给业务层的内部查询参数。
|
||||
//
|
||||
// 输入输出语义:
|
||||
// 1. 所有筛选条件均为可选,Quadrant 为 nil 表示不限象限。
|
||||
// 2. 时间边界为 nil 表示不限时间范围。
|
||||
type TaskQueryParams struct {
|
||||
Quadrant *int
|
||||
SortBy string // deadline | priority | id
|
||||
Order string // asc | desc
|
||||
Limit int
|
||||
IncludeCompleted bool
|
||||
Keyword string
|
||||
DeadlineBefore *time.Time
|
||||
DeadlineAfter *time.Time
|
||||
}
|
||||
|
||||
// TaskQueryResult 描述任务查询工具返回给 LLM 的轻量任务视图。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只承载展示所需字段,避免暴露底层数据库结构。
|
||||
// 2. JSON 序列化后直接作为工具 observation 返回给 LLM。
|
||||
type TaskQueryResult 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"`
|
||||
}
|
||||
|
||||
// ==================== 时间解析 ====================
|
||||
|
||||
// taskQueryTimeLayouts 支持的时间格式列表,按优先级尝试解析。
|
||||
var taskQueryTimeLayouts = []string{
|
||||
time.RFC3339,
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
|
||||
// parseTaskQueryBoundaryTime 解析截止时间上下界。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. isUpper=true 时,纯日期补到当天 23:59:59。
|
||||
// 2. isUpper=false 时,纯日期补到当天 00:00:00。
|
||||
// 3. 不支持的格式直接返回错误,由调用方决定是否回退。
|
||||
func parseTaskQueryBoundaryTime(raw string, isUpper bool) (*time.Time, error) {
|
||||
text := strings.TrimSpace(raw)
|
||||
if text == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loc := time.Local
|
||||
for _, layout := range taskQueryTimeLayouts {
|
||||
var (
|
||||
parsed time.Time
|
||||
err error
|
||||
)
|
||||
if layout == time.RFC3339 {
|
||||
parsed, err = time.Parse(layout, text)
|
||||
if err == nil {
|
||||
parsed = parsed.In(loc)
|
||||
}
|
||||
} else {
|
||||
parsed, err = time.ParseInLocation(layout, text, loc)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 1. 纯日期格式需要根据上下界补齐时分秒,保证时间区间语义正确。
|
||||
// 2. 若用户输入"2026-04-20"作为上界,意图是"截止到那天结束",
|
||||
// 所以补 23:59:59;作为下界则补 00:00:00。
|
||||
if layout == "2006-01-02" {
|
||||
if isUpper {
|
||||
parsed = time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 23, 59, 59, 0, loc)
|
||||
} else {
|
||||
parsed = time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, loc)
|
||||
}
|
||||
}
|
||||
return &parsed, nil
|
||||
}
|
||||
return nil, fmt.Errorf("时间格式不支持: %s", text)
|
||||
}
|
||||
|
||||
// formatTaskQueryTime 将内部时间格式化为给模型展示的分钟级文本。
|
||||
func formatTaskQueryTime(value *time.Time) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return value.In(time.Local).Format("2006-01-02 15:04")
|
||||
}
|
||||
|
||||
// ==================== 工具 Handler ====================
|
||||
|
||||
// NewTaskQueryToolHandler 创建 query_tasks 工具的 handler 闭包。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责参数校验、时间解析、调 deps 查库、组装返回;
|
||||
// 2. 不负责 LLM 交互和会话管理。
|
||||
// 3. state 参数忽略——任务查询不需要 ScheduleState,已注册到 scheduleFreeTools。
|
||||
func NewTaskQueryToolHandler(deps TaskQueryDeps) ToolHandler {
|
||||
return func(state *schedule.ScheduleState, args map[string]any) string {
|
||||
_ = state
|
||||
|
||||
// 1. 提取 _user_id(由 execute 节点在调用前注入)。
|
||||
userID := 0
|
||||
if uid, ok := args["_user_id"].(int); ok {
|
||||
userID = uid
|
||||
}
|
||||
if userID <= 0 {
|
||||
return "工具调用失败:无法识别用户身份。"
|
||||
}
|
||||
|
||||
// 2. 提取并校验查询参数。
|
||||
params, err := extractTaskQueryParams(args)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("工具调用失败:%s", err)
|
||||
}
|
||||
|
||||
// 3. 调用依赖查库。
|
||||
results, err := deps.QueryTasks(context.Background(), userID, params)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("工具调用失败:查询任务时出错(%s)。", err)
|
||||
}
|
||||
|
||||
// 4. 为每条结果填充优先级中文标签。
|
||||
for i := range results {
|
||||
results[i].PriorityLabel = taskQueryPriorityLabelCN(results[i].PriorityGroup)
|
||||
}
|
||||
|
||||
// 5. 返回结构化 JSON。
|
||||
if len(results) == 0 {
|
||||
return `{"total":0,"items":[],"message":"当前没有匹配的任务。"}`
|
||||
}
|
||||
|
||||
output := struct {
|
||||
Total int `json:"total"`
|
||||
Items []TaskQueryResult `json:"items"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Total: len(results),
|
||||
Items: results,
|
||||
Message: fmt.Sprintf("找到 %d 条匹配任务。", len(results)),
|
||||
}
|
||||
|
||||
jsonBytes, marshalErr := json.Marshal(output)
|
||||
if marshalErr != nil {
|
||||
// JSON 序列化失败时降级为纯文本,确保 LLM 仍能拿到关键信息。
|
||||
return fmt.Sprintf("找到 %d 条匹配任务。", len(results))
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// extractTaskQueryParams 从 args 提取并校验任务查询参数。
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. 先准备默认值,保证空参数也能执行一次合理查询。
|
||||
// 2. 再校验象限、排序、条数和时间区间,阻止非法参数下沉到业务层。
|
||||
// 3. 若上下界冲突,则直接返回错误。
|
||||
func extractTaskQueryParams(args map[string]any) (TaskQueryParams, error) {
|
||||
params := TaskQueryParams{
|
||||
SortBy: "deadline",
|
||||
Order: "asc",
|
||||
Limit: defaultTaskQueryLimit,
|
||||
IncludeCompleted: false,
|
||||
}
|
||||
|
||||
// 2.1 象限:1~4,超出范围拒绝。
|
||||
if v, ok := args["quadrant"]; ok {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
q := int(val)
|
||||
if q < 1 || q > 4 {
|
||||
return TaskQueryParams{}, fmt.Errorf("quadrant=%d 非法,必须在 1~4", q)
|
||||
}
|
||||
params.Quadrant = &q
|
||||
case int:
|
||||
if val < 1 || val > 4 {
|
||||
return TaskQueryParams{}, fmt.Errorf("quadrant=%d 非法,必须在 1~4", val)
|
||||
}
|
||||
params.Quadrant = &val
|
||||
}
|
||||
}
|
||||
|
||||
// 2.2 排序字段:仅支持 deadline/priority/id。
|
||||
if v, ok := args["sort_by"].(string); ok {
|
||||
sortBy := strings.ToLower(strings.TrimSpace(v))
|
||||
if sortBy != "" {
|
||||
switch sortBy {
|
||||
case "deadline", "priority", "id":
|
||||
params.SortBy = sortBy
|
||||
default:
|
||||
return TaskQueryParams{}, fmt.Errorf("sort_by=%s 非法,仅支持 deadline|priority|id", sortBy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.3 排序方向:仅支持 asc/desc。
|
||||
if v, ok := args["order"].(string); ok {
|
||||
order := strings.ToLower(strings.TrimSpace(v))
|
||||
if order != "" {
|
||||
switch order {
|
||||
case "asc", "desc":
|
||||
params.Order = order
|
||||
default:
|
||||
return TaskQueryParams{}, fmt.Errorf("order=%s 非法,仅支持 asc|desc", order)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.4 条数:默认 5,上限 20。
|
||||
if v, ok := args["limit"]; ok {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
params.Limit = int(val)
|
||||
case int:
|
||||
params.Limit = val
|
||||
}
|
||||
}
|
||||
if params.Limit <= 0 {
|
||||
params.Limit = defaultTaskQueryLimit
|
||||
}
|
||||
if params.Limit > maxTaskQueryLimit {
|
||||
params.Limit = maxTaskQueryLimit
|
||||
}
|
||||
|
||||
// 2.5 是否包含已完成任务。
|
||||
if v, ok := args["include_completed"]; ok {
|
||||
switch val := v.(type) {
|
||||
case bool:
|
||||
params.IncludeCompleted = val
|
||||
}
|
||||
}
|
||||
|
||||
// 2.6 关键词。
|
||||
if v, ok := args["keyword"].(string); ok {
|
||||
params.Keyword = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
// 2.7 时间边界解析,解析失败直接报错,避免查出无意义的结果。
|
||||
beforeRaw, _ := args["deadline_before"].(string)
|
||||
before, err := parseTaskQueryBoundaryTime(beforeRaw, true)
|
||||
if err != nil {
|
||||
return TaskQueryParams{}, fmt.Errorf("deadline_before 格式错误: %s", err)
|
||||
}
|
||||
params.DeadlineBefore = before
|
||||
|
||||
afterRaw, _ := args["deadline_after"].(string)
|
||||
after, err := parseTaskQueryBoundaryTime(afterRaw, false)
|
||||
if err != nil {
|
||||
return TaskQueryParams{}, fmt.Errorf("deadline_after 格式错误: %s", err)
|
||||
}
|
||||
params.DeadlineAfter = after
|
||||
|
||||
// 2.8 时间区间合法性校验:下界不能晚于上界。
|
||||
if params.DeadlineBefore != nil && params.DeadlineAfter != nil &&
|
||||
params.DeadlineAfter.After(*params.DeadlineBefore) {
|
||||
return TaskQueryParams{}, fmt.Errorf("deadline_after 不能晚于 deadline_before")
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
@@ -38,13 +38,13 @@ func (s *AgentService) runTaskQueryFlow(
|
||||
Deps: agentnode.TaskQueryToolDeps{
|
||||
QueryTasks: func(ctx context.Context, req agentnode.TaskQueryRequest) ([]agentnode.TaskQueryTaskRecord, error) {
|
||||
req.UserID = userID
|
||||
return s.queryTasksForAgent(ctx, req)
|
||||
return s.QueryTasksForTool(ctx, req)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AgentService) queryTasksForAgent(ctx context.Context, req agentnode.TaskQueryRequest) ([]agentnode.TaskQueryTaskRecord, error) {
|
||||
func (s *AgentService) QueryTasksForTool(ctx context.Context, req agentnode.TaskQueryRequest) ([]agentnode.TaskQueryTaskRecord, error) {
|
||||
_ = ctx
|
||||
if req.UserID <= 0 {
|
||||
return nil, errors.New("invalid user_id in task query")
|
||||
|
||||
Reference in New Issue
Block a user