Files
smartmate/backend/newAgent/tools/quicknote.go
Losita 4fbf9397d2 Version: 0.9.27.dev.260418
后端:
1. SSE 心跳保活——解决 Vite dev proxy 在 LLM thinking 静默期判 idle 断连
- api/agent.go:ChatAgent 新增 5 秒 heartbeat ticker,select 增加 heartbeat.C 分支,每 5 秒写入 SSE 注释行 : ping\n\n 并 Flush
- service/agentsvc/agent_newagent.go:graph 执行失败时增加 context.Canceled / requestCtx.Err() 判断,客户端断连只记 warn 不推 errChan 也不跑 fallback,消除 "错误通道已满" 日志噪音
2. 随口记工具(quick_note_create)接入新 Agent 链路
- agent/node/quicknote.go:parseOptionalDeadlineWithNow / quickNoteLocation 首字母大写导出,供新链路复用旧链路成熟的时间解析和时区能力
- agent/node/quicknote_tool.go:parseOptionalDeadline / quickNoteLocation 同步导出,补充调用目的注释
- newAgent/tools/quicknote.go:新增 QuickNoteToolHandler,实现新链路 quick_note_create 工具的参数校验、时间解析、写库调用
- newAgent/tools/registry.go:DefaultRegistryDeps 新增 QuickNote 字段;新增 RequiresScheduleState 方法和 scheduleFreeTools 集合;注册 quick_note_create 工具(不加入 writeTools,不走 confirm 确认)
- cmd/start.go:NewDefaultRegistryWithDeps 注入 QuickNote.CreateTask 闭包,捕获 taskRepo 实例写库
3. Execute 节点随口记 speak 清空 + 非 ScheduleState 工具支持
- newAgent/node/execute.go:新增非写工具 confirm→continue 自动降级逻辑;新增 quick_note_create speak 强制清空,收口统一交给 deliver,避免 execute + deliver 重复废话
- newAgent/node/execute.go:executeToolCall / executePendingTool 中 scheduleState nil 检查改为仅拦截 RequiresScheduleState 的工具;为不依赖 ScheduleState 的工具自动注入 _user_id 参数
- newAgent/prompt/execute.go:有 plan / ReAct 两套系统 prompt 中,"写操作"规则细化为"日程写操作";新增 quick_note_create 专属执行规则:speak 必须留空,收口由 deliver 完成,调用成功后可 continue 处理多任务
- newAgent/prompt/chat.go:execute 路由描述补充"记录任务/提醒"场景

前端:
1. Vite dev proxy SSE 透传配置
- vite.config.ts:/api 代理新增 configure 回调,设置 x-accel-buffering: no 和 cache-control: no-cache,禁用代理缓冲
2.SSE 流式处理修复
- AssistantPanel.vue:reasoning_content 守卫放宽,移除 !assistantMessage.content.trim() 外层条件,正文回流后仍允许追加 reasoning(工具调用摘要、阶段状态等),不再吞掉 execute/deliver 的 reasoning_content
- AssistantPanel.vue:流式完成后跳过 loadConversationMessages,避免 persistVisibleMessage 尚未落库时 merge 产生重复或丢失

仓库:无
2026-04-18 11:20:49 +08:00

