Skip to content

feat: Add model alias resolution and provider-based prefix mapping for LiteLLM#313

Closed
weidonglian wants to merge 5 commits intoHKUDS:mainfrom
weidonglian:resolve-model-names
Closed

feat: Add model alias resolution and provider-based prefix mapping for LiteLLM#313
weidonglian wants to merge 5 commits intoHKUDS:mainfrom
weidonglian:resolve-model-names

Conversation

@weidonglian
Copy link
Copy Markdown

@weidonglian weidonglian commented Feb 7, 2026

Summary

This PR adds model alias support with automatic provider prefixing. Users can now define short, memorable names for complex model identifiers. The provider prefix is automatically added based on which provider section the alias is configured under - no more guessing from URLs or model names.

The Problem

The old approach had several issues:

  • Models like z-ai/glm4.7 (NVIDIA NIM) failed because "zai" substring incorrectly matched Zhipu provider
  • URL-based detection was unreliable and non-deterministic
  • Adding new custom endpoints required updating heuristic detection logic

The Solution

Automatic provider prefixing based on config structure:

  1. User configures alias WITHOUT prefix: "nvgm": "z-ai/glm4.7" under providers.vllm
  2. resolve_model() detects it's under vllm provider
  3. Automatically adds hosted_vllm/ prefix → hosted_vllm/z-ai/glm4.7
  4. is_resolved=True tells LiteLLMProvider to skip all prefixing (model already has prefix)

Why this works:

  • Deterministic: Provider determined by config structure, not URLs
  • No confusion: No URL parsing, no substring matching
  • Simple: User just specifies the model, prefix added automatically
  • Easy to extend: Add new provider = add one entry to provider map
  • No breaking changes: All existing configs work unchanged

Changes

1. Model Alias Configuration (nanobot/config/schema.py)

Added models field to ProviderConfig:

class ProviderConfig(BaseModel):
    models: dict[str, str] = Field(default_factory=dict)  # alias -> actual_model

Added resolve_model() that returns resolved model with prefix:

def resolve_model(self, model: str) -> tuple[str, ProviderConfig | None]:
    """Resolve model alias to actual model with provider prefix.

    Returns:
        (resolved_model, provider_config) tuple.
        If alias found, returns (model_with_prefix, provider_config).
        If alias not found, returns (model, None).

    Note:
        The alias config should NOT include the provider prefix.
        This function adds the prefix based on which provider the alias came from.
    """
    # Provider map: (provider_name, provider_config, prefix)
    provider_map = [
        ("vllm", p.vllm, "hosted_vllm"),
        ("openrouter", p.openrouter, "openrouter"),
        ("aihubmix", p.aihubmix, "openai"),
        # ... other providers
    ]
    for provider_name, provider_config, prefix in provider_map:
        if model in provider_config.models:
            actual_model = provider_config.models[model]
            # Add provider prefix if not already present
            if not actual_model.startswith(f"{prefix}/"):
                actual_model = f"{prefix}/{actual_model}"
            return actual_model, provider_config
    return model, None

2. Simplified Provider Logic (nanobot/providers/litellm_provider.py)

Renamed parameter from provider_name to is_resolved:

def __init__(
    self,
    api_key: str | None = None,
    api_base: str | None = None,
    default_model: str = "anthropic/claude-opus-4-5",
    extra_headers: dict[str, str] | None = None,
    is_resolved: bool = False,  # True if from alias (has prefix), False if direct model
):

Simplified chat() prefixing logic:

# Skip all prefixing for resolved models (already have prefix)
if not self.is_resolved:
    # Keyword-based prefixing
    # Detection-based prefixing (api_base, etc.)

3. CLI Integration (nanobot/cli/commands.py)

Updated to use 2-tuple and is_resolved:

actual_model, provider_config = config.resolve_model(model)
if provider_config:
    p = provider_config
    model = actual_model
    is_resolved = True
else:
    p = config.get_provider()
    is_resolved = False

LiteLLMProvider(..., is_resolved=is_resolved)

