Getting started with LangGraph and AgentControl configs

This guide shows how to integrate LangGraph agent workflows with LaunchDarkly AgentControl. Using AgentControl configs with LangGraph lets you manage agent instructions, model configuration, and parameters outside of your application code.

This guide uses agent mode for LangGraph workflows. Agent mode uses a single instructions string rather than a messages array, which maps directly to LangGraph’s agent prompts. To learn more, read Agents.

New to AgentControl?

If you’re new to AgentControl, start with the Quickstart and return to this guide when you are ready for a LangGraph-specific example.

To learn more about AgentControl-specific SDKs, read AI SDKs. For Python-specific details, read the Python AI SDK reference.

Prerequisites

To complete this guide, you must have the following prerequisites:

  • A LaunchDarkly account, including:
    • A LaunchDarkly SDK key for your environment.
    • A member role that allows AgentControl actions. The LaunchDarkly project admin, maintainer, and developer project roles, as well as the admin and owner base roles, include this ability. To learn more about LaunchDarkly roles, read Roles.
  • A Python 3.10+ or Node.js 20+ development environment.
  • LangGraph installed in your application.
  • An API key for your chosen model provider (OpenAI, Anthropic, or another supported provider).

Concepts

Before you begin, review these key concepts.

LangGraph agents

LangGraph provides a framework for building agent workflows as directed graphs. The create_agent function (in langchain.agents; replaces the deprecated langgraph.prebuilt.create_react_agent in LangGraph 1.0+) creates a ReAct-style agent that can use tools and maintain state across conversation turns. Agents receive a system prompt that defines their behavior and capabilities.

Agent mode AgentControl configs

Agent mode AgentControl configs use an instructions field instead of a messages array. This single instruction string serves as the system prompt for your agent. Agent mode is ideal for:

  • Multi-step agent workflows
  • Tool-using agents
  • Persistent agent sessions

The agent_config function

The agent_config function retrieves the AgentControl config variation for a given context. It returns an AIAgentConfig object that includes the customized instructions, model configuration, and a create_tracker() (Python) or createTracker() (Node.js) factory method that returns a tracker for recording metrics. Call agent_config each time you create an agent so LaunchDarkly can evaluate targeting and return the current configuration.

Step 1: Install dependencies

Install the LaunchDarkly SDKs and LangGraph packages.

pip install "launchdarkly-server-sdk-ai>=0.20.0" "launchdarkly-server-sdk-ai-langchain>=0.7.0" langgraph langchain langchain-core python-dotenv

Install the LangChain provider package for your model. Common provider packages include:

  • langchain-openai for OpenAI models
  • langchain-anthropic for Anthropic models
  • langchain-google-genai for Google Gemini models

Step 2: Create an AgentControl config in LaunchDarkly

Create an AgentControl config in agent mode to store your agent configuration.

To create an AgentControl config:

  1. In the left sidebar, click Create and select AgentControl config.
  2. In the “Create AgentControl config” dialog, select Agent.
  3. Enter a name for your AgentControl config, for example, “LangGraph Agent.”
  4. Click Create.

Then, create a variation:

  1. On the Variations tab, replace “Untitled variation” with a variation name, such as “GPT-4o Agent”.
  2. Click Select a model and choose the gpt-4o OpenAI model.
  3. Click Parameters and set temperature to 0.7 and max_tokens to 2000.
  4. In the Instructions field, enter your agent’s system prompt:
You are a helpful assistant that can perform calculations and check weather. Use the available tools to provide accurate information. Always explain your reasoning step by step.
  1. Click Review and save.

A completed variation with model configuration and instructions.

A completed variation with model configuration and instructions.

Step 3: Set up targeting rules

Configure targeting rules to control which users receive the AgentControl config variation.

To set up the default rule:

  1. Select the Targeting tab for your AgentControl config.
  2. In the “Default rule” section, click Edit.
  3. Configure the default rule to serve your variation, such as “GPT-4o Agent”.
  4. Click Review and save.

The default targeting rule configured to serve a variation.

The default targeting rule configured to serve a variation.

