Files
Cyrene_v2/internal/persona/loader/awakening.go
T
AskaEth 71fefe5fc3
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(persona,capacity): persona awakening 档位真生效 + 当前主角色已填(裁决 14.4.3)
- loader 新增可选 awakening 块(default_state/overrides{min_confidence,force_state}),载入期校验 + 归一化,
  非法值显式报错(拒绝「写了却静默无效」);缺失 = nil = 无该档位,不补默认
- 组合根把当前主角色档案的档位填进 capacity.Config.Awakening(未知档位名告警后按无档位处理),
  真的参与 Decide → applyAwakening(MinConfidence 优先于全局 0.6;force_state 钉死态)
- 当前主角色 cyrene.yaml 填 default_state=静息 + min_confidence=0.7(设计 §3 陪伴型取静息;
  0.7 压掉 L2 趋势(0.66)误唤醒、仍低于 L1 强信号(0.9),温柔陪伴不误打扰)
- 复核:build/vet 绿 + persona/capacity/cmd 包测试绿;端到端 4 例锁定「同回合有无档位判定相反」;
  变异两轮(yaml tag 改错 → 8 处红;解析但不接线 → 端到端红)均已还原
- 已知盲区(另派):per-role 注入未接线(loop/request.go 的 Input.Awakening);
  面板整块替换 persona 会抹掉 awakening 块(需 applyAwakeningEdit)
2026-10-04 12:49:40 +08:00

117 lines
5.0 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 loader
import (
"errors"
"fmt"
"math"
"strings"
)
// 唤醒档位(design/14 §3,裁决 14.4.3)的规范态名,与能力决策层
// capacity.State 的三个态同名。persona YAML 里既可按设计原文写中文档位名
// (静息/唤醒/全能),也可写这里的规范态名;载入期一律归一化为规范态名,
// 组合根据此直接映射到 capacity.Awakening,不做二次猜测。
const (
// AwakeningStateQuiet 是静息档(静·陪伴):保守落点,窄工具面。
AwakeningStateQuiet = "quiet"
// AwakeningStateAwake 是唤醒档(醒·办事):识别到真实任务时唤醒工具。
AwakeningStateAwake = "awake"
// AwakeningStateFull 是全能档(全·复杂任务):完整能力窗口。
AwakeningStateFull = "full"
)
// awakeningStateLabels 是设计文档写法的中文档位名到规范态名的映射。
func awakeningStateLabels() map[string]string {
return map[string]string{
"静息": AwakeningStateQuiet,
"唤醒": AwakeningStateAwake,
"全能": AwakeningStateFull,
}
}
// ErrInvalidAwakening 由 awakening 块的校验错误包裹,使调用方能识别
// 「块写了、但档位名或阈值不合法」,而不是被静默忽略
// (design/01 §2.1 红线:不允许静默无效的配置)。
var ErrInvalidAwakening = errors.New("persona: invalid awakening config")
// AwakeningParams 是人格可选的 `awakening` 块(design/14 §3):人格倾向
// (default_state)加上对意图感知结果的覆盖规则(overrides)。它只描述
// 「唤醒倾向」这一档位,**不构成能力边界**——能力始终全局全能。
//
// 语义:**块缺席 = 无覆盖**,不是配置错误(裁决 14.4.3「缺席就是没有」)。
// 因此整块与子块都用指针:nil 明确表示「未声明」,本层**绝不**补任何默认档位,
// 没写的角色就由决策层走全局默认(静息倾向 + 全局阈值)。
type AwakeningParams struct {
// DefaultState 是人格倾向落点(default_state):意图各层无定论或低置信时
// 落到该态。空串 = 未声明该档位。
DefaultState string `yaml:"default_state,omitempty"`
// Overrides 是覆盖规则(overrides);整块缺席时为 nil。
Overrides *AwakeningOverrides `yaml:"overrides,omitempty"`
}
// AwakeningOverrides 是 `awakening.overrides` 块的字段(design/14 §3)。
type AwakeningOverrides struct {
// MinConfidence 是人格级唤醒阈值(min_confidence):正值优先于全局
// CAPACITY_MIN_CONFIDENCE;低于阈值强制回落 default_state(保守、不打扰)。
// 零值 = 未声明(沿用全局阈值)。
MinConfidence float64 `yaml:"min_confidence,omitempty"`
// ForceState 设定即无条件钉死状态(force_state),无视意图感知输出。
// 空串 = 未声明。
ForceState string `yaml:"force_state,omitempty"`
}
// ValidateAwakening 校验并**就地归一化**该人格的 awakening 块。非法档位名或
// 越界阈值返回 %w 包裹的 ErrInvalidAwakening(明确报错,绝不静默忽略);
// 块缺席(nil)返回 nil——缺席就是没有该档位,不是错误。归一化后
// DefaultState / Overrides.ForceState 一律是 AwakeningStateQuiet/Awake/Full,
// 组合根可直接映射成决策层的态。
func (p *Persona) ValidateAwakening() error {
if p == nil || p.Awakening == nil {
return nil
}
state, err := normalizeAwakeningState(p.Awakening.DefaultState, "default_state")
if err != nil {
return err
}
p.Awakening.DefaultState = state
if p.Awakening.Overrides == nil {
return nil
}
force, err := normalizeAwakeningState(p.Awakening.Overrides.ForceState, "overrides.force_state")
if err != nil {
return err
}
p.Awakening.Overrides.ForceState = force
if err := validateAwakeningConfidence(p.Awakening.Overrides.MinConfidence); err != nil {
return err
}
return nil
}
// normalizeAwakeningState 把档位字面量归一化为规范态名。空串(未声明)原样返回;
// 非法值返回 %w 包裹的 ErrInvalidAwakening,错误信息点名出错字段与可选值。
func normalizeAwakeningState(raw, field string) (string, error) {
trimmed := strings.TrimSpace(raw)
switch strings.ToLower(trimmed) {
case "":
return "", nil
case AwakeningStateQuiet, AwakeningStateAwake, AwakeningStateFull:
return strings.ToLower(trimmed), nil
}
if canonical, ok := awakeningStateLabels()[trimmed]; ok {
return canonical, nil
}
return "", fmt.Errorf("%w: %s %q (want %s|%s|%s 或 静息|唤醒|全能)",
ErrInvalidAwakening, field, raw, AwakeningStateQuiet, AwakeningStateAwake, AwakeningStateFull)
}
// validateAwakeningConfidence 校验人格级阈值:它是一次判定的置信度,取值域为
// [0,1];负数、超过 1 或 NaN 都判为非法——越界阈值会让「是否唤醒」变成无意义的
// 恒定值,宁可显式报错也不静默生效。
func validateAwakeningConfidence(v float64) error {
if math.IsNaN(v) || v < 0 || v > 1 {
return fmt.Errorf("%w: overrides.min_confidence %v (want 0..1)", ErrInvalidAwakening, v)
}
return nil
}