Skip to main content

ADLI-SDK

Official Python SDK for ADLI — runtime strategy learning for AI agents.

ADLI makes your agents smarter with every run:

  1. Inject — before each run, checks if there's a relevant strategy and modifies the user message.
  2. Learn — after each run, captures the full conversation trace (messages, tool calls, reasoning, usage) and sends it to ADLI for strategy evolution.

Installation

pip install adli-sdk

Quick Start

PydanticAI

from pydantic_ai import Agent
from adli_sdk import ADLI

Agent.instrument_all()

adli = ADLI(token="adli-xxx", project_id=1)
adli.instrument()

agent = Agent("openai:gpt-4o", system_prompt="You are a SQL analyst.")
agent = adli.wrap(agent, agent_name="sql-agent")

result = await agent.run("Get all customers with outstanding debt")

LangChain

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from adli_sdk import ADLI

@tool
def lookup_schema(table_name: str) -> str:
    """Look up the database schema for a table."""
    return f"{table_name}(id, name, created_at)"

adli = ADLI(token="adli-xxx", project_id=1)

model = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a senior SQL analyst. Use tools to check table structures."),
    ("human", "{input}"),
    MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(model, [lookup_schema], prompt)
executor = AgentExecutor(agent=agent, tools=[lookup_schema])

executor = adli.wrap(executor, agent_name="sql-agent")
result = executor.invoke({"input": "What's the schema of the customers table?"})

LangGraph

Multi-node StateGraph with tools, conditional edges, and tool loops — works the same way. Pass input_key="messages" for MessagesState.

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import MessagesState
from langgraph.prebuilt import ToolNode, tools_condition
from adli_sdk import ADLI

@tool
def search_docs(query: str) -> str:
    """Search documentation."""
    return f"Results for '{query}'..."

model = ChatOpenAI(model="gpt-4o")
tools = [search_docs]

def researcher(state: MessagesState) -> dict:
    sys = SystemMessage(content="You are a researcher. Use tools to gather info.")
    return {"messages": [model.bind_tools(tools).invoke([sys] + state["messages"])]}

def synthesizer(state: MessagesState) -> dict:
    sys = SystemMessage(content="Summarize all findings into a final answer.")
    return {"messages": [model.invoke([sys] + state["messages"])]}

builder = StateGraph(MessagesState)
builder.add_node("researcher", researcher)
builder.add_node("tools", ToolNode(tools))
builder.add_node("synthesizer", synthesizer)
builder.add_edge(START, "researcher")
builder.add_conditional_edges("researcher", tools_condition, {"tools": "tools", "__end__": "synthesizer"})
builder.add_edge("tools", "researcher")
builder.add_edge("synthesizer", END)
graph = builder.compile()

adli = ADLI(token="adli-xxx", project_id=1)
graph = adli.wrap(graph, agent_name="research-agent", input_key="messages")

result = graph.invoke({
    "messages": [HumanMessage(content="Search for auth docs and summarize.")],
})

ADLI captures the full graph execution: system prompts from each node, all LLM responses, tool calls with arguments, tool returns with results — correctly interleaved and paired by tool_call_id.

CrewAI

from crewai import Crew, Agent, Task, Process
from adli_sdk import ADLI

adli = ADLI(token="adli-xxx", project_id=1)

crew = Crew(agents=[...], tasks=[...], process=Process.sequential)
crew = adli.wrap(crew, agent_name="research-crew")

result = crew.kickoff(inputs={"topic": "AI trends in 2025"})

LlamaIndex

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from adli_sdk import ADLI

adli = ADLI(token="adli-xxx", project_id=1)

index = VectorStoreIndex.from_documents(SimpleDirectoryReader("data").load_data())
query_engine = index.as_query_engine()
query_engine = adli.wrap(query_engine, agent_name="rag-agent")

response = query_engine.query("What are the key findings?")

ChatEngine works the same way — adli.wrap(index.as_chat_engine(), agent_name="...").

