Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ LD_SDK_KEY=your-sdk-key-here
OPENAI_API_KEY=your-openai-key-here
ANTHROPIC_API_KEY=your-anthropic-key-here

# Python LiteLLM runs in-process and uses the provider keys above; no
# LITELLM_BASE_URL or proxy API key is required.

# LaunchDarkly endpoints (leave blank to use production defaults)
LD_BASE_URI=
LD_STREAM_URI=
Expand Down
4 changes: 3 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"packages/openai-agents": "0.2.3",
"packages/openai-messages": "0.2.3",
"packages/langchain-agents": "0.2.3",
"packages/langchain-messages": "0.2.3"
"packages/langchain-messages": "0.2.3",
"packages/litellm-agents": "0.1.0",
"packages/litellm-messages": "0.1.0"
}
24 changes: 24 additions & 0 deletions .sdk_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@
"aiAgentGraph": { "introduced": "0.1" },
"aiTrackMetrics": { "introduced": "0.1" }
}
},
"python-ai-litellm-messages": {
"name": "LiteLLM Messages AI SDK for Python",
"type": "ai-provider",
"path": "packages/litellm-messages",
"languages": ["Python"],
"aiSdkNames": ["launchdarkly-ai-litellm-messages"],
"releases": { "tag-prefix": "launchdarkly-ai-litellm-messages-" },
"features": {
"aiCompletionConfig": { "introduced": "0.1" }
}
},
"python-ai-litellm-agents": {
"name": "LiteLLM Agents AI SDK for Python",
"type": "ai-provider",
"path": "packages/litellm-agents",
"languages": ["Python"],
"aiSdkNames": ["launchdarkly-ai-litellm-agents"],
"releases": { "tag-prefix": "launchdarkly-ai-litellm-agents-" },
"features": {
"aiAgentConfig": { "introduced": "0.1" },
"aiAgentGraph": { "introduced": "0.1" },
"aiTrackMetrics": { "introduced": "0.1" }
}
}
}
}
10 changes: 6 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ graph TD
claude["launchdarkly-ai-claude-agents"]
openai["launchdarkly-ai-openai-agents"]
langchain["launchdarkly-ai-langchain-agents"]
litellm["launchdarkly-ai-litellm-agents"]
newHandler["launchdarkly-ai-new-provider\n(future)"]
end
subgraph tier0 ["Tier 0 — Core"]
Expand All @@ -110,7 +111,7 @@ graph TD

- **Tier 0 — Core** (`launchdarkly-ai-server`): The foundation. Owns all LaunchDarkly integration, telemetry orchestration, shared data types, and the primary entry points (`config()`, `graph()`, `resolve_graph()`). Has no dependency on any other `launchdarkly-ai-*` package.
- **Tier 0 — Convenience barrel** (`launchdarkly-ai-python`): A pure re-export package that makes all of `launchdarkly-ai-server` available under a shorter install name. No new logic — intended as the default install for most Python applications.
- **Tier 1 — Handler packages** (`launchdarkly-ai-claude-agents`, `launchdarkly-ai-claude-messages`, `launchdarkly-ai-openai-agents`, `launchdarkly-ai-openai-messages`, `launchdarkly-ai-langchain-agents`, `launchdarkly-ai-langchain-messages`, …): Each wraps a specific AI provider SDK. Depends on `launchdarkly-ai-server` for shared types and utilities. Must not depend on other Tier 1 packages.
- **Tier 1 — Handler packages** (`launchdarkly-ai-claude-agents`, `launchdarkly-ai-claude-messages`, `launchdarkly-ai-openai-agents`, `launchdarkly-ai-openai-messages`, `launchdarkly-ai-langchain-agents`, `launchdarkly-ai-langchain-messages`, `launchdarkly-ai-litellm-agents`, `launchdarkly-ai-litellm-messages`, …): Each wraps a specific AI provider SDK. Depends on `launchdarkly-ai-server` for shared types and utilities. Must not depend on other Tier 1 packages.
- **Tier 2 — Consumer applications** (e.g. `main.py`, downstream projects): Imports from one or more handler packages and either `launchdarkly-ai-python` or `launchdarkly-ai-server`. Owns tool implementations and orchestration logic. No `launchdarkly-ai-*` package should ever depend on Tier 2 code.

### Rules
Expand Down Expand Up @@ -375,7 +376,7 @@ Requires `handlers` (either in `options` or via `options.registry`) to be set.

Resolves an agent graph's topology and node configs without executing it. The returned `GraphDefinition` carries `enabled`; callers should branch on it before traversing.

