Skip to content

fix(server): stop silently dropping duplicate target model ids - #180

Open
elyasmnvidian wants to merge 1 commit into
mainfrom
emehtabuddin/fix-duplicate-target-diagnostic
Open

fix(server): stop silently dropping duplicate target model ids#180
elyasmnvidian wants to merge 1 commit into
mainfrom
emehtabuddin/fix-duplicate-target-diagnostic

Conversation

@elyasmnvidian

@elyasmnvidian elyasmnvidian commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Two targets accidentally share one model id on one llm client:

[llm_clients.primary]
format = "openai_chat"
base_url = "https://api.openai.com/v1"

[targets.priority]
id = "gpt-4o"
llm_client = "primary"
extra_body = { service_tier = "priority" }

[targets.flex]
id = "gpt-4o"
llm_client = "primary"
extra_body = { service_tier = "flex" }

[routes.priority]
id = "switchyard/priority"
type = "passthrough"
target = "priority"

[routes.flex]
id = "switchyard/flex"
type = "passthrough"
target = "flex"

The server stores each client's models by id, so it keeps one target and drops the other. Both routes start, but one service_tier is gone and nothing says so — every request to switchyard/flex now sends priority:

$ switchyard-server --config deploy.toml --dry-run
server OK: switchyard/priority, switchyard/flex     # flex's service_tier was dropped, no message

The fix

switchyard-server now checks the targets on each llm client before it starts.

  • Same id, different extra_body — dropping one target loses real config, so the server won't start. It prints an error naming both targets and how to fix it.
  • Same id, otherwise the same — dropping one loses nothing, so the server warns and serves both routes (one model under two route names, e.g. a benchmark A/B).
  • Same id on two different llm clients — each client keeps its own model, so nothing is dropped. This still works with no warning; it's how you serve one model through two providers or two API keys.

Evidence

The broken config above now stops at startup, and a fixed config still starts:

$ switchyard-server --config deploy.toml --dry-run
invalid server config deploy.toml: targets flex and priority both use model id gpt-4o on llm client primary but set different extra_body; only one would take effect. Give them different model ids, or point both routes at one target.

$ switchyard-server --config deploy-fixed.toml --dry-run     # distinct ids, or both routes on one target
server OK: switchyard/priority, switchyard/flex

No perf table — this change is about which configs start, not speed.

Compatibility

A config with two same-id targets and different extra_body on one client used to start and drop one silently; it now stops at startup. To fix it, give the targets different ids, or point both routes at one target. The same id across two clients, and same-id targets that are otherwise the same, keep starting as before.

Honest trade-off: two same-id targets that are otherwise the same still start with a warning, so a typo that repeats a model id you already serve is reported but not blocked. Rejecting every same-client duplicate was the alternative — set aside so a deliberate "one model, two route names" setup keeps working without edits. The deeper limit: the client stores models by id alone, so it can't hold two extra_body values under one id — serving two service tiers on one key isn't supported yet, and this change makes that stop loudly instead of dropping one silently.

How tested

cargo test -p switchyard-server passes. Three config tests pin the behavior:

  • rejects_duplicate_target_ids_with_different_extra_body — same id and client, different extra_body; the server stops. Fails on the old code, so it pins the fix.
  • accepts_duplicate_target_model_ids_on_one_client — two same-id targets, otherwise the same; the server warns and both routes still resolve.
  • accepts_same_model_id_on_different_llm_clients — same id on two clients; starts with no warning.

cargo clippy -p switchyard-server --all-targets -- -D warnings and cargo fmt --all --check are clean.

Reproducer:

cargo build -p switchyard-server
./target/debug/switchyard-server --config deploy.toml --dry-run     # then deploy-fixed.toml

@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/fix-duplicate-target-diagnostic branch from 27ad0ce to ec76568 Compare July 29, 2026 18:11
@elyasmnvidian
elyasmnvidian marked this pull request as ready for review July 29, 2026 18:11
@elyasmnvidian
elyasmnvidian requested a review from a team as a code owner July 29, 2026 18:11
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

ServerConfig::build now validates target fields and rejects duplicate (llm_client, target.id) pairs. Tests cover rejection on one client and acceptance across different clients.

Target configuration validation

Layer / File(s) Summary
Centralized target validation
crates/switchyard-server/src/config.rs
Target name and model ID validation moved into ServerConfig::build, which also detects duplicate client-target pairs before client construction.
Duplicate and cross-client coverage
crates/switchyard-server/src/config.rs
Tests verify precise duplicate errors and allow identical model IDs across different LLM clients.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

I’m a rabbit checking targets bright,
No duplicate models sneak from sight.
One client, one pair, kept neat,
Across clients, repeats are sweet.
Hop, hop—tests make configs complete!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes preventing duplicate target model IDs from being silently dropped, matching the pull request's primary change.

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

Comment thread crates/switchyard-server/src/config.rs Outdated
// Each llm client keys its models by id, so two targets that share an id and an
// llm client would silently overwrite each other (last one wins) instead of failing.
// The same id on two different clients is allowed (one model served by two providers).
let mut target_names_by_client_id = BTreeMap::new();

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.

Do you want HashSet instead?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anthropic models love the BTreeMap. Opus 4.8 tried using one for me earlier where it did not seem to make sense.

@elyasmnvidian elyasmnvidian Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to HashMap

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.

I meant Set. Don't you only need to know if it exists already or not? i.e you only need Key, not Key/Value.

@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/fix-duplicate-target-diagnostic branch from ec76568 to 8d679ec Compare July 30, 2026 06:21
@elyasmnvidian elyasmnvidian changed the title fix(server): reject duplicate target model ids fix(server): warn on duplicate target model ids Jul 30, 2026
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/fix-duplicate-target-diagnostic branch from 8d679ec to 2444823 Compare July 30, 2026 16:58
@elyasmnvidian elyasmnvidian changed the title fix(server): warn on duplicate target model ids fix(server): reject conflicting duplicate target model ids Jul 30, 2026
@elyasmnvidian elyasmnvidian changed the title fix(server): reject conflicting duplicate target model ids fix(server): stop silently dropping duplicate target model ids Jul 31, 2026
validate_value(&format!("target {target_name} id"), &target.id)?;
if let Some((first_name, first_target)) = targets_by_client_id.insert(
(target.llm_client.as_str(), target.id.as_str()),
(target_name.as_str(), target),

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.

The HashMap<(&str, &str), (&str, &TargetConfig)> with target four way is awkward. Can we clean that up?

Ideally we'd have a single HashSet that says whether we've seen this before or not.

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.

3 participants