4. Documentation (README.md)

Added model aliases documentation with examples.

Usage Example: NVIDIA NIM

Config (prefix added automatically):

{
  "providers": {
    "vllm": {
      "apiKey": "nvapi-xxx",
      "apiBase": "https://integrate.api.nvidia.com/v1",
      "models": {
        "nvgm": "z-ai/glm4.7",
        "llama": "meta-llama/Llama-3.1-8B-Instruct",
        "mistral": "mistralai/Mistral-7B-Instruct-v0.3"
      }
    }
  },
  "agents": {
    "defaults": { "model": "nvgm" }
  }
}

Flow:

  1. User specifies "model": "nvgm"
  2. resolve_model("nvgm") → found in providers.vllm.models
  3. Adds hosted_vllm/ prefix → "hosted_vllm/z-ai/glm4.7"
  4. is_resolved=True → LiteLLMProvider skips all prefixing
  5. ✅ Final model: hosted_vllm/z-ai/glm4.7

More Examples

Example 1: OpenRouter with Business-Friendly Aliases

{
  "providers": {
    "openrouter": {
      "apiKey": "sk-or-xxx",
      "models": {
        "fast": "anthropic/claude-haiku-3.5",
        "smart": "anthropic/claude-opus-4-5",
        "code": "openai/gpt-4-turbo"
      }
    }
  },
  "agents": {
    "defaults": { "model": "smart" }
  }
}

Flow: "smart"resolve_model()"openrouter/anthropic/claude-opus-4-5" → ✅

Example 2: Multiple Providers

{
  "providers": {
    "vllm": {
      "apiKey": "nvapi-xxx",
      "apiBase": "https://integrate.api.nvidia.com/v1",
      "models": {
        "nvgm": "z-ai/glm4.7"
      }
    },
    "openrouter": {
      "apiKey": "sk-or-xxx",
      "models": {
        "best": "anthropic/claude-opus-4-5"
      }
    }
  }
}
  • "nvgm"hosted_vllm/z-ai/glm4.7 (from vllm provider)
  • "best"openrouter/anthropic/claude-opus-4-5 (from openrouter provider)

Backward Compatibility

100% Backward Compatible

  • Direct model names (no alias) work exactly as before
  • Keyword-based auto-prefixing still applies for non-alias models
  • All existing configurations continue to work

Example: Direct model (no alias) still works:

{
  "providers": {
    "zhipu": { "apiKey": "xxx" }
  },
  "agents": {
    "defaults": { "model": "glm4" }
  }
}

This continues to work with keyword prefixing → zai/glm4

Files Changed

  • nanobot/config/schema.py - Added models field and resolve_model() method
  • nanobot/providers/litellm_provider.py - Changed to is_resolved boolean, simplified prefixing
  • nanobot/cli/commands.py - Updated to use 2-tuple from resolve_model()
  • README.md - Added model aliases documentation

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • Code follows project style guidelines (Ruff, 100 char line limit)
  • No new dependencies added
  • Backward compatible with existing configurations
  • Documentation updated (README.md)

Comment thread README.md Outdated
@tonyxu-io
Copy link
Copy Markdown

Thanks for fixing this!

@tonyxu-io
Copy link
Copy Markdown

Looks like it was fixed in latest version:

[Fix-204]: use correct ZAI_API_KEY for Zhipu/GLM models #204 by @wcmolin in #205

@SergioSV96
Copy link
Copy Markdown
Contributor

Looks like it was fixed in latest version:

[Fix-204]: use correct ZAI_API_KEY for Zhipu/GLM models #204 by @wcmolin in #205

But that doesn't fix for example if I am trying to use GLM 4.7 Flash locally with vLLM, it tries to get me to connect to Z.ai API. I think this solution for explicit provider/model structure is better

@tonyxu-io
Copy link
Copy Markdown

Actually I'm still not able to use zai even in latest version.

@weidonglian
Copy link
Copy Markdown
Author

