Commit a6d1c03f authored by Jan Reimes's avatar Jan Reimes
Browse files

feat(aliases): add provider alias support with base URL overrides

* Introduce ProviderAlias dataclass for managing provider aliases.
* Update PROVIDER_ALIASES to include base URL for specific providers.
* Enhance _resolve_provider function to handle alias resolution.
* Add GSD planning docs to .gitignore for local use.
parent 62f844f7
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -250,3 +250,6 @@ src/teddi-mcp/uv.lock

# kreuzberg artifacts
.kreuzberg

# GSD planning docs (local only)
.planning/
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ This document provides a chronological log of all significant changes and improv

## Recent Changes

- **2026-03-25**: [3gpp-ai list-providers CLI command](history/2026-03-25_SUMMARY_list_providers_cli_command.md)
- **2026-03-25**: [Enhanced RAG pipeline with tables, figures, and equations](history/2026-03-25_SUMMARY_enhanced_rag_pipeline_tables_figures_equations.md)
- **2026-03-24**: [Convert and summarize commands implementation](history/2026-03-24_SUMMARY_convert_summarize_commands_implementation.md)
- **2026-03-23**: [LightRAG migration plan](history/2026-03-23_SUMMARY_LightRAG_migration_plan.md)
+45 −0
Original line number Diff line number Diff line
# 3gpp-ai List-Providers Feature

**Date:** 2026-03-25
**Status:** Complete

## Overview

Added a new CLI command `3gpp-ai providers list` to the `3gpp-ai` package that displays all supported AI providers for document processing and querying operations.

## Features

- `providers list` command with 4 output formats: table (default), JSON, YAML, TOON
- Shows provider capabilities (LLM, Embedding)
- Aliases with base URLs shown as first-class usable providers (openrouter, nvidia, zai-coding-plan)

## Technical Changes

### `packages/3gpp-ai/threegpp_ai/lightrag/rag.py`
- Added `ProviderAlias` dataclass with `canonical` and `base_url` fields
- Extended `PROVIDER_ALIASES` to `dict[str, ProviderAlias]` with base URL support
- Updated `_resolve_provider()` to handle the new structure

### `packages/3gpp-ai/threegpp_ai/cli.py`
- Added `providers_app = typer.Typer(...)` subcommand
- Implemented `providers_list` command with all output formats

## Providers Supported

| Provider | LLM | Embedding | Via / Base URL |
|----------|-----|-----------|----------------|
| hf | Y | Y | - |
| jina | N | Y | - |
| ollama | Y | Y | - |
| openrouter | Y | Y | via openai - https://openrouter.ai/api/v1 |
| nvidia | Y | Y | via openai - https://integrate.api.nvidia.com/v1/ |
| openai | Y | Y | - |
| zai-coding-plan | Y | Y | via zhipu - https://api.z.ai/api/paas/v4 |
| zhipu | Y | Y | - |

## Requirements

- LIST-01: Add `providers` Typer subcommand ✓
- LIST-02: Provider listing command ✓
- LIST-03: Output format option ✓
- LIST-04: Provider metadata (aliases, capabilities) ✓
+18 −4
Original line number Diff line number Diff line
@@ -42,6 +42,14 @@ class ProviderConfig:
    embed_func: Callable | None = None


@dataclass(frozen=True)
class ProviderAlias:
    """Provider alias with optional base URL override."""

    canonical: str
    base_url: str | None = None


PROVIDERS: dict[str, ProviderConfig] = {
    "ollama": ProviderConfig(complete_func=ollama_model_complete, embed_func=ollama_embed),
    "openai": ProviderConfig(complete_func=openai_complete, embed_func=openai_embed),
@@ -50,16 +58,22 @@ PROVIDERS: dict[str, ProviderConfig] = {
    "jina": ProviderConfig(embed_func=jina_embed),
}

PROVIDER_ALIASES = {"zai": "zhipu"}  # workaround for "typical" vs "actual" provider names
PROVIDER_ALIASES: dict[str, ProviderAlias] = {
    "zai": ProviderAlias(canonical="zhipu"),
    "zai-coding-plan": ProviderAlias(canonical="zhipu", base_url="https://api.z.ai/api/paas/v4"),
    "openrouter": ProviderAlias(canonical="openai", base_url="https://openrouter.ai/api/v1"),
    "nvidia": ProviderAlias(canonical="openai", base_url="https://integrate.api.nvidia.com/v1/"),
}


def _resolve_provider(provider: str) -> str:
    """Resolve provider alias to canonical name."""
    resolved = PROVIDER_ALIASES.get(provider, provider)
    if resolved not in PROVIDERS:
    if provider in PROVIDER_ALIASES:
        return PROVIDER_ALIASES[provider].canonical
    if provider not in PROVIDERS:
        available = ", ".join(sorted(PROVIDERS.keys()))
        raise ValueError(f"Unsupported provider '{provider}'. Available: {available}")
    return resolved
    return provider


def _parse_provider(model_string: str) -> tuple[str, str]: