Spaces:
Sleeping
Sleeping
| """ | |
| Nimbus Bank Triage β LLM Client Wrappers | |
| Centralized model configuration for Claude Haiku (fast tasks) | |
| and Claude Sonnet (quality drafting), with retry logic. | |
| """ | |
| import os | |
| from functools import lru_cache | |
| from dotenv import load_dotenv | |
| from langchain_anthropic import ChatAnthropic | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| # Load env vars | |
| ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| load_dotenv(os.path.join(ROOT_DIR, ".env")) | |
| # ββ Model IDs ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| FAST_MODEL = os.environ.get("FAST_MODEL", "claude-haiku-4-5-20251001") | |
| # Default the drafter to the same known-working model unless overridden by env. | |
| DRAFTER_MODEL = os.environ.get("DRAFTER_MODEL", "claude-haiku-4-5-20251001") | |
| def _get_anthropic_api_key() -> str: | |
| """Return a non-empty Anthropic API key or raise a clear config error.""" | |
| api_key = os.environ.get("ANTHROPIC_API_KEY", "").strip() | |
| if not api_key: | |
| raise RuntimeError( | |
| "ANTHROPIC_API_KEY is not configured. Add it to your environment or Hugging Face Space secrets, then restart the app." | |
| ) | |
| return api_key | |
| def anthropic_api_key_configured() -> bool: | |
| """Whether a non-empty Anthropic API key is currently configured.""" | |
| return bool(os.environ.get("ANTHROPIC_API_KEY", "").strip()) | |
| def _build_llm(model: str, temperature: float, max_tokens: int, api_key: str) -> ChatAnthropic: | |
| """Build and cache Anthropic chat clients by model settings and API key.""" | |
| return ChatAnthropic( | |
| model=model, | |
| temperature=temperature, | |
| max_tokens=max_tokens, | |
| api_key=api_key, | |
| ) | |
| def get_fast_llm() -> ChatAnthropic: | |
| """ | |
| Claude Haiku β used for classification, compliance checks, | |
| and injection detection. Low latency, low cost, deterministic. | |
| """ | |
| return _build_llm( | |
| model=FAST_MODEL, | |
| temperature=0.0, | |
| max_tokens=1024, | |
| api_key=_get_anthropic_api_key(), | |
| ) | |
| def get_drafter_llm() -> ChatAnthropic: | |
| """ | |
| Claude Sonnet β used only by the Response Drafter. | |
| Higher quality for nuanced, empathetic customer responses. | |
| """ | |
| return _build_llm( | |
| model=DRAFTER_MODEL, | |
| temperature=0.5, | |
| max_tokens=2048, | |
| api_key=_get_anthropic_api_key(), | |
| ) | |
| def invoke_with_retry(llm: ChatAnthropic, messages: list) -> str: | |
| """ | |
| Invoke an LLM with automatic retry on transient failures. | |
| Exponential backoff: 1s, 2s, 4s. Max 3 attempts. | |
| Returns the string content of the response. | |
| """ | |
| response = llm.invoke(messages) | |
| return response.content | |
| def invoke_structured_with_retry( | |
| llm: ChatAnthropic, | |
| messages: list, | |
| schema: type, | |
| ) -> dict: | |
| """ | |
| Invoke an LLM with structured output (Pydantic schema) and retry. | |
| Uses LangChain's with_structured_output for type-safe responses. | |
| Returns a dict matching the schema. | |
| """ | |
| structured_llm = llm.with_structured_output(schema) | |
| response = structured_llm.invoke(messages) | |
| # Pydantic model -> dict | |
| if hasattr(response, "model_dump"): | |
| return response.model_dump() | |
| return response | |