Skip to content

C7: move LLM env/credentials to praisonai-code for standalone gate#2548

Merged
MervinPraison merged 10 commits into
mainfrom
feat/c7-llm-credentials-standalone
Jul 1, 2026
Merged

C7: move LLM env/credentials to praisonai-code for standalone gate#2548
MervinPraison merged 10 commits into
mainfrom
feat/c7-llm-credentials-standalone

Conversation

@MervinPraison

@MervinPraison MervinPraison commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Move llm/env.py and llm/credentials.py into praisonai_code so credential gates (is_configured, inject_credentials_into_env, endpoint resolution) work without importing the praisonai wrapper
  • Replace wrapper modules with identity-preserving shims (sys.modules redirect) so existing praisonai.llm.* imports and test patches keep working
  • Update CLI imports (app, main, run, init, model_resolver) and route Langfuse/Langextract observability through _wrapper_bridge
  • Extend C5 backward-compat tests and CI standalone smoke to verify is_configured imports without wrapper

Test plan

  • pytest tests/unit/test_c5_backward_compat.py tests/unit/llm/test_env_resolver.py tests/unit/llm/test_credentials_is_configured.py tests/unit/cli/test_init_provider_guard.py — 94 passed
  • Standalone venv (praisonai-agents + praisonai-code only): from praisonai_code.llm.credentials import is_configured succeeds
  • CI smoke + legacy-unit jobs on merge

Summary by CodeRabbit

  • New Features
    • Added a standalone LLM model catalogue with listing, describing, validation, and suggestions.
  • Bug Fixes
    • Improved CLI credential gating by injecting stored credentials into environment variables before checks (including after interactive setup).
    • Hardened credential/model/provider resolution used by the CLI, and made observability module loading more resilient.
  • Documentation
    • Updated integration-gate guidance for standalone installation and deferred follow-up notes.
  • Tests
    • Expanded backward-compat import-path alias coverage and strengthened standalone smoke checks.

Credential checks and endpoint resolution no longer import the wrapper; shims
preserve praisonai.llm.* module identity and CI smoke verifies standalone import.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds praisonai_code.llm modules for endpoint resolution, credential gating, model cataloguing, and config-list building, then rewires CLI call sites to use them. Legacy praisonai.llm modules now alias the new implementations, and docs, smoke checks, and compatibility tests are updated.

Changes

praisonai_code LLM env/credentials migration

Layer / File(s) Summary
LLM env, catalogue, and config
src/praisonai-code/praisonai_code/llm/env.py, src/praisonai-code/praisonai_code/llm/catalogue.py, src/praisonai-code/praisonai_code/llm/config.py, src/praisonai-code/praisonai_code/llm/__init__.py
Adds LLMEndpoint, provider-aware model/base URL resolution, model catalogue loading and validation, AutoGen config-list building, and package re-exports for the new LLM modules.
Credential lookup and gating
src/praisonai-code/praisonai_code/llm/credentials.py
Adds stored-credential lookup, environment injection, provider/model mapping, and is_configured() on top of endpoint resolution with credential fallback.
CLI imports and startup checks
src/praisonai-code/praisonai_code/cli/app.py, src/praisonai-code/praisonai_code/cli/commands/run.py, src/praisonai-code/praisonai_code/cli/commands/init.py, src/praisonai-code/praisonai_code/cli/commands/models.py, src/praisonai-code/praisonai_code/cli/configuration/model_resolver.py, src/praisonai-code/praisonai_code/cli/main.py
Switches CLI startup, init, run, model-resolution, and model-list commands to praisonai_code.llm modules and injects credentials before re-checking configuration.
Legacy praisonai llm shims
src/praisonai/praisonai/llm/env.py, src/praisonai/praisonai/llm/credentials.py, src/praisonai/praisonai/llm/catalogue.py, src/praisonai/praisonai/llm/config.py, src/praisonai/tests/unit/test_c5_backward_compat.py, src/praisonai-agents/AGENTS.md, src/praisonai-code/README.md, .github/workflows/test-optimized.yml
Replaces legacy praisonai.llm modules with module-aliasing shims, extends import-identity compatibility assertions, and updates docs and smoke checks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: moving LLM env/credentials into praisonai-code to enable the standalone gate.
Docstring Coverage ✅ Passed Docstring coverage is 95.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/c7-llm-credentials-standalone

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MervinPraison

Copy link
Copy Markdown
Owner Author

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

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

Model listing and AutoGen config_list building no longer import the wrapper;
shims preserve praisonai.llm.* module identity for backward compatibility.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request moves the LLM environment variable resolution and credential handling from the praisonai wrapper package to the standalone praisonai_code package, establishing backward-compatibility shims in the original paths. The review feedback highlights that the openrouter provider is missing from several credential and environment resolution helpers, which could prevent OpenRouter credentials from being correctly exported, checked, or resolved. Additionally, the reviewer suggests adding defensive None checks for import_wrapper_module in app.py to avoid potential AttributeError exceptions when running in standalone mode.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +61 to +69
env_mappings = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
"gemini": "GEMINI_API_KEY",
"tavily": "TAVILY_API_KEY",
"groq": "GROQ_API_KEY",
"cohere": "COHERE_API_KEY",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The "openrouter" provider is missing from the env_mappings dictionary. Without this mapping, inject_credentials_into_env will not export stored OpenRouter credentials to the environment, causing subsequent calls to fail when they look for OPENROUTER_API_KEY.

Suggested change
env_mappings = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
"gemini": "GEMINI_API_KEY",
"tavily": "TAVILY_API_KEY",
"groq": "GROQ_API_KEY",
"cohere": "COHERE_API_KEY",
}
env_mappings = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
"gemini": "GEMINI_API_KEY",
"tavily": "TAVILY_API_KEY",
"groq": "GROQ_API_KEY",
"cohere": "COHERE_API_KEY",
"openrouter": "OPENROUTER_API_KEY",
}

Comment on lines +99 to +122
def _provider_key_vars_for_model(model: str) -> tuple[str, ...]:
"""Map a model id to the environment variable(s) for its provider."""
m = model.lower()
if m.startswith("anthropic/") or m.startswith("claude"):
return ("ANTHROPIC_API_KEY",)
if m.startswith("google/"):
return ("GOOGLE_API_KEY",)
if m.startswith("gemini/") or m.startswith("gemini"):
return ("GEMINI_API_KEY",)
if m.startswith("groq/"):
return ("GROQ_API_KEY",)
if m.startswith("cohere/"):
return ("COHERE_API_KEY",)
if m.startswith("ollama/"):
return ("OLLAMA_HOST",)
if (
m.startswith("gpt")
or m.startswith("o1")
or m.startswith("o3")
or m.startswith("o4")
or m.startswith("openai/")
):
return ("OPENAI_API_KEY",)
return ()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The "openrouter/" model prefix is missing from _provider_key_vars_for_model. This causes is_configured to return False for explicit OpenRouter models even when OPENROUTER_API_KEY is set, blocking execution in non-interactive environments.

def _provider_key_vars_for_model(model: str) -> tuple[str, ...]:
    """Map a model id to the environment variable(s) for its provider."""
    m = model.lower()
    if m.startswith("anthropic/") or m.startswith("claude"):
        return ("ANTHROPIC_API_KEY",)
    if m.startswith("google/"):
        return ("GOOGLE_API_KEY",)
    if m.startswith("gemini/") or m.startswith("gemini"):
        return ("GEMINI_API_KEY",)
    if m.startswith("groq/"):
        return ("GROQ_API_KEY",)
    if m.startswith("cohere/"):
        return ("COHERE_API_KEY",)
    if m.startswith("ollama/"):
        return ("OLLAMA_HOST",)
    if m.startswith("openrouter/"):
        return ("OPENROUTER_API_KEY",)
    if (
        m.startswith("gpt")
        or m.startswith("o1")
        or m.startswith("o3")
        or m.startswith("o4")
        or m.startswith("openai/")
    ):
        return ("OPENAI_API_KEY",)
    return ()

