Files
Jordan Ritter 65f3790653 fix(showcase/langroid): route A2UI planner through langroid LLM abstraction
The secondary GenerateA2UITool previously hardcoded openai.OpenAI() + gpt-4.1,
forcing non-OpenAI users to keep an OPENAI_API_KEY around just for the A2UI
dashboard demo even when LANGROID_MODEL pointed at another provider.

Route the planner through lm.OpenAIGPT (langroid's universal LLM abstraction)
keyed on A2UI_MODEL (override) → LANGROID_MODEL (inherit) → gpt-4.1 (default).
Default uses the bare model name because `openai/` is NOT a langroid-native
prefix — langroid passes it literally to the OpenAI SDK which rejects it.

Additional hardening:
- _A2uiError TypedDict with _A2uiErrorKind enum, factory with non-empty-string
  validation that raises ValueError (not assert, so python -O can't strip it)
- _ARGS_MISSING sentinel distinguishes "no tool call" from "degraded shape"
  so the frontend gets correct remediation
- Narrow re-raise tuple propagates programmer errors (AttributeError, TypeError,
  NameError, ImportError, ModuleNotFoundError, AssertionError, NotImplementedError,
  pydantic.ValidationError) while wrapping recoverable SDK errors into _a2ui_error
- _ToolErrorKind enum for backend tool error codes — typo-safe parity with _A2uiErrorKind
- Boundary validation after build_a2ui_operations_from_tool_call return
- Backend tool handlers wrap impls in try/except so exceptions don't escape
  to langroid's tool machinery
- @lru_cache(maxsize=4) on _get_a2ui_llm keyed on resolved model string
- FRONTEND_TOOL_NAMES canary raises RuntimeError at import time on drift
- No top-level import of openai (AST walker test pins this contract)

Covered by 109 tests (integration test gated by LANGROID_INTEGRATION_TESTS=1).
2026-04-19 11:38:06 -07:00
..