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
84 changes: 84 additions & 0 deletions examples/python/anthropic_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2026 SecureAgentics
#
# Licensed under the Apache Licence, Version 2.0 (the "Licence").
# You may not use this file except in compliance with the Licence.
# A copy of the Licence is included at LICENSE in the repository root.
"""Minimal quickstart: monitor Anthropic API calls with Adrian.

Run::

export ANTHROPIC_API_KEY="sk-ant-..."
export ADRIAN_API_KEY="..." # optional -- omit to collect locally only
python examples/python/anthropic_quickstart.py
"""

from __future__ import annotations

import asyncio
import os

import anthropic
import adrian

# ------------------------------------------------------------------
# 1. Initialise Adrian. This auto-instruments Anthropic by default.
# ------------------------------------------------------------------
adrian.init(
api_key=os.environ.get("ADRIAN_API_KEY", ""),
session_id="anthropic-quickstart-session",
)

# ------------------------------------------------------------------
# 2. Create an Anthropic client as normal.
# ------------------------------------------------------------------
client = anthropic.AsyncAnthropic(api_key=os.environ["ANTHROPIC_API_KEY"])


async def main() -> None:
print("Sending first request...")

# ------------------------------------------------------------------
# 3. Wrap related calls in an invocation context so Adrian groups them.
# ------------------------------------------------------------------
async with adrian.anthropic_invocation():
response = await client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=256,
system="You are a concise assistant.",
messages=[{"role": "user", "content": "What is 2 + 2? Answer in one sentence."}],
)

text = next(
(block.text for block in response.content if hasattr(block, "text")),
"",
)
print(f"Model says: {text}")

# A second call in the same invocation -- same invocation_id in Adrian.
follow_up = await client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=256,
system="You are a concise assistant.",
messages=[
{"role": "user", "content": "What is 2 + 2? Answer in one sentence."},
{"role": "assistant", "content": text},
{"role": "user", "content": "Now multiply that result by 10."},
],
)

follow_text = next(
(block.text for block in follow_up.content if hasattr(block, "text")),
"",
)
print(f"Follow-up: {follow_text}")

# ------------------------------------------------------------------
# 4. Always shut down Adrian cleanly to flush any pending events.
# ------------------------------------------------------------------
await adrian.shutdown()
print("Done. Check your Adrian dashboard for the captured events.")


if __name__ == "__main__":
asyncio.run(main())
34 changes: 34 additions & 0 deletions sdk/python/adrian/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from langchain_core.runnables.base import Runnable
from langchain_core.runnables.config import ensure_config

from adrian.anthropic_handler import anthropic_invocation, anthropic_invocation_sync
from adrian.config import (
AdrianConfig,
OnAuditCallback,
Expand Down Expand Up @@ -101,6 +102,9 @@
"__version__",
"mcp_servers",
"redact_text",
"patch_anthropic",
"anthropic_invocation",
"anthropic_invocation_sync",
]

logger = logging.getLogger("adrian")
Expand Down Expand Up @@ -341,6 +345,7 @@ def init(

if auto_instrument:
_auto_instrument_langchain()
_auto_instrument_anthropic()

# MCP server tracking is independent of LangChain auto-instrumentation,
# it observes a different library (langchain-mcp-adapters) and is the
Expand Down Expand Up @@ -374,6 +379,26 @@ def shutdown() -> None:
set_config(None)


def patch_anthropic() -> None:
"""Apply Anthropic SDK instrumentation.

Monkey-patches ``anthropic.Anthropic`` and ``anthropic.AsyncAnthropic`` so
that every ``messages.create`` call is captured as an Adrian ``PairedEvent``.
Called automatically by :func:`init` when ``auto_instrument=True``.

Call explicitly only when ``auto_instrument=False``::

adrian.init(api_key="...", auto_instrument=False)
adrian.patch_anthropic()
"""
from adrian.anthropic_handler import patch_anthropic as _patch

_patch(
hooks_getter=lambda: _hooks,
config_getter=lambda: get_config() if is_initialized() else None,
)


def get_handler() -> AdrianCallbackHandler | None:
"""Return the SDK's callback handler, or ``None`` if uninitialised.

Expand Down Expand Up @@ -509,6 +534,15 @@ def _inject_callbacks(config: Any) -> Any: # noqa: ANN401
# ------------------------------------------------------------------


def _auto_instrument_anthropic() -> None:
"""Apply Anthropic SDK monkey-patches if the package is installed."""
try:
patch_anthropic()
logger.debug("Anthropic auto-instrumentation applied")
except Exception:
logger.exception("Anthropic auto-instrumentation failed")


def _auto_instrument_langchain() -> None:
"""Apply all monkey-patches to LangChain / LangGraph."""
try:
Expand Down
Loading