Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions evaluation/memory/longmemeval_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Source, data locks, configuration, documentation, and tests for the LongMemEval-V2 suite live here.
The suite is installed through the shared `evaluation/` environment and provides the independent `longmemeval-v2` command.

The evaluation-only `lineage-fixture` command measures Scope-local lineage concentration,
near-duplicate occupancy and final Context fact coverage using disposable native backends.
See [the fixture and report contract](docs/lineage-occupancy.md) /
[中文说明](docs/lineage-occupancy.zh.md) for prerequisites, commands and interpretation.

## LongMemEval-V2 smoke workload

The LongMemEval-V2 commands run a fixed ten-question smoke subset end to end: preflight
Expand Down
143 changes: 143 additions & 0 deletions evaluation/memory/longmemeval_v2/docs/lineage-occupancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Scope-local lineage and near-duplicate occupancy

`longmemeval-v2 lineage-fixture` implements the evaluation-only fixture mode for
issue #1977. It uses real native Atomic Memory FTS, exact scoped revision/evidence
reads, and the in-process `PreparedContextBuilder`. It does not exercise HTTP/MCP
or a host's delivery workflow. No Runtime configuration, public schema, ranking,
write behavior, lifecycle policy, or Context Receipt is added.

## Prerequisites and execution

The loaded Runtime must include the deterministic FTS baseline from #1976 and
the exact historical Source-evidence API from #1975. A missing `evidence` API
refuses execution before fixture writes. It is never replaced with inferred
trust, Source names, current registration, or adapter audit correlations.

Install the evaluation suite and the pinned `powercontext[builtin]` source in
the same Python environment, then run:

```bash
longmemeval-v2 lineage-fixture \
--backend sqlite \
--runtime-revision EXACT_40_CHARACTER_RUNTIME_COMMIT \
--dependency-state merged \
--output-dir /path/outside/repository/new-lineage-report
```

Use `--dependency-state unmerged` when executing a pinned dependency snapshot
that has not merged. This state remains visible in the manifest, report and
decision. A snapshot run is useful local evidence, not merged-main acceptance.
The command records digests of the loaded Runtime and evaluation source trees;
the declared commit must identify the installed source. Existing report
directories are refused. The SQLite database is temporary, allocated beside the
report directory on the selected output filesystem, and removed on exit.

For OceanBase, configure `POWERCONTEXT_TEST_OCEANBASE_URL` and pass
`--backend oceanbase`. The account needs database creation/deletion privileges.
Only a newly created `pc_lineage_` database is populated and deleted; the
configured database is never populated or deleted. Credentials, connection
strings, machine paths, entry text, Source text, individual content digests and
exact entry/Source/Scope identifiers are not exported. Backend failures must not
be reported as successful or equivalent SQLite execution.

## Workload and measurement

`scope-local-lineage-fixtures-v2` fixes small, separate Scope partitions for:

- byte-identical concentration and a less-represented relevant fact;
- paraphrases grounded in shared evidence;
- distinct facts that share a Source and must all remain recallable;
- revised and historical entries with exact retained revision reads;
- recurring automation with distinct event facts;
- entries without Source evidence;
- held-out byte-identical entries without Sources citing different retained Artifact revisions;
- all six mandatory CJK forms from #1976, literal English and negative controls;
- held-out shared-source facts and held-out byte-budget pressure.

Oracles are declared synthetic fact markers, not model decisions or Source
authority claims. Held-out cases do not tune lexical thresholds. Each query
retrieves one immutable, post-admission RRF pool of at most 16 entries, with no
reranker or model. All alternatives reuse that pool and its recorded order.
Source and Artifact bodies/annotations are read under the same native Scope
boundary. Exact historical reads and before/after authority snapshots verify
that measurement does not alter retained revisions, lineage or state.

The versioned grouping keeps exact Artifact versions and declared Artifact
edges separate from exact Source materialization identity. A Source identity
includes its Scope, type, ID, digest and historical Definition manifest binding;
equal text or equal digests across different Sources do not merge identities.
Missing evidence stays unknown. Shared evidence measures concentration only;
it is not proof of factual duplication or a reason to discard an entry.

