Files
elizaos--eliza/packages/training/scripts/transform_normalize_fact_ops.py
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

204 lines
6.7 KiB
Python

"""Normalize non-canonical fact_extractor op names to the runtime vocabulary.
gpt-oss-120b consistently emits `op: insert` or `op: add` instead of the
canonical `add_durable` / `add_current` pair, despite the prompt
forbidding it. Roughly 30 % of fact_extractor records are affected.
Mapping:
- `op: insert` + `category: durable.*` → `op: add_durable`
- `op: insert` + `category: current.*` → `op: add_current`
- `op: insert` + ambiguous category → infer durable for stable
identity-like categories,
current for state-like
- `op: add` same rules as `op: insert`
- `op: update` same rules as `op: insert`
- Wrapper shape `{"insert": {...}}` (no `op` key) → unwrap and apply
the rules above
Records the script can't reconcile (truly unknown shape) are dropped
unless ``--keep-unparseable`` is passed.
Usage::
python scripts/transform_normalize_fact_ops.py \
--input data/synthesized/evaluators/fact_extractor.jsonl \
--output data/synthesized/evaluators/fact_extractor.jsonl
"""
from __future__ import annotations
import argparse
import json
import logging
import os
import sys
from pathlib import Path
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("normalize-fact-ops")
CANONICAL_OPS = {"add_durable", "add_current", "strengthen", "decay", "contradict"}
INSERT_LIKE = {"insert", "add", "update"}
# Categories the model commonly invents that aren't prefixed `durable.` /
# `current.`. These heuristics mirror the runtime taxonomy in
# eliza/packages/core/src/runtime/fact-types.ts.
DURABLE_HINTS = {
"allergy", "diet", "identity", "hometown", "career", "family",
"education", "founding", "hobby", "preference", "relationship",
"personal.project", "personal", "team.size", "team.composition",
}
CURRENT_HINTS = {
"feeling", "task", "activity", "mood", "today", "current",
"meetings", "meeting", "blocker",
}
def _category_to_kind(category: str | None) -> str | None:
if not isinstance(category, str):
return None
if category.startswith("durable."):
return "add_durable"
if category.startswith("current."):
return "add_current"
head = category.split(".", 1)[0].lower()
if head in DURABLE_HINTS or category in DURABLE_HINTS:
return "add_durable"
if head in CURRENT_HINTS:
return "add_current"
return None
def _normalize_op(op: dict) -> dict | None:
"""Return a normalized op or None if it's truly unsalvageable."""
if not isinstance(op, dict):
return None
# Wrapper shape: {"insert": {...}} or {"add": {...}}
for wrapper in INSERT_LIKE:
if wrapper in op and "op" not in op and isinstance(op[wrapper], dict):
inner = dict(op[wrapper])
inner["op"] = wrapper
return _normalize_op(inner)
# `type: insert` (some teachers use `type` instead of `op`)
if "type" in op and "op" not in op:
op = {**op, "op": op["type"]}
raw = op.get("op")
if raw in CANONICAL_OPS:
return op
if raw in INSERT_LIKE:
# Some teachers nest the actual claim inside `fact: {...}`. Hoist.
if "fact" in op and isinstance(op["fact"], dict) and "category" not in op:
inner = dict(op["fact"])
inner["op"] = raw
for k, v in op.items():
if k not in ("op", "fact"):
inner.setdefault(k, v)
op = inner
kind = _category_to_kind(op.get("category"))
if kind is None:
return None
out = {"op": kind}
for k in ("category", "claim", "factId", "since", "validAt", "value", "proposedText"):
if k in op and op[k] is not None:
out[k] = op[k]
# Allow naked `value: x` to survive as the claim if claim missing.
if "claim" not in out and "value" in op:
out["claim"] = op["value"]
return out
return None
def normalize_record(rec: dict) -> dict | None:
er = rec.get("expectedResponse")
if not isinstance(er, str):
return None
try:
payload = json.loads(er)
except json.JSONDecodeError:
return None
ops = payload.get("ops")
if not isinstance(ops, list):
return None
new_ops: list[dict] = []
for op in ops:
norm = _normalize_op(op)
if norm is None:
return None # one bad op poisons the record
new_ops.append(norm)
payload["ops"] = new_ops
rec["expectedResponse"] = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
rec.setdefault("metadata", {})["fact_op_normalized"] = True
return rec
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--input", type=Path, required=True)
p.add_argument("--output", type=Path, required=True)
args = p.parse_args()
if not args.input.exists():
log.error("input not found: %s", args.input)
return 2
in_place = args.input.resolve() == args.output.resolve()
tmp = args.output.with_suffix(args.output.suffix + ".tmp") if in_place else args.output
n_total = n_already = n_normalized = n_dropped = 0
with args.input.open("r", encoding="utf-8") as fin, \
tmp.open("w", encoding="utf-8") as fout:
for line in fin:
line = line.rstrip("\n")
if not line:
continue
n_total += 1
try:
rec = json.loads(line)
except json.JSONDecodeError:
n_dropped += 1
continue
er = rec.get("expectedResponse")
try:
payload = json.loads(er) if isinstance(er, str) else None
except json.JSONDecodeError:
n_dropped += 1
continue
if not isinstance(payload, dict) or not isinstance(payload.get("ops"), list):
n_dropped += 1
continue
ops = payload["ops"]
if all(
isinstance(op, dict) and op.get("op") in CANONICAL_OPS
for op in ops
):
fout.write(line + "\n")
n_already += 1
continue
normalized = normalize_record(dict(rec))
if normalized is None:
n_dropped += 1
continue
fout.write(json.dumps(normalized, ensure_ascii=False) + "\n")
n_normalized += 1
if in_place:
os.replace(tmp, args.output)
log.info(
"in=%s out=%s total=%d already=%d normalized=%d dropped=%d",
args.input, args.output, n_total, n_already, n_normalized, n_dropped,
)
return 0
if __name__ == "__main__":
sys.exit(main())