mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-19 14:48:58 +08:00
fix auto
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user