From a4a8082e39abef3f0a6d7d8b7b636e4c62f68710 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 11 Jun 2025 23:03:54 +0200 Subject: [PATCH 1/7] Create new anthropic sink file --- aikido_zen/sinks/anthropic.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 aikido_zen/sinks/anthropic.py diff --git a/aikido_zen/sinks/anthropic.py b/aikido_zen/sinks/anthropic.py new file mode 100644 index 000000000..e69de29bb From 49edeea66b693f20117103445eed591a3b626af2 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 11 Jun 2025 23:13:29 +0200 Subject: [PATCH 2/7] Add start of anthropic sink --- aikido_zen/sinks/anthropic.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/aikido_zen/sinks/anthropic.py b/aikido_zen/sinks/anthropic.py index e69de29bb..a53244a80 100644 --- a/aikido_zen/sinks/anthropic.py +++ b/aikido_zen/sinks/anthropic.py @@ -0,0 +1,20 @@ +from aikido_zen.helpers.on_ai_call import on_ai_call +from aikido_zen.helpers.register_call import register_call +from aikido_zen.sinks import on_import, patch_function, after + +@after +def _messages_create(func, instance, args, kwargs, return_value): + op = f"anthropic.resources.messages.messages.Messages.create" + register_call(op, "ai_op") + + on_ai_call( + provider="anthropic", + model=return_value.model, + input_tokens=return_value.usage.input_tokens, + output_tokens=return_value.usage.output_tokens, + ) + + +@on_import("anthropic.resources.messages") +def patch(m): + patch_function(m, "messages.Messages.create", _messages_create) From 3a92849e45baa9696a3babccfccd5bdf95970412 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 11 Jun 2025 23:16:33 +0200 Subject: [PATCH 3/7] Add anthropic to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 947f444d1..13e23babf 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Zen for Python 3 is compatible with: ### AI SDKs * ✅ [`openai`](https://pypi.org/project/openai) ^1.0 +* ✅ [`anthropic`](https://pypi.org/project/anthropic/) ## Reporting to your Aikido Security dashboard From 5381c65b2f5bda38136ed4091b16e0d4dcac8140 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Wed, 11 Jun 2025 23:17:15 +0200 Subject: [PATCH 4/7] Import anthropic sink in protect() --- aikido_zen/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_zen/__init__.py b/aikido_zen/__init__.py index 168b44891..fcbe9b7ac 100644 --- a/aikido_zen/__init__.py +++ b/aikido_zen/__init__.py @@ -68,5 +68,6 @@ def protect(mode="daemon"): # Import AI sinks import aikido_zen.sinks.openai + import aikido_zen.sinks.anthropic logger.info("Zen by Aikido v%s starting.", PKG_VERSION) From ae3f349f871539c2e76b456db629c83162ea9b9e Mon Sep 17 00:00:00 2001 From: BitterPanda63 Date: Fri, 13 Jun 2025 10:13:44 +0200 Subject: [PATCH 5/7] Install anthropic --- poetry.lock | 27 ++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index a5e953a05..dde5a197e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,6 +15,31 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} +[[package]] +name = "anthropic" +version = "0.54.0" +description = "The official Python library for the anthropic API" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "anthropic-0.54.0-py3-none-any.whl", hash = "sha256:c1062a0a905daeec17ca9c06c401e4b3f24cb0495841d29d752568a1d4018d56"}, + {file = "anthropic-0.54.0.tar.gz", hash = "sha256:5e6f997d97ce8e70eac603c3ec2e7f23addeff953fbbb76b19430562bb6ba815"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.25.0,<1" +jiter = ">=0.4.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +typing-extensions = ">=4.10,<5" + +[package.extras] +bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +vertex = ["google-auth[requests] (>=2,<3)"] + [[package]] name = "anyio" version = "4.5.2" @@ -2261,4 +2286,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.8" -content-hash = "4c6f6438f25158b1a1f8401c2e55f3e7a2ce4f9f4fa542f349edbb41169fed2f" +content-hash = "b5a222795eac86eb7a904f9c4cf7b2b0690ab820cf1e67094760458499bb07ec" diff --git a/pyproject.toml b/pyproject.toml index 7e81d2779..195d2ef47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ pygments = "^2.18.0" lxml = "^5.4.0" clickhouse-driver = "^0.2.9" openai = "^1.85.0" +anthropic = "^0.54.0" [build-system] requires = ["poetry-core>=1.0.0"] From 7c9f0f2784b0598d925d9e7afaeab847ed854f5a Mon Sep 17 00:00:00 2001 From: BitterPanda63 Date: Fri, 13 Jun 2025 10:13:54 +0200 Subject: [PATCH 6/7] lint on sinks file --- aikido_zen/sinks/anthropic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_zen/sinks/anthropic.py b/aikido_zen/sinks/anthropic.py index a53244a80..690de6a3e 100644 --- a/aikido_zen/sinks/anthropic.py +++ b/aikido_zen/sinks/anthropic.py @@ -2,6 +2,7 @@ from aikido_zen.helpers.register_call import register_call from aikido_zen.sinks import on_import, patch_function, after + @after def _messages_create(func, instance, args, kwargs, return_value): op = f"anthropic.resources.messages.messages.Messages.create" From c8da9169e155a1a2cfbe45c52fd52aeb54fef59e Mon Sep 17 00:00:00 2001 From: BitterPanda63 Date: Fri, 13 Jun 2025 10:14:07 +0200 Subject: [PATCH 7/7] Add test cases for anthropic --- aikido_zen/sinks/tests/anthropic_test.py | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 aikido_zen/sinks/tests/anthropic_test.py diff --git a/aikido_zen/sinks/tests/anthropic_test.py b/aikido_zen/sinks/tests/anthropic_test.py new file mode 100644 index 000000000..2b11449a5 --- /dev/null +++ b/aikido_zen/sinks/tests/anthropic_test.py @@ -0,0 +1,46 @@ +import os + +import pytest +import aikido_zen.sinks.anthropic +import anthropic + +from aikido_zen.thread.thread_cache import get_cache + +skip_no_api_key = pytest.mark.skipif( + "ANTHROPIC_API_KEY" not in os.environ, + reason="ANTHROPIC_API_KEY environment variable not set", +) + + +@pytest.fixture(autouse=True) +def setup(): + get_cache().reset() + yield + get_cache().reset() + + +def get_ai_stats(): + return get_cache().ai_stats.get_stats() + + +@skip_no_api_key +def test_anthropic_messages_create(): + client = anthropic.Anthropic() + response = client.messages.create( + model="claude-3-opus-20240229", + max_tokens=20, + messages=[ + { + "role": "user", + "content": "Write the longest response possible, just as I am writing a long content", + } + ], + ) + print(response) + + assert get_ai_stats()[0]["model"] == "claude-3-opus-20240229" + assert get_ai_stats()[0]["calls"] == 1 + assert get_ai_stats()[0]["provider"] == "anthropic" + assert get_ai_stats()[0]["tokens"]["input"] == 21 + assert get_ai_stats()[0]["tokens"]["output"] == 20 + assert get_ai_stats()[0]["tokens"]["total"] == 41