Skip to content

Collapse the duplicate Azure Foundry provider data on memex.meshweaver.cloud — two nodes, plus a stale label the seeder will not overwrite #1880

Closed

Description

@sierragolflima

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:

  1. ArrangePickerGroups (line 2358) keys every group on IsPickerHeaderNode(n) ? n.Path : ParentPath(n.Path) — the node path, never content.provider.
  2. ParentPath (line 2346) takes everything before the last /.
  3. RemoveEmptyPickerGroups (line 2371) drops any ModelProvider header whose path no model's ParentPath matches.
  4. The header text is the ModelProvider node's Name (ThreadChatView.razor line 904); the uppercasing is CSS (text-transform: uppercase), so the stored names really are Azure AI Foundry and AzureFoundry.

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:

  1. The model never groups under its provider.
  2. RemoveEmptyPickerGroups deletes the OPENROUTER header, because no model's ParentPath equals Provider/OpenRouter.
  3. 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):

  1. Defect 2 — group by the node's Namespace instead of ParentPath(node.Path). Namespace already holds the correct provider path on every node, including slash-containing ids, since Path is derived as {Namespace}/{Id}.
  2. Defect 3 — use source.EffectiveLabel for the emitted node's Name and content.label. The node id deliberately stays ProviderName, because that is the path every ModelDefinition.ProviderRef points 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.cloud has one Azure Foundry provider node. (data remediation — open)

Investigated read-only against memex.meshweaver.cloud; nothing on that portal was modified.

Activity

  1. sierragolflima commented on Aug 19, 2026

    @sierragolflima
    CollaboratorAuthor
    Image
  2. rbuergi commented on Aug 19, 2026

    @rbuergi
    Contributor

    Status check: defects 2 and 3 are already fixed on main

    Picking 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:

    $ git merge-base --is-ancestor 27793985d1f06bc5d8d603822ede81ccd47a69ea origin/main && echo "in main"
    in main
    

    I verified both fixes are real rather than nominal, by neutering them on top of main while keeping the tests, then restoring:

    grouping key back to ParentPath(node.Path) / label back to ProviderName as shipped on main
    MeshWeaver.Acme.Test.ModelPickerGroupingTest 3 failed, 2 passed 5 passed
    MeshWeaver.AI.Test.CatalogProviderLabelTest 1 failed, 2 passed 3 passed

    The decisive failures reproduce the reported symptoms exactly:

    SlashIdModels_AreNotAbsorbedIntoThePrecedingProviderGroup
      Expected: "OpenRouter"
      Actual:   "Azure Foundry"
    
    ModelIdContainingSlash_KeepsItsProviderHeader
      Expected: "OpenRouter"
      Actual:   null            ← the OPENROUTER header had been deleted outright
    
    SeededProviderNode_UsesTheDeclaredDisplayLabel
      Expected: "Azure Foundry"
      Actual:   "AzureFoundry"
    

    So acceptance criteria 1–3 are met in code, and the tests guarding them are non-vacuous.

    What is still open

    • Defect 1 — two ModelProvider nodes 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 is BuiltInLanguageModelProvider.GetStaticNodes(), a pure static-node enumeration with no mesh access, so it cannot ask whether a node with the same content.provider already 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 (the instanceId handling in ModelProviderService suggests yes, in which case grouping must stay path-based and only the redundant seeding is the defect).
    • The data on 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.

  3. rbuergi commented on Aug 19, 2026

    @rbuergi
    Contributor

    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 main while keeping its tests turns 4 red —

    Expected "OpenRouter"     / Actual "Azure Foundry"
    Expected <non-null>       / Actual: null
    Expected "Azure Foundry"  / Actual "AzureFoundry"
    

    — 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.provider already 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.

  4. sierragolflima commented on Aug 20, 2026

    @sierragolflima
    CollaboratorAuthor

    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.cloud just now (read-only):

    path Name content.label models author ver
    Provider/Azure Azure AI Foundry Azure AI Foundry DeepSeek-V4-Pro, DeepSeek-V4-Flash rbuergi 2
    Provider/AzureFoundry AzureFoundry AzureFoundry DeepSeek-V4-Pro, DeepSeek-V3-0324, DeepSeek-V4-Flash, Kimi-K2.6 system-security 1

    Both carry the same content.provider (AzureFoundry), the same endpoint, and the same apiKey.

    The seeded node's model list is a strict superset of the admin-authored one, so collapsing loses no model — Provider/Azure contributes nothing except its better display name.

    🚨 The merged label fix will NOT repair this deployment

    Provider/AzureFoundry carries syncBehavior: ExcludeThisAndChildren, and the emitter says why in its own comment:

    // create-if-absent: importer seeds it once, then admin owns it.
    SyncBehavior = SyncBehavior.ExcludeThisAndChildren,

    src/MeshWeaver.AI/BuiltInLanguageModelProvider.cs

    So #1881's EffectiveLabel fix 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 keep Name = "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

    1. Delete Provider/Azure. Redundant: same provider, same endpoint, same key, and its two models already exist under Provider/AzureFoundry.
    2. Set Provider/AzureFoundry's Name and content.label to Azure 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.cloud was modified.)

  5. changed the title [-]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[/+] on Aug 20, 2026
  6. rbuergi commented on Aug 26, 2026

    @rbuergi
    Contributor

    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 main turned the guarding tests red with the exact reported symptoms).

    Update since that comment: the grouping mechanism this issue investigated
    (ThreadChatView.razor.cs's ArrangePickerGroups/ParentPath/RemoveEmptyPickerGroups, all keyed
    on 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 #1881
    added) 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 the
    single source of truth the chat view calls directly. It groups by ModelDefinition.Provider (a
    plain 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 any
    test. 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.EffectiveLabel in BuiltInLanguageModelProvider.cs) is untouched and still in place.

    Defect 1 (duplicate Provider/Azure / Provider/AzureFoundry) — data, now resolved

    Read live via MCP just now:

    search nodeType:ModelProvider  →  Provider/Auto, Provider/Anthropic, Provider/OpenAI,
                                       Provider/AzureOpenAI, Provider/OpenAICompatible,
                                       Provider/OpenRouter, Provider/AzureFoundry
                                       (+ two unrelated per-user/partition provider nodes)
    

    No Provider/Azure — it's gone. get Provider/AzureFoundry shows name: "Azure Foundry" and
    content.label: "Azure Foundry" (not the old jammed-together "AzureFoundry"). Both data edits this
    issue's last comment asked for (delete Provider/Azure; relabel Provider/AzureFoundry) have been
    applied.

    Acceptance criteria

    • A model whose id contains / renders under its own provider's header — code, structural now
    • The platform-seeded Azure Foundry group is titled AZURE FOUNDRY — code + confirmed live
    • Regression test covering a vendor/model-style id — AgentPickerModelOrderingTest.cs (the
      original test moved out with the Blazor GUI; this is its replacement against the current
      mechanism)
    • No model id appears twice for one configured upstream provider — confirmed live (single
      Provider/AzureFoundry)
    • memex.meshweaver.cloud has one Azure Foundry provider node — confirmed live

    All five met. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions