Skip to content

Add conflict resolution test and check semantic release - #82

Merged
illeatmyhat merged 7 commits into
mainfrom
llm-tests
Mar 3, 2026
Merged

Add conflict resolution test and check semantic release#82
illeatmyhat merged 7 commits into
mainfrom
llm-tests

Conversation

@illeatmyhat

@illeatmyhat illeatmyhat commented Feb 27, 2026

Copy link
Copy Markdown
Collaborator

A starting point for testing LLM-based conflict resolution

Summary by CodeRabbit

  • Documentation

    • Expanded coding standards with Conventional Commits guidance and an updated pre-commit/commit workflow.
  • Tests

    • Added extensive unit and LLM-driven test suites for conflict resolution, covering ADD/UPDATE/DELETE/NONE, parsing edge cases, retries, and many scenario permutations.
  • Chores

    • Added an auto-format-before-commit target and adjusted test filtering via a new marker.
  • Behavior

    • Refined conflict-resolution semantics to preserve IDs and tighten rules for updates, deletions, and no-change cases.

- Extract unit tests from tests/llm/test_conflict_resolution.py into tests/unit/test_conflict_resolution.py
- Register 'llm' pytest marker in pyproject.toml for LLM integration tests
@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Updated developer workflow docs and tooling (pre-commit/commit message target, pytest marker); refined conflict-resolution prompt logic to preserve IDs and tighten update/delete/no-change rules; added extensive unit and LLM-driven tests covering parsing, retry, and many scenario cases.

Changes

Cohort / File(s) Summary
Dev workflow & config
AGENTS.md, justfile, pyproject.toml
Added explicit pre-commit formatting sequence and Conventional Commits guidance; new justfile target commit message to run ruff, stage, and commit; added llm pytest marker and updated test filter options.
Conflict-resolution prompts
kaizen/llm/conflict_resolution/prompts/conflict_resolution.jinja2, kaizen/llm/conflict_resolution/prompts/default_conflict_resolution.jinja2
Changed ADD guidance to preserve retrieved entity IDs; tightened UPDATE rules to require concrete new information (not paraphrase); narrowed DELETE criteria to require explicit contradiction/negation; updated examples and event semantics.
Conflict-resolution unit tests
tests/unit/test_conflict_resolution.py
Added comprehensive unit tests covering RecordedEntity→SimpleEntity conversion, prompt generation, parsing (including markdown-wrapped JSON), retry logic, error paths, and LLM-mocked responses for ADD/UPDATE/DELETE/NONE scenarios.
LLM-driven scenario tests
tests/llm/test_long_conflict_resolution.py
Added a large, parameterized scenario suite (many cases) exercising resolve_conflicts across ADD/UPDATE/DELETE/NONE and ambiguous outcomes; validates per-entity expected events and accepts multiple valid outcomes where applicable.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Resolver as ConflictResolver
  participant LLM
  participant Parser
  participant Store as Datastore

  Client->>Resolver: resolve_conflicts(old_entities, new_entities, prompt_template)
  Resolver->>LLM: send compiled prompt (default_conflict_resolution / conflict_resolution)
  LLM-->>Resolver: return classification JSON (may be wrapped in markdown)
  Resolver->>Parser: parse & validate JSON (handle markdown, retry on transient errors)
  Parser-->>Resolver: structured events per-entity (ADD/UPDATE/DELETE/NONE)
  Resolver->>Store: apply events or return decisions (preserve IDs, attach old_entity where needed)
  Resolver-->>Client: return result_by_id
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Add AGENTS.md #39: Overlaps with AGENTS.md updates and ruff/formatting workflow adjustments.

Suggested reviewers

  • vinodmut
  • visahak
  • gaodan-fang

Poem

🐰 I hop through prompts and tests with glee,
I keep each ID where it ought to be,
I nudge ruff to tidy every line,
Then nibble bugs until they’re fine,
A carrot-coded patch — hooray for me! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title mentions 'Add conflict resolution test' which aligns with the large test suite additions, but 'check semantic release' is vague and not clearly connected to the actual changes in the PR. Clarify the title to be more specific: either focus on the main change (e.g., 'Add LLM-based conflict resolution tests') or explain what 'check semantic release' refers to if it is a key objective.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch llm-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pyproject.toml (1)

63-70: ⚠️ Potential issue | 🟠 Major

Exclude llm tests from the default pytest run.

Line 64 still uses -m 'not phoenix', so @pytest.mark.llm tests run by default despite the test module documentation explicitly stating they are "excluded from the default run." This will cause failures in default test runs when no LLM backend is configured.

