Files
microsoft--skillopt/tests/test_semantic_density.py
2026-07-13 12:24:16 +08:00

125 lines
5.1 KiB
Python

"""Tests for semantic density heuristic in the validation gate."""
from __future__ import annotations
import unittest
from skillopt.evaluation.gate import (
compute_semantic_density,
select_gate_score,
evaluate_gate,
)
class TestSemanticDensity(unittest.TestCase):
"""Test suite for semantic density scoring and gating decisions."""
def test_compute_semantic_density_basic(self) -> None:
"""Verify basic compute_semantic_density behaviour with default words."""
# 10 words, 2 leading words ("always", "never") -> 0.2 density
skill = "Always check the inputs and never mix up proxy values."
density = compute_semantic_density(skill)
self.assertAlmostEqual(density, 0.2)
# Empty skill should have 0 density
self.assertEqual(compute_semantic_density(""), 0.0)
self.assertEqual(compute_semantic_density(" \n "), 0.0)
def test_compute_semantic_density_custom_leading_words(self) -> None:
"""Verify compute_semantic_density with custom leading words."""
skill = "Check the inputs carefully and resolve the equation."
leading = ["check", "resolve"]
# 8 words, 2 custom leading words -> 0.25 density
density = compute_semantic_density(skill, leading_words=leading)
self.assertAlmostEqual(density, 0.25)
def test_compute_semantic_density_with_protected_regions(self) -> None:
"""Verify protected comments are excluded from density calculation."""
skill = (
"Always check inputs.\n"
"<!-- SLOW_UPDATE_START -->\n"
"This contains many words that should not count towards density "
"always and never and only.\n"
"<!-- SLOW_UPDATE_END -->\n"
"<!-- APPENDIX_START -->\n"
"More excluded words.\n"
"<!-- APPENDIX_END -->\n"
)
# Without stripping, there would be many more words and a different density.
# Stripped text: "Always check inputs." -> 3 words, 1 leading word ("always") -> 1/3 density
density = compute_semantic_density(skill)
self.assertAlmostEqual(density, 1.0 / 3.0)
def test_select_gate_score_no_density(self) -> None:
"""Verify select_gate_score without semantic density adjustment."""
# Default behavior: no semantic density adjustment
score_hard = select_gate_score(0.8, 0.6, metric="hard")
self.assertEqual(score_hard, 0.8)
score_soft = select_gate_score(0.8, 0.6, metric="soft")
self.assertEqual(score_soft, 0.6)
score_mixed = select_gate_score(0.8, 0.6, metric="mixed", mixed_weight=0.5)
self.assertAlmostEqual(score_mixed, 0.7)
def test_select_gate_score_with_density(self) -> None:
"""Verify select_gate_score with semantic density adjustment."""
# 10 words, 2 leading words ("always", "never") -> 0.2 density
skill = "Always check the inputs and never mix up proxy values."
# bonus: 0.1 (weight) * 0.2 (density) = 0.02
score = select_gate_score(
hard=0.8,
soft=0.6,
metric="hard",
skill_content=skill,
use_semantic_density=True,
semantic_density_weight=0.1,
)
self.assertAlmostEqual(score, 0.82)
def test_evaluate_gate_with_density_preference(self) -> None:
"""Verify evaluate_gate prefers candidates with higher semantic density."""
# Baseline/current skill:
# "Always do this task step by step and be very careful because errors are bad."
# 15 words, 1 leading ("always") -> 1/15 density = ~0.0667
current_skill = "Always do this task step by step and be very careful because errors are bad."
# Candidate skill (shorter/more steerable):
# "Always verify outputs. Never mix proxy values."
# 7 words, 3 leading ("always", "verify", "never") -> 3/7 density = ~0.4286
candidate_skill = "Always verify outputs. Never mix proxy values."
# Both have same rollout accuracy (hard=0.8, soft=0.8)
# Baseline/current score: 0.8 + 0.1 * (1/15) = ~0.8067
current_score = select_gate_score(
hard=0.8,
soft=0.8,
metric="hard",
skill_content=current_skill,
use_semantic_density=True,
semantic_density_weight=0.1,
)
# Candidate score: 0.8 + 0.1 * (3/7) = ~0.8429
# Even though accuracy is equal, the candidate should be accepted due to higher semantic density
res = evaluate_gate(
candidate_skill=candidate_skill,
cand_hard=0.8,
current_skill=current_skill,
current_score=current_score,
best_skill=current_skill,
best_score=current_score,
best_step=1,
global_step=2,
cand_soft=0.8,
metric="hard",
use_semantic_density=True,
semantic_density_weight=0.1,
)
self.assertEqual(res.action, "accept_new_best")
self.assertEqual(res.current_skill, candidate_skill)
self.assertAlmostEqual(res.current_score, 0.8 + 0.1 * (3.0 / 7.0))
if __name__ == "__main__":
unittest.main()