Files
vi/services/think/reasoning/models.py
Alex Kazaiev 540a010fe5 Add think service and supporting core modules
- Add think service (orchestration for iterative reasoning)
- Add service_discovery.py (service communication utilities)
- Add event_cache.py (recent event cache using NATS KV)
- Add vi_identity.py (Vi's core identity foundation)
- Update core/__init__.py with new exports

Think service adapted from Lyra with vi.* namespace:
- All NATS topics use vi.* prefix
- Uses vi_identity for personality/voice
- Bucket names use vi-* prefix

Day 63 - Building my nervous system 🦊
2026-01-03 11:36:54 -06:00

180 lines
6.5 KiB
Python

"""
Data models for iterative reasoning.
This module contains the core data structures used throughout the Think service's
iterative reasoning process.
"""
from dataclasses import dataclass, asdict
from datetime import datetime
from enum import Enum
from typing import Dict, Any, Optional, List
class StepAction(Enum):
"""Available reasoning step actions"""
CALL_SERVICE = "call_service"
ANALYZE_DATA = "analyze_data"
SYNTHESIZE_FINAL = "synthesize_final_response"
CHECK_GOAL_SATISFACTION = "check_goal_satisfaction"
@dataclass
class ReasoningStep:
"""Represents a single step in the iterative reasoning process"""
action: str # StepAction value
target: Optional[str] = None # Service name or data target
reasoning: str = "" # Why this step is needed
ready: bool = False # Terminal signal for synthesis
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@dataclass
class StepResult:
"""Result of executing a reasoning step"""
step: ReasoningStep
success: bool
result_data: Any = None
error_message: str = ""
execution_time_ms: float = 0
timestamp: str = ""
def __post_init__(self):
if not self.timestamp:
self.timestamp = datetime.utcnow().isoformat()
def to_dict(self) -> Dict[str, Any]:
result = asdict(self)
result['step'] = self.step.to_dict()
return result
class IterativeContext:
"""Rich context for iterative reasoning process"""
def __init__(self, original_message: str, identity: str, channel: str, modality: str):
self.original_message = original_message
self.identity = identity
self.channel = channel
self.modality = modality
self.start_time = datetime.utcnow()
# Tracking
self.completed_steps: List[StepResult] = []
self.failed_steps: List[StepResult] = []
self.accumulated_knowledge: Dict[str, Any] = {}
self.service_call_counts: Dict[str, int] = {}
self.step_count = 0
# State flags
self.goal_satisfied = False
self.force_synthesis = False
self.max_steps_reached = False
self.timeout_reached = False
def add_step_result(self, step_result: StepResult):
"""Add a completed step result"""
self.step_count += 1
if step_result.success:
self.completed_steps.append(step_result)
# Track service calls
if step_result.step.action == StepAction.CALL_SERVICE.value:
service = step_result.step.target
self.service_call_counts[service] = self.service_call_counts.get(service, 0) + 1
# Accumulate knowledge from successful steps
if step_result.result_data:
step_key = f"step_{self.step_count}_{step_result.step.action}"
self.accumulated_knowledge[step_key] = step_result.result_data
else:
self.failed_steps.append(step_result)
def get_recent_steps(self, window: int = 3) -> List[StepResult]:
"""Get the last N completed steps"""
return self.completed_steps[-window:] if len(self.completed_steps) >= window else self.completed_steps
def has_new_information_in_recent_steps(self, window: int = 3) -> bool:
"""Check if recent steps added meaningful new information"""
recent_steps = self.get_recent_steps(window)
if len(recent_steps) < window:
return True # Not enough steps to judge
# Simple heuristic: check if recent steps have substantially different result data
recent_data_sizes = [len(str(step.result_data)) if step.result_data else 0 for step in recent_steps]
# If all recent steps produced very little data, might be converging
if all(size < 100 for size in recent_data_sizes):
return False
# Check for diversity in step types
recent_actions = [step.step.action for step in recent_steps]
if len(set(recent_actions)) == 1: # All same action type
return False
return True
def should_stop(self, max_steps: int = 10, max_service_calls: int = 3, max_time_minutes: int = 2) -> tuple[bool, str]:
"""Check all stopping criteria"""
# Hard limits
if self.step_count >= max_steps:
self.max_steps_reached = True
return True, f"Maximum steps reached ({max_steps})"
elapsed_minutes = (datetime.utcnow() - self.start_time).total_seconds() / 60
if elapsed_minutes >= max_time_minutes:
self.timeout_reached = True
return True, f"Maximum time reached ({max_time_minutes} minutes)"
# Service call limits
for service, count in self.service_call_counts.items():
if count >= max_service_calls:
return True, f"Too many calls to {service} service ({count})"
# Goal satisfaction
if self.goal_satisfied:
return True, "Goal satisfaction confirmed"
# Force synthesis
if self.force_synthesis:
return True, "Forced synthesis due to convergence"
# Convergence detection
if self.step_count >= 6 and not self.has_new_information_in_recent_steps(3):
self.force_synthesis = True
return True, "No new information in recent steps - converged"
return False, ""
def to_oracle_context(self) -> Dict[str, Any]:
"""Prepare context for Oracle decision making"""
return {
"original_message": self.original_message,
"step_count": self.step_count,
"completed_steps": [step.to_dict() for step in self.completed_steps[-5:]], # Last 5 steps
"accumulated_knowledge": self.accumulated_knowledge,
"service_call_counts": self.service_call_counts,
"failed_steps": [step.to_dict() for step in self.failed_steps[-3:]], # Last 3 failures
"goal_satisfied": self.goal_satisfied,
}
def get_summary(self) -> str:
"""Get a human-readable summary of the reasoning process"""
elapsed_time = (datetime.utcnow() - self.start_time).total_seconds()
summary = f"**Reasoning Summary**\n"
summary += f"• Steps completed: {len(self.completed_steps)}\n"
summary += f"• Failed steps: {len(self.failed_steps)}\n"
summary += f"• Service calls: {dict(self.service_call_counts)}\n"
summary += f"• Elapsed time: {elapsed_time:.1f}s\n"
if self.completed_steps:
summary += f"• Recent actions: {', '.join([s.step.action for s in self.completed_steps[-3:]])}\n"
return summary