Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
uses: astral-sh/setup-uv@v2

- name: Install project dependencies
run: uv sync --extra dev --extra knowledge --extra slack
run: uv sync --extra dev --extra knowledge

- name: Run all tests (core, NATS, and API-dependent)
env:
Expand Down
6 changes: 4 additions & 2 deletions build_backend.spec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ datas += copy_metadata('traitlets')
datas += copy_metadata('pyzmq')
datas += copy_metadata('tornado')
datas += copy_metadata('nest_asyncio')
datas += collect_data_files('litellm', includes=['**/*.json'])
datas += collect_data_files('pantheon', subdir='utils', includes=['llm_catalog.json'])
datas += collect_data_files('tiktoken_ext', includes=['**/*.py'])
# fakeredis: model/_command_info.py loads os.path.join(dirname(__file__), '..', 'commands.json')
# PyInstaller must include the JSON so the relative path resolves at runtime.
Expand Down Expand Up @@ -65,9 +65,10 @@ a = Analysis(
'pantheon.toolsets.rag',
'pantheon.toolsets.scfm',
'nats',
'litellm',
'openai',
'anthropic',
'google.genai',
'tiktoken',
'fastmcp',
'fastmcp.server',
'fastmcp.client',
Expand Down Expand Up @@ -137,6 +138,7 @@ exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='pantheon-backend-exe',
debug=False,
bootloader_ignore_signals=False,
Expand Down
4 changes: 2 additions & 2 deletions docs/source/api/agent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ Constructor Parameters
* - tool_timeout
- int
- Tool execution timeout in seconds (default: 600)
* - force_litellm
* - relaxed_schema
- bool
- Force use of litellm backend (default: False)
- Use relaxed (non-strict) tool schema mode (default: False)
* - max_tool_content_length
- int | None
- Maximum length for tool outputs (default: 100000)
Expand Down
2 changes: 1 addition & 1 deletion docs/source/api/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Common Functions

from pantheon.utils.llm import (
acompletion_openai,
acompletion_litellm,
acompletion, # adapter-based completion
process_messages_for_model,
remove_hidden_fields
)
Expand Down
2 changes: 1 addition & 1 deletion docs/source/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Every agent has instructions that define its behavior and personality. These ins
Agents become powerful through tools - functions that extend their abilities beyond pure conversation. Tools allow agents to interact with external systems, perform calculations, access databases, browse the web, execute code, and much more. The tool system is extensible, allowing you to add custom capabilities tailored to your specific needs.

#### Model Selection
Agents can use any LLM supported by LiteLLM. Model selection can be configured at the agent level or globally through settings. Fallback chains allow graceful degradation when primary models are unavailable.
Agents can use any LLM from the supported providers (OpenAI, Anthropic, Gemini, DeepSeek, and more). Model selection can be configured at the agent level or globally through settings. Fallback chains allow graceful degradation when primary models are unavailable.

---

Expand Down
8 changes: 4 additions & 4 deletions docs/source/configuration/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Configure which LLM models your agents use.
Overview
--------

Pantheon uses `LiteLLM <https://github.com/BerriAI/litellm>`_ as its unified LLM interface, providing access to **100+ LLM providers** through a consistent API. This means any model supported by LiteLLM works with Pantheon.
Pantheon provides a unified LLM interface with native SDK adapters, giving access to **many LLM providers** through a consistent API.

Key features:

Expand Down Expand Up @@ -119,7 +119,7 @@ Provider priority (configurable in settings.json):
Supported Providers
-------------------

Pantheon supports all LiteLLM providers. Here are the most common ones:
Pantheon supports many LLM providers. Here are the most common ones:

Major Cloud Providers
~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -238,7 +238,7 @@ Chinese Providers

.. note::

For the complete list of 100+ supported providers, see the `LiteLLM Providers Documentation <https://docs.litellm.ai/docs/providers>`_.
For additional providers, see the Pantheon documentation or configure custom endpoints.

Model Format
------------
Expand Down Expand Up @@ -428,7 +428,7 @@ Or add to your ``.env`` / ``~/.pantheon/.env`` file:

**Priority rules:**

- **Base URL**: ``OPENAI_API_BASE`` / ``LITELLM_API_BASE`` (provider-specific) > ``LLM_API_BASE`` (universal)
- **Base URL**: ``OPENAI_API_BASE`` (provider-specific) > ``LLM_API_BASE`` (universal)
- **API Key (unified proxy mode)**: When ``LLM_API_BASE`` is set, ``LLM_API_KEY`` takes priority over provider-specific keys (e.g. ``OPENAI_API_KEY``). This ensures all requests to the proxy use the correct credentials.
- **API Key (normal mode)**: When no ``LLM_API_BASE`` is set, provider-specific keys (e.g. ``OPENAI_API_KEY``) take priority over ``LLM_API_KEY``.

Expand Down
2 changes: 1 addition & 1 deletion docs/source/configuration/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ See :doc:`models` for full details on custom API endpoints and priority rules.
Models
~~~~~~

Pantheon uses LiteLLM and supports smart model selection with quality tags. See :doc:`models` for full details.
Pantheon supports smart model selection with quality tags. See :doc:`models` for full details.

.. code-block:: json

Expand Down
4 changes: 2 additions & 2 deletions docs/source/getting-started/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Core Dependencies

Core dependencies are automatically installed:

- **LiteLLM** - Unified LLM API access (OpenAI, Anthropic, etc.)
- **Provider Adapters** - Unified LLM API access (OpenAI, Anthropic, etc.)
- **Rich** - Terminal UI and formatting
- **prompt-toolkit** - Interactive REPL
- **NATS** - Distributed messaging
Expand All @@ -155,7 +155,7 @@ Set up your LLM provider API keys:
# Anthropic Claude
export ANTHROPIC_API_KEY="your-anthropic-key"

# Or use other providers supported by LiteLLM
# Or use other supported providers
export GEMINI_API_KEY="your-gemini-key"

You can also create a ``.env`` file in your project directory:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/interfaces/api/agent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Use provider/model format for exact model selection:
agent = Agent(model="anthropic/claude-opus-4-5-20251101")
agent = Agent(model="anthropic/claude-sonnet-4-5-20250929")

# Other providers (via LiteLLM)
# Other providers (via native SDK adapters)
agent = Agent(model="gemini/gemini-3-pro-preview")
agent = Agent(model="deepseek/deepseek-chat")
agent = Agent(model="mistral/mistral-large")
Expand Down
2 changes: 1 addition & 1 deletion docs/source/toolsets/image_generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Supported Models

- ``dall-e-3``
- ``dall-e-2``
- Any model supported by LiteLLM's ``aimage_generation`` API
- Any model supported by the provider adapter's ``aimage_generation`` API

Model Selection
---------------
Expand Down
6 changes: 0 additions & 6 deletions pantheon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@
except ImportError:
pass

# Suppress litellm debug output via env vars (avoid importing litellm at startup,
# it costs ~1.5s. The actual suppress_debug_info/set_verbose flags are set in
# utils/llm.py:import_litellm() the first time litellm is actually used.)
os.environ.setdefault("LITELLM_LOG", "ERROR")
# Suppress CLIENT_IP_ENCRYPTION_KEY warning by setting a default value
os.environ.setdefault("CLIENT_IP_ENCRYPTION_KEY", "pantheon-default-key")

# Suppress MCP SDK INFO logs ("Processing request of type...") that pollute CLI output
import logging
Expand Down
44 changes: 23 additions & 21 deletions pantheon/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,18 +386,15 @@ def __init__(self, partial_message: dict | None = None):

def _is_retryable_error(error: Exception) -> bool:
"""Determine if an LLM API error is transient and worth retrying."""
try:
from litellm.exceptions import (
ServiceUnavailableError,
InternalServerError,
RateLimitError,
APIConnectionError,
)
if isinstance(error, (ServiceUnavailableError, InternalServerError,
RateLimitError, APIConnectionError)):
return True
except ImportError:
pass
from pantheon.utils.adapters.base import (
ServiceUnavailableError,
InternalServerError,
RateLimitError,
APIConnectionError,
)
if isinstance(error, (ServiceUnavailableError, InternalServerError,
RateLimitError, APIConnectionError)):
return True
# Fallback: string matching for common transient error indicators
error_str = str(error).lower()
return any(kw in error_str for kw in (
Expand Down Expand Up @@ -487,7 +484,7 @@ class Agent:
memory: The memory to use for the agent.
If not provided, a new memory will be created.
tool_timeout: The timeout for the tool. (default: from settings.endpoint.local_toolset_timeout, or 3600s)
force_litellm: Whether to force using LiteLLM. (default: False)
relaxed_schema: Use relaxed (non-strict) tool schema mode. (default: False)
max_tool_content_length: The maximum length of the tool content. (default: 100000)
description: The description of the agent. (default: None)
think_tool: Whether to enable the think tool for structured reasoning. (default: False)
Expand All @@ -505,7 +502,7 @@ def __init__(
use_memory: bool = True,
memory: "Memory | None" = None,
tool_timeout: int | None = None,
force_litellm: bool = False,
relaxed_schema: bool = False,
max_tool_content_length: int | None = None,
description: str | None = None,
think_tool: bool = False,
Expand Down Expand Up @@ -559,7 +556,7 @@ def __init__(
# Input queue for run_loop() — messages/notifications enter here
self.input_queue: asyncio.Queue = asyncio.Queue()
self._loop_running: bool = False
self.force_litellm = force_litellm
self.relaxed_schema = relaxed_schema
self.icon = icon

# Provider management (MCP, ToolSet, etc.)
Expand Down Expand Up @@ -871,7 +868,7 @@ async def get_tools_for_llm(self) -> list[dict]:
"""
# 1. Get tools from _base_functions (Agent's own tools - no prefix)
base_tools = self._convert_functions(
litellm_mode=self.force_litellm, allow_transfer=True
relaxed_schema=self.relaxed_schema, allow_transfer=True
)

# 2. Get tools from providers (dynamic retrieval - uses provider caching)
Expand Down Expand Up @@ -1130,7 +1127,7 @@ async def call_tool(
# ===== Legacy MCP method (deprecated, kept for backward compatibility) =====

def _convert_functions(
self, litellm_mode: bool, allow_transfer: bool
self, relaxed_schema: bool, allow_transfer: bool
) -> list[dict]:
"""Convert function to the format that the model can understand."""
functions = []
Expand All @@ -1153,7 +1150,7 @@ def _convert_functions(
func_dict = desc_to_openai_dict(
desc,
skip_params=skip_params,
litellm_mode=litellm_mode,
relaxed_schema=relaxed_schema,
)
functions.append(func_dict)

Expand Down Expand Up @@ -1416,7 +1413,7 @@ async def _acompletion(
messages = process_messages_for_model(messages, model)

# Step 2: Detect provider and get configuration
provider_config = detect_provider(model, self.force_litellm)
provider_config = detect_provider(model, self.relaxed_schema)

# Step 3: Get base URL and API key from environment if available
# Skip if detect_provider already set them (e.g. OpenAI-compatible providers)
Expand Down Expand Up @@ -1505,6 +1502,9 @@ async def _acompletion(
model_params=model_params,
)

if message is None:
message = {"role": "assistant", "content": "Error: Empty response from model."}

# Step 8: Add metadata to message
end_timestamp = time.time()
total_time = tracker.end("total")
Expand Down Expand Up @@ -1576,9 +1576,9 @@ async def _acompletion_with_models(

For each model, transient errors (overloaded, rate-limit, 5xx) are
retried with exponential backoff. Non-transient errors skip directly
to the next model. LiteLLM's ``num_retries`` still handles initial
to the next model. The adapter's ``num_retries`` still handles initial
connection-level retries; this layer covers mid-stream failures that
LiteLLM cannot retry on its own.
the adapter cannot retry on its own.
"""
# --- Read retry settings (with sensible defaults) ---
from .settings import get_settings
Expand Down Expand Up @@ -1629,6 +1629,8 @@ async def _acompletion_with_models(
raise
except Exception as e:
last_error = e
import traceback
logger.error(f"[Agent:{self.name}] Full traceback:\n{traceback.format_exc()}")

if _is_retryable_error(e) and attempt < max_retries:
delay = min(base_delay * (2 ** attempt), max_delay)
Expand Down
71 changes: 70 additions & 1 deletion pantheon/chatroom/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,75 @@
from pantheon.repl.setup_wizard import check_and_run_setup


def oauth(action: str = "status", provider: str = "codex"):
"""Manage OAuth authentication for LLM providers.

Args:
action: One of 'login', 'import', 'status', 'logout'
provider: OAuth provider name (default: 'codex')

Examples:
pantheon-chatroom oauth login # Browser-based login
pantheon-chatroom oauth import # Import from Codex CLI
pantheon-chatroom oauth status # Check auth status
pantheon-chatroom oauth logout # Remove stored tokens
"""
if provider != "codex":
print(f"Unsupported OAuth provider: {provider}")
print("Supported providers: codex")
return

from pantheon.utils.oauth import CodexOAuthManager, CodexOAuthError
mgr = CodexOAuthManager()

if action == "status":
if mgr.is_authenticated():
account_id = mgr.get_account_id()
print(f"Codex OAuth: authenticated")
print(f" Account ID: {account_id}")
print(f" Auth file: {mgr.auth_file}")
print(f" Use model prefix: codex/gpt-5.4-mini, codex/gpt-5, etc.")
else:
print(f"Codex OAuth: not authenticated")
print(f" Run: pantheon-chatroom oauth login")
print(f" Or: pantheon-chatroom oauth import (if Codex CLI is installed)")

elif action == "login":
print("Starting Codex OAuth login...")
print("A browser window will open. Please log in with your OpenAI account.")
try:
mgr.login(open_browser=True, timeout_seconds=300)
print(f"\nLogin successful!")
print(f" Account ID: {mgr.get_account_id()}")
print(f" You can now use codex/ models (e.g., codex/gpt-5.4-mini)")
except CodexOAuthError as e:
print(f"\nLogin failed: {e}")
except KeyboardInterrupt:
print("\nLogin cancelled.")

elif action == "import":
print("Importing from Codex CLI (~/.codex/auth.json)...")
result = mgr.import_from_codex_cli()
if result:
print(f"Import successful!")
print(f" Account ID: {mgr.get_account_id()}")
else:
print(f"Import failed. Make sure Codex CLI is installed and authenticated.")
print(f" Install: npx @anthropic-ai/codex")
print(f" Or use: pantheon-chatroom oauth login")

elif action == "logout":
if mgr.auth_file.exists():
mgr.auth_file.unlink()
print("Codex OAuth tokens removed.")
else:
print("No Codex OAuth tokens found.")

else:
print(f"Unknown action: {action}")
print("Actions: login, import, status, logout")


if __name__ == "__main__":
# Check for API keys and run setup wizard if none found
check_and_run_setup()
Expand All @@ -29,6 +98,6 @@
if len(sys.argv) == 1 or (len(sys.argv) > 1 and sys.argv[1].startswith("-")):
sys.argv.insert(1, "start")
fire.Fire(
{"start": start_services},
{"start": start_services, "oauth": oauth},
name="pantheon-chatroom",
)
Loading
Loading