"""测量:读取器的 score_margin 能否区分「同一属性的更新」与「另一属性的事实」。 背景:为了让自动层能版本化 Agent 经接口写入的记录,曾尝试用读取器判断"这一轮讲的是哪件事"再继承冲突键; 结果误伤了不相关事实(存了 默认语言=日语 与 项目代号=蓝鲸-47 后,说「我的项目代号是 蓝鲸-47」 却把默认语言那条取代掉)。因此动更新语义之前,先量 margin 分布。 做法(走正在运行的控制面,不额外占卡): 正类 = 先存「我的是 v1」,再问「我的改成 v2」 —— 期望:读取器明确指向那条记录 负类 = 先存「我的是 v1」,再问「我的是 v2」(B≠A) —— 期望:不该指向那条记录 每类各跑一轮,输出 need_memory / stop_reason / margin / 命中是否是那条记录,并给两类区间。 """ from __future__ import annotations import argparse import json import statistics import urllib.error import urllib.request BASE = "http://127.0.0.1:8766/v1" # 取自 data/realistic_v2/eval.manifest.json 的 eval 属性族(未出现在训练里) FAMILIES = [ "主库地址", "仓库库位", "代码仓库", "供应商", "保险到期", "值班电话", "值班表", "发布窗口", "合同编号", "告警阈值", "周会时间", "培训周期", ] VALUES_A = ["A-7719", "v3.14.2", "64GB", "B3-204", "91.5%", "C1-105", "D-9021", "v1.9.7"] VALUES_B = ["v4.0.0-rc1", "128GB", "E-4402", "73%", "F-1180", "v2.0.1", "G-6613", "88%"] def call(path: str, payload: dict | None = None, method: str = "POST") -> dict: url = f"{BASE}{path}" data = json.dumps(payload, ensure_ascii=False).encode("utf-8") if payload is not None else None request = urllib.request.Request( url, data=data, headers={"Content-Type": "application/json; charset=utf-8"}, method=method ) try: with urllib.request.urlopen(request, timeout=120) as response: return json.loads(response.read().decode("utf-8")) except urllib.error.HTTPError as error: return {"__error__": f"HTTP {error.code}: {error.read().decode('utf-8', 'replace')[:200]}"} def trial(store_attribute: str, store_value: str, query: str) -> dict: call("/nm2/reset", {}) written = call("/nm2/write", { "text": f"我的{store_attribute}是 {store_value}。", "entity": "user", "attribute": store_attribute, "value": store_value, }) record_id = (written.get("record") or {}).get("record_id", "") probed = call("/nm2/probe", {"query": query}) if "__error__" in probed: return {"error": probed["__error__"]} hits = probed.get("records") or [] return { "need_memory": bool(probed.get("need_memory")), "stop_reason": str(probed.get("stop_reason")), "margin": probed.get("score_margin"), "top_score": probed.get("top_score"), "hit_is_stored": bool(hits) and str(hits[0].get("record_id")) == record_id, "hits": len(hits), } def summarise(name: str, rows: list[dict]) -> dict: margins = [float(r["margin"]) for r in rows if r.get("margin") is not None] usable = [r for r in rows if "error" not in r] pointed = sum(1 for r in usable if r.get("hit_is_stored")) print(f"\n=== {name}({len(usable)} 次)===") print(f" need_memory 为真: {sum(1 for r in usable if r['need_memory'])}/{len(usable)}") print(f" 命中就是那条已存记录: {pointed}/{len(usable)}") if margins: print(f" margin: 最小 {min(margins):.4f} 中位 {statistics.median(margins):.4f} 最大 {max(margins):.4f}") print(f" margin 取值: {', '.join(f'{m:.4f}' for m in sorted(margins))}") reasons: dict[str, int] = {} for r in usable: reasons[r["stop_reason"]] = reasons.get(r["stop_reason"], 0) + 1 print(f" stop_reason: {reasons}") return {"margins": margins, "pointed": pointed, "total": len(usable)} def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--rounds", type=int, default=1, help="每个属性跑几轮") args = parser.parse_args() positive: list[dict] = [] negative: list[dict] = [] for round_index in range(args.rounds): for index, attribute in enumerate(FAMILIES): value = VALUES_A[(index + round_index) % len(VALUES_A)] other = FAMILIES[(index + 1) % len(FAMILIES)] other_value = VALUES_B[(index + round_index) % len(VALUES_B)] # 正类:同一属性的更新("改成") positive.append(trial(attribute, value, f"我的{attribute}改成 {VALUES_B[(index + round_index) % len(VALUES_B)]}。")) # 负类:另一个属性的事实 negative.append(trial(attribute, value, f"我的{other}是 {other_value}。")) pos = summarise("正类:同一属性的更新", positive) neg = summarise("负类:另一属性的事实", negative) print("\n=== 判别力检查 ===") if pos["margins"] and neg["margins"]: print(f" 正类 margin 区间: [{min(pos['margins']):.4f}, {max(pos['margins']):.4f}]") print(f" 负类 margin 区间: [{min(neg['margins']):.4f}, {max(neg['margins']):.4f}]") overlap_lo = max(min(pos["margins"]), min(neg["margins"])) overlap_hi = min(max(pos["margins"]), max(neg["margins"])) if overlap_hi > overlap_lo: print(f" → 两类区间重叠于 [{overlap_lo:.4f}, {overlap_hi:.4f}]:**单靠 margin 无法干净分开**") else: print(" → 两类区间不重叠:存在可用门限") print(f" 命中率:正类 {pos['pointed']}/{pos['total']},负类 {neg['pointed']}/{neg['total']}") return 0 if __name__ == "__main__": raise SystemExit(main())