This commit is contained in:
lpf
2026-02-16 20:57:00 +08:00
parent 29c3454802
commit 8fe823f030
2 changed files with 93 additions and 14 deletions

View File

@@ -59,6 +59,7 @@ type autonomySession struct {
lastUserAt time.Time lastUserAt time.Time
lastNudgeAt time.Time lastNudgeAt time.Time
pending bool pending bool
focus string
} }
type AgentLoop struct { type AgentLoop struct {
@@ -96,6 +97,7 @@ type autoLearnIntent struct {
type autonomyIntent struct { type autonomyIntent struct {
action string action string
idleInterval *time.Duration idleInterval *time.Duration
focus string
} }
type stageReporter struct { type stageReporter struct {
@@ -412,7 +414,7 @@ func (al *AgentLoop) stopAllAutonomySessions() {
} }
} }
func (al *AgentLoop) startAutonomy(msg bus.InboundMessage, idleInterval time.Duration) string { func (al *AgentLoop) startAutonomy(msg bus.InboundMessage, idleInterval time.Duration, focus string) string {
if msg.Channel == "cli" { if msg.Channel == "cli" {
return "自主模式需要在 gateway 运行模式下使用(持续消息循环)。" return "自主模式需要在 gateway 运行模式下使用(持续消息循环)。"
} }
@@ -438,12 +440,30 @@ func (al *AgentLoop) startAutonomy(msg bus.InboundMessage, idleInterval time.Dur
started: time.Now(), started: time.Now(),
idleInterval: idleInterval, idleInterval: idleInterval,
lastUserAt: time.Now(), lastUserAt: time.Now(),
focus: strings.TrimSpace(focus),
} }
al.autonomyBySess[msg.SessionKey] = s al.autonomyBySess[msg.SessionKey] = s
al.autonomyMu.Unlock() al.autonomyMu.Unlock()
go al.runAutonomyLoop(sessionCtx, msg) go al.runAutonomyLoop(sessionCtx, msg)
return fmt.Sprintf("自主模式已开启:自动拆解执行 + 阶段汇报;空闲超过 %s 会主动推进并汇报。", idleInterval.Truncate(time.Second)) if s.focus != "" {
al.bus.PublishInbound(bus.InboundMessage{
Channel: msg.Channel,
SenderID: "autonomy",
ChatID: msg.ChatID,
SessionKey: msg.SessionKey,
Content: buildAutonomyFocusPrompt(s.focus),
Metadata: map[string]string{
"source": "autonomy",
"round": "0",
"mode": "focus_bootstrap",
},
})
}
if s.focus != "" {
return fmt.Sprintf("自主模式已开启,当前研究方向:%s。空闲超过 %s 会继续按该方向主动推进并汇报。", s.focus, idleInterval.Truncate(time.Second))
}
return fmt.Sprintf("自主模式已开启:自动拆解执行 + 阶段回报;空闲超过 %s 会主动推进并汇报。", idleInterval.Truncate(time.Second))
} }
func (al *AgentLoop) stopAutonomy(sessionKey string) bool { func (al *AgentLoop) stopAutonomy(sessionKey string) bool {
@@ -493,12 +513,16 @@ func (al *AgentLoop) autonomyStatus(sessionKey string) string {
uptime := time.Since(s.started).Truncate(time.Second) uptime := time.Since(s.started).Truncate(time.Second)
idle := time.Since(s.lastUserAt).Truncate(time.Second) idle := time.Since(s.lastUserAt).Truncate(time.Second)
focus := strings.TrimSpace(s.focus)
if focus == "" {
focus = "未设置"
}
return fmt.Sprintf("自主模式运行中:空闲阈值 %s已运行 %s最近用户活跃距今 %s自动推进 %d 轮。", return fmt.Sprintf("自主模式运行中:空闲阈值 %s已运行 %s最近用户活跃距今 %s自动推进 %d 轮。",
s.idleInterval.Truncate(time.Second), s.idleInterval.Truncate(time.Second),
uptime, uptime,
idle, idle,
s.rounds, s.rounds,
) ) + fmt.Sprintf(" 当前研究方向:%s。", focus)
} }
func (al *AgentLoop) runAutonomyLoop(ctx context.Context, msg bus.InboundMessage) { func (al *AgentLoop) runAutonomyLoop(ctx context.Context, msg bus.InboundMessage) {
@@ -535,6 +559,7 @@ func (al *AgentLoop) maybeRunAutonomyRound(msg bus.InboundMessage) bool {
round := s.rounds round := s.rounds
s.lastNudgeAt = now s.lastNudgeAt = now
s.pending = true s.pending = true
focus := strings.TrimSpace(s.focus)
idleFor := now.Sub(s.lastUserAt).Truncate(time.Second) idleFor := now.Sub(s.lastUserAt).Truncate(time.Second)
al.autonomyMu.Unlock() al.autonomyMu.Unlock()
@@ -549,7 +574,7 @@ func (al *AgentLoop) maybeRunAutonomyRound(msg bus.InboundMessage) bool {
SenderID: "autonomy", SenderID: "autonomy",
ChatID: msg.ChatID, ChatID: msg.ChatID,
SessionKey: msg.SessionKey, SessionKey: msg.SessionKey,
Content: buildAutonomyFollowUpPrompt(round), Content: buildAutonomyFollowUpPrompt(round, focus),
Metadata: map[string]string{ Metadata: map[string]string{
"source": "autonomy", "source": "autonomy",
"round": strconv.Itoa(round), "round": strconv.Itoa(round),
@@ -567,8 +592,17 @@ func (al *AgentLoop) finishAutonomyRound(sessionKey string) {
} }
} }
func buildAutonomyFollowUpPrompt(round int) string { func buildAutonomyFollowUpPrompt(round int, focus string) string {
return fmt.Sprintf("自主模式第 %d 轮推进:用户暂时未继续输入。请基于当前会话上下文和已完成工作,自主完成一个高价值下一步,并给出简短进展汇报。", round) focus = strings.TrimSpace(focus)
if focus == "" {
return fmt.Sprintf("自主模式第 %d 轮推进:用户暂时未继续输入。请基于当前会话上下文和已完成工作,自主完成一个高价值下一步,并给出简短进展汇报。", round)
}
return fmt.Sprintf("自主模式第 %d 轮推进:用户暂时未继续输入。请围绕研究方向“%s”继续推进高价值下一步并给出简短进展汇报。", round, focus)
}
func buildAutonomyFocusPrompt(focus string) string {
focus = strings.TrimSpace(focus)
return fmt.Sprintf("自主模式已启动,本轮请优先围绕研究方向“%s”展开先明确本轮目标再执行并汇报阶段性进展与结果。", focus)
} }
func (al *AgentLoop) startAutoLearner(msg bus.InboundMessage, interval time.Duration) string { func (al *AgentLoop) startAutoLearner(msg bus.InboundMessage, interval time.Duration) string {
@@ -781,7 +815,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
if intent.idleInterval != nil { if intent.idleInterval != nil {
idle = *intent.idleInterval idle = *intent.idleInterval
} }
return al.startAutonomy(msg, idle), nil return al.startAutonomy(msg, idle, intent.focus), nil
case "stop": case "stop":
if al.stopAutonomy(msg.SessionKey) { if al.stopAutonomy(msg.SessionKey) {
return "自主模式已关闭。", nil return "自主模式已关闭。", nil
@@ -984,7 +1018,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content)) al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content))
// 如果 finalContent 中没有包含 tool calls (即最后一次 LLM 返回的结果) // 如果 finalContent 中没有包含 tool calls (即最后一次 LLM 返回的结果)
// 我们已经通过循环内部 AddMessageFull 存储了前面的步骤 // 我们已经通过循环内部 AddMessageFull 存储了前面的步骤
// 这里的 AddMessageFull 会存储最终回复 // 这里的 AddMessageFull 会存储最终回复
al.sessions.AddMessageFull(sessionKey, providers.Message{ al.sessions.AddMessageFull(sessionKey, providers.Message{
Role: "assistant", Role: "assistant",
@@ -1787,10 +1821,40 @@ func parseAutonomyIntent(content string) (autonomyIntent, bool) {
return autonomyIntent{}, false return autonomyIntent{}, false
} }
focus := extractAutonomyFocus(text)
if d, ok := extractChineseAutoLearnInterval(text); ok { if d, ok := extractChineseAutoLearnInterval(text); ok {
return autonomyIntent{action: "start", idleInterval: &d}, true return autonomyIntent{action: "start", idleInterval: &d, focus: focus}, true
} }
return autonomyIntent{action: "start"}, true return autonomyIntent{action: "start", focus: focus}, true
}
func extractAutonomyFocus(text string) string {
text = strings.TrimSpace(text)
if text == "" {
return ""
}
patterns := []string{
"研究方向是",
"研究方向:",
"研究方向:",
"方向是",
"方向:",
"方向:",
"聚焦在",
"重点研究",
}
for _, marker := range patterns {
if idx := strings.Index(text, marker); idx >= 0 {
focus := strings.TrimSpace(text[idx+len(marker):])
focus = strings.Trim(focus, ",。; ")
if cut := findFirstIndex(focus, "并", "然后", "每", "空闲", "主动", "汇报", "。"); cut > 0 {
focus = strings.TrimSpace(focus[:cut])
}
return strings.Trim(focus, ",。; ")
}
}
return ""
} }
func parseAutoLearnIntent(content string) (autoLearnIntent, bool) { func parseAutoLearnIntent(content string) (autoLearnIntent, bool) {
@@ -1909,14 +1973,19 @@ func (al *AgentLoop) handleSlashCommand(msg bus.InboundMessage) (bool, string, e
switch strings.ToLower(fields[1]) { switch strings.ToLower(fields[1]) {
case "start": case "start":
idle := autonomyDefaultIdleInterval idle := autonomyDefaultIdleInterval
focus := ""
if len(fields) >= 3 { if len(fields) >= 3 {
d, err := parseAutonomyIdleInterval(fields[2]) d, err := parseAutonomyIdleInterval(fields[2])
if err != nil { if err != nil {
return true, "", err focus = strings.Join(fields[2:], " ")
} else {
idle = d
} }
idle = d
} }
return true, al.startAutonomy(msg, idle), nil if focus == "" && len(fields) >= 4 {
focus = strings.Join(fields[3:], " ")
}
return true, al.startAutonomy(msg, idle, focus), nil
case "stop": case "stop":
if al.stopAutonomy(msg.SessionKey) { if al.stopAutonomy(msg.SessionKey) {
return true, "自主模式已关闭。", nil return true, "自主模式已关闭。", nil

View File

@@ -93,7 +93,7 @@ func TestParseAutoLearnIntent_StatusNaturalLanguage(t *testing.T) {
} }
func TestParseAutonomyIntent_StartNaturalLanguage(t *testing.T) { func TestParseAutonomyIntent_StartNaturalLanguage(t *testing.T) {
intent, ok := parseAutonomyIntent("以后你自动拆解并自动执行任务每15分钟主动找我汇报一次") intent, ok := parseAutonomyIntent("以后你自动拆解并自动执行任务每15分钟主动找我汇报一次,研究方向是日志异常聚类")
if !ok { if !ok {
t.Fatalf("expected intent") t.Fatalf("expected intent")
} }
@@ -103,6 +103,9 @@ func TestParseAutonomyIntent_StartNaturalLanguage(t *testing.T) {
if intent.idleInterval == nil || *intent.idleInterval != 15*time.Minute { if intent.idleInterval == nil || *intent.idleInterval != 15*time.Minute {
t.Fatalf("unexpected interval: %v", intent.idleInterval) t.Fatalf("unexpected interval: %v", intent.idleInterval)
} }
if intent.focus != "日志异常聚类" {
t.Fatalf("unexpected focus: %q", intent.focus)
}
} }
func TestParseAutonomyIntent_StopNaturalLanguage(t *testing.T) { func TestParseAutonomyIntent_StopNaturalLanguage(t *testing.T) {
@@ -140,3 +143,10 @@ func TestParseAutonomyIntent_NoFalsePositiveOnSingleTask(t *testing.T) {
t.Fatalf("expected no intent, got: %+v", intent) t.Fatalf("expected no intent, got: %+v", intent)
} }
} }
func TestExtractAutonomyFocus_EmptyWhenNotProvided(t *testing.T) {
focus := extractAutonomyFocus("开启自主模式每30分钟主动汇报")
if focus != "" {
t.Fatalf("expected empty focus, got: %q", focus)
}
}