Three registered fixture arms are evaluated:

| Arm | Measurement |
| --- | --- |
| `lineage-baseline-v1` | Original pool and native final Context build. |
| `lineage-exact-collapse-measurement-v1` | First `(kind, exact UTF-8 text)` representative per equality class, projected only in memory into the same Builder. The original pool is unchanged. |
| `lineage-lexical-annotations-v1` | NFC/casefold/whitespace-normalized character-trigram Jaccard annotations at threshold 0.75. No suppression, reordering or different Context input. |

Equality projection is a counterfactual measurement, not a replacement pool or
a production deduper. No arm admits an entry from outside the original pool.
Jev is optional and absent here: manifests explicitly record `not_provided` and
no authority. No model call is made in this fixture suite.

Pool and delivered-origin aggregates include top-k Source/Artifact concentration,
exact redundant slots, lexical-neighbour occupancy, unique evidence/facts,
unknown evidence and neutral declarations. Delivered-origin fact counts describe
selected records; actual answer recall is measured against the rendered Context
bytes, so truncation cannot be mistaken for successful factual delivery.
For held-out fixtures, disjoint declared facts in a lexical-neighbour pair count
as a false relation annotation, independently of actual suppression.

Evidence accounting uses `scoped-source-artifact-evidence-v1`, pinned as
`evidence_version` in manifests and reports. `unique_evidence_count` is the
deduplicated union of direct Source materialization identities and exact scoped
Artifact citations, including family and revision. `unique_source_evidence_count`
and `unique_artifact_evidence_count` expose each part. Coverage and
`lost_baseline_evidence` use the same identity rules; Source and Artifact losses
are also reported separately. Artifact references are counted at the retained
cited version, without following current heads or recursively expanding lineage.
These are distinct direct references, not a claim of independent factual content.
Manifests without the evidence-accounting version are rejected for comparison.

Task outcomes separately record retrieval misses, final omitted facts, explicit
byte-budget drops/truncations, entry-limit omissions, baseline facts/evidence
lost by an alternative, task regressions, and negative-control false hits.
Latency separates retrieval, exact-evidence reads, and Builder work. Model calls
and model cost are zero. Counts sum across each Scope's queries; concentration,
occupancy and recall ratios are means over applicable samples. No per-entry
diagnostic or cross-Scope identifiers are exported.

## Artifacts and comparisons

One output directory contains the pinned baseline manifest, per-arm manifests,
aggregate `report.json` and `report.md`, `backend-matrix.json`, a path-free replay
command, `decision.md`, and artifact hashes. Fixture and source digests, backend
capabilities, dependency state, Python/library versions and all algorithm knobs
are comparison conditions. Timing is observed output, not a pinned input.

```bash
longmemeval-v2 lineage-compare \
--baseline /reports/lineage-baseline-v1.manifest.json \
--alternative /reports/lineage-exact-collapse-measurement-v1.manifest.json
```

Comparisons reject missing manifests, unknown arms, changed knobs, and every
manifest difference outside `experiment_arm`, including newly introduced fields.
SQLite and OceanBase are reported separately; their manifests cannot be compared
as if backend differences were an evaluation arm. An unexecuted backend is
explicitly `not_run`. The report's recommendation is **no product change**:
bounded synthetic results alone do not justify production suppression or ranking.

## Validation

```bash
python -m pytest -c evaluation/pyproject.toml evaluation/memory/longmemeval_v2/tests -q
ruff check evaluation/memory/longmemeval_v2/src evaluation/memory/longmemeval_v2/tests
```

Run native acceptance with the pinned evidence-capable Runtime. On an older
Runtime, the prerequisite-refusal test executes and the evidence-capable SQLite
acceptance is explicitly skipped. This does not establish backend acceptance.
Keep report directories and raw local replay evidence outside Git.
57 changes: 57 additions & 0 deletions evaluation/memory/longmemeval_v2/docs/lineage-occupancy.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Scope 内谱系集中度与近似重复占比评测