Comment on lines +125 to +133
_VAR_TO_STORED_PROVIDERS = {
"OPENAI_API_KEY": ("openai",),
"ANTHROPIC_API_KEY": ("anthropic",),
"GEMINI_API_KEY": ("gemini", "google"),
"GOOGLE_API_KEY": ("google", "gemini"),
"GROQ_API_KEY": ("groq",),
"COHERE_API_KEY": ("cohere",),
"OLLAMA_HOST": ("ollama",),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The "OPENROUTER_API_KEY" mapping is missing from _VAR_TO_STORED_PROVIDERS. This prevents is_configured from checking stored credentials for OpenRouter models.

Suggested change
_VAR_TO_STORED_PROVIDERS = {
"OPENAI_API_KEY": ("openai",),
"ANTHROPIC_API_KEY": ("anthropic",),
"GEMINI_API_KEY": ("gemini", "google"),
"GOOGLE_API_KEY": ("google", "gemini"),
"GROQ_API_KEY": ("groq",),
"COHERE_API_KEY": ("cohere",),
"OLLAMA_HOST": ("ollama",),
}
_VAR_TO_STORED_PROVIDERS = {
"OPENAI_API_KEY": ("openai",),
"ANTHROPIC_API_KEY": ("anthropic",),
"GEMINI_API_KEY": ("gemini", "google"),
"GOOGLE_API_KEY": ("google", "gemini"),
"GROQ_API_KEY": ("groq",),
"COHERE_API_KEY": ("cohere",),
"OLLAMA_HOST": ("ollama",),
"OPENROUTER_API_KEY": ("openrouter",),
}

Comment on lines +148 to +152
known_keys = (
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
"OLLAMA_HOST",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The "OPENROUTER_API_KEY" environment variable is missing from known_keys in is_configured. This causes the general credential check to fail when only OPENROUTER_API_KEY is configured.

Suggested change
known_keys = (
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
"OLLAMA_HOST",
)
known_keys = (
"OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "GROQ_API_KEY", "COHERE_API_KEY",
"OLLAMA_HOST", "OPENROUTER_API_KEY",
)

fallback_base = None
if not api_key and fallback_lookup:
try:
for provider in ["openai", "anthropic", "google", "gemini", "groq", "cohere"]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The "openrouter" provider is missing from the fallback lookup list. This prevents resolve_llm_endpoint from retrieving stored OpenRouter credentials when the environment variable is not set.

Suggested change
for provider in ["openai", "anthropic", "google", "gemini", "groq", "cohere"]:
for provider in ["openai", "anthropic", "google", "gemini", "groq", "cohere", "openrouter"]:

Comment on lines 20 to +25
def _setup_langfuse_observability(*, verbose: bool = False) -> None:
"""Set up Langfuse observability by wiring TraceSink to action emitter."""
try:
from praisonai.observability.langfuse import LangfuseSink
from praisonai_code._wrapper_bridge import import_wrapper_module
langfuse = import_wrapper_module("praisonai.observability.langfuse")
LangfuseSink = langfuse.LangfuseSink

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In standalone mode, import_wrapper_module may return None if the wrapper is not installed. Accessing langfuse.LangfuseSink directly without a None check will raise an AttributeError. Adding a defensive guard prevents unnecessary exceptions and warnings when running in standalone mode.

Suggested change
def _setup_langfuse_observability(*, verbose: bool = False) -> None:
"""Set up Langfuse observability by wiring TraceSink to action emitter."""
try:
from praisonai.observability.langfuse import LangfuseSink
from praisonai_code._wrapper_bridge import import_wrapper_module
langfuse = import_wrapper_module("praisonai.observability.langfuse")
LangfuseSink = langfuse.LangfuseSink
def _setup_langfuse_observability(*, verbose: bool = False) -> None:
"""Set up Langfuse observability by wiring TraceSink to action emitter."""
try:
from praisonai_code._wrapper_bridge import import_wrapper_module
langfuse = import_wrapper_module("praisonai.observability.langfuse")
if langfuse is None:
return
LangfuseSink = langfuse.LangfuseSink

Comment on lines +64 to +67
from praisonai_code._wrapper_bridge import import_wrapper_module
langextract_mod = import_wrapper_module("praisonai.observability.langextract")
LangextractSink = langextract_mod.LangextractSink
LangextractSinkConfig = langextract_mod.LangextractSinkConfig

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In standalone mode, import_wrapper_module may return None if the wrapper is not installed. Accessing langextract_mod.LangextractSink directly without a None check will raise an AttributeError. Adding a defensive guard prevents unnecessary exceptions and warnings when running in standalone mode.

Suggested change
from praisonai_code._wrapper_bridge import import_wrapper_module
langextract_mod = import_wrapper_module("praisonai.observability.langextract")
LangextractSink = langextract_mod.LangextractSink
LangextractSinkConfig = langextract_mod.LangextractSinkConfig
from praisonai_code._wrapper_bridge import import_wrapper_module
langextract_mod = import_wrapper_module("praisonai.observability.langextract")
if langextract_mod is None:
return
LangextractSink = langextract_mod.LangextractSink
LangextractSinkConfig = langextract_mod.LangextractSinkConfig

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves the LLM helper layer into praisonai_code for standalone CLI use. The main changes are:

  • Added standalone LLM env, credential, config, and catalogue modules.
  • Replaced old praisonai.llm.* modules with identity-preserving shims.
  • Updated CLI credential checks and imports to use praisonai_code.llm.
  • Added OpenRouter and stored endpoint metadata handling in the resolver.
  • Expanded compatibility and standalone smoke coverage.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The latest updates address the standalone export, OpenRouter credential, and stored endpoint metadata paths covered by the changed files.

Important Files Changed

Filename Overview
src/praisonai-code/praisonai_code/llm/init.py Adds the public standalone exports for the moved LLM helpers.
src/praisonai-code/praisonai_code/llm/credentials.py Moves credential gating and injection into praisonai_code with OpenRouter coverage.
src/praisonai-code/praisonai_code/llm/env.py Moves endpoint resolution into praisonai_code and preserves stored endpoint metadata.
src/praisonai-code/praisonai_code/llm/config.py Builds runtime config through the credential-aware resolver.
src/praisonai/praisonai/llm/credentials.py Redirects the legacy credential module to the new standalone implementation.
src/praisonai/praisonai/llm/env.py Redirects the legacy env module to the new standalone implementation.
src/praisonai/praisonai/llm/config.py Redirects the legacy config module to the new standalone implementation.
src/praisonai/praisonai/llm/catalogue.py Redirects the legacy catalogue module to the new standalone implementation.

Reviews (7): Last reviewed commit: "fix(c7): recover stored base_url for exp..." | Re-trigger Greptile

Comment thread src/praisonai-code/praisonai_code/llm/__init__.py
Comment thread src/praisonai-code/praisonai_code/llm/credentials.py
Comment thread src/praisonai-code/praisonai_code/llm/env.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/praisonai-code/praisonai_code/cli/app.py (1)

696-727: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Export stored credentials before starting the TUI.

The new is_configured() can return True from CredentialStore, but this path never calls inject_credentials_into_env(). A user configured via praisonai setup can pass the gate and then start the TUI without the key in process env; cli/main.py already injects before checking.

🐛 Proposed fix
-        from praisonai_code.llm.credentials import is_configured
+        from praisonai_code.llm.credentials import inject_credentials_into_env, is_configured
         import sys
         
+        inject_credentials_into_env()
         if not is_configured():  # Check for any configured credentials
@@
                 if exit_code != 0:
                     typer.echo("Setup failed. Exiting.", err=True)
                     raise typer.Exit(exit_code)
                 
                 # Re-check credentials after setup
+                inject_credentials_into_env()
                 if not is_configured():
🤖 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-code/praisonai_code/cli/app.py` around lines 696 - 727, The
credential check path in the CLI currently allows `is_configured()` to pass
without exporting stored credentials into the process environment. Update the
flow around the existing `is_configured()` check in `app.py` to call
`inject_credentials_into_env()` before starting the TUI, matching the behavior
already used in `cli/main.py`; keep the existing setup-wizard fallback intact
and ensure the env injection happens after successful setup and before any TUI
launch.
src/praisonai-code/praisonai_code/cli/commands/run.py (1)

531-563: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Inject stored credentials before the early run gate.

is_configured(model) can now return True from stored credentials, but run_main() does not export them before continuing. That lets praisonai setup users pass the gate and still fail later when the LLM call reads env vars.

🐛 Proposed fix
-        from praisonai_code.llm.credentials import is_configured
+        from praisonai_code.llm.credentials import inject_credentials_into_env, is_configured
         import sys
         
         # Check if credentials are configured (use model if provided, else check general)
+        inject_credentials_into_env()
         if not is_configured(model):
@@
                 output.print_success("Setup complete! Continuing with your run...")
                 # Re-check after setup
+                inject_credentials_into_env()
                 if not is_configured(model):
🤖 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-code/praisonai_code/cli/commands/run.py` around lines 531 -
563, `run_main()` is checking `is_configured(model)` before exporting any stored
credentials, so the command can pass the gate and then fail later when the LLM
client reads environment variables. Update the credential-check flow in
`run_main()` to load/export stored credentials before the early exit path, using
the existing `is_configured` and setup-related logic so later model calls can
see the keys. Keep the fix localized to the run command’s preflight section and
ensure the non-interactive and interactive branches both operate on the same
exported credential state.
🤖 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-code/praisonai_code/llm/__init__.py`:
- Around line 5-12: The package-level exports in llm/__init__.py are listed in
__all__ but the symbols are not actually bound in the module namespace, so
imports like from praisonai_code.llm import LLMEndpoint and wildcard imports
will fail. Update the llm package initializer to import and re-export the named
symbols from their defining module, keeping __all__ in sync with the actual
bindings for LLMEndpoint, default_model_for_available_provider,
resolve_llm_endpoint, inject_credentials_into_env, is_configured, and
resolve_llm_endpoint_with_credentials.

In `@src/praisonai-code/praisonai_code/llm/credentials.py`:
- Around line 61-69: OpenRouter is missing from the credential lookup in
credentials.py, so openrouter models can fail the API-key check even when
OPENROUTER_API_KEY is set. Update the env_mappings used by the credential gate
to include openrouter -> OPENROUTER_API_KEY, ensure the OpenRouter model path is
recognized by the same lookup logic that handles openai/anthropic/google, and
add OPENROUTER_API_KEY to known_keys so it is treated as a valid configured
credential.

In `@src/praisonai-code/praisonai_code/llm/env.py`:
- Around line 42-51: Add OpenRouter to the provider-aware default model mapping
in env.py. Update the _PROVIDER_DEFAULTS tuple used by the default-selection
logic so OPENROUTER_API_KEY is recognized alongside the other provider keys, and
give it an OpenRouter-compatible fallback model. This should be done in the same
place that defines the provider defaults so the existing lookup path can choose
the correct model when only an OpenRouter key is present.
- Around line 100-105: Align `_provider_from_model` with `is_configured` so bare
`claude...` and `gemini...` model IDs resolve to the Anthropic/Gemini provider
metadata instead of falling through to OpenAI defaults. Update the prefix
matching logic in `_provider_from_model` to recognize the same model patterns
handled by the credential gate, and ensure the returned key variable and base
URL match the provider inferred by `is_configured()` and any related provider
map entries.
- Around line 146-164: Scope the stored-credential fallback in env.py so it only
uses the provider implied by the resolved model when MODEL_NAME or config is
explicit. Update the fallback_lookup loop to prefer the selected provider from
the parsed model (and keep the broader scan only for inferred defaults), and
ensure api_key, fallback_model, and fallback_base are not mixed across providers
in the env resolution logic.

---

Outside diff comments:
In `@src/praisonai-code/praisonai_code/cli/app.py`:
- Around line 696-727: The credential check path in the CLI currently allows
`is_configured()` to pass without exporting stored credentials into the process
environment. Update the flow around the existing `is_configured()` check in
`app.py` to call `inject_credentials_into_env()` before starting the TUI,
matching the behavior already used in `cli/main.py`; keep the existing
setup-wizard fallback intact and ensure the env injection happens after
successful setup and before any TUI launch.

In `@src/praisonai-code/praisonai_code/cli/commands/run.py`:
- Around line 531-563: `run_main()` is checking `is_configured(model)` before
exporting any stored credentials, so the command can pass the gate and then fail
later when the LLM client reads environment variables. Update the
credential-check flow in `run_main()` to load/export stored credentials before
the early exit path, using the existing `is_configured` and setup-related logic
so later model calls can see the keys. Keep the fix localized to the run
command’s preflight section and ensure the non-interactive and interactive
branches both operate on the same exported credential state.
🪄 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: 2c645514-8b7c-4515-8ac2-0f9bbf93e4f0

📥 Commits

Reviewing files that changed from the base of the PR and between 36d910d and 2b5bc6e.

📒 Files selected for processing (14)
  • .github/workflows/test-optimized.yml
  • src/praisonai-agents/AGENTS.md
  • src/praisonai-code/README.md
  • src/praisonai-code/praisonai_code/cli/app.py
  • src/praisonai-code/praisonai_code/cli/commands/init.py
  • src/praisonai-code/praisonai_code/cli/commands/run.py
  • src/praisonai-code/praisonai_code/cli/configuration/model_resolver.py
  • src/praisonai-code/praisonai_code/cli/main.py
  • src/praisonai-code/praisonai_code/llm/__init__.py
  • src/praisonai-code/praisonai_code/llm/credentials.py
  • src/praisonai-code/praisonai_code/llm/env.py
  • src/praisonai/praisonai/llm/credentials.py
  • src/praisonai/praisonai/llm/env.py
  • src/praisonai/tests/unit/test_c5_backward_compat.py

Comment thread src/praisonai-code/praisonai_code/llm/__init__.py
Comment thread src/praisonai-code/praisonai_code/llm/credentials.py
Comment thread src/praisonai-code/praisonai_code/llm/env.py
Comment thread src/praisonai-code/praisonai_code/llm/env.py
Comment thread src/praisonai-code/praisonai_code/llm/env.py
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review pipeline/blocked:cooldown Blocked: post-push or @claude cooldown pipeline/reviews-pending Waiting for CodeRabbit/Qodo/Copilot reviews labels Jul 1, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

Reviewed against AGENTS.md, incorporated findings from Gemini, CodeRabbit, Greptile, Qodo & Copilot. All valid issues fixed and pushed to feat/c7-llm-credentials-standalone (commit 0507797d0).

Verification: 94/94 tests pass (test_c5_backward_compat, test_env_resolver, test_credentials_is_configured, test_init_provider_guard). Shim identity + standalone import confirmed.

  • Read new LLM modules + CLI call sites
  • Validated reviewer findings against current code
  • Fixed valid issues
  • Ran tests (94 passed)
  • Committed & pushed
  • Posted verdict

Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl lives in praisonai-code; wrapper reduced to sys.modules shims (-501 lines). Correct routing.
Backward compatible ✅ Shims preserve praisonai.llm.* import paths — module identity is equal, so existing test patches keep working.
Performance ✅ No hot-path regressions. Imports stay lazy (inside functions); no new module-level heavy imports.
SDK value ✅ Genuine value: unblocks standalone praisonai-code install without the praisonai wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fixes Applied

llm/__init__.py — Bound __all__ names by importing from .env/.credentials. (P1 — Greptile/CodeRabbit: direct & star imports previously raised ImportError.)

llm/credentials.py — Wired OpenRouter through the gate: env_mappings, _provider_key_vars_for_model (openrouter/), _VAR_TO_STORED_PROVIDERS, known_keys. (P1 — Gemini/CodeRabbit/Greptile: openrouter/ runs were falsely blocked.)

llm/env.py

  • Added OPENROUTER_API_KEY to _PROVIDER_DEFAULTS (zero-config default) + fallback provider scan.
  • _provider_from_model now maps bare claude…/gemini… to Anthropic/Gemini, matching is_configured. (CodeRabbit: gate/endpoint mismatch.)
  • Scoped stored-credential fallback to the resolved provider for explicit/config models (via _KEY_VAR_TO_FALLBACK_PROVIDERS); full scan only for inferred defaults. (CodeRabbit: prevented cross-provider key/model mixing.)

cli/app.py & cli/commands/run.py — Call inject_credentials_into_env() before (and after setup) the is_configured() gate, matching cli/main.py. (CodeRabbit: setup-stored creds passed gate but weren't in env.) Added None guards on import_wrapper_module in observability setup. (Gemini.)

Verified live: is_configured('openrouter/…')=True with only OPENROUTER_API_KEY; default model → openrouter/openai/gpt-4o-mini; bare claude/gemini → correct provider metadata.

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added smoke assertion is already correct and in scope.
  • run.py:549 wrapper import (from praisonai.cli.commands.setup import _run_setup) — pre-existing, inside the interactive-only setup branch, out of scope for this PR's flagged issues. Recommend a follow-up to route it through _wrapper_bridge for full standalone parity.

Phase 3 — Verdict

Approve. All blocking (P1) reviewer findings resolved, backward compat preserved, 94/94 tests green. The OpenRouter gate gap was pre-existing (env.py resolved openrouter/ but the gate never did); this PR now makes them consistent. Ready to merge once CI passes.


Job run • branch feat/c7-llm-credentials-standalone

…lback

- Bind __all__ names in praisonai_code.llm/__init__.py so direct/star imports work
- Add OpenRouter to credential gate (env mapping, model-key vars, stored-provider
  map, known_keys) and to env.py provider defaults + fallback scan
- Align _provider_from_model with is_configured for bare claude/gemini model ids
- Scope stored-credential fallback to the resolved provider for explicit/config
  models; only scan all providers for inferred defaults
- Inject stored credentials before is_configured gate in app.py/run.py
- Guard None from import_wrapper_module in observability setup (standalone mode)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

Verified all reviewer findings (Gemini, CodeRabbit, Greptile, Qodo, Copilot) against the current branch state. All valid P1/major issues are already resolved in commit 0507797d0. No further code changes were required this round.

  • Read new LLM modules (env.py, credentials.py, __init__.py)
  • Read CLI call sites (app.py, run.py, main.py)
  • Verify OpenRouter wiring (Gemini/CodeRabbit/Greptile P1)
  • Verify __all__ bindings (Greptile/CodeRabbit P1)
  • Verify inject_credentials_into_env before gate (CodeRabbit)
  • Verify None guards on import_wrapper_module (Gemini)
  • Ran tests (94 passed) + live behavioral verification
  • Post final verdict

Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl now in praisonai-code; wrapper reduced to sys.modules shims (praisonai/llm/{env,credentials}.py −501 lines). Correct routing.
Backward compatible ✅ Shims are identity-preserving (praisonai.llm.credentials is praisonai_code.llm.credentials), verified by test_c5_backward_compat. Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Provider maps are module-level constants; wrapper/catalogue imports stay lazy (inside functions). No new module-level heavy imports.
SDK value ✅ Genuine value — unblocks standalone praisonai-code install (credential gate works without the praisonai wrapper). Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Reviewer Findings: all confirmed FIXED

Finding (reviewer) Status Evidence
__all__ names not bound → ImportError (Greptile/CodeRabbit P1) llm/__init__.py:5-14 imports all 6 symbols from .env/.credentials.
OpenRouter missing from env_mappings (Gemini) credentials.py:69 "openrouter": "OPENROUTER_API_KEY".
openrouter/ missing in _provider_key_vars_for_model (Gemini/Greptile) credentials.py:113-114.
OPENROUTER_API_KEY missing in _VAR_TO_STORED_PROVIDERS (Gemini) credentials.py:135.
OPENROUTER_API_KEY missing in known_keys (Gemini) credentials.py:155.
OpenRouter missing from _PROVIDER_DEFAULTS (CodeRabbit) env.py:65.
Stored OpenRouter creds skipped in fallback (Greptile/Gemini) env.py:51,54 + _ALL_FALLBACK_PROVIDERS includes openrouter.
_provider_from_model mismatch for bare claude/gemini (CodeRabbit) env.py:118-122.
Cross-provider key/model mixing in fallback (CodeRabbit) env.py:44-52,172-194 scope fallback via _KEY_VAR_TO_FALLBACK_PROVIDERS for explicit/config models; full scan only for inferred defaults.
inject_credentials_into_env() before gate in TUI (CodeRabbit) cli/app.py:706,734.
inject_credentials_into_env() before run gate (CodeRabbit) cli/commands/run.py:538,566.
None guards on import_wrapper_module (Gemini) cli/app.py:25-26,68-69.

Verification (live, standalone — no praisonai wrapper installed)

standalone star-imports OK
is_configured('openrouter/openai/gpt-4o'): True     # only OPENROUTER_API_KEY set
default model: openrouter/openai/gpt-4o-mini
resolve endpoint model: openrouter/openai/gpt-4o-mini | key set: True
claude -> ('ANTHROPIC_API_KEY', 'https://api.anthropic.com/v1')
gemini -> ('GEMINI_API_KEY', 'https://generativelanguage.googleapis.com/v1beta')

Tests: 94 passed (test_c5_backward_compat, test_env_resolver, test_credentials_is_configured, test_init_provider_guard).

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files. The added is_configured smoke assertion is already correct and in scope; no change needed.
  • run.py:549 from praisonai.cli.commands.setup import _run_setup — pre-existing, inside the interactive-only setup branch; out of scope for this PR's flagged issues. Recommend a follow-up to route through _wrapper_bridge for full standalone parity (non-blocking).
  • CodeRabbit "Autofix" / docstring / unit-test generation checkboxes — optional bot suggestions, not blocking.

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings are resolved on feat/c7-llm-credentials-standalone (0507797d0). Backward compat preserved (identity shims), OpenRouter gate/endpoint now consistent, 94/94 tests green, standalone import verified without the wrapper. Ready to merge once CI passes.

Note: I cannot submit a formal GitHub PR approval (bot security restriction) — this comment serves as the review verdict.


Job run • branch feat/c7-llm-credentials-standalone

…__all__

- catalogue.py: use 'is not None' so zero-cost (free/local) models keep 0.0 cost instead of falling back to None (Gemini)
- catalogue.py: validate_model now does a single describe_model lookup instead of is_valid_model + describe_model double pass (Gemini)
- llm/__init__.py: bind all names advertised in __all__ so 'from praisonai_code.llm import *' and named imports resolve (Greptile)

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
Comment thread src/praisonai-code/praisonai_code/llm/credentials.py
C7: move LLM catalogue and config to praisonai-code
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + checkout branch
  • Read new LLM modules (env.py, credentials.py, __init__.py)
  • Verify latest Greptile finding (credentials.py:88 — stored endpoint metadata lost)
  • Fix still-valid issue in env.py
  • Run tests (90 passed) + behavioral verification
  • Commit, push & post verdict

Pushed 9a7b30534 to feat/c7-llm-credentials-standalone.


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl in praisonai-code; wrapper reduced to identity-preserving sys.modules shims (−501 lines).
Backward compatible praisonai.llm.{env,credentials} is praisonai_code.llm.* (verified by test_c5_backward_compat). Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Provider maps are module-level constants; catalogue/wrapper imports stay lazy. New store consult only fires on the zero-config default path when a fallback_lookup is supplied.
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate without the wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fix Applied This Round

llm/env.pyStored endpoint metadata lost (Greptile P1, credentials.py:88).
Previously, once inject_credentials_into_env() exported a stored key (e.g. OPENROUTER_API_KEY), resolve_llm_endpoint() saw the env key set and skipped the credential-store fallback entirely — so a stored credential's custom model/base_url were dropped for the hard-coded provider default.

Fix: for inferred (zero-config) default models only, consult the stored credential of the resolved provider to recover its saved model/base_url. Guardrails prevent regressions:

  • Explicit MODEL_NAME/config models are not overridden.
  • Env base_url (OPENAI_BASE_URL/OPENAI_API_BASE/OLLAMA_API_BASE) still wins.
  • Scoped to the single provider via _KEY_VAR_TO_FALLBACK_PROVIDERS — no cross-provider key/model mixing.

Behavioral verification (live):

stored OpenRouter (custom model+base) + injected OPENROUTER_API_KEY, no MODEL_NAME
 → model=openrouter/anthropic/claude-3-opus  base_url=https://custom.openrouter.example/api/v1  ✅
explicit MODEL_NAME=openrouter/openai/gpt-4o  → not overridden  ✅
env OPENAI_BASE_URL set                       → env base wins    ✅
no fallback_lookup (plain path)               → gpt-4o-mini      ✅ (unchanged)

Confirmed Already-Fixed (prior commit 0507797d0)

__all__ bindings (Greptile/CodeRabbit P1) · OpenRouter in env_mappings/_provider_key_vars_for_model/_VAR_TO_STORED_PROVIDERS/known_keys (Gemini/CodeRabbit/Greptile P1) · _PROVIDER_DEFAULTS OpenRouter (CodeRabbit) · bare claude/gemini → correct provider in _provider_from_model (CodeRabbit) · scoped fallback via _KEY_VAR_TO_FALLBACK_PROVIDERS (CodeRabbit) · inject_credentials_into_env() before gate in app.py/run.py (CodeRabbit) · None guards on import_wrapper_module (Gemini).

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py wrapper _run_setup import — pre-existing, inside the interactive-only setup branch; out of scope. Recommend a follow-up to route through _wrapper_bridge for full standalone parity (non-blocking).

Tests

90 passed (test_env_resolver, test_credentials_is_configured, test_c5_backward_compat). Standalone imports of is_configured/LLMEndpoint/resolve_llm_endpoint/inject_credentials_into_env confirmed without the wrapper.

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings — including the latest Greptile stored-endpoint issue — are resolved. Backward compat preserved via identity shims, OpenRouter gate/endpoint now consistent, and stored endpoint metadata is honoured after injection. 90/90 tests green.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


• branch feat/c7-llm-credentials-standalone • commit 9a7b30534

Comment thread src/praisonai-code/praisonai_code/llm/config.py Outdated
When inject_credentials_into_env() exports a stored provider key (e.g.
OPENROUTER_API_KEY) into the environment, resolve_llm_endpoint saw the key
already set and skipped the credential-store fallback, so a stored credential's
custom model/base_url were dropped in favour of the hard-coded provider default.

For inferred (zero-config) default models only, consult the stored credential
of the resolved provider to recover its saved model/base_url. Explicit
MODEL_NAME/config models and env base_url still take precedence, so no
cross-provider mixing occurs. Addresses Greptile finding at credentials.py:88.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + checkout branch
  • Read new LLM modules (config.py, env.py, credentials.py)
  • Verify latest Greptile finding (config.py:12 — stored endpoint dropped)
  • Fix still-valid issue
  • Run tests (90 passed) + behavioral verification
  • Commit, push & post verdict

Pushed 531cabff1 to feat/c7-llm-credentials-standalone.


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl in praisonai-code; wrapper reduced to identity-preserving sys.modules shims (−501 lines). Correct routing.
Backward compatible praisonai.llm.{env,credentials,config} is praisonai_code.llm.* (verified by test_c5_backward_compat). Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Provider maps are module-level constants; catalogue/credential imports stay lazy (inside build_config_list). Store consult only fires when the env key is absent or a stored model/base_url exists.
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate + runtime config without the wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fix Applied This Round

llm/config.pyStored endpoint dropped in build_config_list (Greptile P1, config.py:12).

build_config_list() called the env-only resolve_llm_endpoint(). Once inject_credentials_into_env() exported a stored key (e.g. OPENROUTER_API_KEY), the builder saw the env key set but never consulted the credential store, so a stored credential's custom model/base_url were dropped in favour of the hard-coded provider default. It now routes through the credential-store aware resolve_llm_endpoint_with_credentials(), with a graceful fallback to the env-only resolver if the bridge is unavailable.

Guardrails (verified live):

stored OpenRouter (custom model+base) + injected OPENROUTER_API_KEY, no MODEL_NAME
 → model=openrouter/anthropic/claude-3-opus  base_url=https://custom.openrouter.example/api/v1  ✅
explicit MODEL_NAME=gpt-4o                    → not overridden (model=gpt-4o)  ✅
credential bridge unavailable                 → falls back to env-only resolver ✅

Env vars (MODEL_NAME, OPENAI_BASE_URL, …) still win inside the resolver — no cross-provider mixing (scoped via _KEY_VAR_TO_FALLBACK_PROVIDERS).

Confirmed Already-Fixed (prior commits 0507797d0, 9a7b30534)

__all__ bindings (Greptile/CodeRabbit P1) · OpenRouter in env_mappings/_provider_key_vars_for_model/_VAR_TO_STORED_PROVIDERS/known_keys (Gemini/CodeRabbit/Greptile P1) · _PROVIDER_DEFAULTS OpenRouter (CodeRabbit) · bare claude/gemini → correct provider in _provider_from_model (CodeRabbit) · scoped fallback via _KEY_VAR_TO_FALLBACK_PROVIDERS (CodeRabbit) · inject_credentials_into_env() before gate in app.py/run.py (CodeRabbit) · None guards on import_wrapper_module (Gemini) · stored-endpoint recovery in env.py after injection (Greptile).

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py wrapper _run_setup import — pre-existing, inside the interactive-only setup branch; out of scope. Recommend a follow-up to route through _wrapper_bridge for full standalone parity (non-blocking).
  • CodeRabbit Autofix / docstring / unit-test-gen checkboxes — optional bot suggestions, not blocking.

Tests

90 passed (test_env_resolver, test_credentials_is_configured, test_c5_backward_compat). Standalone imports of is_configured/LLMEndpoint/resolve_llm_endpoint/inject_credentials_into_env/build_config_list confirmed without the wrapper. (13 environment-only import errors — typer/dotenv/toml missing in this runner — resolved by installing deps; not code issues.)

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings — including the latest Greptile config.py stored-endpoint issue — are now resolved. Backward compat preserved via identity shims, OpenRouter gate/endpoint consistent, and stored endpoint metadata honoured through both resolve_llm_endpoint and build_config_list. 90/90 tests green.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


Job run • branch feat/c7-llm-credentials-standalone • commit 531cabff1

Comment thread src/praisonai-code/praisonai_code/llm/config.py Outdated
Route build_config_list through the credential-store aware resolver so a
stored credential's own model/base_url (e.g. a saved OpenRouter endpoint)
is used after inject_credentials_into_env exports only the API key. Falls
back to the env-only resolver if the credential bridge is unavailable;
environment variables still take precedence inside the resolver.

Addresses Greptile P1 (config.py:12 — stored endpoint dropped).

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + read AGENTS.md
  • Read LLM modules (env.py, credentials.py, config.py, __init__.py)
  • Verify all reviewer findings against current code
  • Fix still-valid issue (__init__.py cleanup)
  • Run tests (77 passed)
  • Commit, push & post verdict

Pushed 59b6e0413 to feat/c7-llm-credentials-standalone.


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl lives in praisonai-code; wrapper reduced to identity-preserving sys.modules shims (praisonai/llm/{env,credentials,config,catalogue}.py, −986 lines). Correct routing.
Backward compatible praisonai.llm.{env,credentials,config,catalogue} is praisonai_code.llm.* — verified by test_c5_backward_compat (19 passed). Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Provider maps are module-level constants; catalogue/credential/wrapper imports stay lazy (inside functions).
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate + runtime config without the praisonai wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fix Applied This Round

llm/__init__.py — the exports (all 9 symbols) were imported twice (a redundant block before __all__ and again after). Deduplicated to a single import block and sorted __all__ (RUF022). No behavioral change — every advertised export remains bound; verified standalone direct + star imports still succeed.

Reviewer Findings — all confirmed FIXED on current branch

Finding (reviewer) Status Evidence
__all__ names not bound → ImportError (Greptile/CodeRabbit P1) llm/__init__.py:5-16 binds all symbols.
OpenRouter missing from env_mappings (Gemini/CodeRabbit) credentials.py:69.
openrouter/ missing in _provider_key_vars_for_model (Gemini) credentials.py:113-114.
OPENROUTER_API_KEY missing in _VAR_TO_STORED_PROVIDERS (Gemini) credentials.py:135.
OPENROUTER_API_KEY missing in known_keys (Gemini) credentials.py:155.
OpenRouter missing from _PROVIDER_DEFAULTS (CodeRabbit) env.py:65.
Stored OpenRouter creds skipped in fallback (Greptile/Gemini) env.py:53-55 _ALL_FALLBACK_PROVIDERS.
bare claude/gemini → wrong provider in _provider_from_model (CodeRabbit) env.py:118-121.
Cross-provider key/model mixing in fallback (CodeRabbit) env.py:44-52,173-176 scoped via _KEY_VAR_TO_FALLBACK_PROVIDERS.
Stored endpoint metadata lost after injection (Greptile P1) env.py:194-218 recovers stored model/base_url for inferred defaults.
build_config_list drops stored endpoint (Greptile P1) config.py:17-26 routes through resolve_llm_endpoint_with_credentials() with env-only fallback.
inject_credentials_into_env() before gate in TUI (CodeRabbit) cli/app.py:706,734.
inject_credentials_into_env() before run gate (CodeRabbit) cli/commands/run.py:538,566.
None guards on import_wrapper_module (Gemini) cli/app.py observability setup.

Verification (live, standalone — no praisonai wrapper)

standalone imports OK · star import OK
is_configured('openrouter/openai/gpt-4o'): True   # only OPENROUTER_API_KEY set
default model: openrouter/openai/gpt-4o-mini

Tests: 77 passedtest_env_resolver (39) + test_credentials_is_configured (19) + test_c5_backward_compat::TestImportPathAliases (19). Shim identity confirmed for env/credentials/config/catalogue.

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py:553 from praisonai.cli.commands.setup import _run_setup — pre-existing, inside the interactive-only setup branch; out of scope for this PR's flagged issues. Recommend a follow-up to route through _wrapper_bridge for full standalone parity (non-blocking).
  • CodeRabbit Autofix / docstring / unit-test-gen checkboxes — optional bot suggestions, not blocking.

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings are resolved on feat/c7-llm-credentials-standalone. Backward compat preserved via identity shims, OpenRouter gate/endpoint consistent, and stored endpoint metadata honoured through both resolve_llm_endpoint and build_config_list. 77/77 tests green, standalone import verified without the wrapper.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


• branch feat/c7-llm-credentials-standalone • commit 59b6e0413

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/praisonai-code/praisonai_code/llm/catalogue.py (1)

265-270: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep graceful fallback observable.

These paths silently collapse corrupted cache, permission, and litellm metadata failures into fallback mode. Add debug logging while preserving the non-fatal behavior.

Proposed fix
 import os
 import json
 import time
 import difflib
+import logging
 from pathlib import Path
@@
 from dataclasses import dataclass, asdict
 
+logger = logging.getLogger(__name__)
+
@@
-        except Exception:
+        except Exception:  # noqa: BLE001 - optional integration should not break fallback
             # Error loading from litellm, fall back
+            logger.debug("Failed to load model metadata from litellm", exc_info=True)
             return None
@@
-        except Exception:
+        except (OSError, json.JSONDecodeError, TypeError, ValueError, AttributeError):
+            logger.debug("Ignoring invalid model catalogue cache at %s", self.cache_file, exc_info=True)
             return None
@@
-        except Exception:
+        except (OSError, TypeError, ValueError):
             # Ignore cache write errors
-            pass
+            logger.debug("Unable to write model catalogue cache at %s", self.cache_file, exc_info=True)

Also applies to: 282-316

🤖 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-code/praisonai_code/llm/catalogue.py` around lines 265 - 270,
The fallback paths in the litellm catalogue loading logic are swallowing errors
silently, so add debug-level logging before returning None in the exception
handlers. Update the ImportError and broad Exception branches in the catalogue
load flow, including the related fallback blocks around the same litellm
metadata/cache code path, to record the exception/context while keeping the
behavior non-fatal. Use the existing catalogue-loading entrypoints and helper
methods in praisonai_code.llm.catalogue so the graceful fallback remains
observable without changing return values.

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-code/praisonai_code/llm/catalogue.py`:
- Around line 68-87: The o1 and o1-mini fallback ModelInfo entries currently
claim “No streaming” in notes but still inherit streaming support, so update
those specific ModelInfo definitions in catalogue.py to explicitly set
supports_streaming=False alongside supports_tools=False. Keep the change limited
to the o1 fallback entries so models describe reflects the actual capabilities
shown by the notes.
- Around line 373-459: The model validation path in
validate_model/describe_model should normalize provider-qualified IDs before
treating them as unknown, since the fallback catalogue may store the same model
as a bare ID. Update the lookup flow to strip or map prefixes like anthropic/,
groq/, and ollama/ when matching against the fallback data, while keeping the
existing exact and case-insensitive checks intact. Use _load_from_litellm and
the fallback catalogue matching logic as the reference points, and preserve
backward compatibility by accepting both prefixed and bare forms rather than
changing the public API.

In `@src/praisonai-code/praisonai_code/llm/config.py`:
- Around line 10-12: build_config_list() is using the endpoint resolver
directly, which can create a config entry with api_key=None even after
credentials have been accepted. Update the logic in build_config_list() to call
resolve_llm_endpoint_with_credentials() instead of resolve_llm_endpoint(), and
keep the rest of the config assembly unchanged so the CLI runtime path always
uses the credential-aware fallback.

---

Nitpick comments:
In `@src/praisonai-code/praisonai_code/llm/catalogue.py`:
- Around line 265-270: The fallback paths in the litellm catalogue loading logic
are swallowing errors silently, so add debug-level logging before returning None
in the exception handlers. Update the ImportError and broad Exception branches
in the catalogue load flow, including the related fallback blocks around the
same litellm metadata/cache code path, to record the exception/context while
keeping the behavior non-fatal. Use the existing catalogue-loading entrypoints
and helper methods in praisonai_code.llm.catalogue so the graceful fallback
remains observable without changing return values.
🪄 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: 0fbaf970-6661-4034-93dc-75175d6f6af1

📥 Commits

Reviewing files that changed from the base of the PR and between 0507797 and 3e6fa0f.

📒 Files selected for processing (11)
  • .github/workflows/test-optimized.yml
  • src/praisonai-code/README.md
  • src/praisonai-code/praisonai_code/cli/commands/models.py
  • src/praisonai-code/praisonai_code/cli/main.py
  • src/praisonai-code/praisonai_code/llm/__init__.py
  • src/praisonai-code/praisonai_code/llm/catalogue.py
  • src/praisonai-code/praisonai_code/llm/config.py
  • src/praisonai-code/praisonai_code/llm/env.py
  • src/praisonai/praisonai/llm/catalogue.py
  • src/praisonai/praisonai/llm/config.py
  • src/praisonai/tests/unit/test_c5_backward_compat.py
✅ Files skipped from review due to trivial changes (2)
  • src/praisonai/praisonai/llm/config.py
  • src/praisonai-code/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/praisonai-code/praisonai_code/cli/main.py
  • .github/workflows/test-optimized.yml
  • src/praisonai-code/praisonai_code/llm/env.py

Comment thread src/praisonai-code/praisonai_code/llm/catalogue.py
Comment thread src/praisonai-code/praisonai_code/llm/catalogue.py
Comment thread src/praisonai-code/praisonai_code/llm/config.py Outdated
Remove duplicate re-imports in praisonai_code.llm.__init__ and sort
__all__ (RUF022). No behavioral change; all exports remain bound.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + checkout branch
  • Read LLM modules (catalogue.py, config.py, env.py, credentials.py, __init__.py)
  • Verify newest CodeRabbit findings (catalogue.py o1 streaming + provider-qualified ID normalization)
  • Fix still-valid issues
  • Run tests (90 passed)
  • Commit, push & post verdict

Pushed f77f0a8fe to feat/c7-llm-credentials-standalone.


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl in praisonai-code; wrapper reduced to identity-preserving sys.modules shims (praisonai/llm/{env,credentials,config,catalogue}.py). Correct routing.
Backward compatible praisonai.llm.{env,credentials,config,catalogue} is praisonai_code.llm.* — verified by test_c5_backward_compat. Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Catalogue lookups stay O(n) over a small fallback list; litellm/credential imports remain lazy. New provider-qualified branch only runs on cache/litellm-miss.
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate + model catalogue without the praisonai wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fixes Applied This Round (llm/catalogue.py)

Both from CodeRabbit's latest review (18:08:35Z) — verified still-valid against current code:

  1. o1/o1-mini supports_streaming — notes said "No streaming" but the entries inherited the True default, so models describe misreported streaming support. Set supports_streaming=False on both fallback entries.
  2. Provider-qualified ID normalization_load_from_litellm handles groq/…, anthropic/…, ollama/…, but the fallback catalogue stores those as bare IDs. When litellm is unavailable, describe_model("groq/llama-3.3-70b-versatile") / validate_model(...) wrongly returned None/raised. describe_model now splits provider/bare and matches against (provider, bare id) after exact + case-insensitive checks. Unknown providers still return None.

Behavioral verification (fallback-only, litellm mocked out):

o1 supports_streaming: False          (expect False) ✅
o1-mini supports_streaming: False     (expect False) ✅
openai/gpt-4o resolves: gpt-4o        (expect gpt-4o) ✅
anthropic/claude-3-5-sonnet-latest → claude-3-5-sonnet-latest ✅
validate('openai/gpt-4o') → gpt-4o    ✅
unknown/foo → None                    ✅ (unchanged)

Confirmed Already-Fixed (prior commits 0507797d059b6e0413)

__all__ bindings (Greptile/CodeRabbit P1) · OpenRouter in env_mappings/_provider_key_vars_for_model/_VAR_TO_STORED_PROVIDERS/known_keys (Gemini/CodeRabbit/Greptile P1) · _PROVIDER_DEFAULTS OpenRouter (CodeRabbit) · bare claude/gemini → correct provider (CodeRabbit) · scoped fallback via _KEY_VAR_TO_FALLBACK_PROVIDERS (CodeRabbit) · stored-endpoint recovery in env.py after injection (Greptile P1) · build_config_listresolve_llm_endpoint_with_credentials() (Greptile/CodeRabbit P1) · inject_credentials_into_env() before gate in app.py/run.py (CodeRabbit) · None guards on import_wrapper_module (Gemini).

Skipped (with reason)

  • CodeRabbit catalogue debug-logging nitpick (🔵 Trivial) — silent fallback in cache/litellm handlers is intentional graceful degradation; adding a logging import is optional polish, not a correctness issue. Not blocking.
  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py wrapper _run_setup import — pre-existing, inside the interactive-only setup branch; out of scope. Follow-up recommended to route through _wrapper_bridge for full standalone parity (non-blocking).

Tests

90 passedtest_env_resolver + test_credentials_is_configured + test_c5_backward_compat (shim identity for env/credentials/config/catalogue + CLI wrappers). Standalone imports of the full praisonai_code.llm surface + ModelCatalogue confirmed without the wrapper. (8 initial failures were missing-dep typer/pytest_asyncio in this runner — resolved by installing deps; not code issues.)

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings resolved — including the two latest CodeRabbit catalogue issues. Backward compat preserved via identity shims, OpenRouter gate/endpoint consistent, stored endpoint metadata honoured through resolve_llm_endpoint + build_config_list, and the model catalogue now reports accurate streaming flags and resolves provider-qualified IDs in fallback mode. 90/90 tests green.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


• branch feat/c7-llm-credentials-standalone • commit f77f0a8feJob run

Comment thread src/praisonai-code/praisonai_code/llm/env.py Outdated
…l ids

- Set supports_streaming=False on o1/o1-mini fallback entries so
  `models describe` reflects their "No streaming" notes (CodeRabbit)
- describe_model now matches provider-qualified ids (e.g. groq/llama...)
  against bare fallback ids when litellm is unavailable (CodeRabbit)

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + checkout branch
  • Read LLM modules (env.py, credentials.py, config.py)
  • Verify latest Greptile finding (env.py:194 — explicit models skip stored endpoint metadata)
  • Fix still-valid issue in env.py
  • Run tests (77 passed) + behavioral verification
  • Commit, push & post verdict

Pushed 08798792e to feat/c7-llm-credentials-standalone.


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl in praisonai-code; wrapper reduced to identity-preserving sys.modules shims. Correct routing.
Backward compatible praisonai.llm.{env,credentials,config,catalogue} is praisonai_code.llm.* — verified live + test_c5_backward_compat. Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Fallback only fires when a fallback_lookup is supplied and scans a single provider (scoped via _KEY_VAR_TO_FALLBACK_PROVIDERS); no new module-level heavy imports.
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate + honours saved custom endpoints without the wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Fix Applied This Round

llm/env.pyExplicit models skip stored endpoint metadata (Greptile P1, env.py:194).

Previously the stored-endpoint recovery branch was gated on not explicit_or_config_model, so once inject_credentials_into_env() exported a stored key (e.g. OPENROUTER_API_KEY) and the user set MODEL_NAME/config/--model, the resolver saw the env key and dropped the stored credential's custom base_url — sending requests to the provider default URL. inject_credentials_into_env() only mirrors base_url for OpenAI, so non-OpenAI custom endpoints were silently lost.

Fix: the recovery branch now runs whenever the key is present and a fallback_lookup is supplied. Guardrails prevent regressions:

  • Explicit/config models: only the stored base_url is recovered — the user-requested model is never overridden.
  • Inferred defaults: stored model + base_url are both adopted (unchanged behaviour).
  • Env/config base_url always wins (OPENAI_BASE_URL/OPENAI_API_BASE/OLLAMA_API_BASE or resolved_config.agent.base_url).
  • Scoped to the single resolved provider via _KEY_VAR_TO_FALLBACK_PROVIDERS — no cross-provider mixing.

Behavioral verification (live):

S1 explicit MODEL_NAME=openrouter/openai/gpt-4o + injected OPENROUTER_API_KEY, stored custom base
   → model=openrouter/openai/gpt-4o (NOT overridden)  base=https://custom.openrouter.example/api/v1 ✅
S2 inferred default (no MODEL_NAME)
   → model=openrouter/anthropic/claude-3-opus  base=https://custom.openrouter.example/api/v1 ✅
S3 explicit model + OPENAI_BASE_URL set  → env base wins (https://env.example/v1) ✅
S4 no fallback_lookup (plain path)       → gpt-4o-mini / api.openai.com (unchanged) ✅

Confirmed Already-Fixed (prior commits 0507797d0f77f0a8fe)

__all__ bindings (Greptile/CodeRabbit P1) · OpenRouter in env_mappings/_provider_key_vars_for_model/_VAR_TO_STORED_PROVIDERS/known_keys (Gemini/CodeRabbit/Greptile P1) · _PROVIDER_DEFAULTS OpenRouter (CodeRabbit) · bare claude/gemini → correct provider (CodeRabbit) · scoped fallback via _KEY_VAR_TO_FALLBACK_PROVIDERS (CodeRabbit) · build_config_listresolve_llm_endpoint_with_credentials() (Greptile/CodeRabbit P1) · inject_credentials_into_env() before gate in app.py/run.py (CodeRabbit) · None guards on import_wrapper_module (Gemini) · o1/o1-mini supports_streaming=False + provider-qualified catalogue IDs (CodeRabbit).

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py wrapper _run_setup import — pre-existing, inside the interactive-only setup branch; out of scope. Follow-up recommended to route through _wrapper_bridge for full standalone parity (non-blocking).
  • CodeRabbit catalogue debug-logging nitpick (🔵 Trivial) — intentional graceful degradation, optional polish, non-blocking.

Tests

77 passedtest_env_resolver + test_credentials_is_configured + test_c5_backward_compat::TestImportPathAliases. Shim identity confirmed (praisonai.llm.env is praisonai_code.llm.env); standalone imports of resolve_llm_endpoint/LLMEndpoint/is_configured/inject_credentials_into_env verified without the wrapper.

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings — including the latest Greptile explicit-model stored-endpoint issue — are resolved. Backward compat preserved via identity shims, OpenRouter gate/endpoint consistent, and stored base_url is now honoured for both explicit and inferred models after injection. 77/77 tests green, standalone import verified without the wrapper.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


• branch feat/c7-llm-credentials-standalone • commit 08798792e

Comment thread src/praisonai-code/praisonai_code/llm/env.py Outdated
… injection

For explicit/config models, inject_credentials_into_env() only exports the
API key (not the stored base_url) for non-OpenAI providers, and the resolver
previously skipped the credential-store fallback entirely once the key was in
env. A stored OpenRouter/Groq/Gemini credential with a custom base_url could
pass the gate but send requests to the provider default URL.

Now recover the stored base_url (never overriding the user-requested model)
for explicit models, scoped to the resolved provider; env/config base_url still
wins. Inferred defaults keep adopting the stored model as before.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner Author

@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

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2548

  • Setup git identity + read AGENTS.md
  • Read LLM modules (env.py, credentials.py, config.py, catalogue.py, __init__.py)
  • Verify latest reviewer findings against current code (Greptile 18:16Z explicit-model base_url)
  • Run tests (96 passed) + live behavioral verification
  • Post verdict

No new code changes required this round — every valid reviewer finding is already resolved on the current branch (08798792e).


Phase 1 — Architecture (AGENTS.md)

Criterion Verdict
Protocol-driven ✅ Heavy impl in praisonai-code; wrapper reduced to identity-preserving sys.modules shims (praisonai/llm/{env,credentials,config,catalogue}.py). Correct routing.
Backward compatible praisonai.llm.{env,credentials,config,catalogue} is praisonai_code.llm.* — verified by test_c5_backward_compat. Existing patches/imports keep working.
Performance ✅ No hot-path regressions. Provider maps are module-level constants; catalogue/credential/wrapper imports stay lazy. Stored-credential fallback only fires when a fallback_lookup is supplied and is scoped to a single provider.
SDK value ✅ Genuine — unblocks standalone praisonai-code credential gate + runtime config without the praisonai wrapper. Not scope creep.
Agent bloat ✅ N/A — no Agent params added.

Phase 2 — Reviewer Findings: all confirmed FIXED

Finding (reviewer) Status Evidence
__all__ names not bound → ImportError (Greptile/CodeRabbit P1) llm/__init__.py:5-16 binds all 9 symbols.
OpenRouter missing across gate (env_mappings/_provider_key_vars_for_model/_VAR_TO_STORED_PROVIDERS/known_keys) (Gemini/CodeRabbit/Greptile P1) credentials.py:69,113,135,155.
_PROVIDER_DEFAULTS OpenRouter (CodeRabbit) env.py:65.
Stored OpenRouter creds skipped in fallback (Greptile/Gemini) env.py:51,53-55 _ALL_FALLBACK_PROVIDERS.
bare claude/gemini → wrong provider (CodeRabbit) env.py:118-121.
Cross-provider key/model mixing in fallback (CodeRabbit) env.py:44-52,173-176 scoped via _KEY_VAR_TO_FALLBACK_PROVIDERS.
Stored endpoint metadata lost after injection (Greptile P1) env.py:194-227 recovery branch.
build_config_list drops stored endpoint (Greptile/CodeRabbit P1) config.py:17-26resolve_llm_endpoint_with_credentials() with env-only fallback.
Explicit models skip stored base_url (Greptile 18:16Z P1) env.py:194-227 — explicit models now recover stored base_url (model not overridden).
o1/o1-mini supports_streaming=False (CodeRabbit) catalogue.py:76,87.
Provider-qualified catalogue IDs in fallback (CodeRabbit) catalogue.py:401-405.
inject_credentials_into_env() before gate in app.py/run.py (CodeRabbit) present in both call sites.
None guards on import_wrapper_module (Gemini) cli/app.py observability setup.

Verification (live, standalone — no praisonai wrapper)

standalone imports OK; star import OK
is_configured('openrouter/openai/gpt-4o'): True   # only OPENROUTER_API_KEY set
default model: openrouter/openai/gpt-4o-mini
claude -> ('ANTHROPIC_API_KEY', 'https://api.anthropic.com/v1')
gemini -> ('GEMINI_API_KEY', 'https://generativelanguage.googleapis.com/v1beta')

# Explicit-model stored base_url recovery (Greptile 18:16Z):
S1 explicit  MODEL_NAME=openrouter/openai/gpt-4o + stored custom base
   -> model=openrouter/openai/gpt-4o (NOT overridden)  base=https://custom.openrouter.example/api/v1 ✅
S2 inferred  -> model=openrouter/anthropic/claude-3-opus  base=https://custom.openrouter.example/api/v1 ✅
S3 explicit + OPENAI_BASE_URL set -> env base wins (https://env.example/v1) ✅

Tests: 96 passedtest_env_resolver + test_credentials_is_configured + test_c5_backward_compat + test_init_provider_guard.

Skipped (with reason)

  • .github/workflows/test-optimized.yml — GitHub App cannot modify workflow files; the added is_configured smoke assertion is already correct and in scope.
  • run.py wrapper _run_setup import — pre-existing, inside the interactive-only setup branch; out of scope. Recommend a follow-up to route through _wrapper_bridge for full standalone parity (non-blocking).
  • CodeRabbit catalogue debug-logging nitpick (🔵 Trivial) — intentional graceful degradation; optional polish, non-blocking.

Phase 3 — Verdict

Approve. All blocking (P1) and major reviewer findings — including the latest Greptile explicit-model stored-base_url issue — are resolved on feat/c7-llm-credentials-standalone (08798792e). Backward compat preserved via identity shims, OpenRouter gate/endpoint consistent, stored endpoint metadata honoured through resolve_llm_endpoint + build_config_list, and the catalogue reports accurate streaming flags + resolves provider-qualified IDs in fallback. 96/96 tests green, standalone import verified without the wrapper.

Note: I can't submit a formal GitHub PR approval (bot restriction) — this comment is the review verdict. Merge once CI passes.


• branch feat/c7-llm-credentials-standalone • commit 08798792eJob run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/praisonai-code/praisonai_code/llm/config.py (1)

17-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow the fallback to ImportError instead of blind Exception.

Catching Exception here doesn't just guard against the credentials module being unavailable — it also silently swallows any runtime error raised inside resolve_llm_endpoint_with_credentials() (e.g. a bug in credential lookup or catalogue validation), silently downgrading to the env-only resolver and potentially reintroducing an api_key=None entry with no visible error. Since the intent is clearly "fall back if the credentials bridge module isn't importable," scope the catch accordingly.

♻️ Proposed fix
     try:
         from praisonai_code.llm.credentials import (
             resolve_llm_endpoint_with_credentials,
         )
 
         ep = resolve_llm_endpoint_with_credentials()
-    except Exception:
+    except ImportError:
         from praisonai_code.llm.env import resolve_llm_endpoint
 
         ep = resolve_llm_endpoint()
🤖 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-code/praisonai_code/llm/config.py` around lines 17 - 26, The
fallback in config.py is too broad: the current try/except around
resolve_llm_endpoint_with_credentials() catches all Exception and can hide real
runtime failures. Narrow the exception handling to ImportError only, so the
fallback to resolve_llm_endpoint() in the config module is used only when the
credentials bridge cannot be imported. Keep the logic centered on
resolve_llm_endpoint_with_credentials and resolve_llm_endpoint, and avoid
swallowing other errors.

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-code/praisonai_code/llm/env.py`:
- Around line 207-223: The fallback metadata lookup in env.py is reusing stored
model/base_url data without verifying it belongs to the same API key. Update the
fallback_lookup/provider handling in this branch so `cred["model"]` and
`cred["base_url"]` are only adopted when the stored credential key matches the
active env/config API key, and otherwise skip that stored metadata. Keep the
change localized around the fallback credential selection logic and any helper
that compares credential identity.
- Around line 226-227: The metadata recovery path in the environment handling
currently swallows all exceptions in the credential-store lookup, which hides
diagnosable failures. Update the exception handler in the relevant
metadata-recovery logic in env.py to keep the best-effort behavior but emit a
debug-level log with the caught exception instead of using a bare pass. Use the
surrounding env/credential-store recovery flow and any existing logger in this
module to preserve context and make failures traceable.

---

Nitpick comments:
In `@src/praisonai-code/praisonai_code/llm/config.py`:
- Around line 17-26: The fallback in config.py is too broad: the current
try/except around resolve_llm_endpoint_with_credentials() catches all Exception
and can hide real runtime failures. Narrow the exception handling to ImportError
only, so the fallback to resolve_llm_endpoint() in the config module is used
only when the credentials bridge cannot be imported. Keep the logic centered on
resolve_llm_endpoint_with_credentials and resolve_llm_endpoint, and avoid
swallowing other errors.
🪄 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: f90c8806-f2d3-43ca-8f8f-ebcc015a12e9

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6fa0f and 0879879.

📒 Files selected for processing (4)
  • src/praisonai-code/praisonai_code/llm/__init__.py
  • src/praisonai-code/praisonai_code/llm/catalogue.py
  • src/praisonai-code/praisonai_code/llm/config.py
  • src/praisonai-code/praisonai_code/llm/env.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/praisonai-code/praisonai_code/llm/catalogue.py

Comment on lines +207 to +223
cred = fallback_lookup(provider)
if not cred:
continue
cred_model = cred.get("model")
if (
not explicit_or_config_model
and cred_model
and not (env_base or config_base)
):
if validate_model and catalogue:
try:
cred_model = catalogue.validate_model(cred_model)
except ValueError:
cred_model = None
fallback_model = cred_model or fallback_model
if not env_base and not config_base and cred.get("base_url"):
fallback_base = cred["base_url"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Only recover stored metadata for the same API key.

This branch can pair a manually supplied env API key with stale stored model/base_url metadata for the same provider. Require the stored credential key to match before adopting its endpoint metadata, otherwise an env key can be sent to an unintended custom base URL.

Proposed fix
                 cred = fallback_lookup(provider)
                 if not cred:
                     continue
+                if cred.get("api_key") != api_key:
+                    continue
                 cred_model = cred.get("model")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cred = fallback_lookup(provider)
if not cred:
continue
cred_model = cred.get("model")
if (
not explicit_or_config_model
and cred_model
and not (env_base or config_base)
):
if validate_model and catalogue:
try:
cred_model = catalogue.validate_model(cred_model)
except ValueError:
cred_model = None
fallback_model = cred_model or fallback_model
if not env_base and not config_base and cred.get("base_url"):
fallback_base = cred["base_url"]
cred = fallback_lookup(provider)
if not cred:
continue
if cred.get("api_key") != api_key:
continue
cred_model = cred.get("model")
if (
not explicit_or_config_model
and cred_model
and not (env_base or config_base)
):
if validate_model and catalogue:
try:
cred_model = catalogue.validate_model(cred_model)
except ValueError:
cred_model = None
fallback_model = cred_model or fallback_model
if not env_base and not config_base and cred.get("base_url"):
fallback_base = cred["base_url"]
🤖 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-code/praisonai_code/llm/env.py` around lines 207 - 223, The
fallback metadata lookup in env.py is reusing stored model/base_url data without
verifying it belongs to the same API key. Update the fallback_lookup/provider
handling in this branch so `cred["model"]` and `cred["base_url"]` are only
adopted when the stored credential key matches the active env/config API key,
and otherwise skip that stored metadata. Keep the change localized around the
fallback credential selection logic and any helper that compares credential
identity.

Comment on lines +226 to +227
except Exception:
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Ruff no longer reports the silent broad exception in env.py.
fd -p 'src/praisonai-code/praisonai_code/llm/env.py' -x ruff check {} --select S110,BLE001

Repository: MervinPraison/PraisonAI

Length of output: 3606


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/praisonai-code/praisonai_code/llm/env.py"

echo "== top of file =="
sed -n '1,40p' "$file"

echo
echo "== आसपास lines 210-235 =="
sed -n '210,235p' "$file"

echo
echo "== logger references =="
rg -n '\blogger\b|logging\.getLogger|import logging' "$file"

Repository: MervinPraison/PraisonAI

Length of output: 2697


Log the metadata-recovery failure. Keep this best-effort path, but emit a debug log instead of swallowing Exception so credential-store issues are diagnosable.

🧰 Tools
🪛 Ruff (0.15.20)

[error] 226-227: try-except-pass detected, consider logging the exception

(S110)


[warning] 226-226: Do not catch blind exception: Exception

(BLE001)

🤖 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-code/praisonai_code/llm/env.py` around lines 226 - 227, The
metadata recovery path in the environment handling currently swallows all
exceptions in the credential-store lookup, which hides diagnosable failures.
Update the exception handler in the relevant metadata-recovery logic in env.py
to keep the best-effort behavior but emit a debug-level log with the caught
exception instead of using a bare pass. Use the surrounding env/credential-store
recovery flow and any existing logger in this module to preserve context and
make failures traceable.

Source: Linters/SAST tools

@MervinPraison MervinPraison merged commit 2b55453 into main Jul 1, 2026
43 of 48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown pipeline/blocked:manual-review Blocked: requires manual review pipeline/reviews-pending Waiting for CodeRabbit/Qodo/Copilot reviews

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant