Skip to content

refactor: replace aggregated model discovery shims with per-key live cache fanout via OnKeyAdded/Updated/Deleted - #4194

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
feat_wire_up_modelcatalog_composer
Jun 9, 2026
Merged

refactor: replace aggregated model discovery shims with per-key live cache fanout via OnKeyAdded/Updated/Deleted#4194
Pratham-Mishra04 merged 1 commit into
devfrom
feat_wire_up_modelcatalog_composer

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

Replaces the coarse provider-level model discovery (one aggregated live entry per provider) with per-key live cache entries, so adding, updating, or deleting a single key triggers at most 2 list-models calls for that key rather than 2×N calls across all keys. Removes the deprecated compatibility shims (UpsertModelDataForProvider, UpsertUnfilteredModelDataForProvider, DeleteModelDataForProvider) and replaces them with UpsertLiveFromResponse, OnKeyAdded, OnKeyUpdated, and OnKeyDeleted.

Changes

  • modelcatalog/pool.go: Added UpsertLiveFromResponse, which extracts and deduplicates model IDs from a BifrostListModelsResponse before writing to the live cache. A nil response is a no-op, preventing accidental cache eviction.
  • modelcatalog/shims.go: Deleted. The three deprecated shim methods are gone; call sites now use the per-key API directly.
  • modelcatalog/pool_test.go: New test file covering UpsertLiveFromResponse (nil no-op, happy path), extractModelIDs (prefix stripping, gateway nested prefixes, foreign prefix filtering, nil input, deduplication), InvalidateLive, and InvalidateLiveProvider.
  • server/server.go: Replaced populateModelPoolWithListModels (one aggregated entry per provider) with RefreshLiveModelsForProvider (fans out per key in parallel) and FetchAndStoreLiveForKey (issues filtered + unfiltered list-models for a single key). ReloadProvider now reads keys from the in-memory store, calls SetKeyConfigForProvider + InvalidateLiveProvider, then delegates to RefreshLiveModelsForProvider. ForceReloadPricing and ReloadPricingFromDBAndPopulateModelPool no longer trigger a full model pool refresh — pricing reload is now pricing-only. RemoveProvider calls InvalidateLiveProvider + RemoveKeyConfigForProvider instead of the deleted shim. Added OnKeyAdded, OnKeyUpdated, OnKeyDeleted to ServerCallbacks and implemented them on BifrostHTTPServer.
  • handlers/provider_keys.go: Key create/update/delete handlers now call modelsManager.OnKeyAdded/OnKeyUpdated/OnKeyDeleted instead of attemptModelDiscovery. Keyless providers skip the add/update path.
  • handlers/providers.go: Extended ModelsManager interface with OnKeyAdded, OnKeyUpdated, OnKeyDeleted.
  • handlers/providers_test.go and governance/httptransportprehook_test.go: Updated to use UpsertLiveFromResponse and NewTestCatalog(nil) instead of the removed shims and bare struct literals.
  • governance/resolver_test.go: Removed two tests that depended on the shim API; equivalent coverage exists in the catalog-level tests.
  • modelcatalog/models.go: Removed stale comment referencing the pre-refactor file.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/modelcatalog/...
go test ./transports/bifrost-http/...
go test ./plugins/governance/...
go test ./...

After adding a provider key via POST /api/providers/{provider}/keys, verify that only the new key's models are fetched (2 list-models calls) rather than a full provider refresh. After deleting a key, confirm the deleted key's live entries are evicted while other keys' entries remain intact.

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

UpsertModelDataForProvider, UpsertUnfilteredModelDataForProvider, and DeleteModelDataForProvider are removed from ModelCatalog. Any external code calling these methods must migrate to UpsertLiveFromResponse / UpsertLive / InvalidateLiveProvider. ServerCallbacks now requires OnKeyAdded, OnKeyUpdated, and OnKeyDeleted — implementors must add these three methods.

Related issues

N/A

Security considerations

No new auth surfaces. Key validation (BifrostContextKeyValidateKeys) is preserved in FetchAndStoreLiveForKey, maintaining the same key-validation behavior at boot, after key add, and after provider reload.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@Pratham-Mishra04, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 45 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01028477-fc08-40b0-9977-9d052c0cd828

📥 Commits

Reviewing files that changed from the base of the PR and between aa4a44c and 6d9fa38.

📒 Files selected for processing (9)
  • framework/modelcatalog/models.go
  • framework/modelcatalog/pool.go
  • framework/modelcatalog/pool_test.go
  • framework/modelcatalog/shims.go
  • plugins/governance/resolver_test.go
  • transports/bifrost-http/handlers/provider_keys.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/providers_test.go
  • transports/bifrost-http/server/server.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat_wire_up_modelcatalog_composer

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Pratham-Mishra04 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 3/5

The per-key fanout logic is sound, but the clear-before-fetch ordering in ReloadProvider can silently leave the live cache empty after a transient list-models failure, and FetchAndStoreLiveForKey goroutines access s.Config without a nil guard.

