Official Python SDK for ADLI — runtime strategy learning for AI agents
Project description
ADLI-SDK
Official Python SDK for ADLI — runtime strategy learning for AI agents.
ADLI makes your agents smarter with every run:
- Inject — before each run, checks if there's a relevant strategy and modifies the user message.
- 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()beforeAgent.instrument_all()— traces go to both. - LangFuse: add
LangfuseCallbackHandler()to your chain'sconfig={"callbacks": [...]}— runs alongside ADLI's handler. - LangSmith: set
LANGCHAIN_TRACING_V2=trueas 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file adli_sdk-0.1.0.tar.gz.
File metadata
- Download URL: adli_sdk-0.1.0.tar.gz
- Upload date:
- Size: 39.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f026418535e016f42d328f60566e4d3323b3bbd499a2e367d4797404a7f83ba
|
|
| MD5 |
7e4d0e9a29a333f146a39b293be55869
|
|
| BLAKE2b-256 |
77d8e881b147a8ee637fe073327d6c3da7ce563fd58781cef25a3f0298457047
|
File details
Details for the file adli_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: adli_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e1fa7d00e81d860a43ff3e3a63d995ee0bbb0b7e2118317044b50e3810020c8
|
|
| MD5 |
43b3e725a714fe55929c25cf8d078a8d
|
|
| BLAKE2b-256 |
98b7c8f2c48096cfde0b24691621eeb0a4f7618dd06a1f469e75effcf3684b9a
|