generator: add native Anthropic generator#1809
Conversation
Adds AnthropicGenerator backed by the anthropic SDK so Claude models can be exercised directly without going through litellm or bedrock. System turns are pulled out of the Conversation and passed via the Messages API's top-level system param; backoff is wired through the SDK's RateLimitError, APIConnectionError, and APIStatusError. Signed-off-by: Nishchay Mahor <nishchaymahor@gmail.com>
Two CI checks were red against this branch: - tests/test_docs.py was missing docs/source/generators/anthropic.rst plus the corresponding entry in docs/source/index_generators.rst. - tests/test_reqs.py treats every pyproject.toml dependency as spurious unless it also appears in requirements.txt. Add the docs stub modeled on mistral.rst, link it from the toctree, and mirror the anthropic pin in requirements.txt. Signed-off-by: Nishchay Mahor <nishchaymahor@gmail.com>
|
Heads up on the one red check ( |
patriciapampanelli
left a comment
There was a problem hiding this comment.
Thanks for taking this on, @NishchayMahor
| extra_dependency_names = ["anthropic"] | ||
|
|
||
| ENV_VAR = "ANTHROPIC_API_KEY" | ||
| DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | { |
There was a problem hiding this comment.
max_tokens is already in Generator.DEFAULT_PARAMS, and name is supplied via --target_name at runtime. I think we can drop this DEFAULT_PARAMS override entirely.
| _unsafe_attributes = ["client"] | ||
|
|
||
| def _load_unsafe(self): | ||
| self.client = self.anthropic.Anthropic(api_key=self.api_key) |
There was a problem hiding this comment.
Notice OpenAICompatible exposes uri in DEFAULT_PARAMS and passes it as base_url. I think we can mirror that here so endpoint overrides flow through garak config rather than relying only on the SDK env var.
| call_kwargs = { | ||
| "model": self.name, | ||
| "max_tokens": self.max_tokens, | ||
| "messages": messages, | ||
| } | ||
| if system is not None: | ||
| call_kwargs["system"] = system | ||
| if self.temperature is not None: | ||
| call_kwargs["temperature"] = self.temperature | ||
| if self.top_k is not None: | ||
| call_kwargs["top_k"] = self.top_k |
There was a problem hiding this comment.
The kwargs here are enumerated by hand. The convention in OpenAICompatible is to map the known name mismatch (model from name) explicitly and let inspect.signature(self.client.messages.create).parameters pick up the rest. That catches top_p and any future SDK params for free.
| backoff_exception_types = [ | ||
| self.anthropic.RateLimitError, | ||
| self.anthropic.APIConnectionError, | ||
| self.anthropic.APIStatusError, |
There was a problem hiding this comment.
APIStatusError catches every 4xx errors, which can't recover. Combined with no max_tries, this loops indefinitely. We should narrow it to the recoverable subset. Look at the bedrock.py
patriciapampanelli
left a comment
There was a problem hiding this comment.
Thanks for taking this on, @NishchayMahor
- Drop the DEFAULT_PARAMS override for max_tokens and name. max_tokens is inherited from Generator.DEFAULT_PARAMS and name comes in via --target_name. - Add uri to DEFAULT_PARAMS and thread it as base_url on the client, mirroring how OpenAICompatible exposes its endpoint, so config-driven overrides work. - Replace the hand-enumerated call_kwargs with inspect.signature against client.messages.create, matching the openai.py pattern. New SDK params like top_p now flow through without an edit. - Narrow the retry surface from APIStatusError to RateLimitError, APIConnectionError, APITimeoutError, and InternalServerError. 4xx caller bugs no longer loop indefinitely. - Add tests for the new DEFAULT_PARAMS shape and the uri-to-base_url path.
|
Thanks for the careful review @patriciapampanelli, this was super helpful. All four addressed in a9116b8:
Also added two small unit tests: one for the new |
- Drop the DEFAULT_PARAMS override for max_tokens and name. max_tokens is inherited from Generator.DEFAULT_PARAMS and name comes in via --target_name. - Add uri to DEFAULT_PARAMS and thread it as base_url on the client, mirroring how OpenAICompatible exposes its endpoint, so config-driven overrides work. - Replace the hand-enumerated call_kwargs with inspect.signature against client.messages.create, matching the openai.py pattern. New SDK params like top_p now flow through without an edit. - Narrow the retry surface from APIStatusError to RateLimitError, APIConnectionError, APITimeoutError, and InternalServerError. 4xx caller bugs no longer loop indefinitely. - Add tests for the new DEFAULT_PARAMS shape and the uri-to-base_url path. Signed-off-by: Nishchay Mahor <nishchaymahor@gmail.com>
a9116b8 to
de6275d
Compare
Closes #263.
Adds a native
AnthropicGeneratorso Claude models can be targeted directly through Anthropic's API, sitting alongside the existingbedrock/litellmpaths instead of relying on them.Approach
Modeled after
garak/generators/mistral.py, same shape and level of abstraction. The Anthropic Messages API is a small surface so the generator stays under 100 lines.ENV_VAR = "ANTHROPIC_API_KEY"to match the other hosted generators.extra_dependency_names = ["anthropic"]so the SDK loads through_load_depsand the import failure path is consistent with the rest of the codebase.claude-sonnet-4-5(non-dated alias, so it won't 404 when a snapshot is retired)._split_system_and_messageshelper pullssystemturns out of theConversationand passes them via the API's top-levelsystemparam. Anthropic doesn't acceptsystemas a role insidemessages.RateLimitError,APIConnectionError, andAPIStatusErrorviaGeneratorBackoffTrigger, matching the pattern inmistral.py.Tests
tests/generators/test_anthropic.pycovers:https://api.anthropic.com/v1/messagesusingrespx, parallel totest_mistral.py.test_anthropic_chatthat's skipped unlessANTHROPIC_API_KEYis set.Local run:
black --checkclean.Notes
anthropic>=0.40.0topyproject.tomlnext to the other SDK pins.tests/_assets/generators/anthropic.jsonplus ananthropic_compat_mocksentry intests/generators/conftest.py.bedrock.pyorlitellm.py, so the existing Claude-via-Bedrock and Claude-via-LiteLLM paths are unchanged.