Commit Graph

11 Commits

Author SHA1 Message Date
Jordan Ritter 3b1f628266 fix(showcase/ag2): unquarantine multimodal — normalize AG-UI image/document/binary content parts to autogen image_url
AG2's ConversableAgent runs every user message through
``autogen.code_utils.content_str``, which only accepts content-part
types in {"text", "input_text", "image_url", "input_image", "function",
"tool_call", "tool_calls"}. CopilotChat / the AG-UI runtime emits image
and document attachments as the modern shape

  {"type": "image" | "document", "source": {...}}

and the demo page's legacy-converter-shim.tsx ALSO appends a legacy

  {"type": "binary", mimeType, data | url}

mirror alongside it (to keep the @ag-ui/langgraph converter happy on
LangChain-based integrations — it rides through on the ag2 path too).
Both shapes trip autogen's allowed-types gate with

  ValueError("Wrong content format: unknown type image within the
  content")

…BEFORE the request reaches the vision model — observed live in the
D6 multimodal probe (commit d8a0a25db, which originally quarantined
the feature as NSF).

Fix
---
Add ``agents/_multimodal_normalize.py``: a ``NormalizingAGUIStream``
subclass of ``AGUIStream`` that overrides ``dispatch()`` to normalize
AG-UI image/document/binary content parts to OpenAI Chat Completions
``image_url`` parts AFTER ``RunAgentInput`` Pydantic parsing and BEFORE
``AgentService`` serialises the messages for autogen.

This is the only correct interception point:
- Too early (ASGI body rewrite before Pydantic): ``RunAgentInput``
  rejects ``image_url`` because it is not an AG-UI standard type —
  the discriminated union only accepts image/document/binary/text.
- Too late (inside ConversableAgent): requires patching autogen
  internals.

The override works by calling ``normalize_messages_for_autogen()`` on
the dict-serialised messages (same form as ``run_stream`` produces via
``model_dump()``) and re-injecting them via a ``_PatchedRunAgentInput``
wrapper that overrides only ``.messages``, delegating all other
attribute access to the original ``RunAgentInput``.

Conversions:
- {"type": "image", "source": {"type": "data", value, mime_type}} →
  {"type": "image_url", "image_url": {"url": "data:<mime>;base64,<value>"}}
- {"type": "image", "source": {"type": "url", value}} →
  {"type": "image_url", "image_url": {"url": value}}
- {"type": "document", "source": ...} → image_url with the document's
  mime preserved (data:application/pdf;base64,...). The vision model
  still can't natively read PDFs, but the request reaches the model
  instead of being rejected upstream, which is the failure mode this
  fix targets.
- {"type": "binary", mimeType, data | url} → image_url (the
  legacy-shim parts ride through cleanly).
- {"type": "text", ...} and already-normalised image_url parts pass
  through unchanged (identity-preserved on no-op turns).

Failure path: any normalization error is logged at WARNING and the
original messages are forwarded unchanged — autogen's own ValueError
fires verbatim with its error surface intact.

Manifest + fixture
------------------
- showcase/integrations/ag2/manifest.yaml: remove multimodal from
  not_supported_features (with its now-stale comment) and add it back
  to the features list next to voice.
- showcase/aimock/d6/ag2/multimodal.json: add the D6 fixture pair
  using the actual autoPrompt strings from sample-attachment-buttons.tsx
  ("can you tell me what is in this demo image I just attached" /
  "can you tell me what is in this demo pdf I just attached").

TDD evidence (red-green)
------------------------
showcase/integrations/ag2/tests/python/test_multimodal_normalize.py
contains 14 unit tests, pinned at three layers:

1. RED/GREEN against autogen's actual content gate:
   * test_autogen_rejects_raw_agui_image_part — confirms
     content_str([{type: image, source: ...}]) raises the verbatim
     ValueError the D6 probe surfaced. This is the regression pin: if
     autogen ever relaxes the gate, this test fails and we know to
     revisit the normalizer.
   * test_normalized_content_is_accepted_by_autogen — after
     normalize_messages_for_autogen(...), content_str accepts every
     part and renders "<image>" for the image_url part.
2. Shape coverage: modern image data/url, modern document, legacy
   binary data/url, mimeType camelCase alias, plain-text passthrough,
   plain-string content, assistant/tool messages untouched,
   unrecognised source → text placeholder, idempotency.
3. NormalizingAGUIStream class surface tripwire.

Control-plane D6 RED→GREEN:
  RED  (no normalizer, pre-fix container): d6:ag2/multimodal → red
       (HTTP 500 agent_run_error_event from content_str ValueError)
  GREEN (NormalizingAGUIStream applied):   d6:ag2/multimodal → green
2026-07-06 20:47:46 -07:00
github-actions[bot] 691c036789 style: auto-fix formatting 2026-06-19 20:54:20 +00:00
Jordan Ritter 12dd238fbc feat(cvdiag): backend 11-boundary instrumentation for strands + ag2 (L1-C) 2026-06-18 14:30:07 -07:00
Jordan Ritter 0cf30cc030 fix(showcase): resolve ag2 gen_ui_agent crash-on-import from stringified forward-ref
`from __future__ import annotations` turned the set_steps tool's
`context_variables: ContextVariables` param into an unresolved ForwardRef at
AG2 tool-schema-generation time, raising PydanticUserError on import and failing
the showcase-ag2 staging healthcheck since 2026-05-31. Removing it matches the
working sibling agents. Adds a regression test that statically asserts the
future-import stays absent (version-independent) plus a live import check.
2026-06-03 14:01:09 -07:00
Jordan Ritter 5f20887d77 test(showcase): delete orphan e2e specs with no backing demo
reasoning-default-render.spec.ts and shared-state-write.spec.ts navigate to
/demos/reasoning-default-render and /demos/shared-state-write respectively,
but neither demo directory exists in any integration (including the
langgraph-python gold reference) and neither is declared in any manifest.
They are stale, non-canonical leftovers from the #5127 page-mirror.

Removed from all baselines where present: reasoning-default-render from 8
(langgraph-fastapi never had it) and shared-state-write from all 9. The
parity validator stays green (0 fail) and langgraph-fastapi's prior
spec-under-coverage warning clears once its phantom spec is gone and the 5
canonical specs are added.
2026-05-31 20:28:02 -07:00
Jordan Ritter 28fee3dadf test(showcase): add 5 missing canonical e2e specs to baseline integrations
The 9 baseline integrations (ag2, agno, crewai-crews, langgraph-fastapi,
langroid, llamaindex, mastra, spring-ai, strands) were page-mirrored from
langgraph-python but the mirror omitted 5 LGP-canonical Playwright specs
whose demos are present on disk:

  - declarative-hashbrown
  - declarative-json-render
  - reasoning-custom
  - reasoning-default
  - threadid-frontend-tool-roundtrip

Copied each spec verbatim (byte-identical) from langgraph-python, which the
baselines mirror. All 5 backing demo directories exist in every baseline.
The specs are framework-agnostic (navigate by route + testid), so no
per-integration edits are needed. Restores apples-to-apples spec parity.
2026-05-31 20:25:57 -07:00
Jordan Ritter 4211278ae3 test(showcase): fleet test-parity — align all integration e2e specs to LGP canonical
Each non-LGP integration carried its own drifted/stale copy of the e2e specs, causing
inconsistent behavior and noisy diffs across the fleet. Copied langgraph-python's canonical
specs verbatim across ~15 integrations (576 spec files total, SHA-1-verified identical to
LGP) so every integration runs the same assertions.

Also removed 2 orphan specs whose underlying demo pages do not exist:
- showcase/integrations/agno/tests/e2e/hitl-in-chat-booking.spec.ts
- showcase/integrations/built-in-agent/tests/e2e/shared-state-write.spec.ts

Integration-specific variant specs were intentionally left as-is: reasoning-default-render,
byoc-*, agentic-chat-reasoning, and shared-state-write where the demo exists. google-adk and
langgraph-typescript were already in parity from earlier commits and show no new changes.
2026-05-30 08:43:12 -07:00
Alem Tuzlak 4882c61fb6 feat(showcase): align headless demos to north-star parity across all integrations 2026-05-05 15:12:43 +02:00
Alem Tuzlak 9845dadebb fix(aimock): re-key HITL confirmations on toolCallId so back-to-back flows work
Bug: in a single chat session, running both HITL booking flows
back-to-back (Alice 1:1 → then Sales call without refresh) used to
skip the time-picker on the second flow and jump straight to
"Booked ..." text.

Cause: confirmation fixtures were matched on `hasToolResult: true`,
which fires whenever the conversation has ANY tool message in
history. After the first flow finished, the second user message
short-circuited to a confirmation match before the second flow's
toolCall fixture (gated on `hasToolResult: false`) had a chance to
fire. The picker never rendered.

Fix: re-key the two confirmation fixtures on `toolCallId` (the
specific tool_call_id of the matching `book_call` invocation), which
only fires when the LAST conversation message is a tool result with
that id — exactly the moment we want the confirmation. Drop the
`hasToolResult: false` constraint on the toolCall fixtures so they
match a fresh user request regardless of prior tool history.

Add a back-to-back regression test to all 17 hitl-in-chat specs:
walk Alice flow to completion, then sales flow without refresh,
assert two `time-picker-card` elements rendered. If the multi-flow
regression returns, the second card never appears and the test
fails at `toHaveCount(2)`.
2026-05-01 12:42:53 +02:00
Alem Tuzlak 8cb84e88eb test(showcase): replicate hitl-in-chat regression spec across all 17 integrations
The hitl-in-chat demo ships in 17 integrations (langgraph-python plus
16 others — mastra, strands, ag2, agno, crewai-crews,
langgraph-typescript, langgraph-fastapi, pydantic-ai, llamaindex,
langroid, claude-sdk-python, claude-sdk-typescript, ms-agent-python,
ms-agent-dotnet, spring-ai, google-adk). All shipped placeholder e2e
specs that only checked the chat input was visible — none exercised
the actual booking flow.

Replace each with the full booking-flow spec written for
langgraph-python:
1. The "Schedule a 1:1 with Alice" suggestion renders the time-picker
   card AND the Tokyo greeting is absent (regression guard against
   the broad aimock `userMessage: "Alice"` matcher).
2. Picking a slot transitions to the picked-state card and produces
   a "Booked … Alice" assistant follow-up.
3. The "Book a call with sales" suggestion runs the same flow with
   the sales attendee.

Also add the matching aimock fixture pair for the sales suggestion
in feature-parity.json — without it, case 3 would only pass against
real OpenAI, not the aimock-backed CI deployments. The pair mirrors
the Alice fixture pair: `book_call` toolCall on first turn,
confirmation message after the picker resolves.

Per-integration coverage matters because each integration has its
own framework-specific HITL wiring (`useHumanInTheLoop` binding to
the agent, agent-side tool registration, run streaming protocol)
that can regress independently of the shared aimock fixture.
2026-05-01 12:25:36 +02:00
Jordan Ritter dd06dd89d1 refactor(showcase): rename packages/ to integrations/
The showcase framework directories better reflect their role as
integration examples rather than distributable packages.
Renames showcase/packages/ -> showcase/integrations/ and updates
the test docker-compose file reference accordingly.
2026-04-28 07:47:35 -07:00