Observed since 20 Oct, 19:38 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Official Python SDK for ADLI — runtime strategy learning for AI agents
Official Python SDK for ADLI — runtime strategy learning for AI agents.
ADLI makes your agents smarter with every run:
pip install adli-sdk
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")
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?"})
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.
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"})
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="...").
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,
}),
)
For full control over inject timing and callback setup.
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},
)
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]})
ADLI does not replace or interfere with your existing tracing setup:
logfire.configure() before Agent.instrument_all() — traces go to both.LangfuseCallbackHandler() to your chain's config={"callbacks": [...]} — runs alongside ADLI's handler.LANGCHAIN_TRACING_V2=true as usual — ADLI's callback handler works independently.| 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.
Apache 2.0 — see LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 13 Mar 2026 | 18 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 19:38 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:6cffa99712ecf63582e721cc5edffbe58d180172d0e2319762f38e45bd243394