`longmemeval-v2 lineage-fixture` 提供 #1977 的独立 fixture 模式,使用真实 Atomic Memory FTS、精确历史证据读取和进程内 `PreparedContextBuilder`。它不修改生产排序、写入、生命周期、公共 API 或交付契约,不产生 Context Receipt,也不代表 HTTP/MCP 或宿主交付验收。

## 前置条件与运行

加载的 Runtime 必须包含 #1976 的确定性 FTS 基线和 #1975 的精确 Source 证据读取接口。缺少接口时,在写入 fixture 前明确拒绝运行,不根据正文、Source 名称、当前注册配置或 adapter 审计关联猜测历史证据与可信度。

在同一 Python 环境安装评测包和固定的 `powercontext[builtin]` 源码后执行:

```bash
longmemeval-v2 lineage-fixture \
--backend sqlite \
--runtime-revision 固定的40位Runtime提交SHA \
--dependency-state merged \
--output-dir /仓库外/新的谱系报告目录
```

验证未合并的依赖快照时使用 `--dependency-state unmerged`。该状态进入 manifest、报告和决策记录,不能解释为主分支验收通过。命令记录实际加载的 Runtime 和评测源码摘要;提交 SHA 应对应安装的源码。已有输出目录不会被覆盖。SQLite 在报告目录所在文件系统创建临时数据库,退出后清理。

OceanBase 使用 `--backend oceanbase`,通过 `POWERCONTEXT_TEST_OCEANBASE_URL` 提供测试连接。账号需有创建和删除数据库的权限。只写入并清理本次创建的 `pc_lineage_` 数据库,不写入或删除连接配置中的原数据库。连接字符串、凭据、机器路径、条目和 Source 正文、单条内容摘要及精确 entry/Source/Scope 标识不会导出。

## 固定场景与指标

`scope-local-lineage-fixtures-v2` 包括独立 Scope 中的完全相同条目、同证据改写、同 Source 不同事实、修订与历史事实、周期自动来源、未知证据、#1976 的六种中文正例及英文和无关查询对照。另有不参与阈值调整的共享来源、字节预算,以及无直接 Source、正文相同但引用不同历史 Artifact 修订的保留验证案例。

事实答案由合成 fixture 显式声明,不是模型判断或可信度推断。每个查询只取一次最多 16 条的准入后 RRF 候选序列,所有实验复用其成员和顺序。精确历史读取及评测前后的权威记录快照用于验证内容、谱系和状态没有被评测修改。

谱系分组保留精确 Artifact 版本及其声明的前驱/证据边,Source 分组使用 Scope、类型、ID、material digest 和历史 Definition manifest 绑定。不同 Source 即使正文或摘要相同也不会合并。没有安全证据时明确记为未知。共享来源只是集中度观察,不代表事实重复。

| 实验组 | 行为 |
| --- | --- |
| `lineage-baseline-v1` | 原始候选池与原生最终 Context 构建。 |
| `lineage-exact-collapse-measurement-v1` | 按 kind 与精确 UTF-8 正文保留首个代表,仅在内存测量投影上调用相同 Builder,原池保持不变。 |
| `lineage-lexical-annotations-v1` | NFC、大小写及空白规范化后的字符三元组 Jaccard 标注,阈值 0.75;不抑制、不重排。 |

Jev 不是必需依赖,本模式明确记录为 `not_provided`,不作为权威。模型调用和模型费用均为零。

候选池和最终选中来源分别统计 top-k 集中度、精确冗余槽位、词汇邻近占比、独立证据及事实数、未知证据和中性声明。选中条目的事实数不等同于实际交付事实数:答案召回直接检查最终渲染字节,避免将截断后的内容当作完整交付。

证据计数使用 `scoped-source-artifact-evidence-v1`,通过 manifest 和报告中的 `evidence_version` 固定。`unique_evidence_count` 是直接 Source materialization 与精确 Artifact 引用的去重并集,Artifact 身份保留 Scope、family、ID 和 revision。`unique_source_evidence_count` 与 `unique_artifact_evidence_count` 分别报告两类数量。覆盖和 `lost_baseline_evidence` 使用相同身份规则,损失也按 Source 与 Artifact 拆分。统计不追随当前 head,也不递归展开引用;不同直接引用不代表不同事实。缺少证据计数版本的 manifest 无法参与比较。

