Creating a LangGraph package for import

Build custom LangGraph agents that can be imported into watsonx Orchestrate to run within the platform runtime environment.

Required file structure

To import a LangGraph agent, create a package that contains the files that define your agent's behavior, deployment settings, and connection requirements.

Minimum required files

Every LangGraph agent package must include:

  1. agent.yaml

    Configuration file defining agent metadata, deployment settings, and optional connection requirements.

  2. Python module

    A .py file that contains the factory function that creates your agent.

  3. requirements.txt

    Lists Python dependencies needed by your agent.

Optional files

You can also include:

  • Additional Python modules for tools, utilities, or helper functions.
  • Subdirectories to organize your code into logical components.

Example directory structure

A simple agent structure:

my-agent/
├── agent.yaml
├── agent.py
└── requirements.txt

A more complex agent with organized components:

my-agent/
├── agent.yaml
├── agent.py
├── requirements.txt
├── core/
│   ├── __init__.py
│   ├── state.py
│   └── config.py
├── tools/
│   ├── __init__.py
│   └── api_tools.py
└── utils/
    ├── __init__.py
    └── logging.py

Creating the agent.yaml configuration file

The agent.yaml file defines your agent metadata and specifies how watsonx Orchestrate deploys and runs the package.

Required fields

spec_version: v1
kind: agent
name: my_agent_name
description: Description of what your agent does
deployment:
  code_bundle:
    entrypoint: module:function

Where:

  • spec_version

    It must be v1.

  • kind

    It must be agent.

  • name

    The unique identifier for your agent. The name cannot be empty or contain whitespace only, and it has the maximum 40 of characters.

  • description

    Description of what the agent does. The value must not be empty or whitespace only.

    For more information about how to write descriptions, see Recommendations for agent descriptions.

  • deployment.code_bundle.entrypoint

    Entry point in module:function format. The module is the Python module path and the function creates the LangGraph graph.

Optional fields

title: My Agent Display Name
framework: langgraph
checkpointer:
  type: postgres
  connection_string_key: db_connection_string

Where:

  • title

    A display name for the agent.

  • framework

    It defaults to langgraph. This is the only supported framework.

  • checkpointer

    Optional configuration for state persistence. Enables the agent to maintain conversation state across interactions. For detailed information about configuring checkpointers, see Enabling state persistence for LangGraph agents.

Full configuration structure

spec_version: v1
kind: agent
name: <agent-name>
title: <optional-display-title>
description: <agent-description>
framework: langgraph
deployment:
  code_bundle:
    entrypoint: <module:function>
checkpointer:
  type: <postgres|sqlite|memory>
  connection_string_key: <key-name>
        

Implementing the factory function

Your Python module must contain a factory function that creates and returns an uncompiled StateGraph. The watsonx Orchestrate runtime handles compilation, execution, and injection of configured connection credentials.

Factory function requirements

The factory function must:

  • Accept a RunnableConfig parameter.
  • Return an uncompiled StateGraph.
    Important: Do not call the .compile() method to compile the StateGraph.
  • Be specified in agent.yaml by using the module:function format.

Basic factory function structure

Without credentials:

from langchain_core.runnables.config import RunnableConfig
from langgraph.graph import StateGraph, START, END

def create_agent(config: RunnableConfig) -> StateGraph:
    """
    Factory function that creates and returns an uncompiled StateGraph.
    
    Args:
        config: Runtime configuration containing credentials, settings, and other runtime metadata
    
    Returns:
        StateGraph: The uncompiled agent graph
    """
    workflow = StateGraph(YourStateClass)
    
    # Add nodes and edges
    workflow.add_node("your_node", your_node_function)
    workflow.add_edge(START, "your_node")
    workflow.add_edge("your_node", END)
    
    # Return the UNCOMPILED graph
    return workflow

With credentials:

from langchain_core.runnables.config import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END

def create_agent(config: RunnableConfig) -> StateGraph:
    """
    Factory function that accesses credentials and creates an uncompiled StateGraph.
    
    Args:
        config: Runtime configuration containing credentials, settings, and other runtime metadata
    
    Returns:
        StateGraph: The uncompiled agent graph
    """
    # Access credentials from config
    credentials = config.get("configurable", {}).get("credentials", {})
    api_key = credentials.get("openai_api_api_key")
    
    # Initialize LLM with credentials
    llm = ChatOpenAI(model="gpt-4o-mini", api_key=api_key)
    
    # Build workflow
    workflow = StateGraph(YourStateClass)
    workflow.add_node("your_node", your_node_function)
    workflow.add_edge(START, "your_node")
    workflow.add_edge("your_node", END)
    
    return workflow