Two concrete defects in the hot path: ReloadProvider calls InvalidateLiveProvider unconditionally before spawning fetches so if any per-key fetch fails the live entries are gone and the function still returns nil hiding the degradation; FetchAndStoreLiveForKey goroutines dereference s.Config.ModelCatalog without a nil guard creating a latent panic path.

transports/bifrost-http/server/server.go — specifically the ReloadProvider invalidation ordering and FetchAndStoreLiveForKey nil safety.

Important Files Changed

Filename Overview
transports/bifrost-http/server/server.go Core refactor: replaces provider-level aggregated shim calls with per-key fanout. Two issues: InvalidateLiveProvider runs before fetches in ReloadProvider (empty cache on transient failure), and FetchAndStoreLiveForKey goroutines access s.Config without a nil guard.
framework/modelcatalog/pool.go Adds UpsertLiveFromResponse with nil-safe no-op guard and extractModelIDs helper; clean dedup and prefix-stripping logic.
framework/modelcatalog/pool_test.go New test file covering UpsertLiveFromResponse nil no-op, extractModelIDs prefix/dedup/nil, InvalidateLive, and InvalidateLiveProvider.
framework/modelcatalog/shims.go Deleted: removes the three deprecated shim methods as part of the refactor.
transports/bifrost-http/handlers/provider_keys.go Key create/update handlers correctly guard against keyless providers; delete handler relies on a pre-existing keyless guard so OnKeyDeleted is never reached for keyless providers.
transports/bifrost-http/handlers/providers.go Extends ModelsManager interface with OnKeyAdded/Updated/Deleted; minimal change, correctly plumbed.
plugins/governance/resolver_test.go Removes two resolver-level tests for wildcard allow-lists on catalog-opaque providers; pool_test.go does not cover the resolver's isModelAllowed behavior for opaque providers.
transports/bifrost-http/handlers/providers_test.go Updated mock stubs; replaces bare struct literals with NewTestCatalog(nil).
framework/modelcatalog/models.go Trivial comment cleanup only; no logic changes.

Comments Outside Diff (1)

  1. transports/bifrost-http/server/server.go, line 753-824 (link)

    P1 s.Config nil check is partial — goroutines access s.Config unconditionally

    The function guards the operation-allowed check with if s.Config != nil { … }, but both goroutines then call s.Config.ModelCatalog.UpsertLiveFromResponse(…) and s.Config.ConfigStore != nil without any outer nil guard on s.Config. If s.Config is nil when either goroutine runs, the server will panic. Every current caller verifies s.Config != nil first, but the asymmetric check makes the function unsafe to invoke directly in tests or future contexts. An early-return at the top of the function (if s.Config == nil || s.Config.ModelCatalog == nil { return }) would make the contract explicit and match the pattern used by OnKeyAdded, OnKeyUpdated, and OnKeyDeleted.

Reviews (4): Last reviewed commit: "feat: wire up modelcatalog composer" | Re-trigger Greptile

Comment thread plugins/governance/resolver_test.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the feat_wire_up_modelcatalog_composer branch from 74900ce to c46a122 Compare June 9, 2026 08:10
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/4194 to feat_adds_key_param_in_list_models June 9, 2026 08:11
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the feat_adds_key_param_in_list_models branch from 267d94f to 5952bf3 Compare June 9, 2026 09:55
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the feat_wire_up_modelcatalog_composer branch from c46a122 to 2a1f701 Compare June 9, 2026 09:55

Pratham-Mishra04 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Jun 9, 10:11 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 9, 10:58 AM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 9, 10:59 AM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from feat_adds_key_param_in_list_models to graphite-base/4194 June 9, 2026 10:54
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/4194 to dev June 9, 2026 10:56
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the feat_wire_up_modelcatalog_composer branch from 2a1f701 to 6d9fa38 Compare June 9, 2026 10:57
@Pratham-Mishra04
Pratham-Mishra04 merged commit c168fed into dev Jun 9, 2026
15 of 16 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the feat_wire_up_modelcatalog_composer branch June 9, 2026 10:59
Comment on lines +639 to 648
// Refresh keyconfig from the current key list, then drop any stale live
// entries (for keys removed in this update) before refetching per-key.
s.Config.ModelCatalog.SetKeyConfigForProvider(provider, inMemoryKeys)
s.Config.ModelCatalog.InvalidateLiveProvider(provider)
if hasNoKeys {
logger.Warn("model discovery skipped for provider %s: no keys configured", provider)
} else {
s.Config.ModelCatalog.UpsertUnfilteredModelDataForProvider(provider, unfilteredModels)
s.RefreshLiveModelsForProvider(ctx, provider, inMemoryKeys)
}
return updatedProvider, nil

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.

P1 Live cache cleared before fetch; transient failures leave it permanently empty