检索漏召回、关系不确定、预算丢弃/截断、条数上限遗漏、相对基线的事实与证据损失、任务退化、负例误命中分别记录。耗时拆分为检索、精确证据读取和构建。每个类别对应一个 Scope,只导出该 Scope 查询的聚合计数和比率,不提供逐条诊断或跨 Scope 标识。

## 报告与比较

单个目录包含固定 manifest、各实验组 manifest、聚合 JSON/Markdown 报告、后端能力矩阵、无机器路径的重跑命令、Markdown 决策记录和文件摘要。比较条件包括 fixture 与源码摘要、后端能力、依赖状态、Python/库版本及算法参数。

```bash
longmemeval-v2 lineage-compare \
--baseline /报告/lineage-baseline-v1.manifest.json \
--alternative /报告/lineage-exact-collapse-measurement-v1.manifest.json
```

除了声明的实验组,任何 manifest 差异都会被拒绝,包括后端、未知新增字段和缺失条件。SQLite 与 OceanBase 分开执行、分开报告,未执行的后端标记 `not_run`。本评测建议保持产品行为:合成小规模结果不足以批准生产抑制或重排。

验证命令及完整指标语义见 [英文说明](lineage-occupancy.md)。在缺少 #1975 的 Runtime 上,只能验证拒绝行为;证据能力验收会显式跳过,不能据此宣称后端通过。
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,58 @@
app = typer.Typer(no_args_is_help=True, help="Pinned LongMemEval-V2 evaluation.")


@app.command("lineage-fixture")
def lineage_fixture(
output_dir: Annotated[Path, typer.Option("--output-dir")],
runtime_revision: Annotated[str, typer.Option("--runtime-revision")],
backend: Annotated[str, typer.Option("--backend")] = "sqlite",
dependency_state: Annotated[str, typer.Option("--dependency-state")] = "unknown",
oceanbase_url_env: Annotated[str, typer.Option("--oceanbase-url-env")] = "POWERCONTEXT_TEST_OCEANBASE_URL",
) -> None:
"""Measure lineage/occupancy on disposable native-backend fixtures, without models."""
import asyncio

from powercontext_eval_longmemeval_v2.lineage import LineageEvaluationError
from powercontext_eval_longmemeval_v2.lineage_run import run_lineage_fixture

try:
result = asyncio.run(
run_lineage_fixture(
output_dir=output_dir,
backend=backend,
runtime_revision=runtime_revision,
dependency_state=dependency_state,
oceanbase_url_env=oceanbase_url_env,
)
)
except LineageEvaluationError as error:
typer.echo(f"Lineage evaluation refused: {error}", err=True)
raise typer.Exit(code=1) from None
typer.echo(
json.dumps({"report": str(result.report_path), "manifest": str(result.manifest_path)}, ensure_ascii=False)
)


@app.command("lineage-compare")
def lineage_compare(
baseline: Annotated[Path, typer.Option("--baseline")],
alternative: Annotated[Path, typer.Option("--alternative")],
) -> None:
"""Validate two pinned fixture manifests before interpreting an arm comparison."""
from powercontext_eval_longmemeval_v2.lineage import LineageEvaluationError, ensure_comparable_lineage_runs

try:
first = json.loads(baseline.read_text(encoding="utf-8"))
second = json.loads(alternative.read_text(encoding="utf-8"))
if not isinstance(first, dict) or not isinstance(second, dict):
raise LineageEvaluationError("manifest must be a JSON object")
ensure_comparable_lineage_runs(first, second)
except (OSError, ValueError, LineageEvaluationError):
typer.echo("Lineage comparison refused: invalid or incompatible manifests", err=True)
raise typer.Exit(code=1) from None
typer.echo("Comparable: only the declared evaluation arm differs.")


def _parse_price_policy_option(value: str | None, *, label: str) -> ModelPricePolicy | None:
"""Parse an explicitly passed JSON price policy, or keep costs unconfigured as null."""

Expand Down
Loading
Loading