💡 Suggested fix
 [tool.pytest.ini_options]
-addopts = "--ignore=explorations -m 'not phoenix'"
+addopts = "--ignore=explorations -m 'not phoenix and not llm'"
 markers = [
     "e2e",
     "unit",
     "phoenix",
     "llm"
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 63 - 70, The pytest default options currently
exclude only phoenix tests; update the addopts under [tool.pytest.ini_options]
so that marker llm is also excluded by default (e.g., change the -m expression
to exclude both phoenix and llm). Locate the addopts setting in pyproject.toml
and modify its marker expression (the value assigned to addopts) to include "not
llm" alongside "not phoenix" so `@pytest.mark.llm` tests are skipped when running
the default test suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/test_conflict_resolution.py`:
- Line 1: Two test files share the same module name 'test_conflict_resolution',
causing mypy duplicate-module errors; rename one of them (for example change the
unit test module named test_conflict_resolution to test_conflict_resolution_unit
or test_conflict_resolution_unit_tests) and update any references/imports or
test discovery config so the module name no longer collides (ensure the test
runner and any imports that referenced test_conflict_resolution now point to the
new module name).

---

Outside diff comments:
In `@pyproject.toml`:
- Around line 63-70: The pytest default options currently exclude only phoenix
tests; update the addopts under [tool.pytest.ini_options] so that marker llm is
also excluded by default (e.g., change the -m expression to exclude both phoenix
and llm). Locate the addopts setting in pyproject.toml and modify its marker
expression (the value assigned to addopts) to include "not llm" alongside "not
phoenix" so `@pytest.mark.llm` tests are skipped when running the default test
suite.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b3a8e8 and 688c049.

📒 Files selected for processing (5)
  • AGENTS.md
  • justfile
  • pyproject.toml
  • tests/llm/test_conflict_resolution.py
  • tests/unit/test_conflict_resolution.py

Comment thread tests/unit/test_conflict_resolution.py
test(llm)!: improve conflict resolution prompt clarity and add comprehensive test suite

- Enhanced UPDATE operation to only trigger on concrete new information, not mere rewordings
- Clarified DELETE operation to explicitly handle negation phrases like "no longer", "switched away from", "quit"
- Improved NONE operation to properly handle paraphrases and synonyms
- Fixed ADD operation to use retrieved entity ID instead of generating new ID
- Added extensive examples for each operation type with realistic scenarios
- Replaced old test file with comprehensive parametrized test suite covering 60+ scenarios

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kaizen/llm/conflict_resolution/prompts/conflict_resolution.jinja2 (1)

22-36: ⚠️ Potential issue | 🟠 Major

Resolve conflicting ADD ID instructions.

Line 22 says additions can use a new ID, while Line 36 says additions must use the retrieved entity ID. This contradiction can cause inconsistent ADD outputs.

Suggested fix
-            "id" : "<ID of the entity>",                # Use existing ID for updates/deletes, or new ID for additions
+            "id" : "<ID of the entity>",                # Use existing ID for updates/deletes; for additions, use the retrieved entity ID
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kaizen/llm/conflict_resolution/prompts/conflict_resolution.jinja2` around
lines 22 - 36, The prompt has a contradiction between the "id" field comment
that allows a "new ID for additions" and the later rule "If there is an
addition, use the ID from the retrieved entity"; update the template so the "id"
comment and any related bullet points consistently require using the retrieved
entity's ID for ADD events (remove or replace the "new ID for additions"
wording), and ensure the "event" rules and the sentence "If there is an
addition, use the ID from the retrieved entity." remain aligned with the ADD
requirement; search for the "id" field, the "event" field, and the sentence "If
there is an addition, use the ID from the retrieved entity." to make the edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kaizen/llm/conflict_resolution/prompts/default_conflict_resolution.jinja2`:
- Around line 119-123: The example for the entity with "id": "2" (type "fact")
is inconsistent: it has "event": "NONE" but the "content" changes from "Likes
racing" to "Likes cheese pizza"; update the example so that a NONE event
preserves the original content (restore "content" to "Likes racing") or, if a
content change was intended, change "event" to a modifying value (e.g.,
"MODIFIED") to match the new content.

