Skip to content

feat: 对话质量自我进化(CP8-P4 越聊越校准)端到端贯通#16

Merged
Ayleovelle merged 2 commits into
mainfrom
feat/dialogue-quality-drift
Jun 14, 2026
Merged

feat: 对话质量自我进化(CP8-P4 越聊越校准)端到端贯通#16
Ayleovelle merged 2 commits into
mainfrom
feat/dialogue-quality-drift

Conversation

@Ayleovelle

@Ayleovelle Ayleovelle commented Jun 14, 2026

Copy link
Copy Markdown
Owner

背景

Sylanne-next/docs/HANDOFF-sylannengine-drift-upstream.md 要求把「对话质量自我进化漂移」里属于 SDK 的动力学部分用 canonical 正道补进远端 main,退役 feedback_quality 后门。本 PR 在此基础上更进一步:把原本推给 Sylanne-next 侧的「质量信号怎么喂」那半截闭环也直接在 SDK 收掉,让特性开箱即用、并在 AGENT_GUIDE 里教会怎么用。

做了什么

完整链路打通,全程走 process() 自动漂移通道,无后门

engine.process(values={"dialogue_quality": q})
  → kernel 从 values 取出
  → spine.process(dialogue_quality=q)
  → 写进 result
  → DriftSignalExtractor 产 dialogue_quality_high/low
  → _drift_embodiment 漂人格

上层 engine / host / kernel 三层签名零改动(质量分走既有的 values 数值信号通道透传)。

改动清单(5 源 + 2 测试 + 2 文档)

文件 改动
personality.py DRIFT_SIGNALSdialogue_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.md 新增「场景 5:越聊越校准」带代码示例 + 滞后反馈时序说明;漂移信号表补两条 + 脚注
CHANGELOG.md 记端到端贯通

关键语义

  • 滞后反馈:质量分是对「上一轮回复」的评价,要在「下一轮」调 process() 时传入(和 feedback_* 同理)。
  • 质量信号是「推一把」不是「说了算」:它与 expression_fired 等信号叠加,不保证绝对方向——故测试用同输入的高/低差分检验,隔离掉共有信号噪声。
  • 不传 dialogue_quality 时行为完全不变(默认 None)。

验证

  • 全量测试 498 passed(含本 PR 新增 9 个:extract 四分支 / 映射方向 / spine 接线 / 高质量抬升 / 高低差分)
  • 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:

  • Support passing dialogue_quality scores through engine values to computation and resonance spines to drive automatic embodiment drift.
  • Introduce dialogue_quality_high and dialogue_quality_low drift signals so high- and low-quality replies modulate expression drive and relational gravity traits.

Enhancements:

  • Enable canonical embodiment drift in ResonanceSpine so it now applies personality drift on each process() result like ComputationSpine, including rate limiting and trait reapplication logic.
  • Include dialogue_quality in computation caching keys and results to ensure quality feedback is respected in cached responses and exposed to the drift extractor.

Documentation:

  • Document the dialogue_quality feedback loop and usage in AGENT_GUIDE, including timing semantics and new drift signals, and record the feature in the changelog.

Tests:

  • Add tests covering dialogue_quality signal extraction, mapping to embodiment traits, resonance embodiment drift wiring, and behavioral differences between high and low dialogue quality, plus the optionality of dialogue_quality.

把 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>
@sourcery-ai

sourcery-ai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements 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 drift

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Wire dialogue_quality drift signal semantics into personality drift mapping and extraction.
  • Introduce dialogue-quality thresholds and map dialogue_quality_high/low signals onto expression_drive_trait and relational_gravity in the DRIFT_SIGNALS map.
  • Extend DriftSignalExtractor.extract() to read result["dialogue_quality"], clamp it to [0,1], and emit dialogue_quality_high or dialogue_quality_low based on high/low thresholds with normalized strength.
  • Add unit tests covering dialogue_quality high/low/mid/absent extraction behavior and trait mapping effects via compute_embodiment_drift.
