feat: 对话质量自我进化(CP8-P4 越聊越校准)端到端贯通#16
Conversation
把 HANDOFF 清单里属于 SDK 的漂移动力学用 canonical 正道补进 main,
并把原本推给 Sylanne-next 的「质量信号怎么喂」那半截闭环直接在 SDK 收掉,
退役 feedback_quality 后门思路,全程走 process() 自动漂移通道。
链路:engine.process(values={"dialogue_quality": q}) → kernel 从 values 取出
→ spine.process(dialogue_quality=q) → 写进 result → DriftSignalExtractor 产
高/低信号 → _drift_embodiment 漂人格。上面三层签名零改动。
- personality.py: DRIFT_SIGNALS 加 dialogue_quality_high/low 两条映射;
extract() 认 result["dialogue_quality"],阈值 _HIGH=0.7/_LOW=0.3,中间不触发
- resonance_integration.py: ResonanceSpine 补 canonical _drift_embodiment(照抄
ComputationSpine 范本),process() return 前调一次——此前默认 spine 基建就绪
却从不漂移;process() 加可选 dialogue_quality 入参
- computation_spine.py: process() 加 dialogue_quality 入参,折进 cache_key
- kernel.py: 从 values 通道透传 dialogue_quality 给 spine
- engine.py: process docstring 标注 values["dialogue_quality"] 用法
- AGENT_GUIDE: 新增「场景 5:越聊越校准」带代码示例 + 滞后反馈时序说明
- CHANGELOG: 记端到端贯通
质量分是滞后反馈:对第 N 轮回复的评分在第 N+1 轮传入;不传时行为完全不变。
测试 +9(extract 四分支 / 映射方向 / spine 接线 / 高质量抬升 / 高低差分)。
全量 498 passed,mypy strict + ruff 对改动文件全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer's GuideImplements end-to-end canonical support for dialogue-quality-driven personality drift: a new dialogue_quality signal flows from engine.process values through kernel into both spines, is written onto the result, converted by DriftSignalExtractor into dialogue_quality_high/low drift signals, and consumed by canonical embodiment drift wiring in ResonanceSpine; behavior remains unchanged when the signal is absent. Sequence diagram for dialogue_quality-driven canonical personality driftsequenceDiagram
actor Agent
participant SylanneEngine as SylanneEngine
participant Kernel as Kernel
participant ResonanceSpine as ResonanceSpine
participant DriftSignalExtractor as DriftSignalExtractor
participant EmbodimentDrift as compute_embodiment_drift
Agent->>SylanneEngine: process(session_id, text, values_dialogue_quality)
SylanneEngine->>Kernel: _tick_inner(event_with_values)
Kernel->>Kernel: _dq = event.values.get(dialogue_quality)
Kernel->>ResonanceSpine: process(text, now, assessment, dialogue_quality=_dq)
ResonanceSpine->>ResonanceSpine: result = _build_result(...)
ResonanceSpine->>ResonanceSpine: result[dialogue_quality] = dialogue_quality
ResonanceSpine->>DriftSignalExtractor: extract(result)
DriftSignalExtractor-->>ResonanceSpine: signals(dialogue_quality_high_low)
ResonanceSpine->>EmbodimentDrift: compute_embodiment_drift(embodiment_traits, signals, drift_tick, ...)
EmbodimentDrift-->>ResonanceSpine: updated_traits
ResonanceSpine-->>Kernel: result_with_personality_drift
Kernel-->>SylanneEngine: result_with_personality_drift
SylanneEngine-->>Agent: surface
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
💩 屎山代码检测报告
🎯 本次检测概览
🎭 整体印象臭气扑鼻,建议佩戴口罩阅读 🧭 下一步这代码像个叛逆期的青少年,需要适当管教才能成才 📥 详细数据 由 fuck-u-code 自动检测 · 本评论不代表人类观点 |
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The new
_drift_embodimentimplementation inResonanceSpinelargely duplicates the logic already present inComputationSpine; consider extracting a shared helper/mixin for embodiment drift to keep the two spines in sync and reduce the risk of future divergence. - When threading
dialogue_qualityfromkernelintocomputation.processyou coerce withfloat(_dq)but otherwise don't check bounds; since the extractor later clamps to[0,1], you might want to either normalise/clamp once at ingestion or document that upstream is expected to pre-normalise to avoid surprises from out-of-range values.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `_drift_embodiment` implementation in `ResonanceSpine` largely duplicates the logic already present in `ComputationSpine`; consider extracting a shared helper/mixin for embodiment drift to keep the two spines in sync and reduce the risk of future divergence.
- When threading `dialogue_quality` from `kernel` into `computation.process` you coerce with `float(_dq)` but otherwise don't check bounds; since the extractor later clamps to `[0,1]`, you might want to either normalise/clamp once at ingestion or document that upstream is expected to pre-normalise to avoid surprises from out-of-range values.
## Individual Comments
### Comment 1
<location path="sylanne_core/compute/kernel.py" line_range="242-247" />
<code_context>
self.hot_pool.amplify_event(event_dict)
+ # dialogue_quality 走 values 通道("额外数值信号")→ 透传给 spine 的 canonical
+ # 漂移入参。滞后反馈:上一轮回复的自评在本轮 process 时随 values 一并进来。
+ _dq = event.values.get("dialogue_quality")
self._last_computation_result = self.computation.process(
- event.text, event.now, assessment=assessment
+ event.text,
+ event.now,
+ assessment=assessment,
+ dialogue_quality=float(_dq) if _dq is not None else None,
)
collapse_record = self.hot_pool.tick(body=self.body, spine=self.computation)
</code_context>
<issue_to_address>
**issue:** Casting dialogue_quality with float() can raise unexpectedly on bad inputs.
Since `dialogue_quality` comes from `event.values`, casting it directly with `float(_dq)` means any non-numeric or unexpected type (e.g., dict/list/string) will raise and abort the tick. Consider validating or safely casting this value (e.g., type-checking numeric types or wrapping the cast in try/except with a fallback to `None`) so bad inputs fail gracefully instead of breaking the computation.
</issue_to_address>
### Comment 2
<location path="tests/test_personality.py" line_range="111-127" />
<code_context>
+ signals = ext.extract({"should_express": False})
+ assert not any(k.startswith("dialogue_quality") for k in signals)
+
+ def test_dialogue_quality_high_maps_to_traits(self):
+ # high-quality 信号经 canonical 漂移应抬升表达欲 + 拉近关系引力
+ traits = {
+ name: TraitMemory(0.5)
+ for name in (
+ "expression_drive_trait",
+ "perception_acuity",
+ "boundary_permeability",
+ "inner_order",
+ "relational_gravity",
+ )
+ }
+ compute_embodiment_drift(
+ traits, {"dialogue_quality_high": 1.0}, tick_count=0, dt=30.0
+ )
+ assert traits["expression_drive_trait"].value > 0.5
+ assert traits["relational_gravity"].value > 0.5
+
</code_context>
<issue_to_address>
**suggestion (testing):** Also assert the effect of dialogue_quality_low on expression_drive_trait to cover both directions of the mapping
To fully validate `DRIFT_SIGNALS`, please add a complementary test for `dialogue_quality_low` that starts traits at 0.5, applies `compute_embodiment_drift(traits, {"dialogue_quality_low": 1.0}, ...)`, then asserts `expression_drive_trait` decreases and unrelated traits (e.g. `relational_gravity`) stay unchanged. This will verify both the sign and isolation of the low-quality mapping.
```suggestion
def test_dialogue_quality_high_maps_to_traits(self):
# high-quality 信号经 canonical 漂移应抬升表达欲 + 拉近关系引力
traits = {
name: TraitMemory(0.5)
for name in (
"expression_drive_trait",
"perception_acuity",
"boundary_permeability",
"inner_order",
"relational_gravity",
)
}
compute_embodiment_drift(
traits, {"dialogue_quality_high": 1.0}, tick_count=0, dt=30.0
)
assert traits["expression_drive_trait"].value > 0.5
assert traits["relational_gravity"].value > 0.5
def test_dialogue_quality_low_maps_to_traits(self):
# low-quality 信号应降低表达欲,且不应影响无关特质(如 relational_gravity)
traits = {
name: TraitMemory(0.5)
for name in (
"expression_drive_trait",
"perception_acuity",
"boundary_permeability",
"inner_order",
"relational_gravity",
)
}
compute_embodiment_drift(
traits, {"dialogue_quality_low": 1.0}, tick_count=0, dt=30.0
)
assert traits["expression_drive_trait"].value < 0.5
assert traits["relational_gravity"].value == 0.5
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def test_dialogue_quality_high_maps_to_traits(self): | ||
| # high-quality 信号经 canonical 漂移应抬升表达欲 + 拉近关系引力 | ||
| traits = { | ||
| name: TraitMemory(0.5) | ||
| for name in ( | ||
| "expression_drive_trait", | ||
| "perception_acuity", | ||
| "boundary_permeability", | ||
| "inner_order", | ||
| "relational_gravity", | ||
| ) | ||
| } | ||
| compute_embodiment_drift( | ||
| traits, {"dialogue_quality_high": 1.0}, tick_count=0, dt=30.0 | ||
| ) | ||
| assert traits["expression_drive_trait"].value > 0.5 | ||
| assert traits["relational_gravity"].value > 0.5 |
There was a problem hiding this comment.
suggestion (testing): Also assert the effect of dialogue_quality_low on expression_drive_trait to cover both directions of the mapping
To fully validate DRIFT_SIGNALS, please add a complementary test for dialogue_quality_low that starts traits at 0.5, applies compute_embodiment_drift(traits, {"dialogue_quality_low": 1.0}, ...), then asserts expression_drive_trait decreases and unrelated traits (e.g. relational_gravity) stay unchanged. This will verify both the sign and isolation of the low-quality mapping.
| def test_dialogue_quality_high_maps_to_traits(self): | |
| # high-quality 信号经 canonical 漂移应抬升表达欲 + 拉近关系引力 | |
| traits = { | |
| name: TraitMemory(0.5) | |
| for name in ( | |
| "expression_drive_trait", | |
| "perception_acuity", | |
| "boundary_permeability", | |
| "inner_order", | |
| "relational_gravity", | |
| ) | |
| } | |
| compute_embodiment_drift( | |
| traits, {"dialogue_quality_high": 1.0}, tick_count=0, dt=30.0 | |
| ) | |
| assert traits["expression_drive_trait"].value > 0.5 | |
| assert traits["relational_gravity"].value > 0.5 | |
| def test_dialogue_quality_high_maps_to_traits(self): | |
| # high-quality 信号经 canonical 漂移应抬升表达欲 + 拉近关系引力 | |
| traits = { | |
| name: TraitMemory(0.5) | |
| for name in ( | |
| "expression_drive_trait", | |
| "perception_acuity", | |
| "boundary_permeability", | |
| "inner_order", | |
| "relational_gravity", | |
| ) | |
| } | |
| compute_embodiment_drift( | |
| traits, {"dialogue_quality_high": 1.0}, tick_count=0, dt=30.0 | |
| ) | |
| assert traits["expression_drive_trait"].value > 0.5 | |
| assert traits["relational_gravity"].value > 0.5 | |
| def test_dialogue_quality_low_maps_to_traits(self): | |
| # low-quality 信号应降低表达欲,且不应影响无关特质(如 relational_gravity) | |
| traits = { | |
| name: TraitMemory(0.5) | |
| for name in ( | |
| "expression_drive_trait", | |
| "perception_acuity", | |
| "boundary_permeability", | |
| "inner_order", | |
| "relational_gravity", | |
| ) | |
| } | |
| compute_embodiment_drift( | |
| traits, {"dialogue_quality_low": 1.0}, tick_count=0, dt=30.0 | |
| ) | |
| assert traits["expression_drive_trait"].value < 0.5 | |
| assert traits["relational_gravity"].value == 0.5 |
There was a problem hiding this comment.
Code Review
This pull request integrates ResonanceSpine with embodiment personality drift and implements end-to-end support for dialogue quality self-evolution, allowing agents to feed back a normalized dialogue quality score to drive personality changes. The review feedback highlights potential runtime exceptions (ValueError/TypeError) when casting the dialogue quality value to a float in both kernel.py and personality.py, recommending defensive try-except blocks and NaN/Inf checks to ensure system robustness.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| _dq = event.values.get("dialogue_quality") | ||
| self._last_computation_result = self.computation.process( | ||
| event.text, event.now, assessment=assessment | ||
| event.text, | ||
| event.now, | ||
| assessment=assessment, | ||
| dialogue_quality=float(_dq) if _dq is not None else None, | ||
| ) |
There was a problem hiding this comment.
在将 dialogue_quality 转换为 float 时,如果传入的 _dq 不是合法的数字(例如用户在 values 中传入了非数字字符串或其它类型),直接调用 float(_dq) 会抛出 ValueError 或 TypeError 异常,从而导致整个 process 流程崩溃。\n\n为了提高系统的健壮性,建议采用防御性编程,使用 try-except 块安全地进行类型转换,并在转换失败时记录警告日志并回退为 None。
_dq = event.values.get("dialogue_quality")\n _dq_float = None\n if _dq is not None:\n try:\n _dq_float = float(_dq)\n except (ValueError, TypeError):\n logger.warning("Invalid dialogue_quality value: %s, ignoring", _dq)\n self._last_computation_result = self.computation.process(\n event.text,\n event.now,\n assessment=assessment,\n dialogue_quality=_dq_float,\n )| dq = result.get("dialogue_quality") | ||
| if dq is not None: | ||
| dq = max(0.0, min(1.0, float(dq))) | ||
| if dq >= _DIALOGUE_QUALITY_HIGH: | ||
| signals["dialogue_quality_high"] = min( | ||
| 1.0, (dq - _DIALOGUE_QUALITY_HIGH) / max(1e-6, 1.0 - _DIALOGUE_QUALITY_HIGH) | ||
| ) | ||
| elif dq <= _DIALOGUE_QUALITY_LOW: | ||
| signals["dialogue_quality_low"] = min( | ||
| 1.0, (_DIALOGUE_QUALITY_LOW - dq) / max(1e-6, _DIALOGUE_QUALITY_LOW) | ||
| ) |
There was a problem hiding this comment.
在 DriftSignalExtractor.extract 中提取 dialogue_quality 信号时,如果 dq 无法被转换为 float,或者其值为 NaN / Inf,直接调用 float(dq) 或进行 max/min 裁剪可能会引发异常或导致非预期的计算结果(例如 NaN 会导致后续的比较全部失效)。\n\n建议在此处增加防御性校验,安全地转换并过滤掉 NaN 和 Inf 等异常数值,确保信号提取的稳定性。
dq = result.get("dialogue_quality")\n if dq is not None:\n try:\n dq_val = float(dq)\n if not math.isnan(dq_val) and not math.isinf(dq_val):\n dq_val = max(0.0, min(1.0, dq_val))\n if dq_val >= _DIALOGUE_QUALITY_HIGH:\n signals["dialogue_quality_high"] = min(\n 1.0, (dq_val - _DIALOGUE_QUALITY_HIGH) / max(1e-6, 1.0 - _DIALOGUE_QUALITY_HIGH)\n )\n elif dq_val <= _DIALOGUE_QUALITY_LOW:\n signals["dialogue_quality_low"] = min(\n 1.0, (_DIALOGUE_QUALITY_LOW - dq_val) / max(1e-6, _DIALOGUE_QUALITY_LOW)\n )\n except (ValueError, TypeError):\n passSourcery / gemini-code-assist 两个 reviewer 都指出 float(dialogue_quality) 在 坏输入上会抛异常掀翻整个 tick。values 是不可信输入,确为真漏洞,采纳: - kernel.py: float(_dq) 包 try/except,非数字(str/list/dict)安全回退 None 并记 warning,不让一条坏值中断 process - personality.py extract(): 直接调 spine.process 的路径不经 kernel 守卫,故在此 choke point 兜底——try/except 防坏类型 + 过滤 NaN/Inf(NaN 会让后续比较全失效) - 补 test_dialogue_quality_low_maps_to_traits(Sourcery 建议):验证低质量降表达欲 且不污染无映射特质(relational_gravity 精确 == 0.5) - 补 test_dialogue_quality_bad_input_ignored:坏类型/NaN/±Inf 不抛异常、不产信号 - 格式化两个测试文件(仅本 PR 新增行,ruff format) 全量 500 passed,改动文件 mypy strict + ruff 全过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
已在 commit 704e4fd 回应两位 reviewer 的反馈: @sourcery-ai / @gemini-code-assist 关于 采纳。
@sourcery-ai 关于补 low 方向映射测试(test_personality.py) 采纳,已加 全量 500 passed,改动文件 mypy strict + ruff 全过。
|
背景
Sylanne-next/docs/HANDOFF-sylannengine-drift-upstream.md要求把「对话质量自我进化漂移」里属于 SDK 的动力学部分用 canonical 正道补进远端 main,退役feedback_quality后门。本 PR 在此基础上更进一步:把原本推给 Sylanne-next 侧的「质量信号怎么喂」那半截闭环也直接在 SDK 收掉,让特性开箱即用、并在 AGENT_GUIDE 里教会怎么用。做了什么
完整链路打通,全程走
process()自动漂移通道,无后门:上层 engine / host / kernel 三层签名零改动(质量分走既有的
values数值信号通道透传)。改动清单(5 源 + 2 测试 + 2 文档)
personality.pyDRIFT_SIGNALS加dialogue_quality_high/low两条映射;extract()认result["dialogue_quality"],阈值_HIGH=0.7/_LOW=0.3,中间区不触发resonance_integration.pyResonanceSpine补 canonical_drift_embodiment(照抄ComputationSpine范本)+process()return 前调一次——此前默认 spine 基建就绪却从不漂移;process()加可选dialogue_quality入参computation_spine.pyprocess()加dialogue_quality入参,折进cache_key防缓存吞信号kernel.pyvalues通道透传dialogue_quality给 spineengine.pyprocessdocstring 标注values["dialogue_quality"]用法AGENT_GUIDE.mdCHANGELOG.md关键语义
process()时传入(和feedback_*同理)。expression_fired等信号叠加,不保证绝对方向——故测试用同输入的高/低差分检验,隔离掉共有信号噪声。dialogue_quality时行为完全不变(默认None)。验证
mypy --strict+ruff对全部改动文件通过(仅剩本地环境 cupy/jsonschema 缺 stub 的预存问题,与本 PR 无关,CI 的.[dev]会装上)边界
self_score这类「什么叫好回复」的质量判断仍属应用层(Sylanne-next 的dialogue.py),不进 SDK;本 PR 只把「吃质量分 → 推 trait」的漂移动力学放进引擎。边界清晰。🤖 Generated with Claude Code
Summary by Sourcery
Wire dialogue-quality feedback into the canonical embodiment drift pipeline so dialogue quality scores can influence personality over time via the standard process() path, without changing public engine/kernel/host signatures.
New Features:
Enhancements:
Documentation:
Tests: