diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 29dcc7d7b..2057dceae 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -102,3 +102,12 @@ jobs: cp apps/agent/.env.example apps/agent/.env if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> apps/agent/.env; fi timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langgraph.json + + - name: Build and test prerelease reqs service + if: steps.changed-files.outputs.all + working-directory: libs/cli/examples/graph_prerelease_reqs + run: | + langgraph build -t langgraph-test-h + cp ../.env.example .env + if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env; fi + timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-h diff --git a/libs/cli/examples/graph_prerelease_reqs/agent.py b/libs/cli/examples/graph_prerelease_reqs/agent.py new file mode 100644 index 000000000..b33b1a5d3 --- /dev/null +++ b/libs/cli/examples/graph_prerelease_reqs/agent.py @@ -0,0 +1,95 @@ +from collections.abc import Sequence +from typing import Annotated, Literal, TypedDict + +from langchain.chat_models import init_chat_model +from langchain_community.tools.tavily_search import TavilySearchResults +from langchain_core.messages import BaseMessage +from langchain_openai import ChatOpenAI +from langgraph.graph import END, StateGraph, add_messages +from langgraph.prebuilt import ToolNode + +tools = [TavilySearchResults(max_results=1)] + +model_anth = init_chat_model("claude-3-7-sonnet-20250219", model_provider="anthropic") +model_oai = ChatOpenAI(temperature=0) + +model_anth = model_anth.bind_tools(tools) +model_oai = model_oai.bind_tools(tools) + + +class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], add_messages] + + +# Define the function that determines whether to continue or not +def should_continue(state): + messages = state["messages"] + last_message = messages[-1] + # If there are no tool calls, then we finish + if not last_message.tool_calls: + return "end" + # Otherwise if there is, we continue + else: + return "continue" + + +# Define the function that calls the model +def call_model(state, config): + if config["configurable"].get("model", "anthropic") == "anthropic": + model = model_anth + else: + model = model_oai + messages = state["messages"] + response = model.invoke(messages) + # We return a list, because this will get added to the existing list + return {"messages": [response]} + + +# Define the function to execute tools +tool_node = ToolNode(tools) + + +class ContextSchema(TypedDict): + model: Literal["anthropic", "openai"] + + +# Define a new graph +workflow = StateGraph(AgentState, context_schema=ContextSchema) + +# Define the two nodes we will cycle between +workflow.add_node("agent", call_model) +workflow.add_node("action", tool_node) + +# Set the entrypoint as `agent` +# This means that this node is the first one called +workflow.set_entry_point("agent") + +# We now add a conditional edge +workflow.add_conditional_edges( + # First, we define the start node. We use `agent`. + # This means these are the edges taken after the `agent` node is called. + "agent", + # Next, we pass in the function that will determine which node is called next. + should_continue, + # Finally we pass in a mapping. + # The keys are strings, and the values are other nodes. + # END is a special node marking that the graph should finish. + # What will happen is we will call `should_continue`, and then the output of that + # will be matched against the keys in this mapping. + # Based on which one it matches, that node will then be called. + { + # If `tools`, then we call the tool node. + "continue": "action", + # Otherwise we finish. + "end": END, + }, +) + +# We now add a normal edge from `tools` to `agent`. +# This means that after `tools` is called, `agent` node is called next. +workflow.add_edge("action", "agent") + +# Finally, we compile it! +# This compiles it into a LangChain Runnable, +# meaning you can use it as you would any other runnable +graph = workflow.compile() diff --git a/libs/cli/examples/graph_prerelease_reqs/langgraph.json b/libs/cli/examples/graph_prerelease_reqs/langgraph.json new file mode 100644 index 000000000..8bc7d8b2c --- /dev/null +++ b/libs/cli/examples/graph_prerelease_reqs/langgraph.json @@ -0,0 +1,11 @@ +{ + "python_version": "3.12", + "dependencies": [ + "." + ], + "graphs": { + "agent": "./agent.py:graph" + }, + "env": "../.env" + } + \ No newline at end of file diff --git a/libs/cli/examples/graph_prerelease_reqs/requirements.txt b/libs/cli/examples/graph_prerelease_reqs/requirements.txt new file mode 100644 index 000000000..92e4c3b5c --- /dev/null +++ b/libs/cli/examples/graph_prerelease_reqs/requirements.txt @@ -0,0 +1,6 @@ +requests +langchain_anthropic +langchain_openai +langchain_community +langchain +langgraph==1.0.0a2 \ No newline at end of file diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py index fc987064f..87d4d5c72 100644 --- a/libs/cli/langgraph_cli/config.py +++ b/libs/cli/langgraph_cli/config.py @@ -1256,7 +1256,7 @@ def python_config_to_docker( else: pip_installer = "pip" if pip_installer == "uv": - install_cmd = "uv pip install --system" + install_cmd = "uv pip install --system --prerelease=allow" elif pip_installer == "pip": install_cmd = "pip install" else: diff --git a/libs/cli/tests/unit_tests/cli/test_cli.py b/libs/cli/tests/unit_tests/cli/test_cli.py index d722b5666..6b1fca6ed 100644 --- a/libs/cli/tests/unit_tests/cli/test_cli.py +++ b/libs/cli/tests/unit_tests/cli/test_cli.py @@ -15,7 +15,7 @@ from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Versi from langgraph_cli.util import clean_empty_lines FORMATTED_CLEANUP_LINES = _get_pip_cleanup_lines( - install_cmd="uv pip install --system", + install_cmd="uv pip install --system --prerelease=allow", to_uninstall=("pip", "setuptools", "wheel"), pip_installer="uv", ) @@ -149,7 +149,7 @@ services: COPY --from=cli_1 . /deps/cli_1 # -- End of local package ../../.. -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")} diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index 40feb08f9..3b3efb6ec 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -20,7 +20,7 @@ from langgraph_cli.config import ( from langgraph_cli.util import clean_empty_lines FORMATTED_CLEANUP_LINES = _get_pip_cleanup_lines( - install_cmd="uv pip install --system", + install_cmd="uv pip install --system --prerelease=allow", to_uninstall=("pip", "setuptools", "wheel"), pip_installer="uv", ) @@ -422,7 +422,7 @@ def test_config_to_docker_simple(): FROM langchain/langgraph-api:3.11 # -- Installing local requirements -- COPY --from=outer-requirements.txt requirements.txt /deps/outer-graphs_reqs_a/graphs_reqs_a/requirements.txt -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -r /deps/outer-graphs_reqs_a/graphs_reqs_a/requirements.txt +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -r /deps/outer-graphs_reqs_a/graphs_reqs_a/requirements.txt # -- End of local requirements install -- # -- Adding local package ../../examples -- COPY --from=examples . /deps/examples @@ -456,7 +456,7 @@ RUN set -ex && \\ done # -- End of non-package dependency graphs_reqs_a -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGGRAPH_HTTP='{{"app": "/deps/examples/my_app.py:app"}}' ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' @@ -512,7 +512,7 @@ RUN set -ex && \\ done # -- End of non-package dependency tests -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}' """ @@ -559,7 +559,7 @@ RUN set -ex && \\ done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- -RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}' """ @@ -621,7 +621,7 @@ RUN set -ex && \\ done # -- End of non-package dependency graphs -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-graphs/src/agent.py:graph"}}' {FORMATTED_CLEANUP_LINES}\ @@ -657,7 +657,7 @@ dependencies = ["langchain"]""" ADD . /deps/unit_tests # -- End of local package . -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{"agent": "/deps/unit_tests/graphs/agent.py:graph"}' """ @@ -689,7 +689,7 @@ def test_config_to_docker_end_to_end(): ARG meow ARG foo ADD pipconfig.txt /pipconfig.txt -RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt langchain langchain_openai +RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt langchain langchain_openai # -- Adding non-package dependency graphs -- ADD ./graphs/ /deps/outer-graphs/src RUN set -ex && \\ @@ -705,7 +705,7 @@ RUN set -ex && \\ done # -- End of non-package dependency graphs -- # -- Installing all local dependencies -- -RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-graphs/src/agent.py:graph"}}' {FORMATTED_CLEANUP_LINES}""" @@ -811,7 +811,7 @@ RUN set -ex && \\ done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGGRAPH_UI='{{"agent": "./graphs/agent.ui.jsx"}}' ENV LANGGRAPH_UI_CONFIG='{{"shared": ["nuqs"]}}' @@ -857,7 +857,7 @@ RUN set -ex && \\ done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- -RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"python": "/deps/outer-unit_tests/unit_tests/multiplatform/python.py:graph", "js": "/deps/outer-unit_tests/unit_tests/multiplatform/js.mts:graph"}}' # -- Installing JS dependencies -- @@ -887,7 +887,7 @@ def test_config_to_docker_pip_installer(): docker_auto, _ = config_to_docker( PATH_TO_CONFIG, config_auto, "langchain/langgraph-api:0.2.47" ) - assert "uv pip install --system" in docker_auto + assert "uv pip install --system --prerelease=allow" in docker_auto assert "rm /usr/bin/uv /usr/bin/uvx" in docker_auto # Test explicit pip setting @@ -895,7 +895,7 @@ def test_config_to_docker_pip_installer(): docker_pip, _ = config_to_docker( PATH_TO_CONFIG, config_pip, "langchain/langgraph-api:0.2.47" ) - assert "uv pip install --system" not in docker_pip + assert "uv pip install --system --prerelease=allow" not in docker_pip assert "pip install" in docker_pip assert "rm /usr/bin/uv" not in docker_pip @@ -904,7 +904,7 @@ def test_config_to_docker_pip_installer(): docker_uv, _ = config_to_docker( PATH_TO_CONFIG, config_uv, "langchain/langgraph-api:0.2.47" ) - assert "uv pip install --system" in docker_uv + assert "uv pip install --system --prerelease=allow" in docker_uv assert "rm /usr/bin/uv /usr/bin/uvx" in docker_uv # Test auto behavior with older image (should use pip) @@ -914,7 +914,7 @@ def test_config_to_docker_pip_installer(): docker_auto_old, _ = config_to_docker( PATH_TO_CONFIG, config_auto_old, "langchain/langgraph-api:0.2.46" ) - assert "uv pip install --system" not in docker_auto_old + assert "uv pip install --system --prerelease=allow" not in docker_auto_old assert "pip install" in docker_auto_old assert "rm /usr/bin/uv" not in docker_auto_old @@ -923,7 +923,7 @@ def test_config_to_docker_pip_installer(): docker_default, _ = config_to_docker( PATH_TO_CONFIG, config_default, "langchain/langgraph-api:0.2.47" ) - assert "uv pip install --system" in docker_default + assert "uv pip install --system --prerelease=allow" in docker_default def test_config_retain_build_tools(): @@ -998,7 +998,7 @@ def test_config_to_compose_simple_config(): done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")} @@ -1039,7 +1039,7 @@ def test_config_to_compose_env_vars(): done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")} @@ -1084,7 +1084,7 @@ def test_config_to_compose_env_file(): done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")} @@ -1122,7 +1122,7 @@ def test_config_to_compose_watch(): done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")} @@ -1169,7 +1169,7 @@ def test_config_to_compose_end_to_end(): done # -- End of non-package dependency unit_tests -- # -- Installing all local dependencies -- - RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e /deps/* + RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --prerelease=allow --no-cache-dir -c /api/constraints.txt -e /deps/* # -- End of local dependencies install -- ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-unit_tests/unit_tests/agent.py:graph"}}' {textwrap.indent(textwrap.dedent(FORMATTED_CLEANUP_LINES), " ")}