The AgentControl config is enabled by default. After you add the integration code to your application, LaunchDarkly serves the configured variation to your users.

Step 4: Integrate LangGraph with AgentControl configs

The integration involves these key steps:

  1. Define the tools your agent can call.
  2. Initialize the LaunchDarkly SDK and AI client.
  3. Get the agent config using agent_config() (Python) or aiClient.agentConfig() (Node.js).
  4. Build a LangChain model from the AgentControl config using the LaunchDarkly LangChain provider.
  5. Create a LangGraph ReAct agent with a MemorySaver checkpointer.
  6. Invoke the agent and track metrics with the config’s tracker.

Define the agent’s tools.

from langchain_core.tools import tool
@tool
def get_order_status(order_id: str) -> str:
"""Look up the status of a customer order by order ID."""
orders = {
"ORD-123": "Shipped — arrives Thursday",
"ORD-456": "Processing — estimated ship date: tomorrow",
"ORD-789": "Delivered on Monday",
}
return orders.get(order_id, f"No order found with ID {order_id}")
# Map tool keys (matching the LaunchDarkly tool keys) to local handlers. The agent
# build step below resolves the active tool list from `agent_config.model.parameters['tools']`
# so detaching `get_order_status` from the variation in LaunchDarkly takes effect on
# the next agent invocation, with no code change.
TOOL_REGISTRY = {"get_order_status": get_order_status}

Initialize the LaunchDarkly SDK and AI client, fetch the agent config, build the LangChain model with create_langchain_model (Python) or createLangChainModel (Node.js), and create the ReAct agent.

The provider reads the model name, provider, and all parameters (temperature, max tokens, and others) from the variation, maps LaunchDarkly provider names to LangChain equivalents — for example, "gemini" to "google_genai" — and returns a configured chat model. The same AgentControl config key can serve OpenAI, Anthropic, or any other provider-backed variation from the same code path.

import os
import ldclient
from ldclient import Context
from ldclient.config import Config
from ldai.client import LDAIClient
from ldai.providers.types import LDAIMetrics
from ldai_langchain import (
create_langchain_model,
get_tool_calls_from_response,
sum_token_usage_from_messages,
)
from langchain_core.tools import tool
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
ldclient.set_config(Config(os.environ.get("LAUNCHDARKLY_SDK_KEY")))
ai_client = LDAIClient(ldclient.get())
context = Context.builder("user-123").kind("user").name("Sandy").build()
# Pass a default for improved resiliency when the AgentControl config is unavailable
# or LaunchDarkly is unreachable; omit for a disabled default.
# Example:
# from ldai import AIAgentConfigDefault
# default = AIAgentConfigDefault(
# enabled=True,
# model={"name": "gpt-5"},
# provider={"name": "openai"},
# instructions="You are a helpful assistant.",
# )
# agent_config = ai_client.agent_config("langgraph-agent", context, default)
agent_config = ai_client.agent_config("langgraph-agent", context)
# create_langchain_model reads agent_config.model.name / .parameters and picks the
# right chat model class (OpenAI, Anthropic, …) with no per-provider branching.
llm = create_langchain_model(agent_config)
# Resolve the agent's tool list from the active variation. LaunchDarkly returns
# attached tools under `parameters.tools` in OpenAI function shape; we only need
# the names to look up the local handlers from TOOL_REGISTRY.
ld_tool_params = (agent_config.model.to_dict().get("parameters") or {}).get("tools") or []
resolved_tools = [
TOOL_REGISTRY[t["name"]] for t in ld_tool_params if t["name"] in TOOL_REGISTRY
]
# MemorySaver gives the ReAct agent short-term memory per thread_id —
# follow-up turns on the same thread see the earlier conversation.
checkpointer = MemorySaver()
agent = create_agent(
llm,
resolved_tools,
system_prompt=agent_config.instructions,
checkpointer=checkpointer,
)