For detailed information about accessing credentials and connection patterns, see Creating connections for LangGraph agents.

Naming your module and function

You can use any module and function names. Specify them in agent.yaml, for example:

  • agent:create_agent

    Function create_agent() in agent.py.

  • my_agent:build_graph

    Function build_graph() in my_agent.py.

  • custom:my_factory

    Function my_factory() in custom.py.

Example implementation

Simple agent without LLM

This example creates a basic agent that responds with a greeting.

agent.py:

from typing import Annotated, List, TypedDict
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.runnables.config import RunnableConfig
from langgraph.graph import StateGraph, START, END

class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], "conversation history"]

def hello_world_node(state: AgentState) -> AgentState:
    response = AIMessage(content="Hello! How can I help you today?")
    return {"messages": state["messages"] + [response]}

def create_agent(config: RunnableConfig) -> StateGraph:
    workflow = StateGraph(AgentState)
    workflow.add_node("hello_world", hello_world_node)
    workflow.add_edge(START, "hello_world")
    workflow.add_edge("hello_world", END)
    return workflow

agent.yaml:

spec_version: v1
kind: agent
name: hello_world_agent
title: Hello World Agent
description: Simple agent that returns a greeting
deployment:
  code_bundle:
    entrypoint: agent:create_agent

requirements.txt:

langgraph>=0.2.0
langchain-core>=0.3.0

Agent with LLM integration

This example shows how to read injected connection credentials and initialize an LLM from one of several supported providers.

agent.py:

from typing import Annotated, List, TypedDict
from langchain_core.messages import BaseMessage
from langchain_core.runnables.config import RunnableConfig
from langchain_openai import ChatOpenAI
from langchain_google_genai import ChatGoogleGenerativeAI
from langgraph.graph import StateGraph, START, END

class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], "conversation history"]

def create_agent(config: RunnableConfig) -> StateGraph:
    # Get credentials from config
    credentials = config.get("configurable", {}).get("credentials", {})
    openai_api_key = credentials.get("openai_api_api_key")
    gemini_api_key = credentials.get("gemini_api_api_key")
    
    # Initialize LLM based on available credentials
    if openai_api_key:
        llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
    elif gemini_api_key:
        llm = ChatGoogleGenerativeAI(
            model="gemini-2.0-flash-exp", 
            api_key=gemini_api_key
        )
    else:
        raise ValueError("No LLM credentials provided")
    
    def agent_node(state: AgentState):
        response = llm.invoke(state["messages"])
        return {"messages": [response]}
    
    workflow = StateGraph(AgentState)
    workflow.add_node("agent", agent_node)
    workflow.add_edge(START, "agent")
    workflow.add_edge("agent", END)
    
    return workflow

agent.yaml:

spec_version: v1
kind: agent
name: llm_agent
title: LLM-Powered Agent
description: Agent that uses an LLM to respond to queries
deployment:
  code_bundle:
    entrypoint: agent:create_agent

requirements.txt:

langgraph>=0.2.0
langchain-core>=0.3.0
langchain-openai>=0.2.0
langchain-google-genai>=2.0.0

Agent with tools

This example demonstrates how to create an agent that can use tools.

agent.py:

from typing import Annotated, List, TypedDict
from langchain_core.messages import BaseMessage
from langchain_core.runnables.config import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode, tools_condition

class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], "conversation history"]

def get_weather(location: str) -> str:
    """Get the current weather for a location."""
    return f"The weather in {location} is sunny and 72°F"

def create_agent(config: RunnableConfig) -> StateGraph:
    credentials = config.get("configurable", {}).get("credentials", {})
    llm = ChatOpenAI(
        model="gpt-4o-mini",
        api_key=credentials.get("openai_api_api_key")
    )
    
    tools = [get_weather]
    tool_node = ToolNode(tools)
    
    def agent_node(state: AgentState):
        response = llm.bind_tools(tools).invoke(state["messages"])
        return {"messages": [response]}
    
    workflow = StateGraph(AgentState)
    workflow.add_node("agent", agent_node)
    workflow.add_node("tools", tool_node)
    workflow.add_conditional_edges(
        "agent",
        tools_condition,
        {"tools": "tools", "__end__": END}
    )
    workflow.add_edge("tools", "agent")
    workflow.set_entry_point("agent")
    
    return workflow

Packaging your agent

Once you create the required files, package them into a ZIP file for import:

  1. Go to your agent directory.
  2. Select all files and subdirectories.
  3. Create a ZIP archive.

Ensure that the ZIP file preserves the relative paths of your files. The agent.yaml file must be at the root of the package. If your ZIP file contains a single top-level directory, the import process flattens it automatically.

What to do next