multi-agent-system / app /core /consistency_validator.py
firepenguindisopanda
cleanup
1c1cc75
Raw
History Blame Contribute Delete
4.2 kB
"""
ConsistencyValidator - cross-agent contradiction detection.
Validates knowledge graph entities across all role-scoped sub-graphs
to surface contradictions before SRS assembly.
The validator is purely deterministic (no LLM calls). Output is
append-only (merge_lists reducer); no code path modifies KG entities.
"""
import logging
from typing import Any
from .kg_service import KGService
logger = logging.getLogger("consistency_validator")
class ConsistencyValidator:
"""Deterministic cross-agent contradiction detection.
Merges role-scoped KGs, delegates to KGService.find_contradictions(),
deduplicates results, and provides markdown formatting for warnings.
"""
def __init__(self):
self._kg = KGService()
def validate_all(self, state: dict[str, Any]) -> list[dict]:
"""Validate consistency across all agent role graphs.
Args:
state: AgentState dict containing knowledge_graph key.
Returns:
Deduplicated list of contradiction dicts (empty if none).
"""
kg = state.get("knowledge_graph", {})
if not kg:
return []
# Merge all role-scoped sub-graphs into one unified graph
merged = self._kg.merge_role_graphs(state)
if not merged.get("nodes"):
return []
# Find contradictions in the merged graph
contradictions = self._kg.find_contradictions(merged)
# Deduplicate by entity_id pair (sorted tuple of IDs)
seen: set[tuple[str, ...]] = set()
deduplicated: list[dict] = []
for c in contradictions:
key = tuple(sorted(c.get("entity_ids", [])))
if key not in seen:
seen.add(key)
deduplicated.append(c)
for c in deduplicated:
logger.warning(
"Contradiction [%s]: %s (roles: %s)",
c.get("type", "unknown"),
c.get("detail", ""),
", ".join(c.get("roles", [])),
)
return deduplicated
@staticmethod
def format_warnings_section(contradictions: list[dict]) -> str:
"""Render contradictions as a markdown warnings section.
Args:
contradictions: List of contradiction dicts from validate_all().
Returns:
Markdown string, empty string if no contradictions.
"""
if not contradictions:
return ""
lines = ["## Consistency Warnings"]
lines.append(
"\n> **Note:** The following inconsistencies were detected across "
"agent outputs. These do not block SRS assembly but should be "
"reviewed before final adoption.\n"
)
for i, c in enumerate(contradictions, 1):
sev = c.get("severity", "medium")
# The severity already appears in the heading; this prefix just
# makes high-severity rows scannable without relying on emoji.
sev_marker = "[HIGH]" if sev == "high" else "[MED]"
ctype = c.get("type", "unknown").replace("_", " ").title()
detail = c.get("detail", "")
roles = ", ".join(c.get("roles", []))
entity_ids = ", ".join(c.get("entity_ids", []))
lines.append(f"### {i}. {sev_marker} **{ctype}** *(Severity: {sev})*")
lines.append(f"- **Detail:** {detail}")
if entity_ids:
lines.append(f"- **Entities:** {entity_ids}")
if roles:
lines.append(f"- **Agent Roles:** {roles}")
conflicts = c.get("conflicts", [])
if conflicts:
for conflict in conflicts:
prop = conflict.get("property", "unknown")
vals = conflict.get("values", {})
if vals:
val_str = "; ".join(
f"{role}: {val}" for role, val in vals.items()
)
lines.append(f"- **{prop}:** {val_str}")
else:
lines.append(f"- **{prop}:** conflicting values")
lines.append("")
return "\n".join(lines)