Files
smartmate/backend/newAgent/router/chat_route.go
LoveLosita 8daae62812 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
2026-04-24 14:02:27 +08:00

165 lines
5.5 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 newagentrouter
import (
"fmt"
"regexp"
"strings"
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
)
var (
// chatRouteHeaderRegex 从模型流式输出中解析 SMARTFLOW_ROUTE 控制码头部。
//
// 格式示例:
// <SMARTFLOW_ROUTE nonce="abc" route="execute" rough_build="true" refine="false" reorder="false" thinking="true"/>
//
// 属性说明:
// 1. nonce防注入校验必须与调用方传入的 nonce 精确匹配;
// 2. route路由目标direct_reply / execute / deep_answer / plan
// 3. rough_build可选仅 route=execute 时有效,默认 false
// 4. refine可选仅 rough_build=true 时有效,默认 false
// 5. reorder可选仅 route=execute 时有效,默认 false
// 6. thinking可选仅 route=execute 时有效,默认 false。
chatRouteHeaderRegex = regexp.MustCompile(
`(?is)<\s*SMARTFLOW_ROUTE\b` +
`[^>]*\bnonce\s*=\s*["']?([a-zA-Z0-9\-]+)["']?` +
`[^>]*\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)["']?)?` +
`(?:[^>]*\bthinking\s*=\s*["']?(true|false)["']?)?` +
`[^>]*/\s*>`)
)
// StreamRouteParser 从 LLM 流式输出中增量提取路由决策。
//
// 协议约定:模型输出以 SMARTFLOW_ROUTE 控制码标签开头,标签结束后是用户可见内容。
// 例如:<SMARTFLOW_ROUTE nonce="abc" route="direct_reply"/>你好!很高兴见到你...
//
// 职责边界:
// 1. 只负责从流式 chunk 中提取控制码并解析为 ChatRoutingDecision
// 2. 不负责推送 SSE chunk不负责决定后续走哪条链路
// 3. 控制码解析失败时标记 fallback由上层决定降级策略。
type StreamRouteParser struct {
buf strings.Builder
nonce string
routeFound bool
decision *newagentmodel.ChatRoutingDecision
}
// NewStreamRouteParser 创建流式路由解析器。
func NewStreamRouteParser(nonce string) *StreamRouteParser {
return &StreamRouteParser{
nonce: strings.ToLower(strings.TrimSpace(nonce)),
}
}
// Feed 写入一段 chunk content。
//
// 返回值:
// - visible控制码标签之后的内容用户可见文本
// - routeReady路由决策是否已确定
// - err解析错误。
//
// 调用方应在 routeReady=true 后调用 Decision() 获取路由决策,
// 并根据 route 进入对应分支处理 visible 及后续 chunk。
func (p *StreamRouteParser) Feed(content string) (visible string, routeReady bool, err error) {
if p.routeFound {
// 路由已解析,后续 chunk 直接透传。
return content, true, nil
}
p.buf.WriteString(content)
text := p.buf.String()
match := chatRouteHeaderRegex.FindStringSubmatchIndex(text)
if match == nil {
// 控制码尚未完整,检查是否应该 fallback。
if len(text) > 500 {
// 超过 500 字符仍未匹配到控制码 -> fallback 到 plan。
p.routeFound = true
p.decision = &newagentmodel.ChatRoutingDecision{
Route: newagentmodel.ChatRoutePlan,
Raw: text,
}
return text, true, fmt.Errorf("控制码解析超时fallback 到 plan")
}
return "", false, nil
}
// 提取匹配到的子组。
groups := chatRouteHeaderRegex.FindStringSubmatch(text)
if len(groups) < 3 {
return "", false, fmt.Errorf("控制码正则子组不足: %d", len(groups))
}
// nonce 校验。
parsedNonce := strings.ToLower(strings.TrimSpace(groups[1]))
if parsedNonce != p.nonce {
return "", false, fmt.Errorf("nonce 不匹配: got=%s expected=%s", parsedNonce, p.nonce)
}
// 解析 route。
route := newagentmodel.ChatRoute(strings.TrimSpace(groups[2]))
// 解析可选布尔属性(默认 false
roughBuild := parseOptionalBool(groups, 3)
refine := parseOptionalBool(groups, 4)
reorder := parseOptionalBool(groups, 5)
thinking := parseOptionalBool(groups, 6)
p.decision = &newagentmodel.ChatRoutingDecision{
Route: route,
NeedsRoughBuild: roughBuild,
NeedsRefineAfterRoughBuild: refine,
AllowReorder: reorder,
Thinking: thinking,
Raw: groups[0],
}
// 归一化与校验。
if validateErr := p.decision.Validate(); validateErr != nil {
// 校验失败 -> fallback 到 plan。
p.decision.Route = newagentmodel.ChatRoutePlan
p.decision.NeedsRoughBuild = false
p.decision.NeedsRefineAfterRoughBuild = false
p.decision.AllowReorder = false
p.decision.Thinking = false
}
p.routeFound = true
// 控制码标签之后的文本作为 visible 返回。
fullMatch := groups[0]
tagEndIdx := strings.Index(text, fullMatch)
if tagEndIdx >= 0 {
afterTag := text[tagEndIdx+len(fullMatch):]
// 去掉标签后紧跟的换行符(如果有)。
afterTag = strings.TrimPrefix(afterTag, "\r\n")
afterTag = strings.TrimPrefix(afterTag, "\n")
return afterTag, true, nil
}
return "", true, nil
}
// RouteReady 返回路由决策是否已确定。
func (p *StreamRouteParser) RouteReady() bool {
return p.routeFound
}
// Decision 返回已解析的路由决策RouteReady=true 后可用)。
func (p *StreamRouteParser) Decision() *newagentmodel.ChatRoutingDecision {
return p.decision
}
// parseOptionalBool 从正则子组中解析可选布尔值。
// 如果子组不存在或为空,返回 false。
func parseOptionalBool(groups []string, index int) bool {
if index >= len(groups) {
return false
}
return strings.TrimSpace(groups[index]) == "true"
}