generated from coulomb/repo-seed
REUSE-WP-0018-T03: LLM semantic rerank for plan-check
Some checks failed
ci / validate-registry (push) Has been cancelled
Some checks failed
ci / validate-registry (push) Has been cancelled
llm-connect came up locally (mock provider, 127.0.0.1:8080), unblocking this task. reuse_surface/plan_check.py: build_rerank_prompt/request_rerank/apply_rerank, reusing llm_bridge.execute_prompt/extract_json_object (same pattern as maintain_llm.py's request_maintain_patches). New schema schemas/plan-check-rerank.schema.json rejects malformed responses (missing fields, non-JSON, invented candidate ids outside the input set) rather than guessing. Per design principle 3 (deterministic matches always rank first), apply_rerank appends LLM-scored entries after the deterministic list (kind: 'llm') instead of reordering it -- the trusted base result is identical with or without the rerank pass. Graceful skip via a 'notes' field when LLM_CONNECT_URL is unset or the response is malformed, mirroring maintain.py's no_llm/skip pattern. New --llm-url/--no-llm CLI flags. Also extended plan-check-result.schema.json for 'notes' and the (pre-existing, previously unschema'd) 'filed_capability_request' field. 9 new pytest cases; 89 total pass. Live-verified against the running llm-connect instance: it correctly rejected the mock provider's non-JSON response and surfaced the skip note, with the deterministic verdict and match order completely unaffected. REUSE-WP-0018 is now fully done (T01-T06). Updated docs/IntentScopeGapAnalysis.md priority 29 to Closed and the workplan's own frontmatter status to finished. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,15 @@ from __future__ import annotations
|
||||
import json
|
||||
|
||||
from reuse_surface.plan_check import (
|
||||
Match,
|
||||
MatchQuery,
|
||||
apply_rerank,
|
||||
load_query_from_intent,
|
||||
load_query_from_workplan,
|
||||
maybe_file_capability_request,
|
||||
match_query,
|
||||
record_outcome,
|
||||
request_rerank,
|
||||
run_plan_check,
|
||||
verdict_for_score,
|
||||
)
|
||||
@@ -196,3 +199,153 @@ def test_cmd_plan_check_rejects_both_inputs(tmp_path):
|
||||
workplan.write_text("---\nid: X\n---\nbody")
|
||||
exit_code = main(["plan-check", str(workplan), "--intent", "also this"])
|
||||
assert exit_code == 1
|
||||
|
||||
|
||||
# --- T03: LLM rerank ---
|
||||
|
||||
SAMPLE_MATCHES = [
|
||||
Match(
|
||||
id="capability.infotech.issue-tracking",
|
||||
score=0.36,
|
||||
kind="deterministic",
|
||||
vector="D4 / A2 / C2 / R1",
|
||||
owner="issue-core",
|
||||
summary="Unified interface for issue tracking.",
|
||||
),
|
||||
Match(
|
||||
id="capability.audit.event-retain",
|
||||
score=0.12,
|
||||
kind="deterministic",
|
||||
vector="D4 / A2 / C2 / R1",
|
||||
owner="audit-core",
|
||||
summary="Collect and retain audit events.",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_request_rerank_valid_response(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.execute_prompt",
|
||||
lambda prompt, **kwargs: json.dumps(
|
||||
{
|
||||
"candidates": [
|
||||
{"id": "capability.infotech.issue-tracking", "confidence": 0.9, "rationale": "strong match"},
|
||||
{"id": "capability.audit.event-retain", "confidence": 0.1, "rationale": "unrelated"},
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
query = load_query_from_intent("issue tracking across platforms")
|
||||
result = request_rerank(query, SAMPLE_MATCHES)
|
||||
assert len(result) == 2
|
||||
assert result[0]["confidence"] == 0.9
|
||||
|
||||
|
||||
def test_request_rerank_rejects_malformed_response(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.execute_prompt",
|
||||
lambda prompt, **kwargs: json.dumps({"candidates": [{"id": "capability.infotech.issue-tracking"}]}),
|
||||
)
|
||||
query = load_query_from_intent("issue tracking across platforms")
|
||||
try:
|
||||
request_rerank(query, SAMPLE_MATCHES)
|
||||
assert False, "expected ValueError for missing confidence field"
|
||||
except ValueError as exc:
|
||||
assert "schema validation failed" in str(exc)
|
||||
|
||||
|
||||
def test_request_rerank_rejects_non_json_response(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.execute_prompt",
|
||||
lambda prompt, **kwargs: "not json at all",
|
||||
)
|
||||
query = load_query_from_intent("issue tracking across platforms")
|
||||
try:
|
||||
request_rerank(query, SAMPLE_MATCHES)
|
||||
assert False, "expected ValueError for non-JSON response"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def test_request_rerank_ignores_invented_ids(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.execute_prompt",
|
||||
lambda prompt, **kwargs: json.dumps(
|
||||
{
|
||||
"candidates": [
|
||||
{"id": "capability.infotech.issue-tracking", "confidence": 0.8},
|
||||
{"id": "capability.madeup.not-real", "confidence": 0.99},
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
query = load_query_from_intent("issue tracking across platforms")
|
||||
result = request_rerank(query, SAMPLE_MATCHES)
|
||||
assert [c["id"] for c in result] == ["capability.infotech.issue-tracking"]
|
||||
|
||||
|
||||
def test_apply_rerank_appends_after_deterministic_matches():
|
||||
rerank_result = [
|
||||
{"id": "capability.audit.event-retain", "confidence": 0.95, "rationale": "actually a great fit"},
|
||||
]
|
||||
combined = apply_rerank(SAMPLE_MATCHES, rerank_result)
|
||||
# Deterministic matches keep their original order and scores untouched.
|
||||
assert combined[0].id == "capability.infotech.issue-tracking"
|
||||
assert combined[0].score == 0.36
|
||||
assert combined[0].kind == "deterministic"
|
||||
assert combined[1].id == "capability.audit.event-retain"
|
||||
assert combined[1].score == 0.12
|
||||
assert combined[1].kind == "deterministic"
|
||||
# LLM entry is appended after, separately labeled.
|
||||
assert combined[2].id == "capability.audit.event-retain"
|
||||
assert combined[2].score == 0.95
|
||||
assert combined[2].kind == "llm"
|
||||
|
||||
|
||||
def test_run_plan_check_skips_llm_gracefully_when_unset(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.load_federated_capabilities",
|
||||
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
|
||||
)
|
||||
monkeypatch.delenv("LLM_CONNECT_URL", raising=False)
|
||||
query = load_query_from_intent("issue tracking gitea github gitlab")
|
||||
result = run_plan_check(query)
|
||||
assert result["verdict"] in {"reuse", "extend"}
|
||||
assert any("LLM_CONNECT_URL not set" in n for n in result.get("notes", []))
|
||||
assert all(m["kind"] == "deterministic" for m in result["matches"])
|
||||
|
||||
|
||||
def test_run_plan_check_no_llm_flag_skips_without_attempting(monkeypatch):
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.load_federated_capabilities",
|
||||
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.request_rerank",
|
||||
lambda *a, **k: called.append(1) or [],
|
||||
)
|
||||
query = load_query_from_intent("issue tracking gitea github gitlab")
|
||||
run_plan_check(query, use_llm=False)
|
||||
assert called == []
|
||||
|
||||
|
||||
def test_run_plan_check_integrates_successful_rerank(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.load_federated_capabilities",
|
||||
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"reuse_surface.plan_check.execute_prompt",
|
||||
lambda prompt, **kwargs: json.dumps(
|
||||
{"candidates": [{"id": "capability.infotech.issue-tracking", "confidence": 0.77}]}
|
||||
),
|
||||
)
|
||||
query = load_query_from_intent("issue tracking gitea github gitlab")
|
||||
result = run_plan_check(query)
|
||||
assert "notes" not in result or not result["notes"]
|
||||
llm_entries = [m for m in result["matches"] if m["kind"] == "llm"]
|
||||
assert len(llm_entries) == 1
|
||||
assert llm_entries[0]["score"] == 0.77
|
||||
# deterministic top match is still first and unaffected
|
||||
assert result["matches"][0]["kind"] == "deterministic"
|
||||
|
||||
Reference in New Issue
Block a user