mirror of
https://github.com/pydantic/pydantic-ai.git
synced 2026-10-12 02:21:31 +00:00
fix: also close a model built for a name a resolve_model_id capability declined
When a `resolve_model_id` capability returns `None`, the run falls back to `infer_model`, and that model's HTTP client was never closed either. The run now enters it like any other model it built.
This commit is contained in:
@@ -3139,8 +3139,9 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
||||
"""Resolve a concrete model selection through the capability chain.
|
||||
|
||||
A name that only `infer_model` can resolve builds a new model, with its own provider and HTTP client.
|
||||
While the agent is entered, that model is entered on the agent's exit stack and reused by later runs;
|
||||
otherwise it's added to `run_built_models` so the run can enter it and close its client when it ends.
|
||||
While the agent is entered and has no `resolve_model_id` capability, that model is entered on the agent's
|
||||
exit stack and reused by later runs; otherwise it's added to `run_built_models` so the run can enter it
|
||||
and close its client when it ends.
|
||||
"""
|
||||
if not isinstance(selection, str):
|
||||
return selection
|
||||
@@ -3150,14 +3151,13 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
||||
if capability.has_resolve_model_id:
|
||||
# Resolution can depend on the run's deps, so it isn't reused across runs.
|
||||
resolution_ctx = models.ModelResolutionContext(agent=self, deps=deps)
|
||||
resolved = await capability.resolve_model_id(resolution_ctx, model_id=selection)
|
||||
resolved_model = resolved if resolved is not None else models.infer_model(selection)
|
||||
resolved_model = await capability.resolve_model_id(resolution_ctx, model_id=selection)
|
||||
else:
|
||||
resolved_model = await self._entered_model_for_name(selection)
|
||||
if resolved_model is None:
|
||||
resolved_model = models.infer_model(selection)
|
||||
if run_built_models is not None:
|
||||
run_built_models.append(resolved_model)
|
||||
if resolved_model is None:
|
||||
resolved_model = models.infer_model(selection)
|
||||
if run_built_models is not None:
|
||||
run_built_models.append(resolved_model)
|
||||
if resolved_models is not None:
|
||||
resolved_models[cache_key] = resolved_model
|
||||
return resolved_model
|
||||
|
||||
@@ -149,7 +149,7 @@ async def test_run_leaves_client_of_passed_model_open(provider_clients: list[htt
|
||||
|
||||
|
||||
async def test_names_resolved_through_capability_are_not_reused(provider_clients: list[httpx2.AsyncClient]):
|
||||
"""With a `resolve_model_id` capability, a name can resolve differently per run, so it's resolved every run."""
|
||||
"""With a `resolve_model_id` capability, a name is resolved every run, and a model built for it closed when the run ends."""
|
||||
seen: list[str] = []
|
||||
|
||||
def resolve(ctx: ModelResolutionContext[Any], model_id: str) -> Model | None:
|
||||
@@ -162,4 +162,4 @@ async def test_names_resolved_through_capability_are_not_reused(provider_clients
|
||||
await agent.run('What is the capital of France?', model='openai-chat:gpt-5.2')
|
||||
await agent.run('What is the capital of France?', model='openai-chat:gpt-5.2')
|
||||
assert seen == ['openai-chat:gpt-5.2', 'openai-chat:gpt-5.2']
|
||||
assert len(provider_clients) == 2
|
||||
assert [client.is_closed for client in provider_clients] == [True, True]
|
||||
|
||||
Reference in New Issue
Block a user