Skip to content

Commit 5ecda99

Browse files
jeremymanningclaude
andcommitted
fix: Remove fallback_to_heuristics and all heuristic methods from AmbiguityResolver
- Removed fallback_to_heuristics parameter from __init__ - Removed all heuristic resolution methods (_resolve_*_heuristic) - Removed _resolve_with_heuristics method - Removed _classify_ambiguity method - Removed _fallback_resolution method - Updated error message to require a real model - AmbiguityResolver now always requires a real AI model This ensures tests fail properly when no real model is available, following the NO MOCKS policy. The GitHub Actions test expecting a fallback will now correctly fail and need to be fixed to provide a real model. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent c706217 commit 5ecda99

File tree

1 file changed

+5
-212
lines changed

1 file changed

+5
-212
lines changed

src/orchestrator/compiler/ambiguity_resolver.py

Lines changed: 5 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -38,35 +38,22 @@ class AmbiguityResolver:
3838
def __init__(
3939
self,
4040
model: Optional[Model] = None,
41-
model_registry: Optional[ModelRegistry] = None,
42-
fallback_to_heuristics: bool = False
41+
model_registry: Optional[ModelRegistry] = None
4342
) -> None:
4443
"""
4544
Initialize ambiguity resolver.
4645
4746
Args:
4847
model: Specific model to use for resolution
4948
model_registry: Model registry to select models from
50-
fallback_to_heuristics: Whether to fall back to heuristics if AI fails
5149
5250
Raises:
5351
ValueError: If no model is provided and registry has no models
5452
"""
5553
self.model = model
5654
self.model_registry = model_registry
57-
self.fallback_to_heuristics = fallback_to_heuristics
5855
self.resolution_cache: Dict[str, Any] = {}
5956

