Tracing
Auto-Instrumentation

Auto-Instrumentation

Mantis SDK can automatically instrument popular AI frameworks to capture LLM calls without manual code changes.

Overview

Auto-instrumentation uses OpenTelemetry instrumentors to automatically trace:

  • LLM API calls (tokens, latency, model)
  • Tool/function calls
  • Framework-specific operations

Enabling Instrumentation

Default Instrumentation

Enable the core set of instrumentors:

import mantisdk.tracing as tracing
 
tracing.init(instrument_default=True)

This enables instrumentation for:

  • OpenAI
  • Anthropic
  • LangChain
  • LlamaIndex
  • LiteLLM

Selective Instrumentation

Enable specific instrumentors only:

import mantisdk.tracing as tracing
 
tracing.init(instrument_default=False)
 
# Enable only what you need
tracing.instrument(names=["anthropic", "dspy"])

Skipping Instrumentors

Enable defaults but skip specific ones:

import mantisdk.tracing as tracing
 
tracing.init(instrument_default=False)
tracing.instrument(skip=["langchain"])  # Enable all except LangChain

Supported Frameworks

Core Set (Default)

FrameworkInstrumentor NameAuto-Captured
OpenAIopenaiChat completions, embeddings, tokens
AnthropicanthropicMessages, tool use, tokens
LangChainlangchainChains, agents, tools, retrievers
LlamaIndexllama_indexQueries, retrievers, indices
LiteLLMlitellmMulti-provider LLM calls

Additional Frameworks

FrameworkInstrumentor NameAuto-Captured
DSPydspyModules, signatures, optimizers
Google ADKgoogle_adkAgents, tools, sessions
MistralmistralChat completions, embeddings
GroqgroqChat completions
BedrockbedrockAWS Bedrock model calls

Usage Examples

DSPy

import mantisdk.tracing as tracing
 
# Initialize and instrument DSPy
tracing.init(service_name="dspy-agent", instrument_default=False)
tracing.instrument(names=["dspy"])
 
# Your DSPy code is now automatically traced
import dspy
 
class MyModule(dspy.Module):
    def forward(self, question):
        # LLM calls are auto-traced
        return self.chain_of_thought(question=question)

Anthropic

import mantisdk.tracing as tracing
 
tracing.init(service_name="anthropic-agent", instrument_default=False)
tracing.instrument(names=["anthropic"])
 
# All Anthropic API calls are now traced
import anthropic
 
client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    messages=[{"role": "user", "content": "Hello!"}]
)
# ^ This call is automatically traced with:
#   - Model name
#   - Input/output tokens
#   - Latency
#   - Response content

Google ADK

import mantisdk.tracing as tracing
 
tracing.init(service_name="adk-agent", instrument_default=False)
tracing.instrument(names=["google_adk"])
 
# Google ADK operations are traced
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
 
agent = Agent(name="my-agent", model="gemini-2.0-flash", tools=[...])
runner = InMemoryRunner(agent=agent)
# Agent runs are automatically traced

LangChain

import mantisdk.tracing as tracing
 
tracing.init(instrument_default=False)
tracing.instrument(names=["langchain"])
 
# LangChain chains and agents are traced
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
 
llm = ChatOpenAI()
chain = LLMChain(llm=llm, prompt=my_prompt)
result = chain.invoke({"input": "Hello"})
# ^ Chain execution and LLM calls are traced

Combining Manual and Auto Tracing

Auto-instrumentation works alongside manual tracing:

import mantisdk.tracing as tracing
 
tracing.init(service_name="my-agent", instrument_default=False)
tracing.instrument(names=["anthropic"])
 
import anthropic
 
@tracing.trace
def process_query(query: str) -> str:
    """Manual trace wraps auto-instrumented calls."""
    with tracing.span("prepare") as s:
        prepared = prepare_query(query)
        s.set_attribute("prepared_length", len(prepared))
 
    # This Anthropic call is auto-traced as a child span
    client = anthropic.Anthropic()
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        messages=[{"role": "user", "content": prepared}]
    )
 
    return response.content[0].text

The resulting trace hierarchy:

process_query (manual trace)
├── prepare (manual span)
└── anthropic.messages.create (auto-instrumented)
    ├── model: claude-sonnet-4-20250514
    ├── input_tokens: 15
    └── output_tokens: 42

Instrumentation Timing

Always initialize tracing before importing the framework:

# CORRECT ORDER
import mantisdk.tracing as tracing
tracing.init(instrument_default=False)
tracing.instrument(names=["anthropic"])
 
import anthropic  # Import AFTER instrumentation
# WRONG ORDER - instrumentation may not work
import anthropic  # Imported before instrumentation!
 
import mantisdk.tracing as tracing
tracing.init(instrument_default=False)
tracing.instrument(names=["anthropic"])

Complete Example

#!/usr/bin/env python3
"""Auto-instrumentation with Anthropic."""
 
from dotenv import load_dotenv
load_dotenv()
 
# Initialize tracing FIRST
import mantisdk.tracing as tracing
from mantisdk.tracing.exporters.insight import is_insight_configured
 
exporters = []
if is_insight_configured():
    exporters.append(tracing.insight_exporter())
 
tracing.init(
    service_name="anthropic-agent",
    exporters=exporters if exporters else None,
    instrument_default=False,
)
tracing.instrument(names=["anthropic"])
 
# NOW import Anthropic
import anthropic
 
 
def main():
    client = anthropic.Anthropic()
 
    with tracing.trace("chat_session") as root:
        root.set_attribute(tracing.semconv.USER_ID, "user-123")
 
        # This call is auto-instrumented
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=[
                {"role": "user", "content": "What is the capital of France?"}
            ]
        )
 
        answer = response.content[0].text
        root.set_attribute(tracing.semconv.TRACE_OUTPUT, answer)
        print(answer)
 
 
if __name__ == "__main__":
    try:
        main()
    finally:
        tracing.shutdown()

Next Steps