In the latest, it adds a new ProviderConfig to specify more metadata which is a good. The challeging is till to figure out the Provider from model, it will still be messy.
I can try to rebase this PR with latest.
With my PR, I could uv tool too install it locally and get it working with different models. I have tried it out, not sure I am still interesting in this project 😄 .
Openclaw is definitely bloated with all the AI written code for sure, not easy to read the code.
Nanobot is definite portable and ultra-lightweighted, it is good if you want to learn and practice writing your own tiny agent and control it via messagers; however with my testing, it is not working robustly enough, not at the same level as openclaw 😄 .
Today's opensource does not mean necessarily we need to engage with openclaw and nanobot. Now I lean to build your own toy, since technically it is not at all complicated. I'd like to build it with Typescript or Golang. Typescript might be the most straight-forward choice and nodejs has all the required eco-system. Golang is not there yet, but could be a good choice for your own AI assitant, just run an executable, no package installation is needed:).

@SergioSV96
Copy link
Copy Markdown
Contributor

In the latest, it adds a new ProviderConfig to specify more metadata which is a good. The challeging is till to figure out the Provider from model, it will still be messy. I can try to rebase this PR with latest. With my PR, I could uv tool too install it locally and get it working with different models. I have tried it out, not sure I am still interesting in this project 😄 . Openclaw is definitely bloated with all the AI written code for sure, not easy to read the code. Nanobot is definite portable and ultra-lightweighted, it is good if you want to learn and practice writing your own tiny agent and control it via messagers; however with my testing, it is not working robustly enough, not at the same level as openclaw 😄 . Today's opensource does not mean necessarily we need to engage with openclaw and nanobot. Now I lean to build your own toy, since technically it is not at all complicated. I'd like to build it with Typescript or Golang. Typescript might be the most straight-forward choice and nodejs has all the required eco-system. Golang is not there yet, but could be a good choice for your own AI assitant, just run an executable, no package installation is needed:).

I can help you improve the PR if you would like, I am heavy investing my efforts on this nanobot repo, I think this is a great approach at agentic AI bots. Hit me up if you need help with this please.

@weidonglian
Copy link
Copy Markdown
Author

Just based the PR relative to latest main, briefly tested, it seems to be working fine!

@Re-bin
Copy link
Copy Markdown
Collaborator

Re-bin commented Feb 9, 2026

Thanks for the PR! I will check this soon.

This is important.

@Re-bin
Copy link
Copy Markdown
Collaborator

Re-bin commented Feb 9, 2026

Thanks for the PR and the great idea! Model aliases would be really useful.

A heads-up: we recently refactored provider prefixing into a central registry (PROVIDERS in registry.py), so the hardcoded provider_map in resolve_model() would duplicate that logic and has a couple of wrong prefixes (Anthropic should be "", Zhipu should be "zai").

The cleanest fix would be to check aliases inside _match_provider() and let the existing registry handle prefixing — that way LiteLLMProvider doesn't need changes (is_resolved flag can be dropped).

Happy to help if you'd like to iterate on this, or I can pick it up from here if you prefer. Either way works — the core idea (alias dict per provider) is solid! 👍

@weidonglian
Copy link
Copy Markdown
Author

I can fix and improve as you mentioned to fetch the prefix from provider definition.

@weidonglian
Copy link
Copy Markdown
Author

I take a look the new changes, I think the ProviderConfig could be passed to LiteLLMProvider - one object contains all provider data and easily figure out for this PR as well, right now:
self,
api_key: str | None = None,
api_base: str | None = None,
default_model: str = "anthropic/claude-opus-4-5",
extra_headers: dict[str, str] | None = None,
provider_name: str | None = None,

the default model may not be compatible with provider_name and also the provider should be able to figure out the api_key and api_base, it seems to be convoluted.
I would leave you guys to continue this PR if you want:).

@sam-imot
Copy link
Copy Markdown

This should be useful for all providers and models.

@weidonglian weidonglian closed this by deleting the head repository Mar 3, 2026
WTHDonghai pushed a commit to WTHDonghai/nanobot that referenced this pull request Mar 22, 2026
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

5 participants