InvalidateLiveProvider clears all existing entries for the provider before RefreshLiveModelsForProvider runs. If any per-key FetchAndStoreLiveForKey call fails (errors are logged and swallowed, not propagated), the affected key's live entry is never written back. ReloadProvider then returns (updatedProvider, nil), so the caller has no signal that the cache is now empty. Until the next successful reload, GetModelsForProvider returns nothing for those keys, and routing silently degrades to the static catalog.

The old code wrote atomically (invalidate + upsert happened inside UpsertModelDataForProvider in a single shim call), so a failed list-models call still left a populated entry. A safer ordering would be to collect the per-key results first and only replace the live entries that were successfully refreshed — leave entries for keys whose fetch failed in place rather than pre-emptively clearing them.

akshaydeo pushed a commit that referenced this pull request Jun 12, 2026
…cache fanout via `OnKeyAdded/Updated/Deleted` (#4194)

## Summary

Replaces the coarse provider-level model discovery (one aggregated live entry per provider) with per-key live cache entries, so adding, updating, or deleting a single key triggers at most 2 list-models calls for that key rather than 2×N calls across all keys. Removes the deprecated compatibility shims (`UpsertModelDataForProvider`, `UpsertUnfilteredModelDataForProvider`, `DeleteModelDataForProvider`) and replaces them with `UpsertLiveFromResponse`, `OnKeyAdded`, `OnKeyUpdated`, and `OnKeyDeleted`.

## Changes

- **`modelcatalog/pool.go`**: Added `UpsertLiveFromResponse`, which extracts and deduplicates model IDs from a `BifrostListModelsResponse` before writing to the live cache. A nil response is a no-op, preventing accidental cache eviction.
- **`modelcatalog/shims.go`**: Deleted. The three deprecated shim methods are gone; call sites now use the per-key API directly.
- **`modelcatalog/pool_test.go`**: New test file covering `UpsertLiveFromResponse` (nil no-op, happy path), `extractModelIDs` (prefix stripping, gateway nested prefixes, foreign prefix filtering, nil input, deduplication), `InvalidateLive`, and `InvalidateLiveProvider`.
- **`server/server.go`**: Replaced `populateModelPoolWithListModels` (one aggregated entry per provider) with `RefreshLiveModelsForProvider` (fans out per key in parallel) and `FetchAndStoreLiveForKey` (issues filtered + unfiltered list-models for a single key). `ReloadProvider` now reads keys from the in-memory store, calls `SetKeyConfigForProvider` + `InvalidateLiveProvider`, then delegates to `RefreshLiveModelsForProvider`. `ForceReloadPricing` and `ReloadPricingFromDBAndPopulateModelPool` no longer trigger a full model pool refresh — pricing reload is now pricing-only. `RemoveProvider` calls `InvalidateLiveProvider` + `RemoveKeyConfigForProvider` instead of the deleted shim. Added `OnKeyAdded`, `OnKeyUpdated`, `OnKeyDeleted` to `ServerCallbacks` and implemented them on `BifrostHTTPServer`.
- **`handlers/provider_keys.go`**: Key create/update/delete handlers now call `modelsManager.OnKeyAdded/OnKeyUpdated/OnKeyDeleted` instead of `attemptModelDiscovery`. Keyless providers skip the add/update path.
- **`handlers/providers.go`**: Extended `ModelsManager` interface with `OnKeyAdded`, `OnKeyUpdated`, `OnKeyDeleted`.
- **`handlers/providers_test.go`** and **`governance/httptransportprehook_test.go`**: Updated to use `UpsertLiveFromResponse` and `NewTestCatalog(nil)` instead of the removed shims and bare struct literals.
- **`governance/resolver_test.go`**: Removed two tests that depended on the shim API; equivalent coverage exists in the catalog-level tests.
- **`modelcatalog/models.go`**: Removed stale comment referencing the pre-refactor file.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [x] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/modelcatalog/...
go test ./transports/bifrost-http/...
go test ./plugins/governance/...
go test ./...
```

After adding a provider key via `POST /api/providers/{provider}/keys`, verify that only the new key's models are fetched (2 list-models calls) rather than a full provider refresh. After deleting a key, confirm the deleted key's live entries are evicted while other keys' entries remain intact.

## Screenshots/Recordings

N/A

## Breaking changes

- [x] Yes
- [ ] No

`UpsertModelDataForProvider`, `UpsertUnfilteredModelDataForProvider`, and `DeleteModelDataForProvider` are removed from `ModelCatalog`. Any external code calling these methods must migrate to `UpsertLiveFromResponse` / `UpsertLive` / `InvalidateLiveProvider`. `ServerCallbacks` now requires `OnKeyAdded`, `OnKeyUpdated`, and `OnKeyDeleted` — implementors must add these three methods.

## Related issues

N/A

## Security considerations

No new auth surfaces. Key validation (`BifrostContextKeyValidateKeys`) is preserved in `FetchAndStoreLiveForKey`, maintaining the same key-validation behavior at boot, after key add, and after provider reload.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
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.

2 participants