60-
# Heuristic strategies for fallback
61-
self.resolution_strategies = {
62-
"parameter": self._resolve_parameter_heuristic,
63-
"value": self._resolve_value_heuristic,
64-
"list": self._resolve_list_heuristic,
65-
"boolean": self._resolve_boolean_heuristic,
66-
"number": self._resolve_number_heuristic,
67-
"string": self._resolve_string_heuristic,
68-
}
69-
7057
# If no model provided, try to get one from registry
7158
if not model and model_registry:
7259
available_models = model_registry.list_models()
@@ -79,10 +66,10 @@ def __init__(
7966
self.model = model_registry.get_model(available_models[0])
8067

8168
# Verify we have a model for AI resolution
82-
if not self.model and not self.fallback_to_heuristics:
69+
if not self.model:
8370
raise ValueError(
8471
"No AI model available for ambiguity resolution. "
85-
"Either provide a model or enable fallback_to_heuristics."
72+
"A real model must be provided."
8673
)
8774

8875
async def resolve(self, content: str, context_path: str) -> Any:
@@ -105,31 +92,19 @@ async def resolve(self, content: str, context_path: str) -> Any:
10592
return self.resolution_cache[cache_key]
10693

10794
try:
108-
# Try AI resolution first if we have a model
95+
# Try AI resolution with the model
10996
if self.model:
11097
result = await self._resolve_with_ai(content, context_path)
111-
elif self.fallback_to_heuristics:
112-
# Fall back to heuristic resolution
113-
result = await self._resolve_with_heuristics(content, context_path)
11498
else:
11599
raise AmbiguityResolutionError(
116-
"No AI model available and heuristic fallback is disabled"
100+
"No AI model available for ambiguity resolution"
117101
)
118102

119103
# Cache result
120104
self.resolution_cache[cache_key] = result
121105
return result
122106

123107
except Exception as e:
124-
if self.fallback_to_heuristics and self.model:
125-
# If AI failed, try heuristics
126-
try:
127-
result = await self._resolve_with_heuristics(content, context_path)
128-
self.resolution_cache[cache_key] = result
129-
return result
130-
except:
131-
pass
132-
133108
raise AmbiguityResolutionError(
134109
f"Failed to resolve ambiguity '{content}' at {context_path}: {e}"
135110
) from e
@@ -252,188 +227,6 @@ def _parse_ai_response(self, response: str, expected_type: str, original_content
252227
# String - clean up the response
253228
return response.strip('"\'')
254229

255-
async def _resolve_with_heuristics(self, content: str, context_path: str) -> Any:
256-
"""Fall back to heuristic resolution."""
257-
# Classify ambiguity type
258-
resolution_type = self._classify_ambiguity(content, context_path)
259-
260-
# Apply resolution strategy
261-
if resolution_type in self.resolution_strategies:
262-
strategy = self.resolution_strategies[resolution_type]
263-
return await strategy(content, context_path)
264-
else:
265-
# Default to string
266-
return content
267-
268-
def _classify_ambiguity(self, content: str, context_path: str) -> str:
269-
"""Classify the type of ambiguity for heuristic resolution."""
270-
content_lower = content.lower()
271-
272-
# Check for explicit choice patterns
273-
if ("choose" in content_lower or "select" in content_lower):
274-
if any(word in content_lower for word in ["true", "false"]):
275-
return "boolean"
276-
elif any(word in content_lower for word in ["number", "size", "count", "amount"]):
277-
return "number"
278-
elif "list" in content_lower or "array" in content_lower:
279-
return "list"
280-
else:
281-
return "value"
282-
283-
# Check for parameter context
284-
if "parameters" in context_path:
285-
return "parameter"
286-
287-
# Check for specific patterns
288-
if re.search(r'"[^"]*"', content):
289-
return "string"
290-
291-
# Context-based classification
292-
path_parts = context_path.lower().split('.')
293-
if any(part in ["format", "type", "style", "method"] for part in path_parts):
294-
return "string"
295-
elif any(part in ["enable", "disable", "support"] for part in path_parts):
296-
return "boolean"
297-
elif any(part in ["count", "size", "limit", "timeout", "batch"] for part in path_parts):
298-
return "number"
299-
elif any(part in ["languages", "formats", "items", "sources", "tags", "options", "list"] for part in path_parts):
300-
return "list"
301-
302-
return "string"
303-
304-
# Heuristic resolution methods
305-
async def _resolve_parameter_heuristic(self, content: str, context_path: str) -> Any:
306-
"""Heuristic parameter resolution."""
307-
# Check context for common patterns
308-
if "format" in context_path:
309-
return "json"
310-
elif "method" in context_path:
311-
return "default"
312-
elif "type" in context_path:
313-
return "auto"
314-
return "default"
315-
316-
async def _resolve_value_heuristic(self, content: str, context_path: str) -> Any:
317-
"""Heuristic value resolution."""
318-
# Extract choices if present
319-
choices = self._extract_choices(content)
320-
if choices:
321-
return choices[0]
322-
323-
# Check for specific patterns
324-
if "format" in context_path:
325-
return "json"
326-
elif "method" in context_path:
327-
return "auto"
328-
elif "style" in context_path:
329-
return "default"
330-
331-
return "default"
332-
333-
async def _resolve_list_heuristic(self, content: str, context_path: str) -> List[str]:
334-
"""Heuristic list resolution."""
335-
# Context-based defaults
336-
if "source" in context_path:
337-
return ["web", "documents", "database"]
338-
elif "format" in context_path:
339-
return ["json", "csv", "xml"]
340-
elif "language" in context_path:
341-
return ["en", "es", "fr"]
342-
return ["item1", "item2", "item3"]
343-
344-
async def _resolve_boolean_heuristic(self, content: str, context_path: str) -> bool:
345-
"""Heuristic boolean resolution."""
346-
content_lower = content.lower()
347-
348-
# Check for explicit indicators
349-
positive = ["enable", "true", "yes", "allow", "support"]
350-
negative = ["disable", "false", "no", "deny", "block"]
351-
352-
for word in positive:
353-
if word in content_lower:
354-
return True
355-
356-
for word in negative:
357-
if word in content_lower:
358-
return False
359-
360-
# Context-based defaults
361-
if "enable" in context_path or "support" in context_path:
362-
return True
363-
364-
return False
365-
366-
async def _resolve_number_heuristic(self, content: str, context_path: str) -> Union[int, float]:
367-
"""Heuristic number resolution."""
368-
# Context-based defaults
369-
if "batch" in context_path:
370-
return 32
371-
elif "timeout" in context_path:
372-
return 30
373-
elif "retry" in context_path:
374-
return 3
375-
elif "size" in context_path:
376-
return 100
377-
elif "limit" in context_path:
378-
return 1000
379-
return 10
380-
381-
async def _resolve_string_heuristic(self, content: str, context_path: str) -> str:
382-
"""Heuristic string resolution."""
383-
# Extract quoted content
384-
quotes = self._extract_quotes(content)
385-
if quotes:
386-
return quotes[0]
387-
388-
# Context-based defaults
389-
if "query" in context_path:
390-
return "default search query"
391-
elif "format" in context_path:
392-
return "json"
393-
elif "method" in context_path:
394-
return "auto"
395-
elif "style" in context_path:
396-
return "default"
397-
elif "type" in context_path:
398-
return "standard"
399-
400-
return "default"
401-
402-
def _extract_choices(self, content: str) -> List[str]:
403-
"""Extract choices from content."""
404-
# Pattern for "Choose: A, B, C" or "Select from: A, B, C"
405-
pattern = r'(?:choose|select)(?:\s+from)?:\s*([^.]+)'
406-
match = re.search(pattern, content, re.IGNORECASE)
407-
408-
if match:
409-
choices_text = match.group(1)
410-
# Split by comma and clean
411-
choices = [c.strip() for c in choices_text.split(',')]
412-
# Remove 'or' from choices
413-
choices = [c.replace(' or ', '').replace('or ', '').strip() for c in choices]
414-
return [c for c in choices if c]
415-
416-
return []
417-
418-
def _extract_quotes(self, content: str) -> List[str]:
419-
"""Extract quoted strings from content."""
420-
pattern = r'"([^"]*)"'
421-
return re.findall(pattern, content)
422-
423-
def _fallback_resolution(self, content: str, context_path: str) -> Any:
424-
"""Ultimate fallback resolution."""
425-
# This is used by tests that expect specific behavior
426-
if "query" in context_path:
427-
return "default search query"
428-
elif "format" in context_path:
429-
return "json"
430-
elif "method" in context_path:
431-
return "auto"
432-
elif "style" in context_path:
433-
return "default"
434-
elif "type" in context_path:
435-
return "standard"
436-
return "default"
437230

438231
def clear_cache(self) -> None:
439232
"""Clear the resolution cache."""

0 commit comments

Comments
 (0)