feat(sdk): add Anthropic SDK integration#61
Conversation
|
Thanks for the PR Uche! I'll leave to the engineers to work with you but huge thanks for taking on Anthropic integration and i'd like to invite you to the private contributors discord community channel. https://discord.gg/zcwYwfdAFz I am 'maxicorbs' DM on there and i'll add you |
4417956 to
a379747
Compare
|
Thanks for the PR! Currently it's audit-only. Could you add logic to actually hold the tool calls for HITL and Block modes? You can use the TS OpenAI or Python LangChain integrations for reference. |
Okay, I will look into this and fix thank you |
|
I've merged #78 which modularises the existing LangChain integration from |
Patches anthropic.Anthropic and anthropic.AsyncAnthropic so every
messages.create call is captured as an Adrian PairedEvent and emitted
through the hook registry, with the same lifecycle guarantees as the
existing LangChain/LangGraph integration.
- sdk/python/adrian/anthropic_handler.py: core patch module
- _flatten_content / _flatten_anthropic_messages: normalise Anthropic
message params (strings, TypedDict blocks, SDK objects) to ChatMessage
- _extract_anthropic_tool_calls: pull ToolUseBlock records from response
- _extract_anthropic_usage: map input/output tokens to TokenUsage
- build_anthropic_llm_pair: assemble PairedEvent from request + response
- _emit_pair / _schedule_emit: async and sync emission paths
- patch_anthropic: idempotent monkey-patch; reads hooks/config at call
time so shutdown + re-init cycles are handled correctly
- anthropic_invocation / anthropic_invocation_sync: context managers
to group multi-turn calls under a single invocation_id
- sdk/python/adrian/__init__.py: wire auto-instrumentation
- patch_anthropic() public function (manual opt-in path)
- _auto_instrument_anthropic() called by init() when auto_instrument=True
- sdk/python/pyproject.toml: add anthropic optional-dep group and to dev
- sdk/python/tests/test_anthropic_handler.py: 61 unit tests covering all
helper functions, PairedEvent assembly, emission, patching, and context
managers; all passing locally
- examples/python/anthropic_quickstart.py: minimal two-turn async example
All new source files carry the LICENSE_HEADER.txt SPDX header per
CONTRIBUTING.md.
thanks for the heads up, rebased onto main 👍 still investigating this.... Quick question before I do the gate: the Anthropic SDK has no tool-execution step to intercept like LangChain's BaseTool.invoke, the only hook point is the return of messages.create(), where the model hands back tool_use blocks. So when a verdict says block, how do you want it to surface?
|
To keep it consistent with the other integrations let's go for the [BLOCKED] text block, that way the model will know something stopped its action too. |
The Anthropic handler was audit-only: it emitted a PairedEvent after messages.create() but never acted on the verdict. This adds the verdict gate requested in review. The Anthropic Messages API has no discrete tool-execution step to intercept (unlike LangChain's BaseTool.invoke or the TS OpenAI captureTool), so the gate runs at the only choke point the SDK owns: the return of messages.create(). After emitting (which registers the verdict future), the async path awaits each tool_use block's verdict and, when it halts, rewrites that block into a "[BLOCKED by security policy]" text block -- consistent with the LangChain gate, so the model sees its action was stopped. Fail-closed rules mirror langchain_handler: missing LoginAck within 5s blocks; MODE_BLOCK verdict timeout blocks; MODE_HITL waits indefinitely. Scope: async (AsyncMessages) only; sync Messages.create stays audit-only pending maintainer confirmation on parity. _should_halt is a self-contained copy so the Anthropic integration does not depend on the LangChain module. Adds 18 tests: ALERT passthrough, BLOCK halt/allow, verdict-timeout fail-closed, partial block, dict/object block shapes, HITL reject/approve/holds-until-human, and an end-to-end emit->gate seam test.
0a52bb1 to
5212849
Compare
|
Thanks for the review — pushed the fix. The handler now actually holds tool calls instead of just auditing them. On the async path, each A couple of things I'd like your call on:
Added 18 tests, including an end-to-end emit→gate check. |
That's a good shout, |
|
Okay — will do all three: modularise |
Addresses review feedback on the Anthropic integration to reach full parity with the LangChain handler. 1. Modularise should_halt: promoted from langchain_handler into ws.py as a public helper next to policy_active / block_timeout (mirrors the TS core shouldHalt). Both handlers now import it; the Anthropic integration no longer needs a local copy or a dependency on the LangChain module. 2. Fire on_verdict / on_block / on_audit for Anthropic. These callbacks only fire for events registered in the callback handler's event map, and the Anthropic handler previously emitted straight to the hook registry, bypassing it -- so the callbacks never reached the developer. Emission now delegates to the callback handler when available (registering the event and firing on_event), with the direct-hooks path kept as a fallback. 3. Gate the sync Messages.create path. Verdict futures live on the WS event loop, so sync emission + gating are bridged onto it via run_coroutine_threadsafe and the caller blocks for the verdict, matching the LangChain _sync_gate. Degrades to audit-only when gating isn't possible (no WS client, ALERT/pre-login, or a call from an event-loop thread). Tests: should_halt cases moved to test_ws.py; added notification-callback parity tests (block/audit tiers) and sync-gate tests (bridge fail-closed rewrite, event-loop-thread passthrough, _should_gate_sync matrix).
|
Pushed all three:
Tests: |
|
Thanks, all three items are addressed: One change before merge: the sync bridge fails open where it should fail closed. In except Exception:
logger.exception("Anthropic sync gate bridge failed; emitting audit-only")
_schedule_emit(response, kwargs)
return response
|
Summary
Adds first-class Anthropic SDK support to the Adrian Python SDK. Every
messages.createcall on bothanthropic.Anthropicandanthropic.AsyncAnthropicis now captured automatically as aPairedEventand emitted through the hook registry — no changes neededto user agent code beyond
adrian.init().Changes
sdk/adrian/anthropic_handler.py(new): message format conversion,tool call and usage extraction,
PairedEventassembly, idempotentmonkey-patch of
Messages.create/AsyncMessages.create, andanthropic_invocation()/anthropic_invocation_sync()context managersfor grouping multi-turn calls under a single invocation ID.
sdk/tests/test_anthropic_handler.py(new): 61 tests covering allhelper functions, emission, getter lifecycle, the no-package no-op path,
and both context managers. All pass.
sdk/adrian/__init__.py:patch_anthropic(),anthropic_invocation,and
anthropic_invocation_syncexported;_auto_instrument_anthropic()wired into
init()alongside LangChain auto-instrumentation.sdk/pyproject.toml:anthropicoptional dependency group added;anthropic>=0.40.0added to[dev]for the test suite.examples/anthropic_quickstart.py(new): minimal demo showing single-calland multi-turn
anthropic_invocation()usage.Test plan
uv pip install -e ./sdk[dev,anthropic]thenpytest sdk/tests/test_anthropic_handler.py -v— 61 tests passpytest sdk/tests/— no regressions in existing suiteexamples/anthropic_quickstart.pyagainst a live backend andconfirm events appear in the dashboard with correct model, messages,
and usage fields
anthropic_invocation()groups multi-turn calls under ashared
invocation_idin the dashboardChecklist