feat: add model catalogue and model-selection UX#2127
feat: add model catalogue and model-selection UX#2127praisonai-triage-agent[bot] wants to merge 2 commits into
Conversation
- Add 'praisonai models' command with list, describe, and validate subcommands - Create ModelCatalogue class with litellm integration and graceful fallback - Add model validation to LLM resolution with 'did you mean' suggestions - Validate model strings in YAML agent configurations - Warn when tools configured for non-tool-calling models - Cache model metadata locally with TTL to reduce API calls - Support filtering by provider and search patterns Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
|
@coderabbitai review |
|
/review |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
✅ Action performedReview finished.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a ChangesModel Catalogue, Validation, and CLI
Sequence Diagram(s)sequenceDiagram
participant User
participant resolve_llm_endpoint as resolve_llm_endpoint
participant ModelCatalogue as ModelCatalogue
participant litellm as litellm / cache
User->>resolve_llm_endpoint: call with model + validate_model=True
resolve_llm_endpoint->>ModelCatalogue: import + validate_model(model)
ModelCatalogue->>litellm: _load_from_litellm() or _load_from_cache()
litellm-->>ModelCatalogue: model list
ModelCatalogue-->>resolve_llm_endpoint: normalized model ID or ValueError
resolve_llm_endpoint-->>User: LLMEndpoint (validated) or ValueError with suggestions
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings. Review areas:
|
Greptile SummaryThis PR adds a model discovery and validation system: a
Confidence Score: 4/5Safe to merge with a small fix: the not-found exit path in the two new CLI commands prints a spurious empty error line before exiting. In describe_model and validate_model, raise typer.Exit(1) is issued inside a try block whose except Exception handler catches it. This causes the commands to print both the correct not-found message and a second empty error line on every normal model-not-found exit. The rest of the change is well-structured: the prior model_list callable bug is fixed, validation defaults are correctly False, and all validation in agents_generator.py is non-blocking warnings only. src/praisonai/praisonai/cli/commands/models.py — the describe_model and validate_model command handlers need except typer.Exit: raise inserted before the generic except Exception handler. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["praisonai models list/describe/validate"] --> B["commands/models.py"]
B --> C["ModelCatalogue"]
C --> D{"Cache valid?"}
D -- Yes --> E["Load ~/.praison/cache/models.json"]
D -- No --> F{"litellm available?"}
F -- Yes --> G["litellm.model_list + model_cost"]
G --> H["Save to cache"]
F -- No --> I["FALLBACK_MODELS 13 entries"]
E --> J["List / Describe / Validate"]
H --> J
I --> J
J -- "not found" --> K["difflib suggestions"]
K --> L["raise typer.Exit inside try"]
L -- "caught by except Exception" --> M["Spurious error message BUG"]
N["AgentsGenerator._validate_config"] --> O{"catalogue importable?"}
O -- Yes --> P["validate_model per agent/role llm field"]
P -- "ValueError" --> Q["logger.warning only non-blocking"]
O -- No --> R["skip validation"]
S["resolve_llm_endpoint"] --> T{"validate_model=False default"}
T -- "True opt-in" --> U["catalogue.validate_model"]
U -- "ValueError" --> V["re-raise to caller"]
T -- False --> W["skip return LLMEndpoint"]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["praisonai models list/describe/validate"] --> B["commands/models.py"]
B --> C["ModelCatalogue"]
C --> D{"Cache valid?"}
D -- Yes --> E["Load ~/.praison/cache/models.json"]
D -- No --> F{"litellm available?"}
F -- Yes --> G["litellm.model_list + model_cost"]
G --> H["Save to cache"]
F -- No --> I["FALLBACK_MODELS 13 entries"]
E --> J["List / Describe / Validate"]
H --> J
I --> J
J -- "not found" --> K["difflib suggestions"]
K --> L["raise typer.Exit inside try"]
L -- "caught by except Exception" --> M["Spurious error message BUG"]
N["AgentsGenerator._validate_config"] --> O{"catalogue importable?"}
O -- Yes --> P["validate_model per agent/role llm field"]
P -- "ValueError" --> Q["logger.warning only non-blocking"]
O -- No --> R["skip validation"]
S["resolve_llm_endpoint"] --> T{"validate_model=False default"}
T -- "True opt-in" --> U["catalogue.validate_model"]
U -- "ValueError" --> V["re-raise to caller"]
T -- False --> W["skip return LLMEndpoint"]
Reviews (2): Last reviewed commit: "fix: resolve critical bugs in model cata..." | Re-trigger Greptile |
| supports_tools = not any(x in model_id for x in ["o1", "embedding", "whisper", "tts", "dall-e"]) | ||
| supports_vision = any(x in model_id for x in ["vision", "gpt-4o", "gemini", "claude-3"]) | ||
| supports_reasoning = any(x in model_id for x in ["o1", "gpt-4o", "claude-3-5", "gemini-2"]) |
There was a problem hiding this comment.
Capability heuristics use fragile substring checks
"o1" in model_id will match any model ID that happens to contain the two-character string o1 — including hypothetical IDs like provider/model-v1.0 or tool-o1-compat. More practically, OpenAI's o1 and o1-mini models now support tool calling in newer API versions, so supports_tools = not any(x in model_id for x in ["o1", ...]) will incorrectly flag those as incapable, leading to spurious warnings. These heuristics will drift further as providers update capabilities. Using litellm's supports_function_calling or supports_vision metadata keys from model_cost would be more reliable.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/praisonai/praisonai/llm/env.py (2)
134-150:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCredential fallback can bypass model validation.
When stored credentials provide
fallback_model, Line 149 returns it directly (fallback_model or model) without validation. An invalid stored model can still pass through despitevalidate_model=True.💡 Proposed fix
if not api_key and fallback_lookup: try: @@ if cred and cred.get("api_key"): api_key = cred["api_key"] fallback_model = cred.get("model") fallback_base = cred.get("base_url") + if validate_model and fallback_model and 'catalogue' in locals() and catalogue: + fallback_model = catalogue.validate_model(fallback_model) break🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/praisonai/praisonai/llm/env.py` around lines 134 - 150, The fallback_model variable retrieved from the fallback_lookup credentials dictionary on line 141 is used directly in the LLMEndpoint return statement without validation, even when validate_model is enabled. After obtaining fallback_model from the credentials, apply the same model validation logic that would be applied to the model parameter before using fallback_model in the LLMEndpoint initialization on line 149. This ensures that stored credentials cannot bypass model validation requirements.
98-123:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate/normalize the model before provider-key resolution.
Line 98 derives
key_var/provider_basebefore normalization (Lines 115-123). If validation changes the model string (e.g., case-normalization), key/base selection can be wrong for the final model.💡 Proposed fix
- key_var, provider_base = _provider_from_model(model) + if validate_model: + try: + from ..llm.catalogue import ModelCatalogue + catalogue = ModelCatalogue() + model = catalogue.validate_model(model) + except ImportError: + catalogue = None + except ValueError: + raise + else: + catalogue = None + + key_var, provider_base = _provider_from_model(model) @@ - # Validate model if requested - if validate_model: - try: - from ..llm.catalogue import ModelCatalogue - catalogue = ModelCatalogue() - # This will raise ValueError with suggestions if invalid - validated_model = catalogue.validate_model(model) - # Use the normalized model ID - model = validated_model - except ImportError: - # Catalogue not available, skip validation - pass - except ValueError: - # Re-raise validation errors - raise🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/praisonai/praisonai/llm/env.py` around lines 98 - 123, The model validation and normalization (using ModelCatalogue().validate_model() to normalize the model string) occurs after _provider_from_model(model) derives the key_var and provider_base, so if validation changes the model string (such as through case-normalization), the derived key and base URL will be incorrect for the final model. Move the entire validation block that normalizes the model to execute before the _provider_from_model() call on line 98, so that key_var and provider_base are determined from the already-normalized model string.
🧹 Nitpick comments (1)
src/praisonai/praisonai/cli/commands/models.py (1)
118-120: ⚡ Quick winPreserve exception context when raising
typer.Exit.The exception handlers catch broad
Exceptionbut re-raisetyper.Exit(1)without chaining, which loses the original traceback. Useraise typer.Exit(1) from eto preserve exception context for debugging.♻️ Proposed fix to preserve exception context
except Exception as e: output.print_error(f"Error listing models: {e}") - raise typer.Exit(1) + raise typer.Exit(1) from eApply the same pattern to the other two exception handlers at lines 190-192 and 241-243:
except Exception as e: output.print_error(f"Error describing model: {e}") - raise typer.Exit(1) + raise typer.Exit(1) from eexcept Exception as e: output.print_error(f"Error validating model: {e}") - raise typer.Exit(1) + raise typer.Exit(1) from eAlso applies to: 190-192, 241-243
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/praisonai/praisonai/cli/commands/models.py` around lines 118 - 120, In the exception handlers at lines 118-120, 190-192, and 241-243, the `raise typer.Exit(1)` statements lose the original exception context because they do not chain the exceptions. Modify each `raise typer.Exit(1)` to `raise typer.Exit(1) from e` to preserve the exception context and maintain the full traceback for debugging purposes.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/praisonai/praisonai/agents_generator.py`:
- Around line 560-590: Before calling model_catalogue.validate_model() in both
the section-level validation loop (for 'llm' and 'function_calling_llm' fields)
and the top-level validation loop (for 'llm' and 'model' fields), check if the
model_value exists in the top-level config.get('models', {}) dictionary. If the
model is defined locally in the YAML's models section, skip the catalogue
validation call to avoid false warnings about invalid models that are actually
valid local aliases.
In `@src/praisonai/praisonai/cli/commands/models.py`:
- Around line 98-117: In the except ImportError block where fallback_models are
displayed, add a conditional check for the json_output flag. If json_output is
True, format and output the fallback_models list as JSON instead of using the
plain text output loop that iterates through prov and prints individual models.
Keep the existing plain text output logic for when json_output is False,
ensuring consistent JSON output behavior regardless of whether litellm is
installed.
In `@src/praisonai/praisonai/llm/catalogue.py`:
- Line 218: The issue is that model_list() is being called as a function on line
218, but litellm.model_list is actually a static list attribute rather than a
callable function, causing a TypeError that gets silently caught. Fix this by
checking whether litellm.model_list is callable using callable() before deciding
how to access it. If it is callable, invoke it as model_list(), otherwise use it
directly as a list attribute. This will prevent the TypeError and allow the code
to properly iterate over the model list instead of silently falling back.
---
Outside diff comments:
In `@src/praisonai/praisonai/llm/env.py`:
- Around line 134-150: The fallback_model variable retrieved from the
fallback_lookup credentials dictionary on line 141 is used directly in the
LLMEndpoint return statement without validation, even when validate_model is
enabled. After obtaining fallback_model from the credentials, apply the same
model validation logic that would be applied to the model parameter before using
fallback_model in the LLMEndpoint initialization on line 149. This ensures that
stored credentials cannot bypass model validation requirements.
- Around line 98-123: The model validation and normalization (using
ModelCatalogue().validate_model() to normalize the model string) occurs after
_provider_from_model(model) derives the key_var and provider_base, so if
validation changes the model string (such as through case-normalization), the
derived key and base URL will be incorrect for the final model. Move the entire
validation block that normalizes the model to execute before the
_provider_from_model() call on line 98, so that key_var and provider_base are
determined from the already-normalized model string.
---
Nitpick comments:
In `@src/praisonai/praisonai/cli/commands/models.py`:
- Around line 118-120: In the exception handlers at lines 118-120, 190-192, and
241-243, the `raise typer.Exit(1)` statements lose the original exception
context because they do not chain the exceptions. Modify each `raise
typer.Exit(1)` to `raise typer.Exit(1) from e` to preserve the exception context
and maintain the full traceback for debugging purposes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 11fcb54a-34c8-44d7-ae46-f415d896a446
📒 Files selected for processing (6)
src/praisonai/praisonai/agents_generator.pysrc/praisonai/praisonai/cli/app.pysrc/praisonai/praisonai/cli/commands/models.pysrc/praisonai/praisonai/llm/catalogue.pysrc/praisonai/praisonai/llm/credentials.pysrc/praisonai/praisonai/llm/env.py
| # Validate model/llm values if catalogue available | ||
| if model_catalogue: | ||
| for model_field in ('llm', 'function_calling_llm'): | ||
| model_value = section_config.get(model_field) | ||
| if model_value and isinstance(model_value, str): | ||
| try: | ||
| model_catalogue.validate_model(model_value) | ||
| except ValueError as e: | ||
| self.logger.warning( | ||
| f"Invalid model '{model_value}' in {entity_name} '{name}' field '{model_field}': {e}" | ||
| ) | ||
|
|
||
| # Check for tools configured with non-tool-calling models | ||
| if section_config.get('tools'): | ||
| llm_value = section_config.get('llm') | ||
| if llm_value: | ||
| model_info = model_catalogue.describe_model(llm_value) | ||
| if model_info and not model_info.get('supports_tools'): | ||
| self.logger.warning( | ||
| f"{entity_name.capitalize()} '{name}' has tools configured but model '{llm_value}' does not support tool calling" | ||
| ) | ||
|
|
||
| # Also validate top-level llm/model config | ||
| if model_catalogue: | ||
| for model_field in ('llm', 'model'): | ||
| model_value = config.get(model_field) | ||
| if model_value and isinstance(model_value, str): | ||
| try: | ||
| model_catalogue.validate_model(model_value) | ||
| except ValueError as e: | ||
| self.logger.warning(f"Invalid model '{model_value}' in top-level '{model_field}': {e}") |
There was a problem hiding this comment.
Skip catalogue validation for YAML-local model aliases.
The new checks validate only against ModelCatalogue. If a role uses a model key defined in top-level models, this logs a false “invalid model” warning.
💡 Proposed fix
- # Try to load model catalogue for validation
+ # Collect YAML-local model aliases (valid even if not in global catalogue)
+ local_model_aliases = set()
+ models_cfg = config.get("models", {})
+ if isinstance(models_cfg, dict):
+ local_model_aliases = {k for k in models_cfg.keys() if isinstance(k, str)}
+
+ # Try to load model catalogue for validation
model_catalogue = None
@@
model_value = section_config.get(model_field)
if model_value and isinstance(model_value, str):
+ if model_value in local_model_aliases:
+ continue
try:
model_catalogue.validate_model(model_value)
@@
model_value = config.get(model_field)
if model_value and isinstance(model_value, str):
+ if model_value in local_model_aliases:
+ continue
try:
model_catalogue.validate_model(model_value)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/praisonai/praisonai/agents_generator.py` around lines 560 - 590, Before
calling model_catalogue.validate_model() in both the section-level validation
loop (for 'llm' and 'function_calling_llm' fields) and the top-level validation
loop (for 'llm' and 'model' fields), check if the model_value exists in the
top-level config.get('models', {}) dictionary. If the model is defined locally
in the YAML's models section, skip the catalogue validation call to avoid false
warnings about invalid models that are actually valid local aliases.
| except ImportError: | ||
| output.print_warning("Model catalogue not available. Install litellm for full model listing:") | ||
| output.print(" pip install 'praisonai[litellm]'") | ||
|
|
||
| # Show basic fallback models | ||
| fallback_models = [ | ||
| {"provider": "OpenAI", "models": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]}, | ||
| {"provider": "Anthropic", "models": ["claude-3-5-sonnet-latest", "claude-3-opus-latest", "claude-3-haiku-latest"]}, | ||
| {"provider": "Google", "models": ["gemini-1.5-pro", "gemini-1.5-flash"]}, | ||
| {"provider": "Groq", "models": ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"]}, | ||
| ] | ||
|
|
||
| if provider: | ||
| fallback_models = [p for p in fallback_models if p["provider"].lower() == provider.lower()] | ||
|
|
||
| for prov in fallback_models: | ||
| output.print_subheader(f"{prov['provider']} Models") | ||
| for model in prov['models']: | ||
| if not search or search.lower() in model.lower(): | ||
| output.print(f" • {model}") |
There was a problem hiding this comment.
Ensure consistent JSON output when --json flag is set.
When json_output is True and ImportError occurs, the fallback prints plain text instead of JSON. Users expect consistent JSON output when they pass the --json flag, regardless of whether litellm is available.
🛠️ Proposed fix to output JSON in the fallback path
except ImportError:
+ if json_output:
+ # Provide fallback models in JSON format
+ fallback_models = [
+ {"provider": "OpenAI", "models": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]},
+ {"provider": "Anthropic", "models": ["claude-3-5-sonnet-latest", "claude-3-opus-latest", "claude-3-haiku-latest"]},
+ {"provider": "Google", "models": ["gemini-1.5-pro", "gemini-1.5-flash"]},
+ {"provider": "Groq", "models": ["llama-3.3-70b-versatile", "mixtral-8x7b-32768"]},
+ ]
+
+ if provider:
+ fallback_models = [p for p in fallback_models if p["provider"].lower() == provider.lower()]
+
+ # Flatten to model list format
+ models_list = []
+ for prov in fallback_models:
+ for model_id in prov['models']:
+ if not search or search.lower() in model_id.lower():
+ models_list.append({"id": model_id, "provider": prov["provider"]})
+
+ output.print(json.dumps(models_list, indent=2))
+ return
+
output.print_warning("Model catalogue not available. Install litellm for full model listing:")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/praisonai/praisonai/cli/commands/models.py` around lines 98 - 117, In the
except ImportError block where fallback_models are displayed, add a conditional
check for the json_output flag. If json_output is True, format and output the
fallback_models list as JSON instead of using the plain text output loop that
iterates through prov and prints individual models. Keep the existing plain text
output logic for when json_output is False, ensuring consistent JSON output
behavior regardless of whether litellm is installed.
| cost_data = litellm.model_cost | ||
|
|
||
| # Process known models from litellm | ||
| for model_id in model_list() if hasattr(litellm, 'model_list') else []: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
try:
import litellm
ml = getattr(litellm, "model_list", None)
print("type(model_list):", type(ml).__name__)
print("callable(model_list):", callable(ml))
except Exception as e:
print("verification_error:", e)
PYRepository: MervinPraison/PraisonAI
Length of output: 113
🏁 Script executed:
cd src/praisonai/praisonai/llm && head -n 230 catalogue.py | tail -n 20Repository: MervinPraison/PraisonAI
Length of output: 1081
🏁 Script executed:
rg "model_list" src/praisonai/praisonai/llm/catalogue.py -B 5 -A 5Repository: MervinPraison/PraisonAI
Length of output: 973
🌐 Web query:
litellm model_list callable API documentation
💡 Result:
In LiteLLM, the model_list parameter used in the Router class is a static configuration list of model deployments, not a callable API [1][2][3]. It is defined as a list of dictionaries, where each dictionary specifies a model deployment with its associated configuration parameters (e.g., model name, API keys, and other provider-specific settings) [1][2]. Key characteristics of model_list: - Structure: It expects a List[Dict] or List[DeploymentTypedDict] [2][3]. - Purpose: It enables load balancing and routing by mapping a single model alias (model_name) to multiple backend deployments [1][2][4]. - Implementation: When initializing the Router, you pass this list directly [1][5]: router = Router(model_list=[...]) If you are looking for dynamic or callable functionality in LiteLLM, this is typically associated with callbacks (e.g., custom logging or observability), not the model_list itself [6][7][8]. You can add callbacks using callable objects to handle events like success or failure during model calls [7][8]. If you need to dynamically fetch or manage available models, you should look into utilities like litellm.utils.get_valid_models or managed proxy configurations, rather than treating the model_list as a callable [9][10].
Citations:
- 1: https://docs.litellm.ai/docs/routing
- 2: https://berriai-litellm.mintlify.app/sdk/router
- 3: https://github.com/BerriAI/litellm/blob/main/litellm/router.py
- 4: https://berriai-litellm.mintlify.app/features/loadbalancing
- 5: https://github.com/BerriAI/litellm/blob/main/docs/my-website/docs/routing.md
- 6: https://docs.litellm.ai/docs/proxy/dynamic_logging
- 7: https://github.com/BerriAI/litellm/blob/main/litellm/litellm_core_utils/logging_callback_manager.py
- 8: https://github.com/BerriAI/litellm/blob/d251238b/litellm/litellm_core_utils/litellm_logging.py
- 9: python sdk don't have option to get list of models from a provider BerriAI/litellm#5894
- 10: [Feature]: Harmonize output and add 'provider' kwarg for get_valid_models() BerriAI/litellm#9012
🏁 Script executed:
sed -n '200,240p' src/praisonai/praisonai/llm/catalogue.py | cat -nRepository: MervinPraison/PraisonAI
Length of output: 2180
🏁 Script executed:
sed -n '190,250p' src/praisonai/praisonai/llm/catalogue.py | cat -nRepository: MervinPraison/PraisonAI
Length of output: 3484
🏁 Script executed:
sed -n '240,270p' src/praisonai/praisonai/llm/catalogue.py | cat -nRepository: MervinPraison/PraisonAI
Length of output: 1820
Handle both callable and list cases for litellm.model_list.
Line 218 calls model_list() as a function, but litellm.model_list is a static list, not callable. This raises TypeError, which gets swallowed by the broad except Exception at line 28, causing the catalogue to silently fall back every time.
Proposed fix
- for model_id in model_list() if hasattr(litellm, 'model_list') else []:
+ raw_model_list = getattr(litellm, "model_list", [])
+ model_ids = raw_model_list() if callable(raw_model_list) else raw_model_list
+ for model_id in model_ids or []:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/praisonai/praisonai/llm/catalogue.py` at line 218, The issue is that
model_list() is being called as a function on line 218, but litellm.model_list
is actually a static list attribute rather than a callable function, causing a
TypeError that gets silently caught. Fix this by checking whether
litellm.model_list is callable using callable() before deciding how to access
it. If it is callable, invoke it as model_list(), otherwise use it directly as a
list attribute. This will prevent the TypeError and allow the code to properly
iterate over the model list instead of silently falling back.
|
@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding. Phase 1: Review per AGENTS.md
Phase 2: FIX Valid Issues Phase 3: Final Verdict |
|
Claude finished @MervinPraison's task —— View job ✅ Architecture Review & Fixes CompleteTodo List
Architecture Review Results✅ Protocol-driven: Correctly implemented in wrapper layer (praisonai/), not in core SDK Fixed Critical IssuesAll critical bugs identified by reviewers have been resolved: 1. Fixed
|
- Fix TypeError: model_list is a list attribute not callable in litellm - Change validate_model default to False to preserve backward compatibility - Move model validation before provider resolution to ensure correct key/base selection - Add validation for fallback models from stored credentials - Skip catalogue validation for local YAML model aliases - Ensure consistent JSON output in CLI when litellm unavailable Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
| try: | ||
| from ...llm.catalogue import ModelCatalogue | ||
| catalogue = ModelCatalogue() | ||
| info = catalogue.describe_model(model) | ||
|
|
||
| if not info: | ||
| output.print_error(f"Model '{model}' not found") | ||
|
|
||
| # Try to suggest similar models | ||
| suggestions = catalogue.get_suggestions(model) | ||
| if suggestions: | ||
| output.print_info("Did you mean one of these?") | ||
| for suggestion in suggestions[:5]: | ||
| output.print(f" • {suggestion}") | ||
| raise typer.Exit(1) | ||
|
|
||
| # Display model details | ||
| output.print_subheader(f"Model: {info.get('id', model)}") | ||
|
|
||
| if info.get("provider"): | ||
| output.print(f"Provider: {info['provider']}") | ||
|
|
||
| if info.get("description"): | ||
| output.print(f"Description: {info['description']}") | ||
|
|
||
| # Capabilities | ||
| output.print("\nCapabilities:") | ||
| output.print(f" • Tool calling: {'✅' if info.get('supports_tools') else '❌'}") | ||
| output.print(f" • Vision: {'✅' if info.get('supports_vision') else '❌'}") | ||
| output.print(f" • Reasoning: {'✅' if info.get('supports_reasoning') else '❌'}") | ||
| output.print(f" • Streaming: {'✅' if info.get('supports_streaming', True) else '❌'}") | ||
|
|
||
| # Limits | ||
| output.print("\nLimits:") | ||
| if info.get("max_context"): | ||
| output.print(f" • Context window: {info['max_context']:,} tokens") | ||
| if info.get("max_output"): | ||
| output.print(f" • Max output: {info['max_output']:,} tokens") | ||
|
|
||
| # Costs | ||
| if info.get("input_cost") is not None: | ||
| output.print("\nCosts (per 1K tokens):") | ||
| output.print(f" • Input: ${info['input_cost']:.6f}") | ||
| if info.get("output_cost") is not None: | ||
| output.print(f" • Output: ${info['output_cost']:.6f}") | ||
|
|
||
| # Notes | ||
| if info.get("notes"): | ||
| output.print(f"\nNotes: {info['notes']}") | ||
|
|
||
| except ImportError: | ||
| output.print_warning("Model catalogue not available. Install litellm for detailed model info:") | ||
| output.print(" pip install 'praisonai[litellm]'") | ||
| except Exception as e: | ||
| output.print_error(f"Error describing model: {e}") | ||
| raise typer.Exit(1) |
There was a problem hiding this comment.
typer.Exit swallowed by except Exception, emitting a spurious error message
typer.Exit is a direct subclass of click.exceptions.Exit, which is itself a subclass of Exception. When a model is not found (line 162), raise typer.Exit(1) is raised inside the try: block; the except Exception as e: at line 201 catches it, calls output.print_error(f"Error describing model: {e}") (where str(typer.Exit(1)) is an empty string), and then re-raises. The user sees two error messages: the correct "Model not found" + suggestions, followed by an erroneous empty "Error describing model: " line. The same bug exists in validate_model at line 247 / line 252. Add except typer.Exit: raise before the generic handler in both commands.
Fixes #2123
Summary
Adds a comprehensive model discovery and validation system to the PraisonAI CLI, addressing the core "few lines to first success" gap where users previously had to guess model identifiers.
Changes
New Commands
praisonai models list- List available models with capabilities, limits, and costspraisonai models describe <model>- Show detailed information for a specific modelpraisonai models validate <model>- Check if a model ID is validModel Catalogue
Model Validation
Features
--jsonflagImplementation Details
src/praisonai/praisonai/) per architecture guidelinesTesting
Tested locally with:
Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com
Summary by CodeRabbit
modelsCLI command group enabling users to list available models, view detailed information, and validate model identifiers.