Skip to content

feat(adapters): caching interceptor#2

Closed
Glorf wants to merge 2 commits into
feat/adapter-basefrom
feat/adapter-caching
Closed

feat(adapters): caching interceptor#2
Glorf wants to merge 2 commits into
feat/adapter-basefrom
feat/adapter-caching

Conversation

@Glorf

@Glorf Glorf commented Jun 3, 2026

Copy link
Copy Markdown
Owner

PR 3 of 4 — caching family. Built on feat/adapter-base (independent of the observability and request-rewriting PRs).

Adds the caching interceptor and its supporting sqlite disk-cache module:

  • caching (request -> response) SHA-256-keyed disk cache. On cache hit, short-circuits the chain as a RequestToResponseInterceptor; on miss, writes the upstream response back into the cache via the matching ResponseInterceptor. Cache keys are session-scoped when ctx.extra["session_id"] is present.

  • nemo_gym/adapters/cache/disk_cache.py sqlite-backed cache with canonical-body hashing.

  • nemo_gym/adapters/cache/init.py Cache subpackage exports.

Tests

test_adapter_cache_keys.py golden SHA-256 keys
test_adapter_disk_cache.py sqlite round-trip
test_adapter_interceptors.py TestCachingInterceptor behaviour
test_adapter_parity_replay.py cache hit returns byte-equal response

58 tests pass (42 from feat/adapter-base + 16 added).

Docs

fern/.../model-server/adapters.mdx appends Caching section with config
example and per-replica-cache-race
caveat.

Glorf added 2 commits June 3, 2026 13:53
Introduces an interceptor-based middleware framework that runs at every
in-tree server's request/response boundary. PR 1 of 4 — base framework
only. Follow-on PRs add observability / caching / request-rewriting
interceptor families.

Framework

  nemo_gym/adapters/
    pipeline.py      AdapterPipeline + stage-order validation
                     (REQUEST → REQUEST_TO_RESPONSE → RESPONSE)
    middleware.py    install_middleware(app, specs) — FastAPI middleware
                     that wraps call_next; body replay, multi-Set-Cookie
                     preservation, /s/<hex>/... session-prefix routing,
                     GracefulError → 429
    proxy.py         start_adapter_proxy(upstream_url, adapters) —
                     localhost uvicorn host mode for external-inference
                     agents whose SDK respects *_BASE_URL env vars
    registry.py      short-name → Interceptor class + runtime register()
    types.py         AdapterRequest/Response, three Interceptor ABCs,
                     Stage enum, GracefulError, ContextVar-backed
                     per-request context, InterceptorSpec and
                     AdapterProxyConfig pydantic models
    interceptors/
      endpoint.py    Drives upstream HTTP call (required by proxy mode,
                     forbidden inside install_middleware)
      request_logging.py
                     Canonical "did the chain fire?" probe (logs body
                     keys + response status/latency)

Server wire-up

  adapters: list[dict] | None = None  on three base configs:
    BaseResponsesAPIModelConfig
    BaseResponsesAPIAgentConfig
    BaseResourcesServerConfig

  install_middleware(app, self.config.adapters)  at the tail of each
  base's setup_webserver. Every in-tree server inheriting from
  SimpleResponsesAPI{Model,Agent} or SimpleResourcesServer picks up
  the adapters knob automatically.

  adapter_proxy: AdapterProxyConfig | None = None  on
  BaseResponsesAPIAgentConfig. When set, SimpleResponsesAPIAgent.
  setup_webserver starts a localhost uvicorn proxy in a daemon thread,
  stores the ProxyHandle on self._proxy_handle, registers atexit cleanup.

Per-server override fixes

  harbor_agent and mini_swe_agent override setup_webserver without
  calling super(). Inline install_middleware(app, self.config.adapters)
  calls added to both so the adapters field still applies.

claude_code_agent integration

  Reads self._proxy_handle.url when adapter_proxy is set; threads the
  proxy URL into the claude CLI subprocess via ANTHROPIC_BASE_URL +
  ANTHROPIC_AUTH_TOKEN. Preserves the full model-name prefix in proxy
  mode (no .split("/")[-1] stripping).

Safety

  - Stage ordering validated at startup
  - Unknown interceptor name raises at config-validation time
  - install_middleware rejects `endpoint` in chain (host already forwards)
  - start_adapter_proxy rejects user-supplied `endpoint` AND refuses
    host="0.0.0.0" unless unsafe_allow_remote=True (otherwise leaks
    the upstream API key to any caller on the network)
  - best_effort=True interceptors swallow exceptions; strict ones
    propagate

Tests

  42 tests, framework-level coverage. Follow-on PRs add per-interceptor
  test suites.

Docs

  fern/versions/latest/pages/model-server/adapters.mdx       new
  fern/versions/latest/pages/model-server/index.mdx          link added

Signed-off-by: Michal Bien <mbien@nvidia.com>
PR 3 of 4 — caching family. Built on feat/adapter-base (independent of
the observability and request-rewriting PRs).

Adds the `caching` interceptor and its supporting sqlite disk-cache
module:

  - caching (request -> response)
        SHA-256-keyed disk cache. On cache hit, short-circuits the chain
        as a RequestToResponseInterceptor; on miss, writes the upstream
        response back into the cache via the matching ResponseInterceptor.
        Cache keys are session-scoped when ctx.extra["session_id"] is
        present.

  - nemo_gym/adapters/cache/disk_cache.py
        sqlite-backed cache with canonical-body hashing.

  - nemo_gym/adapters/cache/__init__.py
        Cache subpackage exports.

Tests

  test_adapter_cache_keys.py         golden SHA-256 keys
  test_adapter_disk_cache.py         sqlite round-trip
  test_adapter_interceptors.py       TestCachingInterceptor behaviour
  test_adapter_parity_replay.py      cache hit returns byte-equal response

58 tests pass (42 from feat/adapter-base + 16 added).

Docs

  fern/.../model-server/adapters.mdx  appends Caching section with config
                                      example and per-replica-cache-race
                                      caveat.

Signed-off-by: Michal Bien <mbien@nvidia.com>
@Glorf

Glorf commented Jun 22, 2026

Copy link
Copy Markdown
Owner Author

Superseded by NVIDIA-NeMo#1649 — moved upstream and rebased clean onto upstream feat/adapter-base. Fork discontinued.

@Glorf Glorf closed this Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant