-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(anthropic): support bearer token authentication #3038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
be2d7de
0f7a4f6
3f09267
6bf1a3a
513c13e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,30 +72,57 @@ public class AnthropicChatModel extends ChatModelBase { | |
| private final GenerateOptions defaultOptions; | ||
| private final AnthropicBaseFormatter formatter; | ||
|
|
||
| public AnthropicChatModel( | ||
| String baseUrl, | ||
| String apiKey, | ||
| String modelName, | ||
| boolean streamEnabled, | ||
| GenerateOptions defaultOptions, | ||
| AnthropicBaseFormatter formatter, | ||
| ProxyConfig proxyConfig, | ||
| String cacheTtl) { | ||
| this( | ||
| baseUrl, | ||
| apiKey, | ||
| null, | ||
| modelName, | ||
| streamEnabled, | ||
| defaultOptions, | ||
| formatter, | ||
| proxyConfig, | ||
| cacheTtl); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new Anthropic chat model instance. | ||
| * Creates an Anthropic chat model with optional bearer token authentication. | ||
| * | ||
| * @param baseUrl the base URL for Anthropic API (null for default) | ||
| * @param apiKey the API key for authentication (null to load from | ||
| * ANTHROPIC_API_KEY env var) | ||
| * @param modelName the model name to use (e.g., | ||
| * "claude-sonnet-4-5-20250929") | ||
| * @param streamEnabled whether streaming should be enabled | ||
| * <p>{@code apiKey} and {@code authToken} are mutually exclusive. | ||
| * | ||
| * @param baseUrl the base URL for the Anthropic API (null for default) | ||
| * @param apiKey the API key for authentication (null to omit) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor javadoc drift: the pre-existing constructor documents |
||
| * @param authToken the bearer token without the {@code Bearer } prefix (null to omit) | ||
| * @param modelName the model name to use | ||
| * @param streamEnabled whether streaming should be enabled | ||
| * @param defaultOptions default generation options | ||
| * @param formatter the message formatter to use (null for default | ||
| * Anthropic formatter) | ||
| * @param proxyConfig the proxy configuration (null for no proxy) | ||
| * @param cacheTtl the TTL for prompt-caching markers (null for default 5m) | ||
| * @param formatter the message formatter to use (null for the default formatter) | ||
| * @param proxyConfig the proxy configuration (null for no proxy) | ||
| * @param cacheTtl the TTL for prompt-caching markers (null for default 5m) | ||
| * @throws IllegalArgumentException if both API key and bearer token are configured | ||
| */ | ||
| public AnthropicChatModel( | ||
| String baseUrl, | ||
| String apiKey, | ||
| String authToken, | ||
| String modelName, | ||
| boolean streamEnabled, | ||
| GenerateOptions defaultOptions, | ||
| AnthropicBaseFormatter formatter, | ||
| ProxyConfig proxyConfig, | ||
| String cacheTtl) { | ||
| if (apiKey != null && authToken != null) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The guard is null-based, but |
||
| throw new IllegalArgumentException( | ||
| "apiKey and authToken are mutually exclusive; configure only one credential"); | ||
| } | ||
| this.baseUrl = baseUrl; | ||
| this.apiKey = apiKey; | ||
| this.modelName = modelName; | ||
|
|
@@ -112,6 +139,10 @@ public AnthropicChatModel( | |
| clientBuilder.apiKey(apiKey); | ||
| } | ||
|
|
||
| if (authToken != null) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feature-scope gap: the SPI / model-registry path ( |
||
| clientBuilder.authToken(authToken); | ||
| } | ||
|
|
||
| if (baseUrl != null) { | ||
| clientBuilder.baseUrl(baseUrl); | ||
| } | ||
|
|
@@ -282,6 +313,7 @@ public static Builder builder() { | |
| public static class Builder { | ||
| private String baseUrl; | ||
| private String apiKey; | ||
| private String authToken; | ||
| private String modelName = "claude-sonnet-4-5-20250929"; | ||
| private boolean streamEnabled = true; | ||
| private GenerateOptions defaultOptions; | ||
|
|
@@ -312,6 +344,20 @@ public Builder apiKey(String apiKey) { | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the bearer token for authentication with an Anthropic-compatible gateway. | ||
| * | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The javadoc says that if both |
||
| * <p>The SDK adds the {@code Bearer } prefix to the {@code Authorization} header. | ||
| * Configuring both an API key and a bearer token causes model construction to fail. | ||
| * | ||
| * @param authToken the token without the {@code Bearer } prefix (null to omit) | ||
| * @return this builder | ||
| */ | ||
| public Builder authToken(String authToken) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| this.authToken = authToken; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the model name. | ||
| * | ||
|
|
@@ -397,6 +443,7 @@ public AnthropicChatModel build() { | |
| new AnthropicChatModel( | ||
| baseUrl, | ||
| apiKey, | ||
| authToken, | ||
| modelName, | ||
| streamEnabled, | ||
| defaultOptions, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,8 @@ | |
| */ | ||
| package io.agentscope.extensions.model.anthropic; | ||
|
|
||
| import static io.agentscope.core.model.ModelProviderSupport.firstNonBlank; | ||
| import static io.agentscope.core.model.ModelProviderSupport.intOption; | ||
| import static io.agentscope.core.model.ModelProviderSupport.stringOption; | ||
| import static io.agentscope.core.model.ModelProviderSupport.trimToNull; | ||
|
|
||
| import io.agentscope.core.model.GenerateOptions; | ||
|
|
@@ -27,12 +27,20 @@ | |
| import io.agentscope.extensions.model.anthropic.formatter.AnthropicBaseFormatter; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| /** Anthropic provider registered through {@link java.util.ServiceLoader}. */ | ||
| /** | ||
| * Anthropic provider registered through {@link java.util.ServiceLoader}. | ||
| * | ||
| * <p>Credentials come from the context's standard {@code apiKey} field or the {@code | ||
| * "authToken"} context option (a bearer token for Anthropic-compatible gateways; the two are | ||
| * mutually exclusive). When neither is set, the {@code ANTHROPIC_API_KEY} environment variable | ||
| * is used, then {@code ANTHROPIC_AUTH_TOKEN}. | ||
| */ | ||
| public final class AnthropicModelProvider implements ModelProvider { | ||
|
|
||
| private static final String PREFIX = "anthropic:"; | ||
| private static final Pattern MODEL_ID = Pattern.compile("anthropic:.+"); | ||
| private static final String OPTION_CONTEXT_WINDOW_SIZE = "contextWindowSize"; | ||
| private static final String OPTION_AUTH_TOKEN = "authToken"; | ||
|
|
||
| @Override | ||
| public String providerId() { | ||
|
|
@@ -55,10 +63,22 @@ public Model create(String modelId, ModelCreationContext context) { | |
| throw new IllegalArgumentException("Unsupported Anthropic model id: " + modelId); | ||
| } | ||
| String modelName = modelId.substring(PREFIX.length()); | ||
| String apiKey = firstNonBlank(context.getApiKey(), System.getenv("ANTHROPIC_API_KEY")); | ||
| String apiKey = trimToNull(context.getApiKey()); | ||
| String authToken = stringOption(context, OPTION_AUTH_TOKEN); | ||
| if (apiKey == null && authToken == null) { | ||
| // No explicit credential: fall back to the environment, keeping the historical | ||
| // precedence of ANTHROPIC_API_KEY over ANTHROPIC_AUTH_TOKEN. | ||
| apiKey = trimToNull(System.getenv("ANTHROPIC_API_KEY")); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The env fallback is only consulted when neither explicit credential is present, so an explicit |
||
| if (apiKey == null) { | ||
| authToken = trimToNull(System.getenv("ANTHROPIC_AUTH_TOKEN")); | ||
| } | ||
| } | ||
| AnthropicChatModel.Builder builder = | ||
| AnthropicChatModel.builder().apiKey(apiKey).modelName(modelName).stream( | ||
| context.getStream() != null ? context.getStream() : true); | ||
| AnthropicChatModel.builder() | ||
| .apiKey(apiKey) | ||
| .authToken(authToken) | ||
| .modelName(modelName) | ||
| .stream(context.getStream() != null ? context.getStream() : true); | ||
| String baseUrl = trimToNull(context.getBaseUrl()); | ||
| if (baseUrl != null) { | ||
| builder.baseUrl(baseUrl); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Info] This commit dropped the Javadoc from the 8-arg convenience constructor. It is still a public constructor and it now silently encodes a contract ("delegates with
authToken = null", i.e. API-key /X-Api-Keyauth). A one-line comment plus@param/@seeto the 9-arg overload would keep that visible to callers who pick it from IDE completion; the module publishes javadoc for these public types.