Coverage for src / local_deep_research / advanced_search_system / constraints / constraint_analyzer.py: 17%
46 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Constraint analyzer for extracting constraints from queries.
3"""
5import re
6from typing import List
8from langchain_core.language_models import BaseChatModel
9from loguru import logger
11from ...utilities.search_utilities import remove_think_tags
12from .base_constraint import Constraint, ConstraintType
15class ConstraintAnalyzer:
16 """Analyzes queries to extract constraints."""
18 def __init__(self, model: BaseChatModel):
19 """Initialize the constraint analyzer."""
20 self.model = model
22 def extract_constraints(self, query: str) -> List[Constraint]:
23 """Extract constraints from a query."""
24 prompt = f"""
25Generate constraints to verify if an answer candidate correctly answers this question.
27Question: {query}
29Create constraints that would help verify if a proposed answer is correct. Focus on the RELATIONSHIP between the question and answer, not just query analysis.
31Examples:
32- "Which university did Alice study at?" → "Alice studied at this university"
33- "What year was the company founded?" → "The company was founded in this year"
34- "Who invented the device?" → "This person invented the device"
35- "Where is the building located?" → "The building is located at this place"
37For each constraint, identify:
381. Type: property, name_pattern, event, statistic, temporal, location, comparison, existence
392. Description: What relationship must hold between question and answer
403. Value: The specific relationship to verify
414. Weight: How critical this constraint is (0.0-1.0)
43Format your response as:
44CONSTRAINT_1:
45Type: [type]
46Description: [description]
47Value: [value]
48Weight: [0.0-1.0]
50CONSTRAINT_2:
51Type: [type]
52Description: [description]
53Value: [value]
54Weight: [0.0-1.0]
56Focus on answer verification, not query parsing.
57"""
59 response = self.model.invoke(prompt)
60 content = remove_think_tags(response.content)
62 constraints = []
63 current_constraint = {}
64 constraint_id = 1
66 for line in content.strip().split("\n"):
67 line = line.strip()
69 if line.startswith("CONSTRAINT_"):
70 if current_constraint and all(
71 k in current_constraint
72 for k in ["type", "description", "value"]
73 ):
74 constraint = Constraint(
75 id=f"c{constraint_id}",
76 type=self._parse_constraint_type(
77 current_constraint["type"]
78 ),
79 description=current_constraint["description"],
80 value=current_constraint["value"],
81 weight=self._parse_weight(
82 current_constraint.get("weight", 1.0)
83 ),
84 )
85 constraints.append(constraint)
86 constraint_id += 1
87 current_constraint = {}
88 elif ":" in line:
89 key, value = line.split(":", 1)
90 key = key.strip().lower()
91 value = value.strip()
92 if key in ["type", "description", "value", "weight"]:
93 current_constraint[key] = value
95 # Don't forget the last constraint
96 if current_constraint and all(
97 k in current_constraint for k in ["type", "description", "value"]
98 ):
99 constraint = Constraint(
100 id=f"c{constraint_id}",
101 type=self._parse_constraint_type(current_constraint["type"]),
102 description=current_constraint["description"],
103 value=current_constraint["value"],
104 weight=self._parse_weight(
105 current_constraint.get("weight", 1.0)
106 ),
107 )
108 constraints.append(constraint)
110 logger.info(f"Extracted {len(constraints)} constraints from query")
111 return constraints
113 def _parse_constraint_type(self, type_str: str) -> ConstraintType:
114 """Parse constraint type from string."""
115 type_map = {
116 "property": ConstraintType.PROPERTY,
117 "name_pattern": ConstraintType.NAME_PATTERN,
118 "event": ConstraintType.EVENT,
119 "statistic": ConstraintType.STATISTIC,
120 "temporal": ConstraintType.TEMPORAL,
121 "location": ConstraintType.LOCATION,
122 "comparison": ConstraintType.COMPARISON,
123 "existence": ConstraintType.EXISTENCE,
124 }
125 return type_map.get(type_str.lower(), ConstraintType.PROPERTY)
127 def _parse_weight(self, weight_value) -> float:
128 """Parse weight value to float, handling text annotations.
130 Args:
131 weight_value: String or numeric weight value, possibly with text annotations
133 Returns:
134 float: Parsed weight value
135 """
136 if isinstance(weight_value, (int, float)):
137 return float(weight_value)
138 if isinstance(weight_value, str):
139 # Extract the first number from the string
140 match = re.search(r"(\d+(\.\d+)?)", weight_value)
141 if match:
142 return float(match.group(1))
143 return 1.0 # Default weight