141 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package newagenttools
import (
"encoding/json"
"fmt"
"strings"
"time"
agentmodel "github.com/LoveLosita/smartflow/backend/agent/model"
agentnode "github.com/LoveLosita/smartflow/backend/agent/node"
"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
)
// QuickNoteDeps 描述随口记工具所需的外部依赖。
//
// 职责边界:
// 1. CreateTask 负责真正写库,工具层不直接依赖 DAO
// 2. UserID 由 execute 节点通过 args["_user_id"] 注入,工具层不自行解析会话身份。
type QuickNoteDeps struct {
// CreateTask 将解析后的任务字段写入数据库。
// 调用目的:解耦工具层与 DAO 层,方便测试和替换。
CreateTask func(userID int, title string, priorityGroup int, deadlineAt *time.Time) (taskID int, err error)
}
// QuickNoteCreateResult 是 quick_note_create 工具的结构化返回。
type QuickNoteCreateResult struct {
TaskID int `json:"task_id"`
Title string `json:"title"`
PriorityLabel string `json:"priority_label"`
DeadlineAt string `json:"deadline_at,omitempty"`
Message string `json:"message"`
}
// quickNoteFallbackPriority 根据截止时间推断默认优先级。
//
// 推断规则:
// 1. 有截止时间且距今 ≤48h → 1重要且紧急
// 2. 有截止时间且距今 >48h → 2重要不紧急
// 3. 无截止时间 → 3简单不重要
func quickNoteFallbackPriority(deadline *time.Time) int {
if deadline != nil {
if time.Until(*deadline) <= 48*time.Hour {
return agentmodel.QuickNotePriorityImportantUrgent
}
return agentmodel.QuickNotePriorityImportantNotUrgent
}
return agentmodel.QuickNotePrioritySimpleNotImportant
}
// NewQuickNoteToolHandler 创建 quick_note_create 工具的 handler 闭包。
//
// 职责边界:
// 1. 负责参数校验、时间解析、优先级推断、调 deps 写库、组装返回;
// 2. 不负责 LLM 交互和会话管理。
// 3. state 参数忽略——随口记不需要 ScheduleState已注册到 scheduleFreeTools。
func NewQuickNoteToolHandler(deps QuickNoteDeps) 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. 提取必填参数 title。
title := ""
if t, ok := args["title"].(string); ok {
title = strings.TrimSpace(t)
}
if title == "" {
return "工具调用失败:缺少必填参数 title任务标题。"
}
// 3. 提取可选参数 deadline_at复用旧链路时间解析能力。
var deadline *time.Time
if raw, ok := args["deadline_at"].(string); ok {
raw = strings.TrimSpace(raw)
if raw != "" {
// 调用目的:复用旧链路成熟的中文相对时间解析器,支持"明天下午3点"等格式。
parsed, err := agentnode.ParseOptionalDeadline(raw)
if err != nil {
return fmt.Sprintf("工具调用失败:截止时间格式无法解析(%s。支持格式2026-04-20 18:00、明天下午3点、下周一上午9点。", err)
}
deadline = parsed
}
}
// 4. 提取可选参数 priority_group未提供时按截止时间自动推断。
priorityGroup := 0
if pg, ok := args["priority_group"].(float64); ok {
priorityGroup = int(pg)
}
if !agentmodel.IsValidTaskPriority(priorityGroup) {
priorityGroup = quickNoteFallbackPriority(deadline)
}
// 5. 调用依赖写库。
taskID, err := deps.CreateTask(userID, title, priorityGroup, deadline)
if err != nil {
return fmt.Sprintf("工具调用失败:写入任务时出错(%s。", err)
}
if taskID <= 0 {
return "工具调用失败:写入任务后未返回有效 task_id。"
}
// 6. 组装结构化返回,包含 banter 提示引导 LLM 自然生成调侃。
priorityLabel := agentmodel.PriorityLabelCN(priorityGroup)
deadlineStr := ""
if deadline != nil {
deadlineStr = deadline.In(agentnode.QuickNoteLocation()).Format("2006-01-02 15:04")
}
result := QuickNoteCreateResult{
TaskID: taskID,
Title: title,
PriorityLabel: priorityLabel,
DeadlineAt: deadlineStr,
}
// 6.1 成功事实 + banter 提示:通过工具返回值引导 ReAct LLM 在 speak 中自然加入轻松跟进。
if deadlineStr != "" {
result.Message = fmt.Sprintf("已记录:%s%s截止 %s。回复时请用轻松友好的语气加一句与任务内容相关的俏皮话不超过30字。",
title, priorityLabel, deadlineStr)
} else {
result.Message = fmt.Sprintf("已记录:%s%s。回复时请用轻松友好的语气加一句与任务内容相关的俏皮话不超过30字。",
title, priorityLabel)
}
jsonBytes, marshalErr := json.Marshal(result)
if marshalErr != nil {
// 6.2 JSON 序列化失败时降级为纯文本,确保 LLM 仍能拿到关键信息。
return result.Message
}
return string(jsonBytes)
}
}