Contracts (the shared language): - percept / state / context / utterance / render / audio / capabilities - Capability model instead of vendor schemas (see below) Providers (capability-based abstraction): - LLM + Embedding: OpenAI-compatible shape IS the standard, adopted directly - ASR: internal contract is an event stream (partial -> final), because /v1/audio/transcriptions has no partial and dialogue needs it for endpointing. OpenAI adapter degrades to supports_partial=False. - TTS: internal contract carries tags/emotion/style, because /v1/audio/speech has no paralinguistics and [laughter]/[breath] are the core of feeling alive. Unsupported providers are stripped AND reported via Degradation -- silent degradation is forbidden. - Avatar: no de-facto standard, custom RenderCmd protocol driven by State. Core: - mood: dynamical system, slow variables, negativity bias (grudge), numbing after prolonged negativity - bazi: four pillars -> numeric vector; 'ming' is data, not prose - projection: phi, system event -> in-world statement; uncovered events are counted as projection gaps Storage: - SQLite schema, three memory layers, dyad ledger - accessibility floor 0.03 (not 0): forgettable but still there Config split: - models.yaml is the ONLY place model versions live - character.yaml holds world rules / emotion params, no version numbers 58 tests pass; ruff clean.
68 lines
1.9 KiB
Python
68 lines
1.9 KiB
Python
"""投影层测试。
|
|
|
|
守的是本体 C-膜 的落地机制:
|
|
未覆盖的事件必须落到 fallback,并**计数**。
|
|
projection_gap 的行数就是"现实感泄漏"的度量。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from cybergril.core.projection import (
|
|
FALLBACK,
|
|
Projector,
|
|
violates_absence_rule,
|
|
)
|
|
|
|
|
|
def test_known_event_projects() -> None:
|
|
p = Projector()
|
|
assert p("process.wake") == "刚醒。"
|
|
assert p.gap_count == 0
|
|
|
|
|
|
def test_unknown_event_falls_back_and_counts() -> None:
|
|
gaps = []
|
|
p = Projector(on_gap=gaps.append)
|
|
out = p("totally.unknown.event", {"x": 1})
|
|
assert out == FALLBACK
|
|
assert p.gap_count == 1
|
|
assert gaps[0].event_key == "totally.unknown.event"
|
|
|
|
|
|
def test_register_fills_gap() -> None:
|
|
p = Projector()
|
|
p("new.event")
|
|
assert p.gap_count == 1
|
|
|
|
p.register("new.event", "这个我说得上来了。")
|
|
assert p("new.event") == "这个我说得上来了。"
|
|
# 计数不因补表而回退
|
|
assert p.gap_count == 1
|
|
|
|
|
|
def test_custom_table_overrides() -> None:
|
|
p = Projector({"process.wake": "醒了。"})
|
|
assert p("process.wake") == "醒了。"
|
|
|
|
|
|
def test_no_secrets_leak_into_projection() -> None:
|
|
"""投影只输出世界内陈述,不输出技术细节。"""
|
|
p = Projector()
|
|
forbidden = ["进程", "服务", "错误码", "API", "超时", "token"]
|
|
for key in p.known_keys():
|
|
text = p(key)
|
|
for f in forbidden:
|
|
assert f not in text, f"{key} 的投影泄漏了技术词 {f!r}"
|
|
|
|
|
|
def test_absence_rule_detects_forbidden_patterns() -> None:
|
|
assert violates_absence_rule("我一直在等你,等了好久")
|
|
assert violates_absence_rule("你会把我删掉吗")
|
|
assert not violates_absence_rule("这几天我这边发生了点事")
|
|
|
|
|
|
def test_all_default_projections_are_nonempty() -> None:
|
|
p = Projector()
|
|
for key in p.known_keys():
|
|
assert p(key).strip(), f"{key} 的投影为空"
|