Version: 0.6.5.dev.260316

 feat(agent): 通用分流接入随口问图编排,修复任务查询条数与重复输出问题

- ♻️ 将 Agent 路由升级为通用 `action` 分流机制,统一支持 `chat` / `quick_note_create` / `task_query`
- 🧩 新增 `taskquery` 子模块并落地图编排链路:`plan -> quadrant -> time_anchor -> tool_query -> reflect`
- 🔧 在图内接入 `query_tasks` 工具调用,支持自动放宽检索条件与反思重试,最多重试 2 次
- 🚪 保持 `/agent/chat` 作为多合一入口,不额外新增任务查询 HTTP 接口
- 🪄 修复“随口问”场景下的双重列表输出问题:LLM 仅保留简短前缀,任务列表统一由后端进行确定性渲染
- 🎯 修复显式数量约束失效问题:支持提取“来一个”“前 3 个”“top5”等数量表达,并将其锁定为 `limit`
- 🛡️ 防止在重试或放宽检索阶段改写用户显式指定的数量约束
-  补充并更新测试,覆盖路由解析、数量提取、`limit` 生效及重复输出等关键场景

📝 docs: 更新随口问链路文档与决策记录

- 📚 更新 README 5.4,新增/修订随口问链路 Mermaid 图
- 🧭 新增随口问功能决策记录 FDR
This commit is contained in:
Losita
2026-03-16 22:30:45 +08:00
parent 84371e2ff8
commit 09dca9f772
16 changed files with 2371 additions and 105 deletions

View File

@@ -0,0 +1,88 @@
package taskquery
import "time"
const (
// DefaultTaskQueryLimit 是任务查询默认返回条数。
DefaultTaskQueryLimit = 5
// MaxTaskQueryLimit 是任务查询最大返回条数。
MaxTaskQueryLimit = 20
// DefaultReflectRetryMax 是反思重试默认上限。
DefaultReflectRetryMax = 2
)
// TaskQueryState 是任务查询图在节点间传递的统一状态容器。
//
// 职责边界:
// 1. 保存“规划参数、查询结果、反思决策、最终回复”;
// 2. 控制“是否重试 + 已重试次数”状态机;
// 3. 不负责真正查库,查库由工具执行。
type TaskQueryState struct {
// 请求上下文
UserMessage string
RequestNowText string
// 规划结果
UserGoal string
Plan QueryPlan
// ExplicitLimit 表示“用户原话中明确指定的数量”。
//
// 语义说明:
// 1. 0 代表未显式指定;
// 2. >0 时应锁定该数量,不允许反思补丁或自动放宽改写。
ExplicitLimit int
// 上一轮查询结果
LastQueryItems []TaskQueryToolRecord
LastQueryTotal int
// 自动放宽状态
AutoBroadenApplied bool
// 反思状态
RetryCount int
MaxReflectRetry int
NeedRetry bool
ReflectReason string
// 最终输出
FinalReply string
}
// QueryPlan 是“任务查询计划”的统一结构。
//
// 语义说明:
// 1. Quadrants 为空表示“查全部象限”;非空表示“只查这些象限”;
// 2. DeadlineBefore/AfterText 保留原始文本,方便日志和反思 prompt
// 3. DeadlineBefore/After 是解析后的时间对象,供工具调用使用。
type QueryPlan struct {
Quadrants []int
SortBy string
Order string
Limit int
IncludeCompleted bool
Keyword string
DeadlineBeforeText string
DeadlineAfterText string
DeadlineBefore *time.Time
DeadlineAfter *time.Time
}
// NewTaskQueryState 创建任务查询初始状态。
func NewTaskQueryState(userMessage, requestNowText string, maxReflectRetry int) *TaskQueryState {
if maxReflectRetry <= 0 {
maxReflectRetry = DefaultReflectRetryMax
}
return &TaskQueryState{
UserMessage: userMessage,
RequestNowText: requestNowText,
MaxReflectRetry: maxReflectRetry,
LastQueryItems: make([]TaskQueryToolRecord, 0),
NeedRetry: false,
ReflectReason: "",
AutoBroadenApplied: false,
}
}