Skip to content

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

Open

Description

Description

#9748 added deterministic lifecycle to chat clients (async with, close(), connector-owned resource release), but the embedding client family was left out:

  • BaseEmbeddingClient has no __aenter__/__aexit__/close(), so async with does not work for any embedding client, and there is no contract for releasing resources.
  • OpenAIEmbeddingClient creates an AsyncOpenAI/AsyncAzureOpenAI SDK client internally (owning an httpx connection pool) when async_client is not supplied, and offers no way to close it — only GC ever releases the pool.
  • BedrockEmbeddingClient creates a boto3 bedrock-runtime client internally with the same gap.
  • OllamaEmbeddingClient creates an AsyncClient (httpx transport) internally with the same gap.
  • GeminiEmbeddingClient already implements close() with _owns_client — the rest of the family is inconsistent with it and with the chat clients.

Repro

from agent_framework_openai import OpenAIEmbeddingClient

client = OpenAIEmbeddingClient(api_key="...")
async with client:  # TypeError: 'async with' received an object that does not implement __aenter__
    ...
# and the internally created AsyncOpenAI is never closed deterministically

Long-lived processes that build embedding clients per unit of work (e.g. per request/tenant) accumulate unclosed httpx pools until GC runs, which is exactly the class of leak #9748 fixed for chat clients.

Expected

Embedding clients follow the same contract #9748 established for chat clients: base-class context trio with a no-op default close(), and resource-owning implementations that close only the SDK client they created (caller-supplied clients stay open), idempotent on repeated close.

Environment

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 29, 2026
  2. changed the title [-]Embedding clients have no close()/context manager and leak their internally created SDK clients[/-] [+]Python: Embedding clients have no close()/context manager and leak their internally created SDK clients[/+] on Oct 29, 2026
  3. eavanvalkenburg commented on Nov 2, 2026

    @eavanvalkenburg
    Member

    Thanks for raising this. The team will consider a common lifecycle contract for embedding clients, including deterministic release of framework-created SDK clients while leaving caller-supplied resources open. This is a foundational cross-provider concern, rather than a vendor-specific integration. This acknowledgement does not promise implementation or timing; the public lifecycle and ownership details need planning, using existing #9815 as input rather than starting competing work.

  4. eavanvalkenburg commented on Nov 2, 2026

    @eavanvalkenburg
    Member

    /plan Plan the accepted core ownership of a uniform embedding-client lifecycle, using existing #9815 and the chat-client/Gemini ownership contracts as input rather than starting another PR. Decide the additive BaseEmbeddingClient close/async-context contract, no-context use, legacy synchronous overrides, idempotency/reuse and close-failure behavior. Audit OpenAI/Azure OpenAI, Bedrock, Ollama and Gemini: release only framework-created SDK clients and leave injected clients, transports, sessions and credentials caller-owned; verify actual supported SDK close APIs and avoid unnecessary private-transport coupling. Cover normal/exceptional context exit, explicit/repeated close, construction/close failures and serialization of internal ownership flags across providers. Distinguish the confirmed lifecycle API gap from the unmeasured accumulating-pool claim. Review the existing implementation/tests for these boundaries; no ready label or new assignment while the public cross-package contract is pending, and retain triage for planning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions