Skip to content

Commit 64d4761

Browse files
authored
fix: Copy extra_headers to allow model_settings reuse (#3628)
1 parent 467d3ae commit 64d4761

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ def _get_betas_and_extra_headers(
424424
Handles merging custom `anthropic-beta` header from `extra_headers` into betas set
425425
and ensuring `User-Agent` is set.
426426
"""
427-
extra_headers = model_settings.get('extra_headers', {})
427+
extra_headers = dict(model_settings.get('extra_headers', {}))
428428
extra_headers.setdefault('User-Agent', get_user_agent())
429429

430430
betas: set[str] = set()

tests/models/test_anthropic.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,49 @@ def memory(**command: Any) -> Any: # pragma: no cover
786786
)
787787

788788

789+
async def test_model_settings_reusable_with_beta_headers(allow_model_requests: None):
790+
"""Verify that model_settings with extra_headers can be reused across multiple runs.
791+
792+
This test ensures that the beta header extraction doesn't mutate the original model_settings,
793+
allowing the same settings to be used for multiple agent runs.
794+
"""
795+
c = completion_message(
796+
[BetaTextBlock(text='Hello!', type='text')],
797+
BetaUsage(input_tokens=5, output_tokens=10),
798+
)
799+
mock_client = MockAnthropic.create_mock(c)
800+
801+
model_settings = AnthropicModelSettings(extra_headers={'anthropic-beta': 'custom-feature-1, custom-feature-2'})
802+
803+
model = AnthropicModel(
804+
'claude-sonnet-4-5',
805+
provider=AnthropicProvider(anthropic_client=mock_client),
806+
settings=model_settings,
807+
)
808+
809+
agent = Agent(model)
810+
811+
# First run
812+
await agent.run('Hello')
813+
814+
# Verify the original model_settings is not mutated
815+
assert model_settings.get('extra_headers') == {'anthropic-beta': 'custom-feature-1, custom-feature-2'}
816+
817+
# Second run should work with the same beta headers
818+
await agent.run('Hello again')
819+
820+
# Verify again after second run
821+
assert model_settings.get('extra_headers') == {'anthropic-beta': 'custom-feature-1, custom-feature-2'}
822+
823+
# Verify both runs had the correct betas
824+
all_kwargs = get_mock_chat_completion_kwargs(mock_client)
825+
assert len(all_kwargs) == 2
826+
for completion_kwargs in all_kwargs:
827+
betas = completion_kwargs['betas']
828+
assert 'custom-feature-1' in betas
829+
assert 'custom-feature-2' in betas
830+
831+
789832
async def test_anthropic_mixed_strict_tool_run(allow_model_requests: None, anthropic_api_key: str):
790833
"""Exercise both strict=True and strict=False tool definitions against the live API."""
791834
m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key=anthropic_api_key))

0 commit comments

Comments
 (0)