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:
agent.yamlConfiguration file defining agent metadata, deployment settings, and optional connection requirements.
- Python module
A
.pyfile that contains the factory function that creates your agent. requirements.txtLists 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_versionIt must be
v1.kindIt must be
agent.nameThe unique identifier for your agent. The name cannot be empty or contain whitespace only, and it has the maximum 40 of characters.
descriptionDescription 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.entrypointEntry point in
module:functionformat. 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:
titleA display name for the agent.
frameworkIt defaults to
langgraph. This is the only supported framework.checkpointerOptional 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
RunnableConfigparameter. - Return an uncompiled
StateGraph.Important: Do not call the.compile()method to compile theStateGraph. - Be specified in
agent.yamlby using themodule:functionformat.
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_agentFunction
create_agent()inagent.py.my_agent:build_graphFunction
build_graph()inmy_agent.py.custom:my_factoryFunction
my_factory()incustom.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:
- Go to your agent directory.
- Select all files and subdirectories.
- 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.