This is the entry point that framework-native runners (`to_claude_agents`, `to_openai_agents`, `to_lang_graph`) use to build their own execution structure.
This is the entry point that framework-native runners (`to_claude_agents`, `to_openai_agents`, `to_lang_graph`, `to_litellm_agents`) use to build their own execution structure.

### `Registry` / `global_registry` / `compose`

Expand Down Expand Up @@ -657,7 +658,7 @@ def my_provider(

For example, `claude_agents(config_key, user_input, context)` is equivalent to `config(key=config_key, handler=create_claude_agents_handler()).invoke(user_input, context)`.

The naming convention matches the package suffix: `claude_agents`, `claude_messages`, `openai_agents`, `openai_messages`, `langchain_agents`, `langchain_messages`.
The naming convention matches the package suffix: `claude_agents`, `claude_messages`, `openai_agents`, `openai_messages`, `langchain_agents`, `langchain_messages`, `litellm_agents`, `litellm_messages`.

### Graph Export (optional)

Expand All @@ -668,7 +669,7 @@ def claude_graph(key: str, **options) -> GraphInstance:
return graph(key, handlers=[create_claude_agents_handler()], **options)
```

Naming convention: `claude_graph`, `openai_graph`, `langchain_graph`.
Naming convention: `claude_graph`, `openai_graph`, `langchain_graph`, `litellm_graph`.

### Native Graph Adapter (optional)

Expand All @@ -678,6 +679,7 @@ Current adapters:
- `to_claude_agents(def_coro, opts)` — exported from `launchdarkly_ai_claude_agents`
- `to_openai_agents(def_coro, opts)` — exported from `launchdarkly_ai_openai_agents`
- `to_lang_graph(def_coro, opts)` — exported from `launchdarkly_ai_langchain_agents`
- `to_litellm_agents(def_coro, opts)` — exported from `launchdarkly_ai_litellm_agents`

---

Expand Down
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ That call is the whole integration. Everything it does is configured in LaunchDa
- Run agents and multi-step graphs, where each step can use a different provider
- Score output quality with judges, including scoring that stays off the request path
- See cost, latency, token usage, errors, and full conversations with no instrumentation code
- Keep the providers and frameworks you already run: OpenAI, Anthropic, LangChain, or your own handler
- Keep the providers and frameworks you already run: OpenAI, Anthropic, LangChain, LiteLLM, or your own handler

- [What you get](#what-you-get)
- [How It Works](#how-it-works)
Expand Down Expand Up @@ -85,6 +85,8 @@ Tier 0 — Core Client (launchdarkly-ai-server)
| [`launchdarkly-ai-claude-agents`](packages/claude-agents/README.md) | Anthropic | `agent` | Claude Agent SDK — agentic loop with MCP tool support |
| [`launchdarkly-ai-langchain-messages`](packages/langchain-messages/README.md) | `*` (any) | `messages` | Any `BaseChatModel` via LangChain `bind_tools` loop |
| [`launchdarkly-ai-langchain-agents`](packages/langchain-agents/README.md) | `*` (any) | `agent` | LangGraph `StateGraph` — managed ReAct loop |
| [`launchdarkly-ai-litellm-messages`](packages/litellm-messages/README.md) | `*` (any) | `messages` | In-process LiteLLM `acompletion` tool loop |
| [`launchdarkly-ai-litellm-agents`](packages/litellm-agents/README.md) | `*` (any) | `agent` | OpenAI Agents SDK with LiteLLM models |

## Quick Start

Expand All @@ -111,6 +113,9 @@ cp .env.example .env
# Fill in LD_SDK_KEY and the API key for your provider
```

LiteLLM runs in-process in Python, so no proxy endpoint is required. Set the
provider credentials required by the model names in your AI Config.

### 3. Call a model

#### 3a. Convenience functions
Expand Down Expand Up @@ -149,6 +154,8 @@ asyncio.run(main())
| `claude_agents` | `launchdarkly-ai-claude-agents` | `claude-agent-sdk` | Claude Agent SDK (MCP) |
| `langchain_messages` | `launchdarkly-ai-langchain-messages` | `langchain-core` | LangChain `bind_tools` loop |
| `langchain_agents` | `launchdarkly-ai-langchain-agents` | `langgraph` | LangGraph `StateGraph` |
| `litellm_messages` | `launchdarkly-ai-litellm-messages` | `litellm` | LiteLLM `acompletion` |
| `litellm_agents` | `launchdarkly-ai-litellm-agents` | `openai-agents[litellm]` | OpenAI Agents via LiteLLM |

---

Expand Down Expand Up @@ -250,7 +257,7 @@ async def main():
asyncio.run(main())
```

Provider packages also export single-provider conveniences (`claude_graph`, `openai_graph`, `langchain_graph`) that pre-bind their handler.
Provider packages also export conveniences (`claude_graph`, `openai_graph`, `langchain_graph`, `litellm_graph`) that pre-bind their handler.

---

Expand Down Expand Up @@ -311,6 +318,22 @@ result = await to_openai_agents(
).invoke("I was double charged")
```

##### `to_litellm_agents` — OpenAI Agents SDK over LiteLLM

Builds the same native Agents SDK handoff tree while creating each evaluated
node model through LiteLLM in-process.

```python
from launchdarkly_ai_server import resolve_graph
from launchdarkly_ai_litellm_agents import to_litellm_agents

ctx = {"kind": "user", "key": "user-123"}
result = await to_litellm_agents(
resolve_graph("support-graph", context=ctx),
{"tool_handlers": registry.tools, "context": ctx},
).invoke("I was double charged")
```

##### `to_lang_graph` — LangGraph `StateGraph`

Uses a pre-order traversal (root → leaves) to build a compiled `StateGraph`. Single-child edges become direct edges; multi-child edges use `Command`-returning handoff tools so the model picks exactly one target.
Expand Down Expand Up @@ -597,6 +620,9 @@ uv run python main.py [example] [flag-key] [user-input]
| `graph-history` | `uv run python main.py graph-history` | `graph().invoke()` with multimodal `history` forwarded to the root node |
| `openai-only` | `uv run python main.py openai-only` | `config()` with a custom `Registry` restricted to OpenAI handlers |
| `streaming` | `uv run python main.py streaming` | `config().stream()` — token-by-token output |
| `litellm` | `uv run python main.py litellm` | `config()` routing both messages and agents through LiteLLM |
| `litellm-agents` | `uv run python main.py litellm-agents` | `litellm_agents()` with in-process LiteLLM routing |
| `litellm-messages` | `uv run python main.py litellm-messages` | `litellm_messages()` with in-process LiteLLM routing |

**Examples:**

Expand Down Expand Up @@ -636,7 +662,9 @@ python-ai-sdk/
│ ├── openai-agents/ # launchdarkly-ai-openai-agents
│ ├── openai-messages/ # launchdarkly-ai-openai-messages
│ ├── langchain-agents/ # launchdarkly-ai-langchain-agents
│ └── langchain-messages/ # launchdarkly-ai-langchain-messages
│ ├── langchain-messages/ # launchdarkly-ai-langchain-messages
│ ├── litellm-agents/ # launchdarkly-ai-litellm-agents
│ └── litellm-messages/ # launchdarkly-ai-litellm-messages
├── .env.example # Template — copy to .env and fill in your values
└── agents.md # Architecture reference for AI agents and contributors
```
Expand Down
37 changes: 37 additions & 0 deletions examples/litellm_agents_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Example: litellm_agents() — LiteLLM agents handler (wildcard provider).

Usage (via main.py):
python main.py litellm-agents <flag-key> "<user input>"
"""

from __future__ import annotations

import json

from examples.tools import (
fetch_launchdarkly_documentation,
get_preferences,
search_ld_documentation,
web_search,
)
from examples.utils import new_context, write_output
from launchdarkly_ai_litellm_agents import litellm_agents


async def run(key: str, user_input: str) -> None:
response = await litellm_agents(
key,
user_input,
new_context(),
tool_handlers={
"get-user-preferences": get_preferences,
"search-ld-documentation": search_ld_documentation,
"fetch-ld-documentation": fetch_launchdarkly_documentation,
"fetch-launchdarkly-documentation": fetch_launchdarkly_documentation,
"web-search": web_search,
},
)

print(json.dumps(response, indent=2, default=str))
write_output(response)
41 changes: 41 additions & 0 deletions examples/litellm_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Example: config() with LiteLLM handlers for messages and agents.

Python calls LiteLLM in-process, so provider-qualified models and aliases from
the evaluated AI Config are routed using the installed LiteLLM configuration.

Usage (via main.py):
python main.py litellm <flag-key> "<user input>"
"""

from __future__ import annotations

from examples.tools import (
fetch_launchdarkly_documentation,
get_preferences,
search_ld_documentation,
web_search,
)
from examples.utils import new_context, write_output
from launchdarkly_ai_litellm_agents import create_litellm_agents_handler
from launchdarkly_ai_litellm_messages import create_litellm_messages_handler
from launchdarkly_ai_server import config


async def run(key: str, user_input: str) -> None:
response = await config(
key=key,
handler=[
create_litellm_messages_handler(),
create_litellm_agents_handler(),
],
tool_handlers={
"get-user-preferences": get_preferences,
"search-ld-documentation": search_ld_documentation,
"fetch-ld-documentation": fetch_launchdarkly_documentation,
"fetch-launchdarkly-documentation": fetch_launchdarkly_documentation,
"web-search": web_search,
},
).invoke(user_input, new_context(), variables={"user_input": user_input})

write_output(response)
38 changes: 38 additions & 0 deletions examples/litellm_messages_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Example: litellm_messages() — LiteLLM messages handler.

Usage (via main.py):
python main.py litellm-messages <flag-key> "<user input>"
"""

from __future__ import annotations

import json

from examples.tools import (
fetch_launchdarkly_documentation,
get_preferences,
search_ld_documentation,
web_search,
)
from examples.utils import new_context, write_output
from launchdarkly_ai_litellm_messages import litellm_messages


async def run(key: str, user_input: str) -> None:
response = await litellm_messages(
key,
user_input,
new_context(),
tool_handlers={
"get-user-preferences": get_preferences,
"search-ld-documentation": search_ld_documentation,
"fetch-ld-documentation": fetch_launchdarkly_documentation,
"fetch-launchdarkly-documentation": fetch_launchdarkly_documentation,
"web-search": web_search,
},
variables={"user_input": user_input},
)

print(json.dumps(response, indent=2, default=str))
write_output(response)
4 changes: 4 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
python main.py graph-history my-agent-graph ""
python main.py openai-only my-openai-flag "Tell me about feature flags"
python main.py langchain my-langchain-flag "Tell me about feature flags"
python main.py litellm my-litellm-flag "Tell me about feature flags"
python main.py claude-agents launch-darkly-documentation-summarizer "What is the LaunchDarkly AI SDK?"
python main.py openai-agents launch-darkly-documentation-summarizer-open-ai-only "What is the LaunchDarkly AI SDK?"
python main.py langchain-agents launch-darkly-documentation-summarizer "What is the LaunchDarkly AI SDK?"
Expand Down Expand Up @@ -58,6 +59,9 @@
"langchain-agents": "examples.langchain_agents_example",
"langchain-messages": "examples.langchain_messages_example",
"langchain-thinking": "examples.langchain_thinking",
"litellm": "examples.litellm_example",
"litellm-agents": "examples.litellm_agents_example",
"litellm-messages": "examples.litellm_messages_example",
"native-graph": "examples.native_graph",
"native-graph-langchain": "examples.native_graph_langchain",
}
Expand Down
35 changes: 35 additions & 0 deletions packages/litellm-agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `launchdarkly-ai-litellm-agents`

Provider-agnostic LaunchDarkly AI Config and graph handlers built on the OpenAI
Agents SDK's `LitellmModel`.

## Installation

```bash
pip install launchdarkly-ai-server launchdarkly-server-sdk launchdarkly-ai-litellm-agents
```
Comment thread
cursor[bot] marked this conversation as resolved.

Python uses LiteLLM in-process; no proxy endpoint is required. Set the provider
credentials required by the models in your AI Config, such as
`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`. Standard LiteLLM configuration,
gateways, and model aliases are supported.

## Usage

```python
from launchdarkly_ai_litellm_agents import create_litellm_agents_handler
from launchdarkly_ai_server import config

result = await config(
key="assistant",
handler=create_litellm_agents_handler(),
).invoke("Hello", {"kind": "user", "key": "user-123"})
```

Each evaluated config model is authoritative and creates its own `LitellmModel`.
The package supports callable tools, structured output, streaming, the
`litellm_graph` convenience wrapper, and `to_litellm_agents` for native Agents
SDK handoffs.

Because this is a wildcard agent handler, do not register it together with
another `["*", "agent"]` handler such as LangChain in the same registry.
34 changes: 34 additions & 0 deletions packages/litellm-agents/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[project]
name = "launchdarkly-ai-litellm-agents"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"launchdarkly-ai-server",
"openai-agents[litellm]>=0.2.11",
"opentelemetry-api>=1.25",
]
description = "LiteLLM-backed OpenAI Agents handler for LaunchDarkly AI SDK"
readme = "README.md"
license = "Apache-2.0"
authors = [{name = "LaunchDarkly", email = "team@launchdarkly.com"}]
keywords = ["launchdarkly", "ai", "litellm", "agents"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries",
]

[project.urls]
Homepage = "https://github.com/launchdarkly/python-ai-sdk"
Repository = "https://github.com/launchdarkly/python-ai-sdk"
"Bug Tracker" = "https://github.com/launchdarkly/python-ai-sdk/issues"

[build-system]
requires = ["hatchling>=1.32,<2"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/launchdarkly_ai_litellm_agents"]
Loading
Loading