Repository navigation
Description
Summary
In the AI chat "Choose a model" dialog on memex.meshweaver.cloud, the Azure Foundry models appear twice under two differently-titled headers — AZURE AI FOUNDRY (2 models) and AZUREFOUNDRY (6 models) — with DeepSeek V4 Flash and DeepSeek V4 Pro present in both. Two further models (Kimi K3, GLM 5.2) render inside the AZUREFOUNDRY block although they belong to OpenRouter, and no OPENROUTER header appears at all.
This is three separate defects that compound into one confusing dialog. Only defect 1 is data-dependent; defects 2 and 3 are pure code defects reproducible anywhere.
Observed vs. expected
| Observed | Expected | |
|---|---|---|
| Azure Foundry | two groups, 2 overlapping models | one group |
| Seeded group title | AZUREFOUNDRY |
AZURE FOUNDRY |
| OpenRouter models | listed under AZUREFOUNDRY, no own header |
listed under OPENROUTER |
How the picker groups
Verified in src/MeshWeaver.Blazor.Portal/Chat/ThreadChatView.razor.cs:
ArrangePickerGroups(line 2358) keys every group onIsPickerHeaderNode(n) ? n.Path : ParentPath(n.Path)— the node path, nevercontent.provider.ParentPath(line 2346) takes everything before the last/.RemoveEmptyPickerGroups(line 2371) drops anyModelProviderheader whose path no model'sParentPathmatches.- The header text is the
ModelProvidernode'sName(ThreadChatView.razorline 904); the uppercasing is CSS (text-transform: uppercase), so the stored names really areAzure AI FoundryandAzureFoundry.
Defect 1 — two ModelProvider nodes describe the same upstream provider
Both nodes below carry the same content.provider (AzureFoundry), the same endpoint and the same API key, but sit at different paths, so they render as two groups:
| Path | Name |
content.label |
models |
Author | Modified (Zurich) |
|---|---|---|---|---|---|
Provider/Azure |
Azure AI Foundry |
Azure AI Foundry |
2 | rbuergi |
2026-08-03 20:30 |
Provider/AzureFoundry |
AzureFoundry |
AzureFoundry |
4 | system-security |
2026-08-04 12:09 |
The overlap is exact: Provider/Azure/DeepseekV4Flash and Provider/AzureFoundry/DeepSeek-V4-Flash both resolve to model id DeepSeek-V4-Flash on the same endpoint (same for -V4-Pro).
Root cause. BuiltInLanguageModelProvider.GetStaticNodes() emits the platform provider node at Provider/{source.ProviderName} (src/MeshWeaver.AI/BuiltInLanguageModelProvider.cs:156) and relies on SyncBehavior.ExcludeThisAndChildren (line 167) for create-if-absent idempotency. That idempotency is keyed on the path. An administrator-authored provider node for the same upstream provider at any other path is invisible to the check, so the next boot seeded a second, redundant node. Nothing in the pipeline de-duplicates on provider identity, and the picker has no cross-provider de-duplication either.
The timestamps are consistent with an admin curating Provider/Azure on 2026-08-03, followed by the seeder adding Provider/AzureFoundry on the next deployment.
Defect 2 — a model whose id contains / loses its group header
Provider/OpenRouter/z-ai/glm-5.2 has namespace = Provider/OpenRouter and content.providerRef = Provider/OpenRouter, both correct. But because the model id is z-ai/glm-5.2, its node path gains a segment, so ParentPath(path) yields Provider/OpenRouter/z-ai — which matches no ModelProvider node. Consequences:
- The model never groups under its provider.
RemoveEmptyPickerGroupsdeletes theOPENROUTERheader, because no model'sParentPathequalsProvider/OpenRouter.- The orphaned rows sort ordinally right after
Provider/AzureFoundry, so they render as bare rows appended to that block.
This reproduces the screenshot exactly: group ordering Provider/Azure → Provider/AzureFoundry → Provider/OpenRouter/moonshotai (Kimi K3) → Provider/OpenRouter/z-ai (GLM 5.2), and 4 real AzureFoundry children + 2 orphans = the 6 rows shown under AZUREFOUNDRY.
Defect 3 — the seeded provider node ignores its declared DisplayLabel
LanguageModelCatalogSource declares DisplayLabel: "Azure Foundry" (src/MeshWeaver.AI.AzureFoundry/AzureFoundryProvidersAttribute.cs:46) and exposes EffectiveLabel (BuiltInLanguageModelProvider.cs:445). The node emitter never used it — it set Label = source.ProviderName (line 144) and Name = source.ProviderName (line 159). EffectiveLabel was consumed only by memex/Memex.Portal.Shared/Models/ModelProviderService.cs:144. Hence the jammed-together AZUREFOUNDRY title.
Why it does not reproduce on the local portal
The local portal at https://memex.localhost:8443 has neither precondition: there is no Provider/Azure node (only the seeded Provider/AzureFoundry), and its Provider/OpenRouter node has no children, so no model id contains a /.
Fixes
Defects 2 and 3 are fixed in the linked pull request (test-first; the tests were confirmed red against the unfixed code, one failing with the exact reported symptom — expected header OpenRouter, actual Azure Foundry):
- Defect 2 — group by the node's
Namespaceinstead ofParentPath(node.Path).Namespacealready holds the correct provider path on every node, including slash-containing ids, sincePathis derived as{Namespace}/{Id}. - Defect 3 — use
source.EffectiveLabelfor the emitted node'sNameandcontent.label. The node id deliberately staysProviderName, because that is the path everyModelDefinition.ProviderRefpoints at.
Defect 1 is left open here — it needs a maintainer decision. The seeder's create-if-absent should key on provider identity rather than node path, i.e. not emit Provider/{ProviderName} when a ModelProvider node for the same content.provider already exists. That depends on whether multiple instances of one provider type (two Azure Foundry resources with different keys) is a supported configuration; ModelProviderService's instanceId handling suggests it is, in which case grouping must stay path-based and only the redundant seeding is the defect.
Separately, the duplicate on memex.meshweaver.cloud is existing data and needs an administrator to collapse the two Azure Foundry provider nodes into one. No code change removes it.
Acceptance criteria
- A model whose id contains
/renders under its own provider's header. (PR) - The platform-seeded Azure Foundry group is titled
AZURE FOUNDRY. (PR) - Regression test covering a provider with a
vendor/model-style id. (PR) - No model id appears twice in the picker for a single configured upstream provider. (defect 1 — open)
-
memex.meshweaver.cloudhas one Azure Foundry provider node. (data remediation — open)
Investigated read-only against memex.meshweaver.cloud; nothing on that portal was modified.
Activity
sierragolflima commentedon Aug 19, 2026
rbuergi commentedon Aug 19, 2026
Status check: defects 2 and 3 are already fixed on
mainPicking this up today, so recording what is and is not left — the issue's "Fixes" section describes work that has since merged.
PR #1881 ("fix(ai): group each model under its own provider in the model picker") merged 2026-08-19T11:35:21Z and is an ancestor of current
origin/main:I verified both fixes are real rather than nominal, by neutering them on top of
mainwhile keeping the tests, then restoring:ParentPath(node.Path)/ label back toProviderNamemainMeshWeaver.Acme.Test.ModelPickerGroupingTestMeshWeaver.AI.Test.CatalogProviderLabelTestThe decisive failures reproduce the reported symptoms exactly:
So acceptance criteria 1–3 are met in code, and the tests guarding them are non-vacuous.
What is still open
ModelProvidernodes describing the same upstream provider. Still open and still a maintainer decision, as the issue says. Worth noting why no drive-by fix is available: the seeder isBuiltInLanguageModelProvider.GetStaticNodes(), a pure static-node enumeration with no mesh access, so it cannot ask whether a node with the samecontent.provideralready exists at some other path. De-duplicating on provider identity is not a tweak to that method — it needs the check to move somewhere that can query, and that only makes sense once "are multiple instances of one provider type supported?" is answered (theinstanceIdhandling inModelProviderServicesuggests yes, in which case grouping must stay path-based and only the redundant seeding is the defect).memex.meshweaver.cloud— still two Azure Foundry provider nodes; no code change removes it.Both remaining items are exactly as this issue already describes them. Nothing new to fix in the picker.
rbuergi commentedon Aug 19, 2026
Scope update: the code defects are fixed — only the duplicate provider DATA remains
#1881 merged today at 11:35Z and is an ancestor of
origin/main; the "Fixes" section of this issue describes work that has since landed.Confirmed it is not merely nominal: neutering the fix on top of
mainwhile keeping its tests turns 4 red —— with 4 controls staying green; restoring it gives 8 green.
What remains, and why it has no code fix
Only defect 1 — duplicate provider data. The seeder is a static-node enumeration with no mesh access, so it cannot check whether a node carrying the same
content.provideralready exists elsewhere. That is a data/authoring problem, not a rendering one, and fixing it means either de-duplicating the seeded nodes or giving the seeder a mesh read it deliberately does not have.Suggest retitling to name the duplicate-data half alone, since the "mislabelled provider groups" part is done.
sierragolflima commentedon Aug 20, 2026
The remaining half is TWO data edits, not one — the label fix cannot repair this portal
Confirming @rbuergi's scope update, and adding one finding that changes what "done" looks like here.
Both nodes are still live, and one is a strict superset of the other
Read from
memex.meshweaver.cloudjust now (read-only):Namecontent.labelmodelsProvider/AzureAzure AI FoundryAzure AI FoundryDeepSeek-V4-Pro,DeepSeek-V4-FlashrbuergiProvider/AzureFoundryAzureFoundryAzureFoundryDeepSeek-V4-Pro,DeepSeek-V3-0324,DeepSeek-V4-Flash,Kimi-K2.6system-securityBoth carry the same
content.provider(AzureFoundry), the sameendpoint, and the sameapiKey.The seeded node's model list is a strict superset of the admin-authored one, so collapsing loses no model —
Provider/Azurecontributes nothing except its better display name.🚨 The merged label fix will NOT repair this deployment
Provider/AzureFoundrycarriessyncBehavior: ExcludeThisAndChildren, and the emitter says why in its own comment:src/MeshWeaver.AI/BuiltInLanguageModelProvider.csSo #1881's
EffectiveLabelfix applies to deployments where the node does not yet exist. On this portal the node was created 2026-08-04 (v1, never edited) and will keepName = "AzureFoundry"indefinitely.Consequence for closing this issue: acceptance criterion 2 ("seeded group title reads
AZURE FOUNDRY") is satisfied in code but will still look unmet in the UI after #1881 deploys. Anyone re-testing the picker would reasonably reopen this. The criterion needs to be read as "for newly-seeded deployments", with the existing node treated as data to repair.What an administrator needs to do — two edits
Provider/Azure. Redundant: same provider, same endpoint, same key, and its two models already exist underProvider/AzureFoundry.Provider/AzureFoundry'sNameandcontent.labeltoAzure Foundry. No code change does this, per the above.Edit 1 alone removes the duplicate group; edit 2 is what actually makes the header read
AZURE FOUNDRY. Doing only the first leaves the jammed-together title in place and the issue looking half-fixed.Side benefit worth naming
The same API key is currently stored in two nodes. Collapsing to one also removes a rotation hazard — otherwise a key rotation has to be applied twice, and the second copy is easy to miss.
On the retitle
Agreed, with one adjustment: "duplicate data" alone undersells it, because the stale label is also data that no code change repairs. Retitled to name both halves.
(Read-only investigation — nothing on
memex.meshweaver.cloudwas modified.)[-]AI chat model picker renders duplicate and mislabelled provider groups[/-][+]Collapse the duplicate Azure Foundry provider data on memex.meshweaver.cloud — two nodes, plus a stale label the seeder will not overwrite[/+]rbuergi commentedon Aug 26, 2026
All three defects verified resolved — closing
Picked this up while working #2331 (a related but distinct picker-ordering bug). Re-verified every
acceptance criterion from scratch, live.
Defect 2 (slash-id loses its provider header) and defect 3 (stale seeded label) — code, already merged
Both were fixed in #1881 (merged 2026-08-19), confirmed non-vacuous in this issue's own prior comment
(neutering the fix on
mainturned the guarding tests red with the exact reported symptoms).Update since that comment: the grouping mechanism this issue investigated
(
ThreadChatView.razor.cs'sArrangePickerGroups/ParentPath/RemoveEmptyPickerGroups, all keyedon the node path) no longer exists in this repo — it left with #2293 ("Delete Blazor and the portal
GUI from core"), and
test/MeshWeaver.Acme.Test/ModelPickerGroupingTest.cs(the regression test #1881added) was deleted as collateral of that same move.
The picker's grouping is now
AgentPickerProjection.ProjectModels(
src/MeshWeaver.AI/AgentPickerProjection.cs) — the file's own doc comment identifies it as thesingle source of truth the chat view calls directly. It groups by
ModelDefinition.Provider(aplain string field), never by splitting the node path — so defect 2's actual failure mode (an id
containing
/gaining a spurious path segment) cannot recur by construction, independent of anytest. I added coverage for exactly this shape against the current mechanism —
test/MeshWeaver.AI.Test/AgentPickerModelOrderingTest.cs— including slash-containing ids(
z-ai/glm-5.3,moonshotai/kimi-k3) grouping correctly under their own provider. Defect 3's fix(
Name = source.EffectiveLabelinBuiltInLanguageModelProvider.cs) is untouched and still in place.Defect 1 (duplicate
Provider/Azure/Provider/AzureFoundry) — data, now resolvedRead live via MCP just now:
No
Provider/Azure— it's gone.get Provider/AzureFoundryshowsname: "Azure Foundry"andcontent.label: "Azure Foundry"(not the old jammed-together"AzureFoundry"). Both data edits thisissue's last comment asked for (delete
Provider/Azure; relabelProvider/AzureFoundry) have beenapplied.
Acceptance criteria
/renders under its own provider's header — code, structural nowAZURE FOUNDRY— code + confirmed livevendor/model-style id —AgentPickerModelOrderingTest.cs(theoriginal test moved out with the Blazor GUI; this is its replacement against the current
mechanism)
Provider/AzureFoundry)memex.meshweaver.cloudhas one Azure Foundry provider node — confirmed liveAll five met. Closing.