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:
Douwe Maan
2026-10-09 19:47:05 +00:00
parent e50ce2716f
commit f35f846f0a
2 changed files with 10 additions and 10 deletions
@@ -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
+2 -2
View File
@@ -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]