Spaces:
Running
Running
| # Mode Output Contracts - Strict Rules per Mode | |
| """ | |
| Defines output constraints for each mode to ensure consistent, professional responses. | |
| ChatGPT quality comes from constraints, not freedom. | |
| """ | |
| from dataclasses import dataclass | |
| from typing import Optional, List | |
| from enum import Enum | |
| class OutputMode(Enum): | |
| """Available output modes""" | |
| RAG = "rag" | |
| GRAPHRAG = "graphrag" | |
| HYBRID = "hybrid" | |
| VISION = "vision" | |
| PREDICTION = "prediction" | |
| class OutputContract: | |
| """Strict output rules for a mode""" | |
| mode: OutputMode | |
| max_tokens: int | |
| allow_tables: bool | |
| require_tables: bool # Must include table for data queries | |
| allow_charts: bool | |
| max_sections: int | |
| allowed_sections: List[str] | |
| confidence_required: bool | |
| min_data_points_for_chart: int | |
| # ============================================================================ | |
| # MODE CONTRACTS - Strict rules per mode | |
| # ============================================================================ | |
| MODE_CONTRACTS = { | |
| OutputMode.RAG: OutputContract( | |
| mode=OutputMode.RAG, | |
| max_tokens=1200, | |
| allow_tables=True, | |
| require_tables=True, # Data queries need tables | |
| allow_charts=True, | |
| max_sections=3, | |
| allowed_sections=["summary", "data_table", "sources"], | |
| confidence_required=True, | |
| min_data_points_for_chart=3, | |
| ), | |
| OutputMode.GRAPHRAG: OutputContract( | |
| mode=OutputMode.GRAPHRAG, | |
| max_tokens=1500, | |
| allow_tables=True, | |
| require_tables=True, | |
| allow_charts=True, | |
| max_sections=4, | |
| allowed_sections=["summary", "data_table", "graph_insights", "sources"], | |
| confidence_required=True, | |
| min_data_points_for_chart=3, | |
| ), | |
| OutputMode.HYBRID: OutputContract( | |
| mode=OutputMode.HYBRID, | |
| max_tokens=1500, | |
| allow_tables=True, | |
| require_tables=True, | |
| allow_charts=True, | |
| max_sections=4, | |
| allowed_sections=["summary", "data_table", "analysis", "sources"], | |
| confidence_required=True, | |
| min_data_points_for_chart=3, | |
| ), | |
| OutputMode.VISION: OutputContract( | |
| mode=OutputMode.VISION, | |
| max_tokens=1000, | |
| allow_tables=True, | |
| require_tables=False, # Vision may not have tabular data | |
| allow_charts=False, # Vision analyzes existing charts | |
| max_sections=3, | |
| allowed_sections=["summary", "findings", "interpretation"], | |
| confidence_required=True, | |
| min_data_points_for_chart=0, | |
| ), | |
| OutputMode.PREDICTION: OutputContract( | |
| mode=OutputMode.PREDICTION, | |
| max_tokens=1500, | |
| allow_tables=True, | |
| require_tables=True, | |
| allow_charts=True, | |
| max_sections=4, | |
| allowed_sections=["summary", "forecast_table", "trend_analysis", "confidence"], | |
| confidence_required=True, | |
| min_data_points_for_chart=5, # Need more data for predictions | |
| ), | |
| } | |
| def get_contract(mode: str) -> OutputContract: | |
| """Get the output contract for a mode""" | |
| try: | |
| output_mode = OutputMode(mode.lower()) | |
| return MODE_CONTRACTS.get(output_mode, MODE_CONTRACTS[OutputMode.RAG]) | |
| except (ValueError, KeyError): | |
| return MODE_CONTRACTS[OutputMode.RAG] | |
| def get_max_tokens(mode: str) -> int: | |
| """Get max tokens for a mode""" | |
| return get_contract(mode).max_tokens | |
| def should_require_table(mode: str, query_type: str) -> bool: | |
| """Check if table is required for this query""" | |
| contract = get_contract(mode) | |
| # Data queries always need tables | |
| data_query_types = {"factual", "aggregation", "comparison", "list", "ranking"} | |
| if query_type.lower() in data_query_types: | |
| return contract.require_tables | |
| return False | |
| def can_show_chart(mode: str, data_points: int) -> bool: | |
| """Check if chart is allowed for this mode with this data""" | |
| contract = get_contract(mode) | |
| if not contract.allow_charts: | |
| return False | |
| if data_points < contract.min_data_points_for_chart: | |
| return False | |
| return True | |
| # ============================================================================ | |
| # CONFIDENCE SIGNALING | |
| # ============================================================================ | |
| class ConfidenceLevel(Enum): | |
| """Confidence levels for responses""" | |
| HIGH = "high" | |
| MEDIUM = "medium" | |
| LOW = "low" | |
| INSUFFICIENT = "insufficient" | |
| def get_confidence_prefix(level: ConfidenceLevel) -> str: | |
| """Get appropriate prefix for confidence level""" | |
| prefixes = { | |
| ConfidenceLevel.HIGH: "", # No prefix needed for high confidence | |
| ConfidenceLevel.MEDIUM: "Based on available data, ", | |
| ConfidenceLevel.LOW: "With limited data available, ", | |
| ConfidenceLevel.INSUFFICIENT: "**Note: Insufficient data for a complete answer.** ", | |
| } | |
| return prefixes.get(level, "") | |
| def get_confidence_suffix(level: ConfidenceLevel) -> str: | |
| """Get appropriate suffix/disclaimer for confidence level""" | |
| suffixes = { | |
| ConfidenceLevel.HIGH: "", | |
| ConfidenceLevel.MEDIUM: "\n\n*Analysis based on partial data.*", | |
| ConfidenceLevel.LOW: "\n\n*⚠️ Limited data available. Results may be incomplete.*", | |
| ConfidenceLevel.INSUFFICIENT: "\n\n*❌ Data is insufficient to provide a reliable answer. Please upload more data.*", | |
| } | |
| return suffixes.get(level, "") | |
| def assess_data_confidence( | |
| data_points: int, | |
| sources_count: int, | |
| has_null_values: bool = False | |
| ) -> ConfidenceLevel: | |
| """Assess confidence level based on data quality""" | |
| # Insufficient data | |
| if data_points == 0 or sources_count == 0: | |
| return ConfidenceLevel.INSUFFICIENT | |
| # Low confidence | |
| if data_points < 3 or sources_count < 1: | |
| return ConfidenceLevel.LOW | |
| # Medium confidence (some issues) | |
| if has_null_values or data_points < 10: | |
| return ConfidenceLevel.MEDIUM | |
| # High confidence | |
| return ConfidenceLevel.HIGH | |
| def format_confidence_response( | |
| answer: str, | |
| confidence: ConfidenceLevel | |
| ) -> str: | |
| """Format response with appropriate confidence signaling""" | |
| if confidence == ConfidenceLevel.HIGH: | |
| return answer # No modification needed | |
| prefix = get_confidence_prefix(confidence) | |
| suffix = get_confidence_suffix(confidence) | |
| # Don't add prefix to bolded summary lines | |
| lines = answer.split('\n') | |
| if lines and lines[0].startswith('**'): | |
| # Insert prefix after first line | |
| lines.insert(1, f"\n{prefix.strip()}") | |
| return '\n'.join(lines) + suffix | |
| else: | |
| return prefix + answer + suffix | |