Skip to content

Python: Add User-Agent header for Anthropic API calls#13579

Open
mikelambert wants to merge 2 commits intomicrosoft:mainfrom
mikelambert:mike/user-agent
Open

Python: Add User-Agent header for Anthropic API calls#13579
mikelambert wants to merge 2 commits intomicrosoft:mainfrom
mikelambert:mike/user-agent

Conversation

@mikelambert
Copy link
Copy Markdown

Motivation and Context / Description

Applies the existing Semantic Kernel user-agent infrastructure to the Anthropic connector, matching what's already done for OpenAI. This helps ensure the user-agents are plumbed more consistently across backends.

Contribution Checklist

Applies the existing Semantic Kernel user-agent infrastructure to the
Anthropic connector, matching what's already done for OpenAI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mikelambert mikelambert requested a review from a team as a code owner February 23, 2026 04:53
@moonbox3 moonbox3 added the python Pull requests for the Python Semantic Kernel label Feb 23, 2026
@github-actions github-actions bot changed the title Add User-Agent header for Anthropic API calls Python: Add User-Agent header for Anthropic API calls Feb 23, 2026
@moonbox3
Copy link
Copy Markdown
Collaborator

moonbox3 commented Feb 25, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
connectors/ai/anthropic/services
   anthropic_chat_completion.py171994%171, 177, 190, 196, 200, 257, 259, 326, 396
TOTAL28166482982% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3836 23 💤 0 ❌ 0 🔥 1m 52s ⏱️

@mikelambert
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree [company="Anthropic"]

@mikelambert
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Anthropic"

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 91%

✓ Correctness

This PR adds default_headers support to the Anthropic chat completion connector, following the identical pattern already established in open_ai_config_base.py. The header merging logic (copy default_headers, merge APP_INFO, prepend user agent) is correct and consistent with the OpenAI implementation. The three new tests adequately cover the cases: APP_INFO present, APP_INFO with custom headers, and APP_INFO absent. No bugs or correctness issues found.

✓ Security Reliability

This PR adds default_headers support and User-Agent telemetry to the Anthropic chat completion connector. The implementation follows the identical pattern already established in OpenAIConfigBase and AzureConfigBase (copy headers, merge APP_INFO, prepend User-Agent). The code correctly uses copy() to avoid mutating the caller's Mapping, safely handles the None cases for both default_headers and APP_INFO, and only applies headers when constructing a new client (consistent with existing connectors). Tests cover all three branches (APP_INFO present, merged with custom headers, APP_INFO absent). No security or reliability concerns found.

✓ Test Coverage

The three new tests cover the main scenarios for the header-merging logic (APP_INFO present, APP_INFO with custom headers, APP_INFO absent). However, there are notable gaps: no test verifies that passing default_headers when async_client is also provided silently drops those headers; no test covers passing custom default_headers when APP_INFO is None (they should still be forwarded); and no test asserts the original default_headers mapping is not mutated (the copy() exists but is unverified).

✗ Design Approach

This PR adds default_headers support and APP_INFO telemetry injection to the Anthropic connector, mirroring the existing pattern in azure_config_base.py. The implementation is largely correct and consistent with the rest of the codebase. However, there is a silent no-op bug: when a caller supplies both async_client and default_headers, the default_headers are computed (including the APP_INFO merge) but then discarded because the pre-built client is used directly. This parameter silently has no effect in that code path, with no warning and no documentation note. Additionally, the merge order means APP_INFO overwrites any user-supplied header with the same key (e.g., a custom semantic-kernel-version), which matches the existing pattern but is arguably inverted precedence.

Flagged Issues

  • default_headers is silently ignored when async_client is provided. The merged headers are computed but never applied to an externally-supplied client, so passing both async_client and default_headers is a silent no-op. At minimum a warning should be emitted; alternatively, the parameter should be documented as ignored when async_client is provided, or an error should be raised when both are given together.

Suggestions

  • The merge order (merged_headers.update(APP_INFO)) means APP_INFO silently overwrites any user-supplied header with the same key (e.g., semantic-kernel-version). This is consistent with azure_config_base.py but the precedence is arguably backwards — internal telemetry should not trump user-provided values. Consider reversing the order so user values always win.
  • The dict(copy(default_headers)) pattern is redundant: dict(default_headers) already creates a new dict from any Mapping. The copy() call (and its import) can be removed.
  • Add tests for: (1) both async_client and default_headers provided together (to document/guard the silent-ignore behavior), (2) custom default_headers with APP_INFO=None (headers should still be forwarded), and (3) verifying the caller's original default_headers mapping is not mutated by the merge logic.

Automated review by moonbox3's agents


if not async_client:
async_client = AsyncAnthropic(
api_key=anthropic_settings.api_key.get_secret_value(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merged_headers is computed (including APP_INFO injection) for every call, but is only consumed in the if not async_client branch. When a caller passes a pre-built async_client, default_headers is silently discarded. Either emit a logger.warning here when both are provided, or raise early.

Suggested change
api_key=anthropic_settings.api_key.get_secret_value(),
if async_client and default_headers:
logger.warning(
"The `default_headers` parameter is ignored when `async_client` is provided. "
"Set headers directly on the supplied client instead."
)
# Merge APP_INFO into the headers if it exists
merged_headers = dict(default_headers) if default_headers else {}
if APP_INFO:
merged_headers.update(APP_INFO)
merged_headers = prepend_semantic_kernel_to_user_agent(merged_headers)

if not anthropic_settings.chat_model_id:
raise ServiceInitializationError("The Anthropic chat model ID is required.")

# Merge APP_INFO into the headers if it exists
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dict(copy(default_headers)) is redundant — dict(default_headers) already produces a new dict from any Mapping. The copy() call (and the import) can be removed.

Suggested change
# Merge APP_INFO into the headers if it exists
merged_headers = dict(default_headers) if default_headers else {}


call_kwargs = mock_async_anthropic.call_args.kwargs
headers = call_kwargs["default_headers"]
assert headers == {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only covers APP_INFO=None without custom headers. Consider adding sibling tests for: (1) default_headers={"X-Custom": "value"} with APP_INFO=None to verify custom headers are still forwarded, and (2) both async_client and default_headers provided to document the expected behavior (warning, error, or documented no-op).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants