Skip to content

Commit aa4dca2

Browse files
committed
pass container id back
1 parent 3b6dc5e commit aa4dca2

File tree

2 files changed

+34
-3
lines changed

2 files changed

+34
-3
lines changed

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
BetaCodeExecutionToolResultBlockContent,
7575
BetaCodeExecutionToolResultBlockParam,
7676
BetaCodeExecutionToolResultBlockParamContentParam,
77+
BetaContainerParams,
7778
BetaContentBlock,
7879
BetaContentBlockParam,
7980
BetaImageBlockParam,
@@ -200,6 +201,16 @@ class AnthropicModelSettings(ModelSettings, total=False):
200201
See https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching for more information.
201202
"""
202203

204+
anthropic_container: BetaContainerParams | Literal[False]
205+
"""Container configuration for multi-turn conversations.
206+
207+
By default, if previous messages contain a container_id (from a prior response),
208+
it will be reused automatically.
209+
210+
Set to `False` to force a fresh container (ignore any container_id from history).
211+
Set to a dict (e.g. `{'id': 'container_xxx'}`) to explicitly specify a container.
212+
"""
213+
203214

204215
@dataclass(init=False)
205216
class AnthropicModel(Model):
@@ -385,6 +396,7 @@ async def _messages_create(
385396
output_format = self._native_output_format(model_request_parameters)
386397
betas, extra_headers = self._get_betas_and_extra_headers(tools, model_request_parameters, model_settings)
387398
betas.update(builtin_tool_betas)
399+
container = self._get_container(messages, model_settings)
388400
try:
389401
return await self.client.beta.messages.create(
390402
max_tokens=model_settings.get('max_tokens', 4096),
@@ -403,6 +415,7 @@ async def _messages_create(
403415
top_p=model_settings.get('top_p', OMIT),
404416
timeout=model_settings.get('timeout', NOT_GIVEN),
405417
metadata=model_settings.get('anthropic_metadata', OMIT),
418+
container=container or OMIT,
406419
extra_headers=extra_headers,
407420
extra_body=model_settings.get('extra_body'),
408421
)
@@ -439,6 +452,18 @@ def _get_betas_and_extra_headers(
439452

440453
return betas, extra_headers
441454

455+
def _get_container(
456+
self, messages: list[ModelMessage], model_settings: AnthropicModelSettings
457+
) -> BetaContainerParams | None:
458+
"""Get container config for the API request."""
459+
if (container := model_settings.get('anthropic_container')) is not None:
460+
return None if container is False else container
461+
for m in reversed(messages):
462+
if isinstance(m, ModelResponse) and m.provider_details:
463+
if cid := m.provider_details.get('container_id'):
464+
return BetaContainerParams(id=cid)
465+
return None
466+
442467
async def _messages_count_tokens(
443468
self,
444469
messages: list[ModelMessage],
@@ -526,6 +551,9 @@ def _process_response(self, response: BetaMessage) -> ModelResponse:
526551
if raw_finish_reason := response.stop_reason: # pragma: no branch
527552
provider_details = {'finish_reason': raw_finish_reason}
528553
finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
554+
if response.container:
555+
provider_details = provider_details or {}
556+
provider_details['container_id'] = response.container.id
529557

530558
return ModelResponse(
531559
parts=items,
@@ -1125,6 +1153,9 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
11251153
if isinstance(event, BetaRawMessageStartEvent):
11261154
self._usage = _map_usage(event, self._provider_name, self._provider_url, self._model_name)
11271155
self.provider_response_id = event.message.id
1156+
if event.message.container:
1157+
self.provider_details = self.provider_details or {}
1158+
self.provider_details['container_id'] = event.message.container.id
11281159

11291160
elif isinstance(event, BetaRawContentBlockStartEvent):
11301161
current_block = event.content_block
@@ -1239,7 +1270,7 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
12391270
elif isinstance(event, BetaRawMessageDeltaEvent):
12401271
self._usage = _map_usage(event, self._provider_name, self._provider_url, self._model_name, self._usage)
12411272
if raw_finish_reason := event.delta.stop_reason: # pragma: no branch
1242-
self.provider_details = {'finish_reason': raw_finish_reason}
1273+
self.provider_details = {**(self.provider_details or {}), 'finish_reason': raw_finish_reason}
12431274
self.finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason)
12441275

12451276
elif isinstance(event, BetaRawContentBlockStopEvent): # pragma: no branch

tests/models/test_anthropic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5512,7 +5512,7 @@ async def test_anthropic_code_execution_tool(allow_model_requests: None, anthrop
55125512
model_name='claude-sonnet-4-20250514',
55135513
timestamp=IsDatetime(),
55145514
provider_name='anthropic',
5515-
provider_details={'finish_reason': 'end_turn'},
5515+
provider_details={'finish_reason': 'end_turn', 'container_id': 'container_011CTCwceSoRxi8Pf16Fb7Tn'},
55165516
provider_response_id='msg_018bVTPr9khzuds31rFDuqW4',
55175517
finish_reason='stop',
55185518
run_id=IsStr(),
@@ -5579,7 +5579,7 @@ async def test_anthropic_code_execution_tool(allow_model_requests: None, anthrop
55795579
model_name='claude-sonnet-4-20250514',
55805580
timestamp=IsDatetime(),
55815581
provider_name='anthropic',
5582-
provider_details={'finish_reason': 'end_turn'},
5582+
provider_details={'finish_reason': 'end_turn', 'container_id': 'container_011CTCwdXe48NC7LaX3rxQ4d'},
55835583
provider_response_id='msg_01VngRFBcNddwrYQoKUmdePY',
55845584
finish_reason='stop',
55855585
run_id=IsStr(),

0 commit comments

Comments
 (0)