eval

package
v0.35.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

diff.go —— 差异比对:录制投影 vs 重放投影,逐行逐字段判不同。

比对的两端必须是同一个投影函数的产物(两侧都用 Replay/同一 Options), 否则比出的是「两个投影器的差异」而不是「事件的差异」。差异按 (ID, Field) 排序——确定性输出让「同一对输入永远同一份差异」,回归比对才有基线。

Package eval 轨迹的**录制 → 重放 → 差异比对**与**确定性评估判据**(机制面)。

生命周期: Experimental(v0.35.0 定级)——本包仍会改、删、拆,不承诺任何 兼容,升级风险自负;定级理由见 api-lifecycle.yaml,分级规则见 docs/api-compat.md。

它补的是哪一段(issue #111)

@@trajectory@@ 把事件流投影成轨迹行,但「给定一段录制,能否复现同一条 轨迹」「给定一条轨迹,能否给出可判定的结论」此前没有机制面:各宿主 各写一遍录制格式、各比一遍差异、各造一套「看起来跑得对不对」的口径, 于是「同一条轨迹」无从谈起。

回放为什么天然确定(锚点在哪)

轨迹行的**行 ID 是确定性生成的**(trajectory 的 turnRowID / stepRowID / toolRowID / seqID)——同一条事件流投出的行 ID 必然相同,这就是「同一条 轨迹」的锚点。行上的起止时刻取自**事件时刻**(不是墙钟),录制文件里 就带着时刻,重放不需要也不允许重新计时。因此本包的时间观只有一条: **时刻是数据,不是时钟**——录制即定格,重放即复读,没有 sleep、没有 now()。

确定性评估是什么意思

评估判据是**纯函数**(@@Assess@@):同一份轨迹行输入,永远得到同一份 判定输出。它不跑模型、不看结果像不像——「像不像」要再跑一次模型, 那不是评估,是抽签。判据回答的是结构事实:行收没收口、调用配没配对、 有没有重复声明、期望的行在不在。

边界

  • 录制格式只支持 JSON 可表达的载荷(@@Verify@@ 判适航;不适航的事件 录了也录不稳,显式报出而不是静默形变);
  • 评估判据只判**轨迹结构**,不判业务对错(业务结论归宿主的判定面);
  • 差异比对(@@Diff@@)是纯函数,排序确定——同一对输入永远同一份差异。

record.go —— 录制:事件流 → JSONL 定格;重放:定格 → 同一条轨迹。

录制格式刻意朴素:一行一个事件、标准 encoding/json——录制文件是**数据** 不是协议,朴素使它可被任何宿主直接读写。字段顺序由结构体定义固定, 载荷 map 的键序由 encoding/json 排序固定,同一事件序列永远编出同一份 字节(可 diff 的录制,不是黑盒快照)。

Index

Constants

This section is empty.

Variables

View Source
var ErrBadRecord = errors.New("eval: 录制文件不合法")

ErrBadRecord 录制文件不合法(不是合法的 JSONL 事件流)。

View Source
var ErrBadSpec = errors.New("eval: 评估清单不自洽")

ErrBadSpec 评估清单不自洽(判项不在词表内 / 期望清单缺失)。

View Source
var ErrNoChecks = errors.New("eval: 评估清单为空(「没有检查」与「检查通过」必须分得开)")

ErrNoChecks 评估清单为空——判不了就不判,不给「零判项全过」的假绿。

Functions

func Load

func Load(data []byte) ([]eventbus.Event, error)

Load 解码 JSONL 录制(顺序即回放顺序)。

func Record

func Record(evs []eventbus.Event) ([]byte, error)

Record 把事件流定格成 JSONL 字节(一行一个事件,按输入顺序)。

func Replay

func Replay(evs []eventbus.Event, opts trajectory.Options) []trajectory.Row

Replay 把事件流投进全新投影器并收口,返回轨迹行快照。

双方(录制侧与重放侧)都以 Finalize 收口再比对——「不会再有新事件」 是显式事实,两侧口径一致差异才有意义。

Types

type Check

type Check string

Check 评估判项词表(每个判项对应一类结构事实)。

const (
	// CheckClosure 收口完整:不存在「开了没收口」的行(open)。
	//
	// 孤儿行(只有终点、没有起点)单独看:checkpoint 点行**本来就是孤儿
	// (设计事实——点事件没有括号段)**,不在此判项内;其余类目的孤儿行
	// 意味着起点缺失,照判。
	CheckClosure Check = "closure"
	// CheckPairing 配对完整:工具调用行全部配对(无 missing_result /
	// unpaired_result / duplicate_result / duplicate_call)。
	CheckPairing Check = "pairing"
	// CheckNoDuplicates 无重复声明(重复结果 / 重复调用)。
	CheckNoDuplicates Check = "no_duplicates"
	// CheckExpected 期望行存在(Spec.ExpectedIDs 全部在轨迹里)。
	CheckExpected Check = "expected"
	// CheckForbidden 禁止行缺席(Spec.ForbiddenIDs 一条都不许在)。
	CheckForbidden Check = "forbidden"
)

type Divergence

type Divergence struct {
	// ID 行标识(缺失行以 Field="row" 表达)。
	ID string
	// Field 字段名(trajectory.Row 的字段词表)。
	Field string
	// Want 期望值(录制侧)。
	Want string
	// Got 实际值(重放侧)。
	Got string
}

Divergence 一处轨迹差异。

func Diff

func Diff(want, got []trajectory.Row) []Divergence

Diff 比对两份轨迹行,返回确定性排序的差异清单。

空切片 = 完全复现(issue #111 的验收线:回放能复现一条已录制轨迹, 差异为零)。比对字段覆盖轨迹行的全部投影事实(ID 是键、Seq 是顺序锚、 两侧时刻都来自事件本身——时刻不同只说明事件被改过,照样报)。

type Finding

type Finding struct {
	// Check 所属判项。
	Check Check
	// RowID 相关行(不针对单行的结论为空)。
	RowID string
	// Text 人读的说明。
	Text string
}

Finding 一条判定结论(违规即 finding;不违规不产生条目)。

type RecordProblem

type RecordProblem struct {
	// Seq 事件序号(1 起,按输入顺序)。
	Seq int
	// Text 人读的说明。
	Text string
}

RecordProblem 一条录制适航问题。

func Verify

func Verify(evs []eventbus.Event) []RecordProblem

Verify 判事件流是否**录制适航**:逐事件经 JSON 往返必须落进稳定形态。

判据(两层,都在纯函数里):

  • 头部标量字段(id / session / case / step / type / level / 时刻)往返后 必须与原值相等——这些是投影的骨架;
  • 载荷(map[string]any)往返**一次**后必须**幂等**(再往返一次不再变): 整数会落成 float64,这是 JSON 的固有形变;只要形变只发生一次, 「录制 → 重载」拿到的就是同一份输入,重放仍然确定。往返两轮还在变 的载荷(比如不可比较的类型混在里面)录了也录不稳,显式报出。

返回问题清单(空 = 全部适航)。

type Spec

type Spec struct {
	// Checks 判项清单(词表见 Check 各常量)。
	Checks []Check
	// ExpectedIDs 必须存在的行 ID(仅 CheckExpected 使用)。
	ExpectedIDs []string
	// ForbiddenIDs 不允许存在的行 ID(仅 CheckForbidden 使用)。
	ForbiddenIDs []string
}

Spec 一份评估清单:要判哪些判项 + 期望/禁止的行清单。

零值 Spec 判不了任何东西——Assess 对它显式报 ErrNoChecks,不返回 「通过」。判项显式列出,判了什么一目了然(skilldisclose 的同一纪律: 空结果必须写明原因,「没有检查」与「检查通过」在调用方看来不一样)。

type Verdict

type Verdict struct {
	// Pass 是否通过(无 finding 即通过)。
	Pass bool
	// Judged 实际执行的判项数(与 Spec.Checks 等长——「判了几条」是结论
	// 的一部分,不是元信息)。
	Judged int
	// Findings 违规清单,按 (Check, RowID) 排序(确定性)。
	Findings []Finding
}

Verdict 一份评估结论。

func Assess

func Assess(spec Spec, rows []trajectory.Row) (Verdict, error)

Assess 跑一份评估清单:纯函数——同一输入永远同一输出。

判据口径:

  • closure:Status=open 的行即违规;非 checkpoint 类目的 orphan 行 (起点缺失)同样违规——checkpoint 点行的孤儿形态是投影的设计事实, 不判;
  • pairing:工具行(tool_call / tool_result)的 Pairing 不为 paired / none 即违规;
  • no_duplicates:DupResults > 0 或 Pairing 为 duplicate_* 即违规;
  • expected:ExpectedIDs 逐个核对,缺一个报一个;
  • forbidden:ForbiddenIDs 逐个核对,在一条报一条。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL