Skip to content

feat(modelnormalizer): gateway-edge middleware with declarative model alias rules - #33

Closed
weselben wants to merge 1 commit into
mainfrom
proto/kimicode-creative
Closed

feat(modelnormalizer): gateway-edge middleware with declarative model alias rules#33
weselben wants to merge 1 commit into
mainfrom
proto/kimicode-creative

Conversation

@weselben

Copy link
Copy Markdown
Owner

Prototype D of 4 for #27 — a gateway-edge middleware approach. Compare: prototype A (provider-level static map + KIMICODE_MODEL_MAP=off toggle, PR #30), prototype B (provider-level map always-on, PR #29), prototype C (virtual-models + disable_reasoning, PR #31).

Approach

A data-driven normalizer that runs as an Echo middleware before workflow resolution. Operators declare aliases in config.yaml (or MODEL_NORMALIZER env), and the normalizer rewrites the request body so every downstream stage (resolver, failover, budgets, cache) sees the canonical upstream target. No provider package is touched — works for kimicode and any future subscription provider.

model_normalizer:
  - alias: kimi-k2.6
    target: kimicode/kimi-for-coding
    thinking: disabled
    modes: [chat]
    context_window: 262144
  - alias: kimi-k2.7-code
    target: kimicode/kimi-for-coding
    thinking: enabled
    modes: [chat]
    context_window: 262144
  - alias: bge_m3_embed
    target: kimicode/bge_m3_embed
    modes: [embedding]

What's wired

  • internal/modelnormalizer — Rule + Normalizer (AdaptChatRequest) + config loading (config/modelnormalizer.go) + MODEL_NORMALIZER env binding
  • internal/server/model_normalizer_middleware.go — Echo middleware reading the body, calling the normalizer, rewriting the body, and stamping an audit revision when the audit log is enabled
  • internal/server/http.go — registers the middleware via e.Use and merges synthesized /v1/models entries (via ChainedExposedModelLister when another lister exists)
  • internal/app/app.go — builds the normalizer from config and passes it through

Trade-offs vs A/B/C

Aspect A/B (provider-level map) C (virtual-models + flag) D (this)
Code touched kimicode package only virtualmodels + stores server middleware + new modelnormalizer package
Default-on yes operator-only operator-only (yaml)
Metadata for aliases yes (auto from registry) no yes (declared per rule)
Thinking-pin per alias yes yes (disable_reasoning) yes (thinking: enabled/disabled/passthrough)
Reusable across providers kimicode only any redirect target any provider (provider-agnostic)
Token/cost tracking records requested alias records resolved target records the rewritten upstream model — see caveat

Caveats

  • Token/cost tracking records the rewritten upstream model (e.g. kimicode/kimi-for-coding) because the rewrite happens before routing. The canonical alias is preserved on the audit entry via the request-revision snapshot the middleware stamps, so dashboards can still see the client's original model.
  • Streaming: the middleware rewrites the body buffer so it applies uniformly to chat and responses; streaming bodies are unaffected because the rewrite runs before dispatch.
  • The middleware is fail-closed: a normalizer error aborts the request with HTTP 400. A nil normalizer is a no-op.

Tests

  • go build ./... — pass
  • go test ./internal/modelnormalizer/... ./internal/server/... ./config/... — pass:
    • internal/modelnormalizer/config_test.go — rule parsing, env layering, validation
    • internal/modelnormalizer/normalizer_test.go — adapt per rule, unknown IDs pass through, idempotence
    • internal/server/modelnormalizer_server_test.go — chat-completion rewrites the model and injects thinking, unknown aliases pass through, /v1/models merges synthesized entries with metadata, nil normalizer leaves the handler unchanged

Refs #27

…es from declarative rules

Adds a data-driven normalizer that rewrites chat requests before
provider dispatch. Operators declare aliases in a new top-level
`model_normalizer:` section (with a JSON `MODEL_NORMALIZER` env var);
the normalizer rewrites the request `model` to a provider-qualified
target and injects a per-alias thinking policy. Runs as an Echo
middleware before workflow resolution so resolvers, failovers, budgets,
and caching all see the rewritten request. No provider package is
touched — works for kimicode and any future subscription provider.

Rule schema:
  - alias: kimi-k2.6
    target: kimicode/kimi-for-coding
    thinking: disabled
    modes: [chat]
    context_window: 262144

/v1/models merges synthesized entries for each alias with the
advertised metadata; layers on top of any existing ExposedModelLister
via ChainedExposedModelLister.

Refs #27
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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