Commit Graph

6 Commits

Author SHA1 Message Date
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
Jordan Ritter 34b66b7ddf fix(showcase/langroid): narrow handle_run except + comment accuracy
- Narrow the mid-stream except around agent.llm_response_async from a
  broad Exception to (openai.APIError, httpx.HTTPError,
  asyncio.TimeoutError, pydantic.ValidationError). Programmer bugs
  (AttributeError, NameError, TypeError) now propagate instead of
  being sanitized as 'Agent run failed: <ClassName>' — keeps real
  bugs visible in server logs and stack traces, where they belong.
  Expected runtime errors still get the sanitized TEXT_MESSAGE +
  RUN_FINISHED treatment so the UI never hangs mid-stream.

- Update _parse_tool_args call-site comment in handle_run: it used
  to say 'returns None' when args could not be parsed; the function
  now returns ParsedArgs with status in {ok, empty, malformed}, and
  the caller already pattern-matches on .usable.

- Fix stale _try_parse_tool docstring that referenced the old
  agent.agent_response() API; the adapter has used
  llm_response_async() for a while now.

Tests: add test_llm_response_async_programmer_bug_propagates — mocks
llm_response_async to raise AttributeError('typo') and asserts the
exception propagates out of the SSE generator (not sanitized).
Update the existing mid-stream-failure test to raise
asyncio.TimeoutError (a covered exception) instead of RuntimeError
so it continues to exercise the sanitization path.
2026-04-18 20:44:39 -07:00
Jordan Ritter af8207ea22 fix(showcase/langroid): address CR round 4 findings (bytes args, mid-stream SSE wrap)
- _try_parse_tool function_call path now accepts (str, bytes, bytearray)
  for arguments, matching _parse_tool_args. Real OpenAI/httpx stacks can
  deliver bytes; the old narrow isinstance(args, str) guard fell through
  to tool_cls(**args) with raw bytes and silently TypeError'd.
- Wrap agent.llm_response_async in try/except inside event_stream. On
  failure after RUN_STARTED, emit a sanitized TEXT_MESSAGE triple and
  RUN_FINISHED so the frontend never hangs waiting for stream closure.
- Wrap request.json() and RunAgentInput(**body) with explicit handlers
  that return structured JSONResponse errors carrying an errorId for
  client-side correlation (400 for bad JSON, 422 for schema mismatch).
- Defensive guard on run_input.messages: skip entries whose role or
  content are not strings, with a warning. Prevents f-string coercion
  of None/complex values into garbage prompts.
- Wrap the content-JSON-path asyncio.to_thread in try/except that emits
  a sanitized error + RUN_FINISHED before re-raising, so scheduler-level
  failures do not kill the SSE generator mid-stream.
- Duplicate-tool RuntimeError now includes fully qualified
  module.Class identities for each colliding ToolMessage, instead of
  just bare request names.

Tests: 30/30 pass (added 5 new tests covering bytes args, mid-stream
failure, malformed JSON body, invalid RunAgentInput, non-string
role/content, collision error identity).
2026-04-18 20:44:38 -07:00
Jordan Ritter 99136fc64d fix(showcase/langroid): address CR round 3 findings (unified sanitization, narrow excepts, ParsedArgs)
Changes to src/agents/agui_adapter.py:
- Extract _execute_backend_tool / _run_backend_tool helpers so the
  oai_tool_calls path and the content-JSON path share one sanitization
  contract. The _try_parse_tool backend-tool path no longer runs
  .handle() barefoot — it goes through _execute_backend_tool which
  catches the narrowed (pydantic.ValidationError, ValueError), logs
  the traceback server-side, and returns a sanitized JSON error payload.
- Narrow tool-execution except from
  (RuntimeError, ValueError, TypeError, KeyError, pydantic.ValidationError)
  to (pydantic.ValidationError, ValueError). RuntimeError/KeyError from
  unrelated libraries or real config drift now propagate as real bugs
  rather than being misclassified as user-facing tool failures.
- Replace dict | None three-way return of _parse_tool_args with a
  ParsedArgs dataclass (args + status: 'ok' / 'empty' / 'malformed' +
  .usable helper). Call sites pattern-match on status explicitly.
- Treat empty-string raw_args (legacy function_call path) as 'malformed'
  instead of 'ok with {}' — firing a tool with empty args produces a
  meaningless UI card, same rationale as the oai path.
- Drop TypeError from the json.loads except in _try_parse_tool (it
  signals a programmer bug, not plain text). Add an
  isinstance(content, (str, bytes, bytearray)) guard that logs a
  WARNING rather than silencing the case.
- Drop pydantic.errors.PydanticUserError from tool-instantiation except
  lists — it signals malformed model *definitions*, not runtime data
  errors, and must surface loudly at startup.
- Freeze _TOOL_BY_NAME behind types.MappingProxyType so post-import
  mutation raises TypeError (e.g. accidental monkeypatch / shadowing).

Changes to tests/python/conftest.py:
- Tighten the docstring cross-reference to src/agent_server.py (the
  real entry point confirmed to import agents.agui_adapter identically).

Changes to tests/python/test_agui_adapter.py:
- Update helper tests to assert on ParsedArgs.status / .usable / .args.
- Flip empty-string expectation to 'malformed' per the new contract.
- Add: bytes-input parse, unknown-type 'empty' status, MappingProxyType
  frozen-ness of _TOOL_BY_NAME, unified-helper happy path, helper-level
  sanitization + traceback capture, helper propagates non-narrowed
  RuntimeError, _try_parse_tool non-str/bytes guard warns and returns
  None, and str(exc) never appears anywhere in the SSE stream bytes.
- Switch sanitized-error test's raise to ValueError (narrowed except
  covers it) — previously RuntimeError leaked through by accident.

24/24 tests pass (16 existing + 8 new).
2026-04-18 20:44:37 -07:00
Jordan Ritter 858119259c fix(showcase/langroid): address CR round 2 findings (logging noise, sanitization, tests) 2026-04-18 20:44:37 -07:00
Jordan Ritter 7c2d9aa445 fix(showcase/langroid): address CR round 1 findings (thread_id, logging, tests)
- agui_adapter: compute thread_id once so RUN_STARTED/RUN_FINISHED never
  disagree when the caller omits it.
- Narrow silent `except Exception: pass` sites, log parse/tool failures
  with sanitized context, and drop the dead `agent.agent_response` path
  (returns ChatDocument, not ToolMessage).
- Extract `_emit_text_block` helper DRYing the empty-delta guard, and a
  `_parse_tool_args` helper with explicit warning on malformed input.
- Assert tool-class request-name uniqueness at import so collisions fail
  loudly instead of silently shadowing.
- Replace `str(response)` fallback with documented empty default.
- Add unit tests covering the OAI tool-calls happy path, malformed args,
  legacy function_call shape, empty-content skip, thread_id stability,
  and tool-class uniqueness (red-green verified).
2026-04-18 20:44:35 -07:00