sylanne_core/compute/personality.py
tests/test_personality.py
Make ResonanceSpine participate in canonical embodiment drift and accept dialogue_quality as an optional input driving drift.
  • Add an optional dialogue_quality: float
None parameter to ResonanceSpine.process() and document its semantics in the docstring.
  • Ensure ResonanceSpine.process() writes dialogue_quality into the result payload when provided, calls _drift_embodiment(result) before returning, and advances internal drift_tick.
  • Implement _drift_embodiment() in ResonanceSpine mirroring ComputationSpine’s logic: rate limiting based on _drift_min_interval, extracting signals, calling compute_embodiment_drift, and reapplying personality only when traits change beyond a small threshold.
  • Add tests verifying that process() triggers drift, high dialogue_quality increases expression_drive_trait, low vs high dialogue_quality produce differential expression_drive_trait, and that dialogue_quality is optional and not present in results when not passed.
  • Extend ComputationSpine to consume dialogue_quality canonically and make it cache-safe.
    • Add an optional dialogue_quality: float
    None parameter to ComputationSpine.process() with documented lagging-feedback semantics.
  • Include dialogue_quality in the cache_key tuple so different quality scores don’t hit the same cached result and swallow the signal.
  • When dialogue_quality is provided, attach it to the result dict before calling _drift_embodiment() in both fast and slow code paths, and cache the updated result.
  • Plumb dialogue_quality from engine.process() values through the kernel into the spine layer without changing public signatures.
    • In Kernel._tick_inner, read dialogue_quality from event.values, cast it to float when present, and pass it as the dialogue_quality argument to self.computation.process().
    • Clarify in Engine.process() docstring that values["dialogue_quality"] is a normalized [0,1] lagging quality score for the previous turn’s reply, which modulates personality drift (higher scores increasing expression drive and relational gravity, lower scores suppressing expression drive).
    sylanne_core/compute/kernel.py
    sylanne_core/engine.py
    Document the dialogue-quality-driven self-calibration feature and surface it in the changelog.
    • Extend the AGENT_GUIDE drift signal table with dialogue_quality_high/low rows and an explanatory footnote describing how agents inject normalized dialogue quality via values["dialogue_quality"], plus a new scenario section showing end-to-end usage and timing (lagging feedback) at both engine and spine layers.
    • Add CHANGELOG entries describing ResonanceSpine canonical embodiment drift wiring and the end-to-end dialogue quality self-evolution pipeline, including thresholds, new signals, and the unchanged behavior when dialogue_quality is omitted.
    AGENT_GUIDE.md
    CHANGELOG.md

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @github-actions

    github-actions Bot commented Jun 14, 2026

    Copy link
    Copy Markdown

    ⚠️ 代码质量检查报告

    发现部分文件需要格式化或存在代码问题,建议在本地运行 ruff format .ruff check --fix .

    🖌️ 格式化: 9 个文件被重新格式化 (未变更: 80)
    🛡️ 代码检查: 发现 6 个问题

    • 🛠️ 已自动修复: 4
    • ❌ 剩余未修复: 2
    🔍 错误详情

    ```textF841 Local variable h is assigned to but never used
    --> training/train_model.py:121:9
    |
    119 | rng = np.random.RandomState(0)
    120 | d = self.d_model
    121 | h = self.n_heads
    | ^
    122 | dh = self.d_head
    123 | ff = self.d_ff
    |
    help: Remove assignment to unused variable `h`

    F841 Local variable dh is assigned to but never used
    --> training/train_model.py:122:9
    |
    120 | d = self.d_model
    121 | h = self.n_heads
    122 | dh = self.d_head
    | ^^
    123 | ff = self.d_ff
    |
    help: Remove assignment to unused variable dh

    Found 6 errors (4 fixed, 2 remaining).
    No fixes available (2 hidden fixes can be enabled with the --unsafe-fixes option).

    </details>
    

    @github-actions

    github-actions Bot commented Jun 14, 2026

    Copy link
    Copy Markdown

    💩 屎山代码检测报告 允许合并

    🎯 本次检测概览

    「屎山刚开始发酵,臭味初显,但还能抢救」

    维度 数据
    屎山指数 41.26 / 100
    屎山等级 轻度屎山级
    检测时间 2026/6/14 20:22:40

    🎭 整体印象

    臭气扑鼻,建议佩戴口罩阅读

    🧭 下一步

    这代码像个叛逆期的青少年,需要适当管教才能成才


    📥 详细数据

    fuck-u-code 自动检测 · 本评论不代表人类观点

    @sourcery-ai sourcery-ai Bot left a comment

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Hey - I've found 2 issues, and left some high level feedback:

    • 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.
    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>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Comment thread sylanne_core/compute/kernel.py Outdated
    Comment thread tests/test_personality.py
    Comment on lines +111 to +127
    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

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    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.

    Suggested change
    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

    @gemini-code-assist gemini-code-assist Bot left a comment

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    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.

    Comment thread sylanne_core/compute/kernel.py Outdated
    Comment on lines 242 to 248
    _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,
    )

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    medium

    在将 dialogue_quality 转换为 float 时,如果传入的 _dq 不是合法的数字(例如用户在 values 中传入了非数字字符串或其它类型),直接调用 float(_dq) 会抛出 ValueErrorTypeError 异常,从而导致整个 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        )

    Comment thread sylanne_core/compute/personality.py Outdated
    Comment on lines +250 to +260
    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)
    )

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    medium

    DriftSignalExtractor.extract 中提取 dialogue_quality 信号时,如果 dq 无法被转换为 float,或者其值为 NaN / Inf,直接调用 float(dq) 或进行 max/min 裁剪可能会引发异常或导致非预期的计算结果(例如 NaN 会导致后续的比较全部失效)。\n\n建议在此处增加防御性校验,安全地转换并过滤掉 NaNInf 等异常数值,确保信号提取的稳定性。

            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                pass

    Sourcery / 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>
    @Ayleovelle

    Copy link
    Copy Markdown
    Owner Author

    已在 commit 704e4fd 回应两位 reviewer 的反馈:

    @sourcery-ai / @gemini-code-assist 关于 float(dialogue_quality) 健壮性(kernel.py + personality.py)

    采纳。values 是不可信输入,坏类型确实会掀翻整个 tick,是真漏洞:

    • kernel.pyfloat(_dq)try/except,非数字(str/list/dict)安全回退 None 并记 warning
    • personality.py extract():直接调 spine.process(dialogue_quality=...) 的路径不经 kernel 守卫,故在这个 choke point 兜底——try/except 防坏类型 + 过滤 NaN/Inf(NaN 会让后续阈值比较全部失效)

    @sourcery-ai 关于补 low 方向映射测试(test_personality.py)

    采纳,已加 test_dialogue_quality_low_maps_to_traits:验证低质量降表达欲、且不污染无映射特质(relational_gravity 精确 == 0.5,已核 compute_embodiment_drift 只遍历有信号的映射,无信号特质不被 update,断言成立)。另补 test_dialogue_quality_bad_input_ignored 覆盖坏类型/NaN/±Inf。

    全量 500 passed,改动文件 mypy strict + ruff 全过。

    注:CI 的 ruff 检查仍显示红,是 main 预存的全仓格式债(8 个与本 PR 无关的文件未排版 + training/ 脚本的 4 个 lint 错),非本 PR 引入——与 #14/#15 合并时情况一致。训练脚本按 owner 决定暂不上传/不在本 PR 处理。

    @Ayleovelle Ayleovelle merged commit 6cfde32 into main Jun 14, 2026
    7 of 8 checks passed
    @Ayleovelle Ayleovelle deleted the feat/dialogue-quality-drift branch June 14, 2026 12:25
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    1 participant