The orchestrator. Sits between your application logic and the LLM. Composes all eight components: InputGuard → TokenBudget → PromptBuilder → CircuitBreaker → LLMCaller (timeout) → ResponseValidator → RetryEngine (jittered backoff) → FallbackRouter → AuditLogger Swap
| 827 | # ============================================================================= |
| 828 | |
| 829 | class ControlLayer: |
| 830 | """ |
| 831 | The orchestrator. Sits between your application logic and the LLM. |
| 832 | |
| 833 | Composes all eight components: |
| 834 | InputGuard → TokenBudget → PromptBuilder → |
| 835 | CircuitBreaker → LLMCaller (timeout) → ResponseValidator → |
| 836 | RetryEngine (jittered backoff) → FallbackRouter → AuditLogger |
| 837 | |
| 838 | Swap the LLM in one line: |
| 839 | layer = ControlLayer(llm_fn=your_api_call, ...) |
| 840 | |
| 841 | The ControlPacket returned contains the final response, attempt count, |
| 842 | total latency, token usage, and a full audit ID for tracing. |
| 843 | """ |
| 844 | |
| 845 | def __init__( |
| 846 | self, |
| 847 | llm_fn: Callable[[str], str], |
| 848 | system_prompt: str, |
| 849 | schema: Optional[ResponseSchema] = None, |
| 850 | config: Optional[ControlLayerConfig] = None, |
| 851 | # Legacy compat: accept total_tokens and max_attempts directly |
| 852 | total_tokens: Optional[int] = None, |
| 853 | max_attempts: Optional[int] = None, |
| 854 | ): |
| 855 | self.config = config or ControlLayerConfig( |
| 856 | total_tokens=total_tokens or 800, |
| 857 | max_attempts=max_attempts or 3, |
| 858 | ) |
| 859 | self.schema = schema or ResponseSchema() |
| 860 | |
| 861 | self.input_guard = InputGuard(self.config.max_input_chars) |
| 862 | self.prompt_builder = PromptBuilder(system_prompt, self.config) |
| 863 | self.validator = ResponseValidator() |
| 864 | self.retry_engine = RetryEngine(self.config) |
| 865 | self.fallback_router = FallbackRouter() |
| 866 | self.circuit_breaker = CircuitBreaker( |
| 867 | self.config.cb_failure_threshold, |
| 868 | self.config.cb_recovery_seconds, |
| 869 | ) |
| 870 | self.llm_caller = LLMCaller(llm_fn, self.config.timeout_seconds) |
| 871 | self.audit = AuditLogger(self.config.audit_log_path) |
| 872 | |
| 873 | def register_fallback(self, name: str, fn: Callable[[str], str]) -> None: |
| 874 | self.fallback_router.register(name, fn) |
| 875 | |
| 876 | def run( |
| 877 | self, |
| 878 | user_input: str, |
| 879 | constraints: Optional[List[str]] = None, |
| 880 | context: str = "", |
| 881 | ) -> ControlPacket: |
| 882 | constraints = constraints or [] |
| 883 | audit_id = self._make_audit_id(user_input) |
| 884 | start = time.perf_counter() |
| 885 | |
| 886 | # ── Input Guard ──────────────────────────────────────────────────── |
no outgoing calls