Skip to content

Commit 0cf30cc

Browse files
committed
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.
1 parent fdba5dc commit 0cf30cc

2 files changed

Lines changed: 108 additions & 2 deletions

File tree

showcase/integrations/ag2/src/agents/gen_ui_agent.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
ContextVariables slot, isolated from the shared default agent.
2727
"""
2828

29-
from __future__ import annotations
30-
3129
import logging
3230
from textwrap import dedent
3331
from typing import Annotated, List
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""Regression test: gen_ui_agent must import/construct without raising.
2+
3+
Reproduces the crash-on-import that took down `showcase-ag2` staging
4+
deploys since 76874f06d. Importing `agents.gen_ui_agent` constructs the
5+
`ConversableAgent` at module scope, which registers `set_steps` as an LLM
6+
tool. AG2 runs every tool parameter through pydantic
7+
`TypeAdapter(param).json_schema()`. When `set_steps` carries
8+
`from __future__ import annotations`, its `context_variables: ContextVariables`
9+
parameter is seen as an unresolved `ForwardRef('ContextVariables')`, and
10+
schema generation raises `pydantic.errors.PydanticUserError` at import time
11+
-> importing this agent module aborts server startup -> the service never
12+
comes up -> the Railway healthcheck (`/health`, owned by the integration's
13+
top-level entrypoint, not this module) fails.
14+
15+
The success criterion mirrors the failed healthcheck: the module imports
16+
cleanly and the agent object (with its registered tools) is built.
17+
"""
18+
19+
import importlib
20+
import os
21+
from pathlib import Path
22+
23+
import pytest
24+
25+
26+
# ConversableAgent construction validates that an LLM config / key is
27+
# present. The crash we are guarding against happens during tool-schema
28+
# generation, which is reached only after that validation, so seed a dummy
29+
# key. No network call is made at import time.
30+
os.environ.setdefault("OPENAI_API_KEY", "test-key-not-used")
31+
32+
33+
def _gen_ui_agent_source_path() -> Path:
34+
"""Resolve src/agents/gen_ui_agent.py relative to this test file.
35+
36+
This test lives at <integration>/tests/python/, so the integration root
37+
is two levels up; the source then sits under src/agents/. Resolving via
38+
__file__ keeps the guard working regardless of where the repo is checked
39+
out (no hardcoded absolute path).
40+
"""
41+
integration_root = Path(__file__).resolve().parents[2]
42+
return integration_root / "src" / "agents" / "gen_ui_agent.py"
43+
44+
45+
def test_gen_ui_agent_source_has_no_future_annotations():
46+
"""The source must NOT carry `from __future__ import annotations`.
47+
48+
This is the load-bearing regression pin. `from __future__ import
49+
annotations` makes Python stringify every annotation (PEP 563), so the
50+
`set_steps` tool's `context_variables: ContextVariables` parameter is seen
51+
by AG2 as an unresolved `ForwardRef('ContextVariables')`. AG2 runs each
52+
tool parameter through pydantic `TypeAdapter(param).json_schema()`, which
53+
then raises `PydanticUserError` at import time -> importing this agent
54+
module aborts server startup -> the service never comes up -> the
55+
Railway/staging healthcheck (`/health`, owned by the entrypoint, not this
56+
module) fails (regression 76874f06d).
57+
58+
Asserting on the source text (not just "import didn't crash") makes this a
59+
TRUE guard: it fails the instant someone re-adds the future-import, even if
60+
a future AG2/pydantic happens to resolve the forward-ref gracefully and the
61+
import would no longer crash on its own.
62+
"""
63+
source_path = _gen_ui_agent_source_path()
64+
assert source_path.is_file(), f"could not locate source at {source_path}"
65+
66+
offending = "from __future__ import annotations"
67+
source_lines = source_path.read_text(encoding="utf-8").splitlines()
68+
matches = [
69+
f" line {i}: {line!r}"
70+
for i, line in enumerate(source_lines, start=1)
71+
if line.strip() == offending
72+
]
73+
assert not matches, (
74+
f"{source_path} must NOT contain `{offending}`.\n"
75+
"PEP 563 stringifies annotations, turning the set_steps tool's "
76+
"`context_variables: ContextVariables` into an unresolved ForwardRef; "
77+
"AG2's pydantic tool-schema generation then raises PydanticUserError "
78+
"at import time, so importing this agent module aborts server startup "
79+
"and the service never comes up, failing the Railway healthcheck "
80+
"(`/health`, owned by the entrypoint, not this module) (regression "
81+
"76874f06d). Remove the future-import.\nFound at:\n" + "\n".join(matches)
82+
)
83+
84+
85+
def test_gen_ui_agent_imports_without_pydantic_error():
86+
"""Importing the module must not raise PydanticUserError (or anything).
87+
88+
Proves the crash path is actually clear on the *installed* AG2/pydantic
89+
(complements the static source guard above, which is version-independent).
90+
"""
91+
try:
92+
module = importlib.import_module("agents.gen_ui_agent")
93+
except Exception: # noqa: BLE001 - re-raise so the verbatim traceback is kept
94+
# Let the original exception propagate with its full traceback intact:
95+
# for a deep pydantic schema-gen crash the verbatim stack IS the
96+
# load-bearing diagnostic, which a re-wrapped pytest.fail string loses.
97+
pytest.fail(
98+
"gen_ui_agent failed to import (see traceback below)",
99+
pytrace=True,
100+
)
101+
102+
# The agent and its ASGI app must be constructed (i.e. tool registration,
103+
# where the crash occurred, completed).
104+
assert module.agent is not None
105+
assert module.gen_ui_agent_app is not None
106+
# set_steps must be registered as a tool on the agent.
107+
tool_names = {t.name for t in module.agent.tools}
108+
assert "set_steps" in tool_names

0 commit comments

Comments
 (0)