How the eight framework integrations are structured, isolated, and tested. Public usage lives in reference/integrations.md; this page is the design rationale + invariants for contributors.
Each adapter is a thin translation layer that wraps the public
agentcontextos.Client in a framework's native interface. They live inside the
Python SDK at agentcontextos.integrations.<framework> and share one retrieval
path + one chunk-mapping, so a framework can't see a different wire contract
than the SDK exposes.
framework code ──▶ agentcontextos.integrations.<framework>
│ (lazy-imports its framework; raises a friendly
│ "install agentcontextos[<extra>]" if absent)
▼
_common: resolve client → Client.query(pack=False, generate=False)
│
▼
Chunk[] ──▶ framework Document / passage string
An adapter calls Client.query(...) and maps the result; it never re-implements
identity headers, transport, error mapping, or SSE. That reuse is the point:
the adapters inherit the SDK's behaviour and can't drift from the wire contract.
Retrieval always uses pack=False / generate=False so the framework receives
hydrated, scored Chunks (content included) without paying for packing or an
LLM answer.
_common.py owns everything shared:
resolve_sync_client/resolve_async_client— accept an injected client or build one from connection kwargs.retrieve/aretrieve— the/v1/querycall with the standard flags.chunk_text/chunk_metadata— the canonicalChunk → (text, metadata)mapping every adapter uses, so framework documents carry the same fields under the same keys regardless of which adapter produced them.format_passages— the numbered, source-attributed passage string the tool-calling adapters (CrewAI, AutoGen, Semantic Kernel) hand back to the model.
Two adapter shapes fall out of this: retriever adapters (LangChain, LlamaIndex, Haystack, LangGraph) return framework documents; tool adapters (CrewAI, AutoGen, Semantic Kernel) return a rendered passage string.
The frameworks have heavy, sometimes-conflicting dependency trees (CrewAI alone pulls ~60 packages), so none of them may enter the base SDK install.
- Importing
agentcontextos.integrationsimports no framework. - Each submodule imports its framework at module top inside a
try/except ImportErrorthat re-raises viamissing_dependency(...)with an actionable "installagentcontextos[<extra>]" message. - The SDK declares eight optional extras, each pulling only the framework's lightweight core.
So pip install agentcontextos stays light; a user pays only for the
integrations they import.
Each adapter is exercised against a fake SDK client (canned chunks via the
fake_client fixture) and the real framework base classes, so the mapping
logic — text, score, metadata, the framework's document/tool contract — is
verified, not mocked.
- Framework-dependent tests are guarded by
pytest.importorskip, so the default CI environment (SDK without extras) skips them rather than failing. - The tool-string core (
format_passages) and the pure helpers (make_search_function,missing_dependency) are tested without any framework, so the CrewAI / AutoGen / Semantic Kernel rendering has coverage even where the heaviest frameworks aren't installed. - A dedicated
integrationsCI job installs the lighter six extras (LangChain, LlamaIndex, Haystack, DSPy, LangGraph, AutoGen) and runs the adapter tests against the real frameworks; CrewAI + Semantic Kernel stay skip-if-absent there and are verified locally.
With the frameworks absent (the default CI environment), the adapter
modules subclass Any-typed base classes (BaseRetriever, BaseTool) and use
untyped decorators (@component, @kernel_function), which mypy --strict
rejects (disallow_subclassing_any, untyped-decorator). So CI mypy stays on
packages/ apps/gateway/ (the same scope as the SDK, ADR-0016). The modules are
type-checked locally with the frameworks installed; the framework module
roots are in the mypy ignore_missing_imports overrides so that local run is
clean regardless of which extras are present.
- New adapter lazy-imports its framework inside
try/except→missing_dependency? - Uses
_common(resolve_*_client,retrieve,chunk_*/format_passages) rather than re-implementing retrieval or mapping? - Retrieval uses
pack=False/generate=False? [<extra>]added tosdks/python/pyproject.toml+ a mypy override for the framework module?importorskip-guarded test that asserts the mapping (text, score, metadata) against the real framework type?- Pure logic (string rendering, helpers) tested without the framework?
- reference/integrations.md — usage.
- ADR-0017 — the decision + deferrals.
- reference/sdks.md / ADR-0016 — the SDK the adapters wrap.