In `@tests/llm/test_long_conflict_resolution.py`:
- Around line 114-121: The test currently treats expect_any_of as requiring all
listed per-ID events simultaneously which rejects valid mutually-exclusive
outcomes; update the assertion logic in test_conflict_resolution_scenarios so
that expect_any_of is evaluated as a disjunction (any(...) matches) rather than
a conjunction, i.e., for each candidate in expect_any_of accept the entire
candidate map as an alternative valid result and pass if any candidate map fully
matches the actual per-ID results; locate and change the code that
iterates/compares expect_any_of (references: expect_any_of, expect, and the
test_conflict_resolution_scenarios function) so it checks any(candidate_matches)
instead of requiring all expected IDs/events at once.
- Around line 606-607: The test file uses a non-approved pytest marker
"@pytest.mark.llm"; replace that marker with one of the approved markers
("unit", "e2e", or "phoenix") in the test declaration (see the decorator above
the parametrized test in tests/llm/test_long_conflict_resolution.py) so the test
uses the permitted marker naming convention; choose the marker that best
describes the test scope (e.g., "unit" for fast isolated tests, "e2e" for
integration-like flows, or "phoenix" for special suite) and update the decorator
accordingly.

---

Outside diff comments:
In `@kaizen/llm/conflict_resolution/prompts/conflict_resolution.jinja2`:
- Around line 22-36: The prompt has a contradiction between the "id" field
comment that allows a "new ID for additions" and the later rule "If there is an
addition, use the ID from the retrieved entity"; update the template so the "id"
comment and any related bullet points consistently require using the retrieved
entity's ID for ADD events (remove or replace the "new ID for additions"
wording), and ensure the "event" rules and the sentence "If there is an
addition, use the ID from the retrieved entity." remain aligned with the ADD
requirement; search for the "id" field, the "event" field, and the sentence "If
there is an addition, use the ID from the retrieved entity." to make the edits.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 688c049 and 6eb66c9.

📒 Files selected for processing (3)
  • kaizen/llm/conflict_resolution/prompts/conflict_resolution.jinja2
  • kaizen/llm/conflict_resolution/prompts/default_conflict_resolution.jinja2
  • tests/llm/test_long_conflict_resolution.py

Comment thread tests/llm/test_long_conflict_resolution.py
Comment thread tests/llm/test_long_conflict_resolution.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
tests/llm/test_long_conflict_resolution.py (2)

606-607: ⚠️ Potential issue | 🟠 Major

Replace @pytest.mark.llm with an approved test marker.

This suite is currently categorized with a non-approved marker. Please use one of unit, e2e, or phoenix (and update the module docstring run command accordingly).

#!/bin/bash
set -euo pipefail

echo "=== Declared markers in pyproject.toml ==="
fd '^pyproject\.toml$' --exec rg -n '^\s*markers\s*=|^\s*"(unit|e2e|phoenix|llm)\b' {}

echo
echo "=== Marker usage in this file ==="
rg -n --type=py '@pytest\.mark\.(unit|e2e|phoenix|llm)\b' tests/llm/test_long_conflict_resolution.py

As per coding guidelines, tests/**/*.py: Use pytest markers unit, e2e, and phoenix to categorize tests appropriately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/test_long_conflict_resolution.py` around lines 606 - 607, Replace
the non-approved pytest marker `@pytest.mark.llm` with one of the approved markers
(unit, e2e, or phoenix) throughout this module (look for the decorator symbol
"@pytest.mark.llm") and update the module docstring's example/test run command
to reflect the chosen marker; ensure any other occurrences in this file (e.g.,
top-level decorators on tests or class definitions) are updated to the same
approved marker so marker usage and the docstring remain consistent.

623-630: ⚠️ Potential issue | 🟠 Major

Per-ID assertion logic still can reject valid mutually-exclusive outcomes.

The current loop validates each expected ID independently, which can force incompatible combinations in ambiguous scenarios. Model alternatives should be validated as whole-result disjunctions (any(candidate_map_matches)), not merged per-ID constraints.

Suggested direction
-class ConflictScenario(TypedDict):
+class ConflictScenario(TypedDict, total=False):
     label: str
     old: list[RecordedEntity]
     new: list[RecordedEntity]
     expect: dict[str, str | list[str]]
+    expect_any_of: list[dict[str, str | list[str]]]

+def _matches_expected(
+    result_by_id: dict[str, object], expected: dict[str, str | list[str]]
+) -> bool:
+    for entity_id, expected_event in expected.items():
+        if entity_id not in result_by_id:
+            return False
+        actual_event = result_by_id[entity_id].event
+        if isinstance(expected_event, list):
+            if actual_event not in expected_event:
+                return False
+        elif actual_event != expected_event:
+            return False
+    return True
+
 def test_conflict_resolution_scenarios(scenario: ConflictScenario) -> None:
     result_by_id = {update.id: update for update in resolve_conflicts(scenario["old"], scenario["new"])}
-    for entity_id, expected_event in scenario["expect"].items():
-        ...
+    if "expect_any_of" in scenario:
+        assert any(_matches_expected(result_by_id, candidate) for candidate in scenario["expect_any_of"])
+    else:
+        assert _matches_expected(result_by_id, scenario["expect"])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/test_long_conflict_resolution.py` around lines 623 - 630, The
per-ID assertions in the test loop force incompatible combinations; instead,
build the full actual mapping from resolve_conflicts (result_by_id -> map of id
to event) and assert that this entire mapping matches at least one allowed
expected combination for the scenario: replace the per-entity assertions in the
loop over scenario["expect"] with logic that constructs actual_map = {id:
update.event for update in resolve_conflicts(scenario["old"], scenario["new"])}
and then check any(expected_combination_matches(actual_map)) where
expected_combination_matches verifies for every id in the expected combination
that actual_map[id] equals the expected value or is contained in the expected
list; reference resolve_conflicts, result_by_id/actual_map, scenario["expect"],
expected_event and actual_event to locate and implement this change.
🧹 Nitpick comments (1)
tests/llm/test_long_conflict_resolution.py (1)

23-25: Use a fixed timestamp in test entities to reduce run-to-run variance.

datetime.now() makes fixture payloads non-deterministic across runs. For real-LLM tests, a constant created_at improves reproducibility.

Proposed change
 from datetime import datetime
@@
+FIXED_CREATED_AT = datetime(2025, 1, 1, 0, 0, 0)
+
 def _entity(entity_id: str, content: str, entity_type: str = "fact") -> RecordedEntity:
     """Shorthand factory for a RecordedEntity."""
-    return RecordedEntity(id=entity_id, type=entity_type, content=content, metadata={}, created_at=datetime.now())
+    return RecordedEntity(
+        id=entity_id,
+        type=entity_type,
+        content=content,
+        metadata={},
+        created_at=FIXED_CREATED_AT,
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/test_long_conflict_resolution.py` around lines 23 - 25, The _entity
factory uses datetime.now() making test payloads non-deterministic; replace the
dynamic timestamp with a fixed constant (e.g., a module-level TEST_TIMESTAMP =
datetime(2020, 1, 1, 0, 0, 0)) and have _entity pass that constant as created_at
when constructing the RecordedEntity so all test entities use the same stable
timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/llm/test_long_conflict_resolution.py`:
- Around line 606-607: Replace the non-approved pytest marker `@pytest.mark.llm`
with one of the approved markers (unit, e2e, or phoenix) throughout this module
(look for the decorator symbol "@pytest.mark.llm") and update the module
docstring's example/test run command to reflect the chosen marker; ensure any
other occurrences in this file (e.g., top-level decorators on tests or class
definitions) are updated to the same approved marker so marker usage and the
docstring remain consistent.
- Around line 623-630: The per-ID assertions in the test loop force incompatible
combinations; instead, build the full actual mapping from resolve_conflicts
(result_by_id -> map of id to event) and assert that this entire mapping matches
at least one allowed expected combination for the scenario: replace the
per-entity assertions in the loop over scenario["expect"] with logic that
constructs actual_map = {id: update.event for update in
resolve_conflicts(scenario["old"], scenario["new"])} and then check
any(expected_combination_matches(actual_map)) where expected_combination_matches
verifies for every id in the expected combination that actual_map[id] equals the
expected value or is contained in the expected list; reference
resolve_conflicts, result_by_id/actual_map, scenario["expect"], expected_event
and actual_event to locate and implement this change.

---

Nitpick comments:
In `@tests/llm/test_long_conflict_resolution.py`:
- Around line 23-25: The _entity factory uses datetime.now() making test
payloads non-deterministic; replace the dynamic timestamp with a fixed constant
(e.g., a module-level TEST_TIMESTAMP = datetime(2020, 1, 1, 0, 0, 0)) and have
_entity pass that constant as created_at when constructing the RecordedEntity so
all test entities use the same stable timestamp.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 270034c and 668ad67.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • pyproject.toml
  • tests/llm/test_long_conflict_resolution.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

@illeatmyhat
illeatmyhat merged commit 2c715dc into main Mar 3, 2026
15 checks passed
@illeatmyhat
illeatmyhat deleted the llm-tests branch March 3, 2026 18:28
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