Version: 0.9.41.dev.260424
后端: 1. 随口记从 Execute 工具链路迁移到独立 QuickTask 轻量节点——单轮流式提取意图直接调 service,绕过 ReAct 循环 - 新增 QuickTask graph 节点 + Chat→QuickTask→END 分支 - Chat 路由提示词新增 quick_task 路由判别规则,execute 路由收窄为日程类 - Execute 提示词(有 plan / ReAct 两套)移除 quick_note_create / query_tasks 指令 - ToolRegistry 注销 quick_note_create / query_tasks,移除相关依赖与注册 - 依赖注入从 ToolRegistry 改为 Service 层直接注入 QuickTaskDeps 2. urgency_threshold_at 代码兜底 + API 返回补全 - priorityGroup=2 且有 deadline 但 LLM 未填时,自动设为 deadline-24h - 任务查询接口返回结构补充 UrgencyThresholdAt 字段与转换映射 3. 记忆召回条数 5→10
This commit is contained in:
@@ -182,60 +182,57 @@ func Start() {
|
||||
agentService.SetToolRegistry(newagenttools.NewDefaultRegistryWithDeps(newagenttools.DefaultRegistryDeps{
|
||||
RAGRuntime: ragRuntime,
|
||||
WebSearchProvider: webSearchProvider,
|
||||
QuickNote: newagenttools.QuickNoteDeps{
|
||||
CreateTask: func(userID int, title string, priorityGroup int, deadlineAt *time.Time) (int, error) {
|
||||
// 调用目的:随口记工具通过此闭包写库,捕获 start 层 taskRepo 实例。
|
||||
created, err := taskRepo.AddTask(&model.Task{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Priority: priorityGroup,
|
||||
IsCompleted: false,
|
||||
DeadlineAt: deadlineAt,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return created.ID, nil
|
||||
},
|
||||
},
|
||||
TaskQuery: newagenttools.TaskQueryDeps{
|
||||
// 调用目的:桥接新工具参数到旧 service 层查询能力,复用已有的过滤/排序/紧急度提升逻辑。
|
||||
QueryTasks: func(ctx context.Context, userID int, params newagenttools.TaskQueryParams) ([]newagenttools.TaskQueryResult, error) {
|
||||
req := newagentmodel.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.SetCompactionStore(agentRepo)
|
||||
agentService.SetQuickTaskDeps(newagentmodel.QuickTaskDeps{
|
||||
CreateTask: func(userID int, title string, priorityGroup int, deadlineAt *time.Time, urgencyThresholdAt *time.Time) (int, error) {
|
||||
created, err := taskRepo.AddTask(&model.Task{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Priority: priorityGroup,
|
||||
IsCompleted: false,
|
||||
DeadlineAt: deadlineAt,
|
||||
UrgencyThresholdAt: urgencyThresholdAt,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return created.ID, nil
|
||||
},
|
||||
QueryTasks: func(ctx context.Context, userID int, params newagenttools.TaskQueryParams) ([]newagenttools.TaskQueryResult, error) {
|
||||
req := newagentmodel.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.SetMemoryReader(memoryModule, memoryCfg)
|
||||
|
||||
// API 层初始化。
|
||||
|
||||
@@ -43,14 +43,20 @@ func ModelToGetUserTasksResp(tasks []model.Task) []model.GetUserTaskResp {
|
||||
deadline = task.DeadlineAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
urgencyThreshold := ""
|
||||
if task.UrgencyThresholdAt != nil {
|
||||
urgencyThreshold = task.UrgencyThresholdAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
resp = append(resp, model.GetUserTaskResp{
|
||||
ID: task.ID,
|
||||
UserID: task.UserID,
|
||||
Title: task.Title,
|
||||
PriorityGroup: task.Priority,
|
||||
Status: status,
|
||||
Deadline: deadline,
|
||||
IsCompleted: task.IsCompleted,
|
||||
ID: task.ID,
|
||||
UserID: task.UserID,
|
||||
Title: task.Title,
|
||||
PriorityGroup: task.Priority,
|
||||
Status: status,
|
||||
Deadline: deadline,
|
||||
IsCompleted: task.IsCompleted,
|
||||
UrgencyThresholdAt: urgencyThreshold,
|
||||
})
|
||||
}
|
||||
return resp
|
||||
@@ -66,13 +72,18 @@ func ModelToGetUserTaskResp(task *model.Task) model.GetUserTaskResp {
|
||||
if task.DeadlineAt != nil {
|
||||
deadline = task.DeadlineAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
urgencyThreshold := ""
|
||||
if task.UrgencyThresholdAt != nil {
|
||||
urgencyThreshold = task.UrgencyThresholdAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
return model.GetUserTaskResp{
|
||||
ID: task.ID,
|
||||
UserID: task.UserID,
|
||||
Title: task.Title,
|
||||
PriorityGroup: task.Priority,
|
||||
Status: status,
|
||||
Deadline: deadline,
|
||||
IsCompleted: task.IsCompleted,
|
||||
ID: task.ID,
|
||||
UserID: task.UserID,
|
||||
Title: task.Title,
|
||||
PriorityGroup: task.Priority,
|
||||
Status: status,
|
||||
Deadline: deadline,
|
||||
IsCompleted: task.IsCompleted,
|
||||
UrgencyThresholdAt: urgencyThreshold,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +103,14 @@ type UserUndoCompleteTaskResponse struct {
|
||||
}
|
||||
|
||||
type GetUserTaskResp struct {
|
||||
ID int `json:"id"`
|
||||
UserID int `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
PriorityGroup int `json:"priority_group"`
|
||||
Status string `json:"status"`
|
||||
Deadline string `json:"deadline"`
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
ID int `json:"id"`
|
||||
UserID int `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
PriorityGroup int `json:"priority_group"`
|
||||
Status string `json:"status"`
|
||||
Deadline string `json:"deadline"`
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
UrgencyThresholdAt string `json:"urgency_threshold_at,omitempty"`
|
||||
}
|
||||
|
||||
// UserUpdateTaskRequest 是"更新任务属性"接口的请求体。
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
NodeOrderGuard = "order_guard"
|
||||
NodeInterrupt = "interrupt"
|
||||
NodeDeliver = "deliver"
|
||||
NodeQuickTask = "quick_task"
|
||||
)
|
||||
|
||||
func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput) (*newagentmodel.AgentGraphState, error) {
|
||||
@@ -55,6 +56,9 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
if err := g.AddLambdaNode(NodeOrderGuard, compose.InvokableLambda(nodes.OrderGuard)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.AddLambdaNode(NodeQuickTask, compose.InvokableLambda(nodes.QuickTask)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.AddLambdaNode(NodeInterrupt, compose.InvokableLambda(nodes.Interrupt)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -68,7 +72,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
if err := g.AddEdge(compose.START, NodeChat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Chat -> END / Plan / Confirm / RoughBuild / Execute / Deliver / Interrupt
|
||||
// Chat -> END / Plan / Confirm / RoughBuild / Execute / QuickTask / Deliver / Interrupt
|
||||
if err := g.AddBranch(NodeChat, compose.NewGraphBranch(
|
||||
branchAfterChat,
|
||||
map[string]bool{
|
||||
@@ -76,6 +80,7 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
NodeConfirm: true,
|
||||
NodeRoughBuild: true,
|
||||
NodeExecute: true,
|
||||
NodeQuickTask: true,
|
||||
NodeDeliver: true,
|
||||
NodeInterrupt: true,
|
||||
compose.END: true,
|
||||
@@ -150,6 +155,10 @@ func RunAgentGraph(ctx context.Context, input newagentmodel.AgentGraphRunInput)
|
||||
if err := g.AddEdge(NodeDeliver, compose.END); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// QuickTask -> END:轻量路径,直接返回结果。
|
||||
if err := g.AddEdge(NodeQuickTask, compose.END); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// --- 编译运行 ---
|
||||
maxSteps := flowState.MaxRounds + 10
|
||||
@@ -186,6 +195,8 @@ func branchAfterChat(_ context.Context, st *newagentmodel.AgentGraphState) (stri
|
||||
return NodePlan, nil
|
||||
case newagentmodel.PhaseWaitingConfirm:
|
||||
return NodeConfirm, nil
|
||||
case newagentmodel.PhaseQuickTask:
|
||||
return NodeQuickTask, nil
|
||||
case newagentmodel.PhaseExecuting:
|
||||
if flowState.NeedsRoughBuild && st.Deps.RoughBuildFunc != nil {
|
||||
return NodeRoughBuild, nil
|
||||
|
||||
@@ -20,6 +20,9 @@ const (
|
||||
|
||||
// ChatRoutePlan 复杂规划:需要先制定计划,进 Plan 节点。
|
||||
ChatRoutePlan ChatRoute = "plan"
|
||||
|
||||
// ChatRouteQuickTask 快捷任务:随口记增查改删等轻量任务操作,走 QuickTask 轻量路径。
|
||||
ChatRouteQuickTask ChatRoute = "quick_task"
|
||||
)
|
||||
|
||||
// ChatRoutingDecision 是 Chat 节点单次路由决策的结构化输出。
|
||||
@@ -59,7 +62,7 @@ func (d *ChatRoutingDecision) Validate() error {
|
||||
d.Normalize()
|
||||
|
||||
switch d.Route {
|
||||
case ChatRouteDirectReply, ChatRouteExecute, ChatRouteDeepAnswer, ChatRoutePlan:
|
||||
case ChatRouteDirectReply, ChatRouteExecute, ChatRouteDeepAnswer, ChatRoutePlan, ChatRouteQuickTask:
|
||||
// ok
|
||||
case "":
|
||||
return fmt.Errorf("chat routing decision.route 不能为空")
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
PhasePlanning Phase = "planning"
|
||||
PhaseWaitingConfirm Phase = "waiting_confirm"
|
||||
PhaseExecuting Phase = "executing"
|
||||
PhaseQuickTask Phase = "quick_task"
|
||||
PhaseDone Phase = "done"
|
||||
)
|
||||
|
||||
|
||||
@@ -96,6 +96,21 @@ type AgentGraphDeps struct {
|
||||
// PersistVisibleMessage 按 Service 注入,newAgent 每个节点产出的可见 speak
|
||||
// 都会在 AppendHistory 之后立刻调用这个回调,把消息同步落到 Redis + MySQL。
|
||||
PersistVisibleMessage PersistVisibleMessageFunc
|
||||
|
||||
// QuickTaskDeps 快捷任务节点的直接依赖,绕过 ToolRegistry 走轻量路径。
|
||||
QuickTaskDeps QuickTaskDeps
|
||||
}
|
||||
|
||||
// QuickTaskDeps 描述快捷任务节点所需的服务层依赖。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. QuickTask 节点直接调这些函数,不经过 ToolRegistry,不走 ReAct 循环;
|
||||
// 2. CreateTask 和 QueryTasks 的签名与 tools 包的 QuickNoteDeps / TaskQueryDeps 一致。
|
||||
type QuickTaskDeps struct {
|
||||
// CreateTask 创建一条四象限任务,返回 task_id。
|
||||
CreateTask func(userID int, title string, priorityGroup int, deadlineAt *time.Time, urgencyThresholdAt *time.Time) (taskID int, err error)
|
||||
// QueryTasks 按条件查询用户任务列表。
|
||||
QueryTasks func(ctx context.Context, userID int, params newagenttools.TaskQueryParams) ([]newagenttools.TaskQueryResult, error)
|
||||
}
|
||||
|
||||
// --- 记忆 pinned block 常量(供 agentsvc 和 node 层共享) ---
|
||||
|
||||
@@ -198,6 +198,31 @@ func (n *AgentNodes) OrderGuard(ctx context.Context, st *newagentmodel.AgentGrap
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// QuickTask 负责把 graph 的 quick_task 节点请求转给 RunQuickTaskNode。
|
||||
func (n *AgentNodes) QuickTask(ctx context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
return nil, errors.New("quick_task node: state is nil")
|
||||
}
|
||||
|
||||
// QuickTask 不需要工具目录,直接复用 ChatClient。
|
||||
st.EnsureConversationContext().SetToolSchemas(nil)
|
||||
|
||||
if err := RunQuickTaskNode(ctx, QuickTaskNodeInput{
|
||||
RuntimeState: st.EnsureRuntimeState(),
|
||||
ConversationContext: st.EnsureConversationContext(),
|
||||
UserInput: st.Request.UserInput,
|
||||
Client: st.Deps.ResolveChatClient(),
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
QuickTaskDeps: st.Deps.QuickTaskDeps,
|
||||
PersistVisibleMessage: st.Deps.PersistVisibleMessage,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveAgentState(ctx, st)
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Deliver 负责把 graph 的 deliver 节点请求转给 RunDeliverNode。
|
||||
func (n *AgentNodes) Deliver(ctx context.Context, st *newagentmodel.AgentGraphState) (*newagentmodel.AgentGraphState, error) {
|
||||
if st == nil {
|
||||
|
||||
@@ -244,6 +244,12 @@ func streamAndDispatch(
|
||||
case newagentmodel.ChatRoutePlan:
|
||||
return handleRoutePlanStream(reader, emitter, flowState, effectiveThinking, visible)
|
||||
|
||||
case newagentmodel.ChatRouteQuickTask:
|
||||
// 关闭路由流,后续由 QuickTask 节点自行处理。
|
||||
_ = reader.Close()
|
||||
flowState.Phase = newagentmodel.PhaseQuickTask
|
||||
return nil
|
||||
|
||||
default:
|
||||
flowState.Phase = newagentmodel.PhasePlanning
|
||||
return nil
|
||||
|
||||
@@ -389,15 +389,6 @@ func RunExecuteNode(ctx context.Context, input ExecuteNodeInput) error {
|
||||
decision.Action = newagentmodel.ExecuteActionContinue
|
||||
}
|
||||
|
||||
// 随口记工具 speak 清空:
|
||||
// 1. quick_note_create 是轻量记录操作,不需要 execute 阶段向用户输出任何文案;
|
||||
// 2. 收口统一由 deliver 阶段完成,避免 execute + deliver 重复输出导致废话;
|
||||
// 3. 后端强制清空兜底,即使 LLM 误填了 speak 也不会推流到前端。
|
||||
if decision.ToolCall != nil && strings.EqualFold(decision.ToolCall.Name, "quick_note_create") {
|
||||
decision.Speak = ""
|
||||
flowState.UsedQuickNote = true
|
||||
}
|
||||
|
||||
// 自省校验:next_plan / done 必须附带 goal_check,否则不推进,追加修正让 LLM 重试。
|
||||
if decision.Action == newagentmodel.ExecuteActionNextPlan ||
|
||||
decision.Action == newagentmodel.ExecuteActionDone {
|
||||
@@ -2026,8 +2017,6 @@ func resolveToolDisplayNameCN(toolName string) string {
|
||||
"query_target_tasks": "查询目标任务",
|
||||
"query_available_slots": "查询可用时间段",
|
||||
"get_task_info": "查看任务详情",
|
||||
"quick_note_create": "创建提醒任务",
|
||||
"query_tasks": "查询任务列表",
|
||||
"web_search": "网页搜索",
|
||||
"web_fetch": "网页抓取",
|
||||
"move": "移动任务",
|
||||
|
||||
328
backend/newAgent/node/quick_task.go
Normal file
328
backend/newAgent/node/quick_task.go
Normal file
@@ -0,0 +1,328 @@
|
||||
package newagentnode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
infrallm "github.com/LoveLosita/smartflow/backend/infra/llm"
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagentprompt "github.com/LoveLosita/smartflow/backend/newAgent/prompt"
|
||||
newagentrouter "github.com/LoveLosita/smartflow/backend/newAgent/router"
|
||||
newagentshared "github.com/LoveLosita/smartflow/backend/newAgent/shared"
|
||||
newagentstream "github.com/LoveLosita/smartflow/backend/newAgent/stream"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
quickTaskStageName = "quick_task"
|
||||
quickTaskBlockID = "qt_main"
|
||||
)
|
||||
|
||||
// QuickTaskNodeInput 描述快捷任务节点的输入。
|
||||
type QuickTaskNodeInput struct {
|
||||
RuntimeState *newagentmodel.AgentRuntimeState
|
||||
ConversationContext *newagentmodel.ConversationContext
|
||||
UserInput string
|
||||
Client *infrallm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
QuickTaskDeps newagentmodel.QuickTaskDeps
|
||||
PersistVisibleMessage newagentmodel.PersistVisibleMessageFunc
|
||||
}
|
||||
|
||||
// quickTaskDecision 是从 LLM 输出中解析的结构化意图。
|
||||
type quickTaskDecision struct {
|
||||
Action string `json:"action"`
|
||||
Title string `json:"title,omitempty"`
|
||||
DeadlineAt string `json:"deadline_at,omitempty"`
|
||||
PriorityGroup *int `json:"priority_group,omitempty"`
|
||||
UrgencyThresholdAt string `json:"urgency_threshold_at,omitempty"`
|
||||
TaskID *int `json:"task_id,omitempty"`
|
||||
|
||||
// query 参数
|
||||
Quadrant *int `json:"quadrant,omitempty"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
Limit *int `json:"limit,omitempty"`
|
||||
|
||||
// ask 参数
|
||||
Question string `json:"question,omitempty"`
|
||||
}
|
||||
|
||||
// RunQuickTaskNode 执行快捷任务节点:流式 LLM 提取意图 → 直接调 service → 追加结果。
|
||||
func RunQuickTaskNode(ctx context.Context, input QuickTaskNodeInput) error {
|
||||
flowState := input.RuntimeState.EnsureCommonState()
|
||||
emitter := input.ChunkEmitter
|
||||
|
||||
// 1. 构造 messages。
|
||||
messages := newagentprompt.BuildQuickTaskMessagesSimple(input.UserInput)
|
||||
|
||||
// 2. 真流式调用 LLM。
|
||||
reader, err := input.Client.Stream(ctx, messages, infrallm.GenerateOptions{
|
||||
Temperature: 0.3,
|
||||
MaxTokens: 512,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[WARN] quick_task: Stream 调用失败 chat=%s err=%v", flowState.ConversationID, err)
|
||||
_ = emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, "抱歉,处理任务时出了点问题,请重试。", true)
|
||||
flowState.Phase = newagentmodel.PhaseDone
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. 两阶段流式解析。
|
||||
parser := newagentrouter.NewStreamDecisionParser()
|
||||
firstChunk := true
|
||||
var decision *quickTaskDecision
|
||||
var fullText strings.Builder
|
||||
|
||||
// 阶段一:解析决策标签。
|
||||
for {
|
||||
chunk, recvErr := reader.Recv()
|
||||
if recvErr == io.EOF {
|
||||
break
|
||||
}
|
||||
if recvErr != nil {
|
||||
log.Printf("[WARN] quick_task stream recv error chat=%s err=%v", flowState.ConversationID, recvErr)
|
||||
break
|
||||
}
|
||||
|
||||
content := ""
|
||||
if chunk != nil {
|
||||
content = chunk.Content
|
||||
}
|
||||
|
||||
visible, ready, _ := parser.Feed(content)
|
||||
if !ready {
|
||||
continue
|
||||
}
|
||||
|
||||
result := parser.Result()
|
||||
|
||||
// Fallback / 解析失败:把原始文本当作纯回复推送。
|
||||
if result.Fallback || result.ParseFailed {
|
||||
log.Printf("[DEBUG] quick_task: 标签解析失败 chat=%s raw=%s", flowState.ConversationID, result.RawBuffer)
|
||||
if result.RawBuffer != "" {
|
||||
_ = emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, result.RawBuffer, firstChunk)
|
||||
fullText.WriteString(result.RawBuffer)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 解析 JSON。
|
||||
log.Printf("[DEBUG] quick_task: LLM 原始决策 JSON chat=%s json=%s", flowState.ConversationID, result.DecisionJSON)
|
||||
var parseErr error
|
||||
decision, parseErr = infrallm.ParseJSONObject[quickTaskDecision](result.DecisionJSON)
|
||||
if parseErr != nil {
|
||||
log.Printf("[DEBUG] quick_task: JSON 解析失败 chat=%s json=%s", flowState.ConversationID, result.DecisionJSON)
|
||||
if result.RawBuffer != "" {
|
||||
_ = emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, result.RawBuffer, firstChunk)
|
||||
fullText.WriteString(result.RawBuffer)
|
||||
}
|
||||
break
|
||||
}
|
||||
log.Printf("[DEBUG] quick_task: 解析结果 chat=%s action=%s title=%s deadline_at=%s priority_group=%v urgency_threshold_at=%q",
|
||||
flowState.ConversationID, decision.Action, decision.Title, decision.DeadlineAt, decision.PriorityGroup, decision.UrgencyThresholdAt)
|
||||
|
||||
// 阶段二:流式推送标签后正文。
|
||||
if visible != "" {
|
||||
if emitErr := emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, visible, firstChunk); emitErr != nil {
|
||||
log.Printf("[WARN] quick_task emit error chat=%s err=%v", flowState.ConversationID, emitErr)
|
||||
}
|
||||
fullText.WriteString(visible)
|
||||
firstChunk = false
|
||||
}
|
||||
for {
|
||||
chunk2, recvErr2 := reader.Recv()
|
||||
if recvErr2 == io.EOF {
|
||||
break
|
||||
}
|
||||
if recvErr2 != nil {
|
||||
log.Printf("[WARN] quick_task stream error chat=%s err=%v", flowState.ConversationID, recvErr2)
|
||||
break
|
||||
}
|
||||
if chunk2 == nil || chunk2.Content == "" {
|
||||
continue
|
||||
}
|
||||
if emitErr := emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, chunk2.Content, firstChunk); emitErr != nil {
|
||||
log.Printf("[WARN] quick_task emit error chat=%s err=%v", flowState.ConversationID, emitErr)
|
||||
}
|
||||
fullText.WriteString(chunk2.Content)
|
||||
firstChunk = false
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 4. 流结束但未解析到决策 → 降级为纯文本回复。
|
||||
if decision == nil {
|
||||
finalText := fullText.String()
|
||||
if strings.TrimSpace(finalText) == "" {
|
||||
_ = emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, "抱歉,处理任务时出了点问题,请重试。", true)
|
||||
}
|
||||
msg := schema.AssistantMessage(finalText, nil)
|
||||
input.ConversationContext.AppendHistory(msg)
|
||||
persistVisibleAssistantMessage(ctx, input.PersistVisibleMessage, flowState, msg)
|
||||
flowState.Phase = newagentmodel.PhaseDone
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] quick_task: chat=%s action=%s raw_title=%s", flowState.ConversationID, decision.Action, decision.Title)
|
||||
|
||||
// 5. 根据意图执行操作。
|
||||
var resultText string
|
||||
switch decision.Action {
|
||||
case "create":
|
||||
resultText = handleQuickTaskCreate(ctx, input, decision, flowState)
|
||||
case "query":
|
||||
resultText = handleQuickTaskQuery(ctx, input, decision, flowState)
|
||||
case "ask":
|
||||
resultText = decision.Question
|
||||
if resultText == "" {
|
||||
resultText = "你想记录什么呢?告诉我具体内容吧。"
|
||||
}
|
||||
default:
|
||||
resultText = "抱歉,我没有理解你的意思。你可以试试说「记一下明天开会」或「看看我的任务」。"
|
||||
}
|
||||
|
||||
// 6. 追加操作结果文本。
|
||||
if resultText != "" {
|
||||
_ = emitter.EmitAssistantText(quickTaskBlockID, quickTaskStageName, resultText, false)
|
||||
fullText.WriteString(resultText)
|
||||
}
|
||||
|
||||
// 7. 写入对话历史。
|
||||
finalText := fullText.String()
|
||||
msg := schema.AssistantMessage(finalText, nil)
|
||||
input.ConversationContext.AppendHistory(msg)
|
||||
persistVisibleAssistantMessage(ctx, input.PersistVisibleMessage, flowState, msg)
|
||||
|
||||
flowState.Phase = newagentmodel.PhaseDone
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleQuickTaskCreate 处理任务创建。
|
||||
func handleQuickTaskCreate(
|
||||
ctx context.Context,
|
||||
input QuickTaskNodeInput,
|
||||
decision *quickTaskDecision,
|
||||
flowState *newagentmodel.CommonState,
|
||||
) string {
|
||||
title := strings.TrimSpace(decision.Title)
|
||||
if title == "" {
|
||||
return "你想记录什么呢?告诉我具体内容吧。"
|
||||
}
|
||||
|
||||
var deadline *time.Time
|
||||
if raw := strings.TrimSpace(decision.DeadlineAt); raw != "" {
|
||||
parsed, err := newagentshared.ParseOptionalDeadline(raw)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("截止时间格式不太对(%s),不过我先把任务记下来啦。", err)
|
||||
}
|
||||
deadline = parsed
|
||||
}
|
||||
|
||||
priorityGroup := 0
|
||||
if decision.PriorityGroup != nil && newagentshared.IsValidTaskPriority(*decision.PriorityGroup) {
|
||||
priorityGroup = *decision.PriorityGroup
|
||||
}
|
||||
if priorityGroup == 0 {
|
||||
priorityGroup = quickNoteFallbackPriority(deadline)
|
||||
}
|
||||
|
||||
var urgencyThreshold *time.Time
|
||||
if raw := strings.TrimSpace(decision.UrgencyThresholdAt); raw != "" {
|
||||
parsed, err := newagentshared.ParseOptionalDeadline(raw)
|
||||
if err == nil {
|
||||
urgencyThreshold = parsed
|
||||
}
|
||||
}
|
||||
// LLM 经常省略 urgency_threshold_at,代码兜底:priorityGroup=2 且有 deadline 时自动推算。
|
||||
if urgencyThreshold == nil && priorityGroup == 2 && deadline != nil {
|
||||
fallback := deadline.Add(-24 * time.Hour)
|
||||
urgencyThreshold = &fallback
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] quick_task: CreateTask 参数 chat=%s title=%s priorityGroup=%d deadline=%v urgencyThreshold=%v urgency_raw=%q",
|
||||
flowState.ConversationID, title, priorityGroup, deadline, urgencyThreshold, decision.UrgencyThresholdAt)
|
||||
_, err := input.QuickTaskDeps.CreateTask(flowState.UserID, title, priorityGroup, deadline, urgencyThreshold)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("记录失败了(%s),稍后再试试?", err)
|
||||
}
|
||||
|
||||
flowState.UsedQuickNote = true
|
||||
|
||||
priorityLabel := newagentshared.PriorityLabelCN(priorityGroup)
|
||||
deadlineStr := ""
|
||||
if deadline != nil {
|
||||
deadlineStr = deadline.In(newagentshared.ShanghaiLocation()).Format("2006-01-02 15:04")
|
||||
}
|
||||
|
||||
if deadlineStr != "" {
|
||||
return fmt.Sprintf("已记录:%s(%s,截止 %s)", title, priorityLabel, deadlineStr)
|
||||
}
|
||||
return fmt.Sprintf("已记录:%s(%s)", title, priorityLabel)
|
||||
}
|
||||
|
||||
// handleQuickTaskQuery 处理任务查询。
|
||||
func handleQuickTaskQuery(
|
||||
ctx context.Context,
|
||||
input QuickTaskNodeInput,
|
||||
decision *quickTaskDecision,
|
||||
flowState *newagentmodel.CommonState,
|
||||
) string {
|
||||
params := newagenttools.TaskQueryParams{
|
||||
SortBy: "deadline",
|
||||
Order: "asc",
|
||||
Limit: 5,
|
||||
IncludeCompleted: false,
|
||||
}
|
||||
|
||||
if decision.Quadrant != nil && *decision.Quadrant >= 1 && *decision.Quadrant <= 4 {
|
||||
params.Quadrant = decision.Quadrant
|
||||
}
|
||||
if kw := strings.TrimSpace(decision.Keyword); kw != "" {
|
||||
params.Keyword = kw
|
||||
}
|
||||
if decision.Limit != nil && *decision.Limit > 0 && *decision.Limit <= 20 {
|
||||
params.Limit = *decision.Limit
|
||||
}
|
||||
|
||||
results, err := input.QuickTaskDeps.QueryTasks(ctx, flowState.UserID, params)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("查询失败了(%s),稍后再试试?", err)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return "当前没有匹配的任务。"
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("找到 %d 条任务:\n", len(results)))
|
||||
for _, t := range results {
|
||||
label := newagentshared.PriorityLabelCN(t.PriorityGroup)
|
||||
line := fmt.Sprintf("- %s(%s", t.Title, label)
|
||||
if t.DeadlineAt != "" {
|
||||
line += fmt.Sprintf(",截止 %s", t.DeadlineAt)
|
||||
}
|
||||
line += ")"
|
||||
if t.IsCompleted {
|
||||
line += " ✅"
|
||||
}
|
||||
sb.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// quickNoteFallbackPriority 根据截止时间推断默认优先级,与 tools/quicknote.go 保持一致。
|
||||
func quickNoteFallbackPriority(deadline *time.Time) int {
|
||||
if deadline != nil {
|
||||
if time.Until(*deadline) <= 48*time.Hour {
|
||||
return newagentshared.QuickNotePriorityImportantUrgent
|
||||
}
|
||||
return newagentshared.QuickNotePriorityImportantNotUrgent
|
||||
}
|
||||
return newagentshared.QuickNotePrioritySimpleNotImportant
|
||||
}
|
||||
@@ -14,10 +14,18 @@ const chatRoutingSystemPrompt = `
|
||||
|
||||
路由规则:
|
||||
- direct_reply:纯闲聊、简单问答、轻量生活建议、打招呼、感谢等不需要工具、也不需要长链路思考的请求。控制码后直接输出完整回复。
|
||||
- execute:需要用工具处理的请求(记录任务/提醒、查询日程、移动课程、排课等),但不需要先制定计划。控制码后输出简短确认。
|
||||
- quick_task:用户明确想记录/添加/修改/删除一个待办或提醒(如"记一下""提醒我""帮我记"),或查看/筛选任务列表(如"我有什么任务""待办清单""最近急事")。该路由走轻量快捷路径,延迟低、废话少。控制码后不要输出任何内容。
|
||||
- execute:需要用工具处理的日程类请求(查询日程、移动课程、排课等),但不需要先制定计划。控制码后输出简短确认。
|
||||
- deep_answer:复杂问题但不需要工具(如分析建议、知识解释、方案比较、深度讨论等),需要深度思考后回答。控制码后不要输出任何占位过渡语,后端会直接进入第二次正式回答。
|
||||
- plan:用户明确要求先制定计划,或涉及多阶段复杂规划。控制码后输出简短确认。
|
||||
|
||||
quick_task 判别要点:
|
||||
- 用户明确要"记/添加/提醒"一个待办 → quick_task
|
||||
- 用户要查看/筛选/列出任务清单 → quick_task
|
||||
- 用户要修改/删除某个任务 → quick_task
|
||||
- 但如果用户同时提了日程排布(如"把明天的课调一下,再记一下周五开会"),混合操作走 execute
|
||||
- 如果信息不足(如"帮我记一下"但没说记什么),走 direct_reply 追问
|
||||
|
||||
通用回答约束:
|
||||
- 非日程、非任务类问题,只要不需要工具,也应当正常回答。
|
||||
- 不要因为用户的问题不涉及排程,就说自己“只能处理日程/任务安排”。
|
||||
@@ -44,7 +52,7 @@ const chatRoutingSystemPrompt = `
|
||||
|
||||
输出格式(严格两段式):
|
||||
第一段(控制码,用户不可见,后端会截取):
|
||||
<SMARTFLOW_ROUTE nonce="给定nonce" route="direct_reply|execute|deep_answer|plan" rough_build="false" refine="false" reorder="false" thinking="false"/>
|
||||
<SMARTFLOW_ROUTE nonce="给定nonce" route="direct_reply|execute|deep_answer|plan|quick_task" rough_build="false" refine="false" reorder="false" thinking="false"/>
|
||||
第二段(紧接控制码之后,用户可见):
|
||||
根据路由输出对应内容。
|
||||
|
||||
@@ -59,6 +67,8 @@ const chatRoutingSystemPrompt = `
|
||||
<SMARTFLOW_ROUTE nonce="给定nonce" route="direct_reply"/>
|
||||
当然可以,我先直接回答你这个问题。
|
||||
|
||||
<SMARTFLOW_ROUTE nonce="给定nonce" route="quick_task"/>
|
||||
|
||||
<SMARTFLOW_ROUTE nonce="给定nonce" route="execute"/>
|
||||
好的,我来帮你看看今天的安排。
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const executeSystemPromptWithPlan = `
|
||||
你可以做什么:
|
||||
1. 只围绕当前步骤推进,先读后写,逐步完成当前步骤。
|
||||
2. 可调用读工具补充事实,再决定下一步。
|
||||
3. 日程写操作时输出 action=confirm 并附带 tool_call,等待用户确认。quick_note_create 不需要确认,用 action=continue;若信息足够,必须显式填写 priority_group,若信息不足则先 ask_user,不要盲猜。
|
||||
3. 日程写操作时输出 action=confirm 并附带 tool_call,等待用户确认。
|
||||
4. 若用户给出了"二次微调方向"(如负载均衡、某天减负、某类任务后移),优先围绕该方向推进,并在 goal_check 说明满足情况。
|
||||
5. 只有在用户明确允许打乱顺序时,才可使用 min_context_switch 做重排。
|
||||
6. 多任务微调时默认走队列链路:query_target_tasks(enqueue=true) → queue_pop_head → query_available_slots → queue_apply_head_move / queue_skip_head。
|
||||
@@ -39,12 +39,10 @@ const executeSystemPromptWithPlan = `
|
||||
1. 输出格式:先输出一行 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的自然语言正文。JSON 中不要包含 speak 字段——用户可见的话放在标签之后。
|
||||
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. 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。`
|
||||
4. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
5. 仅当当前步骤完成时输出 action=next_plan,并在 goal_check 对照 done_when 给出证据。
|
||||
6. 仅当整体任务完成时输出 action=done,并在 goal_check 总结完成证据。
|
||||
7. 流程应正式终止时输出 action=abort。`
|
||||
|
||||
const executeSystemPromptReAct = `
|
||||
你是 SmartMate 的执行器,当前处于自由执行模式(无预定义 plan 步骤)。
|
||||
@@ -59,7 +57,7 @@ const executeSystemPromptReAct = `
|
||||
1. 你可以基于用户给定的二次微调方向,对 suggested 做定向微调。
|
||||
2. existing 属于已安排事实层,可用于冲突判断和参考,不作为 move/batch_move/spread_even 的目标。
|
||||
3. 你可以先调用读工具补充必要事实(例如 get_overview/query_target_tasks/query_available_slots/get_task_info)。
|
||||
4. 你可以在需要日程写操作时提出 confirm(move/swap/unplace/batch_move/spread_even)。quick_note_create 不需要确认,用 action=continue;若信息足够,必须显式填写 priority_group,若信息不足则先 ask_user。
|
||||
4. 你可以在需要日程写操作时提出 confirm(move/swap/unplace/batch_move/spread_even)。
|
||||
5. 只有用户明确允许打乱顺序时,才可使用 min_context_switch。
|
||||
6. 多任务处理默认使用队列链路:先 query_target_tasks(enqueue=true) 入队,再 queue_pop_head 逐项处理。
|
||||
|
||||
@@ -82,11 +80,9 @@ const executeSystemPromptReAct = `
|
||||
1. 输出格式:先输出一行 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的自然语言正文。JSON 中不要包含 speak 字段——用户可见的话放在标签之后。
|
||||
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. query_tasks(查看/筛选任务列表):读操作,action=continue + tool_call。用于回答"我有什么任务""最近有什么急事"等问题,支持按象限、关键词、截止时间范围筛选和排序。
|
||||
6. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
7. 任务完成:action=done,并在 goal_check 总结完成证据。
|
||||
8. 流程应正式终止:action=abort。`
|
||||
4. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
5. 任务完成:action=done,并在 goal_check 总结完成证据。
|
||||
6. 流程应正式终止:action=abort。`
|
||||
|
||||
// BuildExecuteSystemPrompt 返回执行阶段系统提示词(有 plan 模式)。
|
||||
func BuildExecuteSystemPrompt() string {
|
||||
|
||||
102
backend/newAgent/prompt/quick_task.go
Normal file
102
backend/newAgent/prompt/quick_task.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
const quickTaskSystemPrompt = `
|
||||
你是 SmartMate 的快捷任务助手。用户想记录或查看待办任务。你需要从用户消息中提取操作意图和参数。
|
||||
|
||||
你能做的操作:
|
||||
- create:记录一条新任务/提醒
|
||||
- query:查看/筛选任务列表
|
||||
|
||||
输出格式(两阶段):
|
||||
先输出一行决策标签,标签内是 JSON;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
- action:只能是 create / query / ask
|
||||
- create 时:title 必填,deadline_at 必填,priority_group 必填,范围 1-4;urgency_threshold_at 满足条件时填写,条件在下面
|
||||
- query 时:quadrant 可选 1-4,keyword 可选,limit 可选
|
||||
- ask 时:question 必填
|
||||
|
||||
规则:
|
||||
1. 优先级:1=重要且紧急,2=重要不紧急,3=简单不重要,4=复杂不重要;紧急判定:截止时间距今不超过 48 小时为紧急(1),超过 48 小时为不紧急(2),无截止时间默认 3
|
||||
2. 信息不足时(如缺少 title),输出 {"action":"ask","question":"追问内容"}
|
||||
3. deadline_at 支持「明天下午3点」「下周一」「2026-04-20 18:00」等格式
|
||||
4. 未提供的可选字段直接省略,不要填 null 或空字符串
|
||||
5. JSON 中不要包含 speak 字段,给用户看的话放在 </SMARTFLOW_DECISION> 标签之后
|
||||
6. 紧急分界时间,即任务从"重要不紧急"自动轮换到"重要且紧急"的时间点;格式同 deadline_at ——当 priority_group=2 时必填,你必须根据 deadline 自动推算一个合理的紧急分界时间(通常为 deadline 前 24-48 小时),不要等用户提供;priority_group 为 1、3、4 或无截止时间时不要输出此字段
|
||||
|
||||
示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"create","title":"明天开会","deadline_at":"明天下午3点"}</SMARTFLOW_DECISION>
|
||||
好的,我来帮你记一下。
|
||||
<SMARTFLOW_DECISION>{"action":"create","title":"下周交报告","deadline_at":"下周五 18:00","priority_group":2,"urgency_threshold_at":"下周四 09:00"}</SMARTFLOW_DECISION>
|
||||
好的,我也帮你记一下。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"query","limit":5}</SMARTFLOW_DECISION>
|
||||
我帮你查一下当前的任务。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"ask","question":"你想记录什么呢?告诉我具体内容吧。"}</SMARTFLOW_DECISION>
|
||||
你想记录什么呢?告诉我具体内容吧。`
|
||||
|
||||
// BuildQuickTaskSystemPrompt 返回快捷任务阶段的系统提示词。
|
||||
func BuildQuickTaskSystemPrompt() string {
|
||||
return strings.TrimSpace(quickTaskSystemPrompt)
|
||||
}
|
||||
|
||||
// BuildQuickTaskMessagesSimple 组装快捷任务阶段的最简 messages(无对话历史)。
|
||||
func BuildQuickTaskMessagesSimple(userInput string) []*schema.Message {
|
||||
systemMsg := schema.SystemMessage(BuildQuickTaskSystemPrompt())
|
||||
userMsg := schema.UserMessage(buildQuickTaskUserPrompt(userInput))
|
||||
return []*schema.Message{systemMsg, userMsg}
|
||||
}
|
||||
|
||||
func buildQuickTaskUserPrompt(userInput string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("当前时间=%s\n", time.Now().In(time.Local).Format("2006-01-02 15:04")))
|
||||
sb.WriteString("\n请从用户消息中提取操作意图和参数,严格按 SMARTFLOW_DECISION 标签格式输出。\n\n")
|
||||
|
||||
trimmedInput := strings.TrimSpace(userInput)
|
||||
if trimmedInput != "" {
|
||||
sb.WriteString("用户输入:\n")
|
||||
sb.WriteString(trimmedInput)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// BuildQuickTaskMessages 组装快捷任务阶段的完整 messages(含对话历史)。
|
||||
func BuildQuickTaskMessages(
|
||||
ctx *newagentmodel.ConversationContext,
|
||||
userInput string,
|
||||
toolSchemas []newagentmodel.ToolSchemaContext,
|
||||
) []*schema.Message {
|
||||
return buildUnifiedStageMessages(
|
||||
ctx,
|
||||
StageMessagesConfig{
|
||||
SystemPrompt: BuildQuickTaskSystemPrompt(),
|
||||
Msg1Content: buildChatConversationMessage(ctx),
|
||||
Msg2Content: buildQuickTaskWorkspace(toolSchemas),
|
||||
Msg3Suffix: buildQuickTaskUserPrompt(userInput),
|
||||
Msg3Role: schema.User,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func buildQuickTaskWorkspace(toolSchemas []newagentmodel.ToolSchemaContext) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("可用工具:\n")
|
||||
for _, ts := range toolSchemas {
|
||||
sb.WriteString(fmt.Sprintf("- %s: %s\n 参数: %s\n", ts.Name, ts.Desc, ts.SchemaText))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@@ -24,7 +24,7 @@ var (
|
||||
chatRouteHeaderRegex = regexp.MustCompile(
|
||||
`(?is)<\s*SMARTFLOW_ROUTE\b` +
|
||||
`[^>]*\bnonce\s*=\s*["']?([a-zA-Z0-9\-]+)["']?` +
|
||||
`[^>]*\broute\s*=\s*["']?(direct_reply|execute|deep_answer|plan)["']?` +
|
||||
`[^>]*\broute\s*=\s*["']?(direct_reply|execute|deep_answer|plan|quick_task)["']?` +
|
||||
`(?:[^>]*\brough_build\s*=\s*["']?(true|false)["']?)?` +
|
||||
`(?:[^>]*\brefine\s*=\s*["']?(true|false)["']?)?` +
|
||||
`(?:[^>]*\breorder\s*=\s*["']?(true|false)["']?)?` +
|
||||
|
||||
@@ -31,12 +31,6 @@ type DefaultRegistryDeps struct {
|
||||
|
||||
// WebSearchProvider Web 搜索供应商。为 nil 时 web_search / web_fetch 返回"暂未启用",不阻断主流程。
|
||||
WebSearchProvider web.SearchProvider
|
||||
|
||||
// QuickNote 随口记工具依赖。CreateTask 为 nil 时 quick_note_create 返回错误提示,不阻断主流程。
|
||||
QuickNote QuickNoteDeps
|
||||
|
||||
// TaskQuery 任务查询工具依赖。QueryTasks 为 nil 时 query_tasks 不注册,不影响其他工具。
|
||||
TaskQuery TaskQueryDeps
|
||||
}
|
||||
|
||||
// ToolRegistry 管理工具注册、查找与执行。
|
||||
@@ -129,10 +123,8 @@ var writeTools = map[string]bool{
|
||||
// 调用目的:这些工具不需要日程状态即可执行,execute 节点在 ScheduleState 为 nil 时允许调用。
|
||||
|
||||
var scheduleFreeTools = map[string]bool{
|
||||
"quick_note_create": true,
|
||||
"query_tasks": true,
|
||||
"web_search": true,
|
||||
"web_fetch": true,
|
||||
"web_search": true,
|
||||
"web_fetch": true,
|
||||
}
|
||||
|
||||
// ==================== 默认注册表 ====================
|
||||
@@ -332,30 +324,6 @@ func NewDefaultRegistryWithDeps(deps DefaultRegistryDeps) *ToolRegistry {
|
||||
},
|
||||
)
|
||||
|
||||
// --- 随口记工具 ---
|
||||
// 调用目的:将"帮我记一下明天开会"等随口任务请求直接写入数据库,无需 ScheduleState。
|
||||
// 不加入 writeTools:随口记是用户明确指令,不需要 confirm 节点二次确认。
|
||||
if deps.QuickNote.CreateTask != nil {
|
||||
quickNoteHandler := NewQuickNoteToolHandler(deps.QuickNote)
|
||||
r.Register("quick_note_create",
|
||||
"记录一条任务/提醒/待办事项到用户的任务列表。支持中文相对时间(如“明天下午3点”、“下周一”)。title 必填。记录成功后,回复时应包含一句与任务内容相关的轻松跟进话术(不超过30字),类似朋友间的友好调侃。",
|
||||
`{"name":"quick_note_create","parameters":{"title":{"type":"string","required":true,"description":"任务标题,简洁明确"},"deadline_at":{"type":"string","description":"可选截止时间,支持 yyyy-MM-dd HH:mm 或中文相对时间(明天/下周一/后天等)"},"priority_group":{"type":"int","description":"优先级(1重要且紧急,2重要不紧急,3简单不重要,4复杂不重要);信息足够时请显式填写,不确定时可不填,由工具层自动推断"}}}`,
|
||||
quickNoteHandler,
|
||||
)
|
||||
}
|
||||
|
||||
// --- 任务查询读工具 ---
|
||||
// 调用目的:将"帮我看看有什么任务""最近有什么急事"等查询请求直接查库返回结构化结果,无需 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 模式。
|
||||
|
||||
@@ -61,6 +61,7 @@ type AgentService struct {
|
||||
scheduleProvider newagentmodel.ScheduleStateProvider
|
||||
agentStateStore newagentmodel.AgentStateStore
|
||||
compactionStore newagentmodel.CompactionStore
|
||||
quickTaskDeps newagentmodel.QuickTaskDeps
|
||||
memoryReader MemoryReader
|
||||
memoryCfg memorymodel.Config
|
||||
memoryObserver memoryobserve.Observer
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
newAgentMemoryRetrieveLimit = 5
|
||||
newAgentMemoryRetrieveLimit = 10
|
||||
newAgentMemoryIntroLine = "以下是与当前对话相关的用户记忆,仅在自然且确实有帮助时参考,不要生硬复述。"
|
||||
)
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ func (s *AgentService) runNewAgentGraph(
|
||||
ThinkingExecute: viper.GetBool("agent.thinking.execute"),
|
||||
ThinkingDeliver: viper.GetBool("agent.thinking.deliver"),
|
||||
PersistVisibleMessage: persistVisibleMessage,
|
||||
QuickTaskDeps: s.quickTaskDeps,
|
||||
}
|
||||
|
||||
// 10. 构造 AgentGraphRunInput 并运行 graph。
|
||||
@@ -704,3 +705,8 @@ func (s *AgentService) SetAgentStateStore(store newagentmodel.AgentStateStore) {
|
||||
func (s *AgentService) SetCompactionStore(store newagentmodel.CompactionStore) {
|
||||
s.compactionStore = store
|
||||
}
|
||||
|
||||
// quickTaskDeps 由 cmd/start.go 注入
|
||||
func (s *AgentService) SetQuickTaskDeps(deps newagentmodel.QuickTaskDeps) {
|
||||
s.quickTaskDeps = deps
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user