Invoke the agent and track metrics. Each turn is one execution as far as the tracker is concerned: track_metrics_of_async (Python) / trackMetricsOf (Node.js) records duration and tracks success or error itself, so the surrounding try/except only needs to log. The Python example uses the SDK’s sum_token_usage_from_messages helper to aggregate token counts and get_tool_calls_from_response to feed track_tool_call. The Node.js example uses LangChainProvider.getAIMetricsFromResponse per message and reads msg.tool_calls directly until the matching JS helper ships.

from ldai.providers.types import LDAIMetrics
from ldai_langchain import get_tool_calls_from_response, sum_token_usage_from_messages
async def run_turn(agent, agent_config, user_input, thread_id):
# Each tracker is one execution. `track_metrics_of_async`
# records duration + success/error itself; the extractor only returns
# LDAIMetrics. The SDK helpers handle token aggregation and tool-call
# name extraction.
tracker = agent_config.create_tracker()
try:
result = await tracker.track_metrics_of_async(
lambda: agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]},
config={"configurable": {"thread_id": thread_id}},
),
lambda res: LDAIMetrics(
success=True,
usage=sum_token_usage_from_messages(res.get("messages", [])),
),
)
for msg in result.get("messages", []):
for name in get_tool_calls_from_response(msg):
tracker.track_tool_call(name)
messages = result.get("messages", [])
if messages:
print(f"Agent: {messages[-1].content}")
except Exception as e:
# `track_metrics_of_async` already recorded the error and re-raised.
print(f"Error: {e}")
thread_id = "demo-thread"
await run_turn(agent, agent_config, "What's the status of order ORD-123?", thread_id)
await run_turn(agent, agent_config, "What about ORD-456?", thread_id)
await run_turn(agent, agent_config, "Summarize both orders for me.", thread_id)

The fallback argument to agent_config / agentConfig is optional. When omitted, LaunchDarkly returns a disabled config if the flag is off or the SDK is unreachable. Pass an explicit fallback to keep the agent running during outages.

Complete example

Here is a complete working example that combines all the steps.

import asyncio
import os
import ldclient
from ldclient import Context
from ldclient.config import Config
from ldai.client import LDAIClient
from ldai.providers.types import LDAIMetrics
from ldai_langchain import (
create_langchain_model,
get_tool_calls_from_response,
sum_token_usage_from_messages,
)
from langchain_core.tools import tool
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
from dotenv import load_dotenv
load_dotenv()
SDK_KEY = os.environ.get("LAUNCHDARKLY_SDK_KEY")
AGENT_CONFIG_KEY = "langgraph-agent"
@tool
def get_order_status(order_id: str) -> str:
"""Look up the status of a customer order by order ID."""
orders = {
"ORD-123": "Shipped — arrives Thursday",
"ORD-456": "Processing — estimated ship date: tomorrow",
"ORD-789": "Delivered on Monday",
}
return orders.get(order_id, f"No order found with ID {order_id}")
# Map tool keys (matching the LaunchDarkly tool keys) to local handlers.
TOOL_REGISTRY = {"get_order_status": get_order_status}
async def run_turn(agent, agent_config, user_input, thread_id):
# Each tracker is one execution. `track_metrics_of_async`
# records duration + success/error itself; the extractor only returns
# LDAIMetrics. SDK helpers handle token aggregation and tool-call name
# extraction.
tracker = agent_config.create_tracker()
try:
result = await tracker.track_metrics_of_async(
lambda: agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]},
config={"configurable": {"thread_id": thread_id}},
),
lambda res: LDAIMetrics(
success=True,
usage=sum_token_usage_from_messages(res.get("messages", [])),
),
)
for msg in result.get("messages", []):
for name in get_tool_calls_from_response(msg):
tracker.track_tool_call(name)
messages = result.get("messages", [])
if messages:
print(f"Agent: {messages[-1].content}")
except Exception as e:
# `track_metrics_of_async` already recorded the error and re-raised.
print(f"Error: {e}")
async def async_main():
ldclient.set_config(Config(SDK_KEY))
if not ldclient.get().is_initialized():
print("LaunchDarkly SDK failed to initialize")
return
ai_client = LDAIClient(ldclient.get())
context = Context.builder("user-123").kind("user").name("Sandy").build()
# Pass a default for improved resiliency when the AgentControl config is unavailable
# or LaunchDarkly is unreachable; omit for a disabled default.
# Example:
# from ldai import AIAgentConfigDefault
# default = AIAgentConfigDefault(
# enabled=True,
# model={"name": "gpt-5"},
# provider={"name": "openai"},
# instructions="You are a helpful assistant.",
# )
# agent_config = ai_client.agent_config(AGENT_CONFIG_KEY, context, default)
agent_config = ai_client.agent_config(AGENT_CONFIG_KEY, context)
if not agent_config.enabled:
print("Agent Config is disabled — run the notebook to set up the config")
return
# create_langchain_model reads agent_config.model.name / .parameters and picks the
# right chat model class (OpenAI, Anthropic, …) with no per-provider branching.
llm = create_langchain_model(agent_config)
# Resolve the agent's tool list from the active variation. LaunchDarkly returns
# attached tools under `parameters.tools` in OpenAI function shape.
ld_tool_params = (agent_config.model.to_dict().get("parameters") or {}).get("tools") or []
resolved_tools = [
TOOL_REGISTRY[t["name"]] for t in ld_tool_params if t["name"] in TOOL_REGISTRY
]
# MemorySaver gives the ReAct agent short-term memory per thread_id —
# follow-up turns on the same thread see the earlier conversation.
checkpointer = MemorySaver()
agent = create_agent(
llm,
resolved_tools,
system_prompt=agent_config.instructions,
checkpointer=checkpointer,
)
# Three turns on one thread: first fires the tool, second reuses memory
# to answer a follow-up that reuses the tool, third summarizes — no tool.
# Each turn gets its own tracker (its own runId).
thread_id = "demo-thread"
await run_turn(agent, agent_config, "What's the status of order ORD-123?", thread_id)
await run_turn(agent, agent_config, "What about ORD-456?", thread_id)
await run_turn(agent, agent_config, "Summarize both orders for me.", thread_id)
# Always flush events before closing — trailing events are at risk of being
# lost otherwise, in short-lived scripts and long-running services alike.
ldclient.get().flush()
ldclient.get().close()
def main():
asyncio.run(async_main())
if __name__ == "__main__":
main()

