Files
Cyrene_v2/internal/runtime/loop/request.go
T
AskaEth 91e8d7a029 docs(runtime): internal/runtime/loop 44 个文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;本模块按长期分批协议(一个模块一次派工)执行。
注释行数 1329 → 1329 逐行 1:1(含中文 33 → 1304);行尾注释 5 条一并译。
校验:机械校验(非注释改动行)= 空;独立 Go AST 程序(剥注释复渲)证明 44/44 文件代码全等;
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
gofmt:capacity_loop_test.go / flow.go 改动前即未格式化,按铁律保留原样未动。
2026-10-03 22:28:21 +08:00

179 lines
6.1 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 loop
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/capacity"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// buildRequest 从该 turn 的 agent 公开表面组装 model.Request,
// 镜像 agent.buildRequest,使流式路径的行为与
// RunStep 一致(前导指令消息、工具 schema、模型路由)。agent
// 与推理旋钮从 ctx 解析,因此切换了角色的会话
// 在其下一步就拿到新角色的提示词与预算(design/22 §4.2)。
func (l *Loop) buildRequest(ctx context.Context, messages []model.Message, ag *agent.Agent, knobs LoopKnobs) model.Request {
req := model.Request{
Model: ag.Model(),
Messages: assembleMessages(ag, messages),
Temperature: temperatureOf(ag),
MaxTokens: ag.MaxTokens(),
Purpose: ag.Purpose(),
Tools: l.toolSchemas(ctx, messages),
Thinking: knobs.Thinking,
ReasoningEffort: knobs.ReasoningEffort,
}
if ag.Router() != nil {
if m, _ := ag.Router().RouteRequest(req); m != "" {
req.Model = m
}
}
return req
}
// assembleMessages 在 agent 指令非空时把它们作为前导系统消息
// 前置,否则原样返回输入切片。
func assembleMessages(ag *agent.Agent, messages []model.Message) []model.Message {
instr := ag.Instructions()
if instr == "" {
return messages
}
out := make([]model.Message, 0, len(messages)+1)
out = append(out, model.TextMessage(model.RoleSystem, instr))
out = append(out, messages...)
return out
}
// temperatureOf 返回指向 agent 所配置温度的指针,把
// 零映射为 nil,使 provider 默认值生效(与 agent 包一致)。
func temperatureOf(ag *agent.Agent) *float64 {
t := ag.Temperature()
if t == 0 {
return nil
}
return &t
}
// toolSchemas 向模型广告已注册的工具 schema;未配置
// dispatcher 时为 nil。接上并启用了 capacity 判定器时,它
// 判定当前轮次并只返回该状态的允许集
// (dispatcher 之上的一个视图,见 design/14 §4):模型只看见/调用
// 允许的工具,而 Invoke 本身不被拦截。nil 或被禁用的
// 判定器保持既有行为(每个工具都被广告)。
//
// design/11 §3 安全门最后在 capacity 视图留下的结果之上运行:
// 群聊/陌生人环境中只广告其可编辑标记允许该
// 环境的工具(默认关闭),而 owner 环境保留
// 全集。安全是硬红线而非便利提示,因此它
// 只能收缩暴露面 —— 绝不重新放行被 capacity 排除的工具。
func (l *Loop) toolSchemas(ctx context.Context, messages []model.Message) []model.ToolSpec {
d := l.dispatcher()
if d == nil {
return nil
}
schemas := d.ToolSchemas()
if l.capacity == nil || !l.capacity.Enabled() {
return l.restrictSchemas(ctx, d, schemas)
}
names := make([]string, 0, len(schemas))
for _, s := range schemas {
names = append(names, s.Name)
}
acc := capState(ctx)
dec := l.capacity.Decide(ctx, capacity.Input{
Messages: capacityTexts(messages),
CurrentState: acc.state,
AllToolNames: names,
Recall: l.capacityRecall(ctx, messages),
})
acc.state = dec.State
if l.onCapacity != nil && !isMutedBroadcast(ctx) {
sessionID := loopSession(ctx)
for _, ev := range dec.Events {
ev.SessionID = sessionID
l.onCapacity(ev)
}
}
allowed := make(map[string]bool, len(dec.AllowedTools))
for _, n := range dec.AllowedTools {
allowed[n] = true
}
out := make([]model.ToolSpec, 0, len(dec.AllowedTools))
for _, s := range schemas {
if allowed[s.Name] {
out = append(out, s)
}
}
return l.restrictSchemas(ctx, d, out)
}
// capacityTexts 为 capacity 判定器提取近期对话文本:
// 最后几个非 system/非 tool 的轮次,按时间顺序(最早的在前),
// 使判定器的当前轮窗口最后看到最新的用户消息。
func capacityTexts(messages []model.Message) []string {
texts := make([]string, 0, 6)
for i := len(messages) - 1; i >= 0 && len(texts) < 6; i-- {
m := messages[i]
if m.Role == model.RoleSystem || m.Role == model.RoleTool {
continue
}
if t := messageText(m); t != "" {
texts = append(texts, t)
}
}
for i, j := 0, len(texts)-1; i < j; i, j = i+1, j-1 {
texts[i], texts[j] = texts[j], texts[i]
}
return texts
}
// capacityRecallTopK 限制 capacity L4 层每轮考虑多少条
// 召回的习惯记忆。
const capacityRecallTopK = 5
// capacityRecall 为 capacity L4 习惯对齐层(design §2.2 L4)
// 检索主人的相关长期记忆,这是让判定器学到用户任务表达
// 习惯的记忆输入。它返回 nil(使 L4 保持静默)的情形包括:
// 未配置 retriever/persona —— 向后兼容 —— 或处于受限
// (群聊 / 陌生人)上下文,依据隔离红线:L4 学习主人的
// 习惯,绝不把主人敏感记忆读进群聊或陌生人的意图
// 判定(design §2.2 红线: 不读陌生人敏感)。
func (l *Loop) capacityRecall(ctx context.Context, messages []model.Message) []capacity.RecallItem {
personaID := l.personaFor(ctx)
if l.retriever == nil || personaID == "" {
return nil
}
env := sessionEnvFrom(ctx)
if env.Restricted() {
return nil
}
results, err := l.retriever.Retrieve(ctx, personaID, queryFromMessages(messages), capacityRecallTopK)
if err != nil {
l.log.Warn("capacity recall", "error", err, "env", env.ActorEnv)
return nil
}
items := make([]capacity.RecallItem, 0, len(results))
for _, r := range results {
items = append(items, capacity.RecallItem{
Content: r.Item.Content,
Scope: string(r.Item.Scope),
Kind: string(r.Item.Kind),
Tags: r.Item.Tags,
Score: r.Score,
})
}
return items
}
// decodeToolCalls 经 agent 的共享解码器从 model.Response 解码工具
// 调用,使流式与非流式路径对适配器发出的 provider 原生
// 工具调用(Anthropic tool_use / OpenAI tool_calls)以及
// 旧式 JSON 信封约定保持一致。解码出调用时
// 报告 ok=true(切片非空)。
func decodeToolCalls(resp model.Response) ([]agent.ToolCall, bool) {
return agent.ExtractToolCalls(resp)
}