Skip to content

Python: feat(python): add close() and async context manager to embedding clients - #9895

Closed
Lushiyao5 wants to merge 1 commit into
microsoft:mainfrom
Lushiyao5:feat/embedding-client-lifecycle
Closed

Lushiyao5 wants to merge 1 commit into
microsoft:mainfrom
Lushiyao5:feat/embedding-client-lifecycle

Conversation

@Lushiyao5

Copy link
Copy Markdown

Closes #9814

Embedding clients were left out of the deterministic lifecycle that #9748 gave chat clients: BaseEmbeddingClient had no __aenter__/__aexit__/close(), so async with did 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-op close() default, mirroring BaseChatClient — entering the context is never required for ordinary requests, and resource-owning implementations override close().
  • OpenAIEmbeddingClient: tracks _owns_client = async_client is None; close() awaits client.close() only when the SDK client was created internally (via load_openai_service_settings), covering both the OpenAI and Azure paths.
  • OllamaEmbeddingClient: the same ownership pattern; close() awaits AsyncClient.aclose().

Caller-supplied clients always stay open — their lifecycle belongs to the caller. GeminiEmbeddingClient already implemented this pattern and is untouched. Bedrock's boto3 runtime client has no close() surface, so it keeps the base no-op.

Tests

  • core (test_embedding_client.py): the async context manager protocol works on the base and the default close() is a no-op;
  • openai (test_openai_embedding_client.py): close() releases an internally created client and leaves a caller-supplied one unawaited;
  • ollama suite passes unchanged with the ownership tracking in place.

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
Copilot AI balanced review requested due to automatic review settings October 30, 2026 17:23
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Oct 30, 2026
@github-actions github-actions Bot changed the title feat(python): add close() and async context manager to embedding clients Python: feat(python): add close() and async context manager to embedding clients Oct 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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() to BaseEmbeddingClient.
  • 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()
@Lushiyao5

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@eavanvalkenburg

Copy link
Copy Markdown
Member

Thanks for working on this, Lushiyao5. This overlaps with the earlier open #9815, which addresses the same issue (#9814) and implements the same embedding-client lifecycle work across a broader set of clients. I’m closing this as a duplicate so review can continue in #9815.

This branch was previously deployed

1 inactive deployment
github-app-auth — 8664a6b1 Deployed Oct 30, 2026 by Lushiyao5 via add_label #26719
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: Embedding clients have no close()/context manager and leak their internally created SDK clients

3 participants