Step 5: Monitor results

View metrics for your AgentControl config in the LaunchDarkly UI.

To monitor results:

  1. In LaunchDarkly, navigate to your AgentControl config.
  2. Select the Monitoring tab.

LaunchDarkly displays metrics including:

  • Generation count
  • Token usage (input, output, total)
  • Time to generate
  • Error rate

Use these metrics to compare agent performance, identify cost differences, and make data-driven decisions about which configuration to use for different user segments. To learn more, read Monitor AgentControl configs.

To view aggregated metrics across all your AgentControl configs, navigate to Insights in the left sidebar under the AI section. The Insights overview page displays cost, latency, error rate, invocation counts, and model distribution across your organization. To learn more, read about AI insights.

The Insights overview page showing cost, latency, error rate, and invocation metrics for a LangGraph AgentControl config.

The Insights overview page showing cost, latency, error rate, and invocation metrics for a LangGraph AgentControl config.

Agent mode vs completion mode

AspectAgent ModeCompletion Mode
Config fieldinstructions (string)messages (array)
SDK methodagent_config()completion_config()
Default classAIAgentConfigDefaultAICompletionConfigDefault
Use caseMulti-step workflows, tool useSingle-turn completions

Conclusion

In this guide, you learned how to integrate LangGraph agent workflows with LaunchDarkly AgentControl to manage agent configuration outside of your application code.

You can now:

  • Change agent models and instructions without redeploying your application
  • Target different agent configurations to different users based on context attributes
  • Track and compare agent performance across variations
  • Maintain conversation state with LangGraph checkpointing
  • Coordinate multi-agent workflows with centralized configuration

To explore additional capabilities, read:

For more AgentControl examples, read the other AgentControl guides in this section.

Want to know more? Start a trial.

Your 14-day trial begins as soon as you sign up. Get started in minutes using the in-app Quickstart. You’ll discover how easy it is to release, monitor, and optimize your software.

Want to try it out? Start a trial.