diff --git a/integrations/langgraph/python/ag_ui_langgraph/__init__.py b/integrations/langgraph/python/ag_ui_langgraph/__init__.py index 4f2728bba5..2a1263e910 100644 --- a/integrations/langgraph/python/ag_ui_langgraph/__init__.py +++ b/integrations/langgraph/python/ag_ui_langgraph/__init__.py @@ -17,7 +17,6 @@ LangGraphReasoning, ) from .utils import json_safe_stringify, make_json_safe -from .endpoint import add_langgraph_fastapi_endpoint from .middlewares.state_streaming import StateStreamingMiddleware, StateItem from .a2ui_tool import ( get_a2ui_tools, @@ -27,6 +26,10 @@ BASIC_CATALOG_ID, ) +# FastAPI is an optional extra. Keep the endpoint import lazy so middleware-only +# installs (`pip install ag-ui-langgraph` without `[fastapi]`) can still import +# LangGraphAgent and helpers without requiring fastapi (#2013). + __all__ = [ "LangGraphAgent", "get_a2ui_tools", @@ -53,5 +56,13 @@ "StateStreamingMiddleware", "StateItem", "json_safe_stringify", - "make_json_safe" + "make_json_safe", ] + + +def __getattr__(name: str): + if name == "add_langgraph_fastapi_endpoint": + from .endpoint import add_langgraph_fastapi_endpoint + + return add_langgraph_fastapi_endpoint + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/integrations/langgraph/python/tests/test_lazy_fastapi_import.py b/integrations/langgraph/python/tests/test_lazy_fastapi_import.py new file mode 100644 index 0000000000..9d63690a93 --- /dev/null +++ b/integrations/langgraph/python/tests/test_lazy_fastapi_import.py @@ -0,0 +1,44 @@ +"""Regression for #2013 — package import must not require fastapi. + +`fastapi` is an optional extra, but `__init__.py` used to eagerly import +`endpoint.py`, which imports fastapi at module top. Middleware-only installs +then failed on any `import ag_ui_langgraph`. +""" + +from __future__ import annotations + +import importlib +import sys +import unittest + + +class TestLazyFastapiImport(unittest.TestCase): + def test_init_does_not_import_endpoint_module(self): + # Drop package modules so we re-import against current source. + for name in list(sys.modules): + if name == "ag_ui_langgraph" or name.startswith("ag_ui_langgraph."): + del sys.modules[name] + + import ag_ui_langgraph # noqa: F401 + + self.assertNotIn("ag_ui_langgraph.endpoint", sys.modules) + + # Core exports still resolve without touching the FastAPI path. + from ag_ui_langgraph import LangGraphAgent # noqa: F401 + + self.assertNotIn("ag_ui_langgraph.endpoint", sys.modules) + + def test_endpoint_export_is_available_lazily(self): + for name in list(sys.modules): + if name == "ag_ui_langgraph" or name.startswith("ag_ui_langgraph."): + del sys.modules[name] + + import ag_ui_langgraph + + endpoint_fn = ag_ui_langgraph.add_langgraph_fastapi_endpoint + self.assertTrue(callable(endpoint_fn)) + self.assertIn("ag_ui_langgraph.endpoint", sys.modules) + + +if __name__ == "__main__": + unittest.main()