Coverage for src/local_deep_research/advanced_search_system/evidence/evaluator.py: 100%
49 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-19 23:35 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-19 23:35 +0000
1"""
2Evidence evaluator for assessing evidence quality and relevance.
3"""
5from typing import Dict
7from langchain_core.language_models import BaseChatModel
8from loguru import logger
10from ..constraints.base_constraint import Constraint
11from .base_evidence import Evidence, EvidenceType
14class EvidenceEvaluator:
15 """Evaluates evidence quality and relevance."""
17 def __init__(self, model: BaseChatModel):
18 """Initialize the evidence evaluator."""
19 self.model = model
20 self.source_reliability = {
21 "official": 1.0,
22 "research": 0.95,
23 "news": 0.8,
24 "community": 0.6,
25 "inference": 0.5,
26 "speculation": 0.3,
27 }
29 def extract_evidence(
30 self, search_result: str, candidate: str, constraint: Constraint
31 ) -> Evidence:
32 """Extract evidence from search results for a specific constraint."""
33 prompt = f"""
34Extract evidence regarding whether "{candidate}" satisfies this constraint:
36Constraint: {constraint.description}
37Constraint Type: {constraint.type.value}
38Required Value: {constraint.value}
40Search Results:
41{search_result[:3000]}
43Provide:
441. CLAIM: What the evidence claims about the constraint
452. TYPE: direct_statement, official_record, research_finding, news_report, statistical_data, inference, correlation, or speculation
463. SOURCE: Where this evidence comes from
474. CONFIDENCE: How confident you are this evidence is accurate (0.0-1.0)
485. REASONING: Why this evidence supports or refutes the constraint
496. QUOTE: Relevant quote from the search results (if any)
51Format:
52CLAIM: [specific claim]
53TYPE: [evidence type]
54SOURCE: [source description]
55CONFIDENCE: [0.0-1.0]
56REASONING: [explanation]
57QUOTE: [relevant text]
58"""
60 response = self.model.invoke(prompt)
61 content = response.content
63 # Parse response
64 parsed = self._parse_evidence_response(content)
66 # Create evidence object
67 # Safely parse confidence value, handling potential errors
68 confidence_str = parsed.get("confidence", "0.5")
69 try:
70 confidence = float(confidence_str)
71 # Ensure confidence is between 0 and 1
72 confidence = max(0.0, min(1.0, confidence))
73 except ValueError:
74 logger.warning(
75 f"Failed to parse confidence value: {confidence_str}"
76 )
77 confidence = 0.5
79 evidence = Evidence(
80 claim=parsed.get("claim", "No clear claim"),
81 type=self._parse_evidence_type(parsed.get("type", "speculation")),
82 source=parsed.get("source", "Unknown"),
83 confidence=confidence,
84 reasoning=parsed.get("reasoning", ""),
85 raw_text=parsed.get("quote", ""),
86 metadata={
87 "candidate": candidate,
88 "constraint_id": constraint.id,
89 "constraint_type": constraint.type.value,
90 },
91 )
93 # Adjust confidence based on how well it matches the constraint
94 evidence.confidence *= self._assess_match_quality(evidence, constraint)
96 return evidence
98 def _parse_evidence_response(self, content: str) -> Dict[str, str]:
99 """Parse the LLM response into evidence components."""
100 import re
102 parsed = {}
104 for line in content.strip().split("\n"):
105 if ":" in line:
106 key, value = line.split(":", 1)
107 key = key.strip().lower()
108 value = value.strip()
110 if key in [
111 "claim",
112 "type",
113 "source",
114 "confidence",
115 "reasoning",
116 "quote",
117 ]:
118 # Special handling for confidence to extract just the float value
119 if key == "confidence":
120 # Extract the first float from the value string
121 match = re.search(r"(\d*\.?\d+)", value)
122 if match:
123 parsed[key] = match.group(1)
124 else:
125 parsed[key] = value
126 else:
127 parsed[key] = value
129 return parsed
131 def _parse_evidence_type(self, type_str: str) -> EvidenceType:
132 """Parse evidence type from string."""
133 type_map = {
134 "direct_statement": EvidenceType.DIRECT_STATEMENT,
135 "official_record": EvidenceType.OFFICIAL_RECORD,
136 "research_finding": EvidenceType.RESEARCH_FINDING,
137 "news_report": EvidenceType.NEWS_REPORT,
138 "statistical_data": EvidenceType.STATISTICAL_DATA,
139 "inference": EvidenceType.INFERENCE,
140 "correlation": EvidenceType.CORRELATION,
141 "speculation": EvidenceType.SPECULATION,
142 }
143 return type_map.get(type_str.lower(), EvidenceType.SPECULATION)
145 def _assess_match_quality(
146 self, evidence: Evidence, constraint: Constraint
147 ) -> float:
148 """Assess how well the evidence matches the constraint."""
149 # This is a simplified version - could be made more sophisticated
150 if constraint.value.lower() in evidence.claim.lower():
151 return 1.0
152 if any(
153 word in evidence.claim.lower()
154 for word in constraint.value.lower().split()
155 ):
156 return 0.8
157 return 0.6 # Partial match at best