OpenAI Agents SDK

from agents import Agent, Runner, RunConfig
from adli_sdk import ADLI

adli = ADLI(token="adli-xxx", project_id=1)
adli.instrument_openai_agents()

agent = Agent(name="assistant", instructions="You are a helpful assistant.", tools=[...])

inj = adli.inject("user query", agent_name="assistant")
result = await Runner.run(
    agent,
    inj.message,
    run_config=RunConfig(metadata={
        "adli_trace_id": inj.adli_trace_id,
        "adli_agent_name": "assistant",
        "adli_user_message": inj.message,
    }),
)

Manual Mode

For full control over inject timing and callback setup.

PydanticAI

Agent.instrument_all()
adli = ADLI(token="adli-xxx", project_id=1)
adli.instrument()

agent = Agent("openai:gpt-4o", system_prompt="You are helpful.")

inj = await adli.ainject("user query", agent_name="sql-agent")
result = await agent.run(
    inj.message,
    metadata={"adli_trace_id": inj.adli_trace_id},
)

LangChain / LangGraph

adli = ADLI(token="adli-xxx", project_id=1)

inj = adli.inject("user query", agent_name="my-chain")
handler = adli.langchain_callback(
    agent_name="my-chain",
    adli_trace_id=inj.adli_trace_id,
    user_message=inj.message,
)
result = chain.invoke({"input": inj.message}, config={"callbacks": [handler]})

Works Alongside Your Observability Stack

ADLI does not replace or interfere with your existing tracing setup:

  • Logfire: call logfire.configure() before Agent.instrument_all() — traces go to both.
  • LangFuse: add LangfuseCallbackHandler() to your chain's config={"callbacks": [...]} — runs alongside ADLI's handler.
  • LangSmith: set LANGCHAIN_TRACING_V2=true as usual — ADLI's callback handler works independently.

How It Works

Framework Trace mechanism Intercepted methods
PydanticAI OTel SpanProcessor run, run_sync, run_stream, iter
LangChain / LangGraph BaseCallbackHandler invoke, ainvoke, stream, astream
CrewAI LangChain callbacks (reused) kickoff, kickoff_async
LlamaIndex CallbackManager events query, aquery, chat, achat
OpenAI Agents SDK TracingProcessor Manual inject + Runner.run

wrap() creates a transparent __getattr__ proxy — all attributes and methods delegate to the original object. Only entry-point methods are intercepted to call /inject and attach trace collection.

What Gets Captured

  • System prompts (including per-node prompts in LangGraph)
  • User messages
  • LLM responses (text + reasoning/thinking content)
  • Tool calls (name, arguments, tool_call_id)
  • Tool returns (result, matched to call by tool_call_id)
  • Token usage (input, output, cache)
  • Outcome (success/failure)

License

Apache 2.0 — see LICENSE.

Metadata

Release files for adli-sdk 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for adli-sdk 0.1.0
File Size Uploaded Provenance
adli_sdk-0.1.0.tar.gz 39.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for adli-sdk 0.1.0
File Interpreter ABI Platform
adli_sdk-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 74.5 kB

Release files / adli_sdk-0.1.0.tar.gz

Download URL adli_sdk-0.1.0.tar.gz
Size 39.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1f026418535e016f42d328f60566e4d3323b3bbd499a2e367d4797404a7f83ba
BLAKE2b-256 checksum
How to use checksums
77d8e881b147a8ee637fe073327d6c3da7ce563fd58781cef25a3f0298457047
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Release files / adli_sdk-0.1.0-py3-none-any.whl

Download URL adli_sdk-0.1.0-py3-none-any.whl
Size 35.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e1fa7d00e81d860a43ff3e3a63d995ee0bbb0b7e2118317044b50e3810020c8
BLAKE2b-256 checksum
How to use checksums
98b7c8f2c48096cfde0b24691621eeb0a4f7618dd06a1f469e75effcf3684b9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.2

Security for adli-sdk 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page