【静默广播修复(bug:思维链露出 backthink 推理)】 - 根因:LoopThinker.Think 复用同一 loop.Loop 且 loop 全局带 OnStream,backthink 后台思考时 把 [background autonomous reflection] 的 thinking/流式事件广播到前端,被当聊天思维链显示。 - loop/mute_ctx.go:WithMutedBroadcast(ctx) + isMutedBroadcast(ctx)——context 上静默标志。 - loop 四处广播抑制(stream.go 流式 / step.go 静默走非流式 agentStep / request.go capacity 事件 / tools.go tool_execution),静默时跳过 UI 广播,推理照常返回结论。 - thinker.go LoopThinker.Think 用 loop.WithMutedBroadcast(ctx),backthink 不再泄 thinking。 【XML 动作/旁白结构化解析】 - cyrene.yaml systemPrompt 加「表达方式:动作与旁白」——动作 <action>…</action>、 旁白 <narration>…</narration>;克制≤1-2个/段、贴合唤醒态、重要信息不加、朗读正文忽略标签。 - 前端 parseContent.ts:ContentPart[](text/action/narration),非贪婪+反向引用、跨行、非法/ 未闭合兜底纯文本不崩;summarizeContent 供思维导图/状态。 - 前端 renderContent.tsx:action/narration → 灰斜体(mu-text-lo italic)句中原位渲染。 - 应用到 chat.tsx UserBubble/AssistantBubble + messages 各 mode body。 go build/vet/test(loop/backthink)绿 + 前端 tsc/vite 绿。
48 lines
1.8 KiB
Go
48 lines
1.8 KiB
Go
package backthink
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
|
)
|
|
|
|
// LoopThinker adapts a *loop.Loop into a Thinker. It is the production wiring
|
|
// that lets a background pass drive the full tool loop (memory injection, model
|
|
// steps, tool calls such as IoT/calendar/reminders) without the service knowing
|
|
// the loop's concrete shape.
|
|
type LoopThinker struct {
|
|
loop *loop.Loop
|
|
}
|
|
|
|
// NewLoopThinker wraps l as a Thinker.
|
|
func NewLoopThinker(l *loop.Loop) *LoopThinker { return &LoopThinker{loop: l} }
|
|
|
|
// Think runs one unattended think through the wrapped loop and returns the
|
|
// conclusion text. It returns ErrNoLoop when no loop was injected.
|
|
func (t *LoopThinker) Think(ctx context.Context, sessionID, trigger string) (string, error) {
|
|
if t.loop == nil {
|
|
return "", ErrNoLoop
|
|
}
|
|
messages := []model.Message{model.TextMessage(model.RoleUser, backgroundPrompt(trigger))}
|
|
// Mute UI broadcast: background reflection must not leak its intermediate
|
|
// reasoning into the user-facing chat stream (it only needs the conclusion).
|
|
res, err := t.loop.Run(loop.WithMutedBroadcast(ctx), sessionID, messages)
|
|
if err != nil {
|
|
return "", fmt.Errorf("backthink: think: %w", err)
|
|
}
|
|
return res.Text, nil
|
|
}
|
|
|
|
// backgroundPrompt is the instruction handed to the runtime for one unattended
|
|
// thought. It explicitly avoids asking for a chat-style message to the user.
|
|
func backgroundPrompt(trigger string) string {
|
|
return "[background autonomous reflection]\n" +
|
|
"Trigger: " + trigger + "\n" +
|
|
"Review recent context, device states, calendar and reminders. Decide " +
|
|
"whether anything is worth proactively telling the owner. If nothing " +
|
|
"matters, conclude \"nothing to report\". Keep the conclusion to one or " +
|
|
"two sentences. Do not produce a chat-style message to the user."
|
|
}
|