Files
Cyrene_v2/internal/services/backthink/thinker.go
T
AskaEth 321cc3b763
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(backthink): 思考轮工具面补齐(裁决 15)—— 能力面接入 + 只读 capability_list + 本轮状态快照
- 缺口 d:状态在**思考前**读一次,同一份快照既喂思考轮又喂 Decide(读不到 = Known=false,绝不伪装成中性状态)
- 缺口 c:新增只读工具 capability_list(owner-only、三重输出边界、读写隔离),她可自查当下真实可用面
- 缺口 b:内置允许集补 transcode / capability_list(此前 CAPACITY_ENABLED=true 时新工具会从模型可见面消失)
- 缺口 e:guidancePrompt 旧文案承诺的「IoT/日历/提醒」在全仓根本没有对应工具(chat 侧也没有)⇒ 按诚实化改为「与主人对话同等工具面 + 指名自查入口」,不再有空头支票
- (a) 未做且**不自创**:那三把工具无任何设计规格(名字/schema/读写边界/输出上限)⇒ 已报回并登记进 design/20(裁决:走能力/插件/MCP 暴露路线)
- 变异实测:允许集去掉新工具 ⇒ TestDecide_AllowedSets + 组合根跨层测试红;丢掉状态快照 ⇒ TestLoopThinker_Think_InjectsRoundStateBlock 红
- 已知弱项(如实登记):组合根那行 wireCapabilitySurface() 调用本身无测试覆盖(与既有 wireTranscode 同惯例)
2026-10-05 13:54:57 +08:00

53 lines
2.2 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 backthink
import (
"context"
"fmt"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
)
// LoopThinker 把 *loop.Loop 适配为 Thinker。它是生产接线,
// 让一次自主后台轮驱动完整的工具循环(记忆
// 注入、模型步骤、工具调用),同时看到主会话的最近对话并在 owner 环境下运行。
//
// 它包的就是**主人对话所用的那一个 loop**(组合根传 a.loop),因此「与 chat 同一套
// 工具注册表」不是承诺而是结构事实:两侧共用同一个 dispatcher/registry。
type LoopThinker struct {
loop *loop.Loop
recent recentProvider
}
// NewLoopThinker 把 l 包装成基于给定最近消息提供者的 Thinker
// (生产中是 *history.Store;nil 会让思考降级为仅用引导提示词,
// 见 mainContext)。
func NewLoopThinker(l *loop.Loop, recent recentProvider) *LoopThinker {
return &LoopThinker{loop: l, recent: recent}
}
// Think 经被包装的 loop 跑一轮无人值守思考并返回
// 结论文本。它继承主会话的最近对话作为输入
// 上下文,并注入 owner 会话环境(MemWritable=true、ActorOwner、
// 完整工具集),因此该次思考能读写记忆并调用工具。loop 的
// Run 会话 id 是本服务自己的(不是主会话 id),所以该次思考绝不会
// 与活跃主会话的持久化检查点存储冲突(design/20)。
//
// 本轮的用户状态快照由服务经 ctx 挂上(WithRoundState,X1 缺口 d):
// 读到时它被渲染成引导提示词末尾的状态块,与随后的打扰决策同一份;
// 未挂载时输入逐字与接线前一致。未注入 loop 时返回 ErrNoLoop。
func (t *LoopThinker) Think(ctx context.Context, sessionID, trigger string) (string, error) {
if t.loop == nil {
return "", ErrNoLoop
}
recent := mainContext(ctx, t.recent)
messages := thinkInput(recent, trigger, RoundStateFrom(ctx))
envCtx := loop.WithSessionEnv(ctx, loop.OwnerEnv())
// 静音 UI 广播:后台反思不得把其中间
// 推理泄漏进面向用户的 chat 流(它只需要结论)。
res, err := t.loop.Run(loop.WithMutedBroadcast(envCtx), sessionID, messages)
if err != nil {
return "", fmt.Errorf("backthink: think: %w", err)
}
return res.Text, nil
}