Repository navigation
Conversation
Embedding clients were left out of the deterministic lifecycle that microsoft#9748 gave chat clients: BaseEmbeddingClient had no __aenter__/__aexit__/close(), so 'async with' did not work and internally created SDK clients (OpenAI httpx pools, Ollama httpx transports) were only released by GC. This aligns the embedding family with the chat clients and with the existing GeminiEmbeddingClient.close()/_owns_client pattern. - BaseEmbeddingClient: async context manager and a no-op close() default, mirroring BaseChatClient. - OpenAI embedding client: track _owns_client; close() awaits client.close() only when the SDK client was created internally. - Ollama embedding client: same ownership pattern via AsyncClient.aclose(). Caller-supplied clients always stay open: their lifecycle belongs to the caller. Bedrock's boto3 client has no close() to call and keeps its default no-op. Closes microsoft#9814
|
Lushiyao5 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
🟡 Changes recommended
Internally created Ollama clients raise AttributeError during cleanup because AsyncClient has no close() method, leaving the HTTP transport open.
1 open issue
What changed in this PR
Extends the Python chat-client lifecycle pattern to embedding clients, adding optional context management and ownership-aware cleanup to address #9814.
Changes:
- Adds async context management and a default no-op
close()toBaseEmbeddingClient. - Adds ownership tracking and cleanup methods to OpenAI and Ollama embedding clients.
- Adds tests for the base lifecycle and OpenAI client ownership.
| File | Description |
|---|---|
python/packages/openai/tests/openai/test_openai_embedding_client.py |
Tests cleanup of owned clients and preservation of caller-supplied clients. |
python/packages/openai/agent_framework_openai/_embedding_client.py |
Adds ownership-aware OpenAI SDK cleanup. |
python/packages/ollama/agent_framework_ollama/_embedding_client.py |
Adds ownership tracking and a cleanup override. |
python/packages/core/tests/core/test_embedding_client.py |
Tests context entry and default no-op cleanup. |
python/packages/core/agent_framework/_clients.py |
Defines the shared embedding-client lifecycle methods. |
Review effort: Balanced
| A caller-supplied ``client`` stays open: its lifecycle belongs to the caller. | ||
| """ | ||
| if self._owns_client: | ||
| await self.client.close() |

Closes #9814
Embedding clients were left out of the deterministic lifecycle that #9748 gave chat clients:
BaseEmbeddingClienthad no__aenter__/__aexit__/close(), soasync withdid not work for any embedding client, and internally created SDK clients (OpenAI httpx connection pools, Ollama httpx transports) were only ever released by GC.Change
BaseEmbeddingClient(core): async context manager plus a no-opclose()default, mirroringBaseChatClient— entering the context is never required for ordinary requests, and resource-owning implementations overrideclose().OpenAIEmbeddingClient: tracks_owns_client = async_client is None;close()awaitsclient.close()only when the SDK client was created internally (viaload_openai_service_settings), covering both the OpenAI and Azure paths.OllamaEmbeddingClient: the same ownership pattern;close()awaitsAsyncClient.aclose().Caller-supplied clients always stay open — their lifecycle belongs to the caller.
GeminiEmbeddingClientalready implemented this pattern and is untouched. Bedrock's boto3 runtime client has no close() surface, so it keeps the base no-op.Tests
test_embedding_client.py): the async context manager protocol works on the base and the defaultclose()is a no-op;test_openai_embedding_client.py):close()releases an internally created client and leaves a caller-supplied one unawaited;