Skip to content

Latest commit

 

History

History
113 lines (91 loc) · 5.4 KB

File metadata and controls

113 lines (91 loc) · 5.4 KB

Framework adapters architecture

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.

Overview

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

Wrap the SDK, don't reach into the gateway

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/query call with the standard flags.
  • chunk_text / chunk_metadata — the canonical Chunk → (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.

Dependency isolation: lazy imports + per-framework extras

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.integrations imports no framework.
  • Each submodule imports its framework at module top inside a try/except ImportError that re-raises via missing_dependency(...) with an actionable "install agentcontextos[<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.

Testing strategy

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 integrations CI 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.

Why CI mypy doesn't cover the integration modules

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.

Reviewer checklist

  • New adapter lazy-imports its framework inside try/exceptmissing_dependency?
  • Uses _common (resolve_*_client, retrieve, chunk_* / format_passages) rather than re-implementing retrieval or mapping?
  • Retrieval uses pack=False / generate=False?
  • [<extra>] added to sdks/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?

See also