Compare commits

...

8 Commits

Author SHA1 Message Date
Nik
fe942daf30 refactor(be): migrate search_settings & image_generation to OnyxError
- Replace all HTTPException raises with OnyxError in:
  - backend/onyx/server/manage/search_settings.py (6 occurrences)
  - backend/onyx/server/manage/image_generation/api.py (17 occurrences)
- Update integration tests for new response shape (detail → message)
- "already exists" now returns 409 (DUPLICATE_RESOURCE) instead of 400

Error code mappings:
- 501 NOT_IMPLEMENTED → OnyxErrorCode.NOT_IMPLEMENTED
- 400 validation → OnyxErrorCode.VALIDATION_ERROR
- 404 not found → OnyxErrorCode.NOT_FOUND
- 400 duplicate → OnyxErrorCode.DUPLICATE_RESOURCE (409)
- 400/401 bad creds → OnyxErrorCode.CREDENTIAL_INVALID
2026-03-04 14:17:27 -08:00
Nik
edad23a7b7 fix(test): update persona access tests for OnyxError response shape 2026-03-04 12:55:33 -08:00
Nik
94ab65e47d fix(test): update integration tests for OnyxError response shape
- "detail" → "message" in response body assertions
- 400 → 409 for duplicate provider (DUPLICATE_RESOURCE)
2026-03-04 12:03:09 -08:00
Nik
cce2a2c2d4 fix(test): update api_base tests to expect OnyxError instead of HTTPException 2026-03-04 11:37:00 -08:00
Nik
628b88740f fix(test): update LLM provider tests to expect OnyxError
Update 3 test assertions from pytest.raises(HTTPException) to
pytest.raises(OnyxError) to match the migrated error handling.
2026-03-04 11:15:30 -08:00
Nik
06cf7c5bdd fix(be): address Greptile review feedback
- Bedrock credential/config errors → CREDENTIAL_INVALID (not BAD_GATEWAY)
- Move guard check outside try block in delete_llm_provider for clarity
2026-03-04 10:52:31 -08:00
Nik
b9c7c1cd3b fix(be): use semantic error codes instead of blanket VALIDATION_ERROR
- "already exists" → DUPLICATE_RESOURCE (409)
- "does not exist" on update → NOT_FOUND (404)
- External service failures (Bedrock/Ollama/OpenRouter) → BAD_GATEWAY (502)
2026-03-04 10:33:02 -08:00
Nik
4dfc64d6cf refactor(be): migrate LLM & embedding management to OnyxError
Replace all HTTPException raises in manage/llm/api.py (25) and
manage/embedding/api.py (2) with OnyxError using standardized error
codes. Part of the ongoing OnyxError rollout.
2026-03-04 10:17:25 -08:00
10 changed files with 178 additions and 176 deletions

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -11,6 +10,8 @@ from onyx.db.llm import upsert_cloud_embedding_provider
from onyx.db.models import User
from onyx.db.search_settings import get_all_search_settings
from onyx.db.search_settings import get_current_db_embedding_provider
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.indexing.models import EmbeddingModelDetail
from onyx.natural_language_processing.search_nlp_models import EmbeddingModel
from onyx.server.manage.embedding.models import CloudEmbeddingProvider
@@ -59,7 +60,7 @@ def test_embedding_configuration(
except Exception as e:
error_msg = "An error occurred while testing your embedding model. Please check your configuration."
logger.error(f"{error_msg} Error message: {e}", exc_info=True)
raise HTTPException(status_code=400, detail=error_msg)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_msg)
@admin_router.get("", response_model=list[EmbeddingModelDetail])
@@ -93,8 +94,9 @@ def delete_embedding_provider(
embedding_provider is not None
and provider_type == embedding_provider.provider_type
):
raise HTTPException(
status_code=400, detail="You can't delete a currently active model"
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"You can't delete a currently active model",
)
remove_embedding_provider(db_session, provider_type=provider_type)

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -15,6 +14,8 @@ from onyx.db.llm import remove_llm_provider__no_commit
from onyx.db.models import LLMProvider as LLMProviderModel
from onyx.db.models import ModelConfiguration
from onyx.db.models import User
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.image_gen.exceptions import ImageProviderCredentialsError
from onyx.image_gen.factory import get_image_generation_provider
from onyx.image_gen.factory import validate_credentials
@@ -74,9 +75,9 @@ def _build_llm_provider_request(
# Clone mode: Only use API key from source provider
source_provider = db_session.get(LLMProviderModel, source_llm_provider_id)
if not source_provider:
raise HTTPException(
status_code=404,
detail=f"Source LLM provider with id {source_llm_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Source LLM provider with id {source_llm_provider_id} not found",
)
_validate_llm_provider_change(
@@ -110,9 +111,9 @@ def _build_llm_provider_request(
)
if not provider:
raise HTTPException(
status_code=400,
detail="No provider or source llm provided",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No provider or source llm provided",
)
credentials = ImageGenerationProviderCredentials(
@@ -124,9 +125,9 @@ def _build_llm_provider_request(
)
if not validate_credentials(provider, credentials):
raise HTTPException(
status_code=400,
detail=f"Incorrect credentials for {provider}",
raise OnyxError(
OnyxErrorCode.CREDENTIAL_INVALID,
f"Incorrect credentials for {provider}",
)
return LLMProviderUpsertRequest(
@@ -215,9 +216,9 @@ def test_image_generation(
LLMProviderModel, test_request.source_llm_provider_id
)
if not source_provider:
raise HTTPException(
status_code=404,
detail=f"Source LLM provider with id {test_request.source_llm_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Source LLM provider with id {test_request.source_llm_provider_id} not found",
)
_validate_llm_provider_change(
@@ -236,9 +237,9 @@ def test_image_generation(
provider = source_provider.provider
if provider is None:
raise HTTPException(
status_code=400,
detail="No provider or source llm provided",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No provider or source llm provided",
)
try:
@@ -257,14 +258,14 @@ def test_image_generation(
),
)
except ValueError:
raise HTTPException(
status_code=404,
detail=f"Invalid image generation provider: {provider}",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"Invalid image generation provider: {provider}",
)
except ImageProviderCredentialsError:
raise HTTPException(
status_code=401,
detail="Invalid image generation credentials",
raise OnyxError(
OnyxErrorCode.CREDENTIAL_INVALID,
"Invalid image generation credentials",
)
quality = _get_test_quality_for_model(test_request.model_name)
@@ -276,15 +277,15 @@ def test_image_generation(
n=1,
quality=quality,
)
except HTTPException:
except OnyxError:
raise
except Exception as e:
# Log only exception type to avoid exposing sensitive data
# (LiteLLM errors may contain URLs with API keys or auth tokens)
logger.warning(f"Image generation test failed: {type(e).__name__}")
raise HTTPException(
status_code=400,
detail=f"Image generation test failed: {type(e).__name__}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Image generation test failed: {type(e).__name__}",
)
@@ -309,9 +310,9 @@ def create_config(
db_session, config_create.image_provider_id
)
if existing_config:
raise HTTPException(
status_code=400,
detail=f"ImageGenerationConfig with image_provider_id '{config_create.image_provider_id}' already exists",
raise OnyxError(
OnyxErrorCode.DUPLICATE_RESOURCE,
f"ImageGenerationConfig with image_provider_id '{config_create.image_provider_id}' already exists",
)
try:
@@ -345,10 +346,10 @@ def create_config(
db_session.commit()
db_session.refresh(config)
return ImageGenerationConfigView.from_model(config)
except HTTPException:
except OnyxError:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@admin_router.get("/config")
@@ -373,9 +374,9 @@ def get_config_credentials(
"""
config = get_image_generation_config(db_session, image_provider_id)
if not config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
return ImageGenerationCredentials.from_model(config)
@@ -401,9 +402,9 @@ def update_config(
# 1. Get existing config
existing_config = get_image_generation_config(db_session, image_provider_id)
if not existing_config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
old_llm_provider_id = existing_config.model_configuration.llm_provider_id
@@ -472,10 +473,10 @@ def update_config(
db_session.refresh(existing_config)
return ImageGenerationConfigView.from_model(existing_config)
except HTTPException:
except OnyxError:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@admin_router.delete("/config/{image_provider_id}")
@@ -489,9 +490,9 @@ def delete_config(
# Get the config first to find the associated LLM provider
existing_config = get_image_generation_config(db_session, image_provider_id)
if not existing_config:
raise HTTPException(
status_code=404,
detail=f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"ImageGenerationConfig with image_provider_id {image_provider_id} not found",
)
llm_provider_id = existing_config.model_configuration.llm_provider_id
@@ -503,10 +504,10 @@ def delete_config(
remove_llm_provider__no_commit(db_session, llm_provider_id)
db_session.commit()
except HTTPException:
except OnyxError:
raise
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))
@admin_router.post("/config/{image_provider_id}/default")
@@ -519,7 +520,7 @@ def set_config_as_default(
try:
set_default_image_generation_config(db_session, image_provider_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))
@admin_router.delete("/config/{image_provider_id}/default")
@@ -532,4 +533,4 @@ def unset_config_as_default(
try:
unset_default_image_generation_config(db_session, image_provider_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))

View File

@@ -11,7 +11,6 @@ from botocore.exceptions import ClientError
from botocore.exceptions import NoCredentialsError
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Query
from pydantic import ValidationError
from sqlalchemy.orm import Session
@@ -38,6 +37,8 @@ from onyx.db.llm import upsert_llm_provider
from onyx.db.llm import validate_persona_ids_exist
from onyx.db.models import User
from onyx.db.persona import user_can_access_persona
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.llm.factory import get_default_llm
from onyx.llm.factory import get_llm
from onyx.llm.factory import get_max_input_tokens_from_llm_provider
@@ -186,7 +187,7 @@ def _validate_llm_provider_change(
Only enforced in MULTI_TENANT mode.
Raises:
HTTPException: If api_base or custom_config changed without changing API key
OnyxError: If api_base or custom_config changed without changing API key
"""
if not MULTI_TENANT or api_key_changed:
return
@@ -200,9 +201,9 @@ def _validate_llm_provider_change(
)
if api_base_changed or custom_config_changed:
raise HTTPException(
status_code=400,
detail="API base and/or custom config cannot be changed without changing the API key",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"API base and/or custom config cannot be changed without changing the API key",
)
@@ -222,7 +223,7 @@ def fetch_llm_provider_options(
for well_known_llm in well_known_llms:
if well_known_llm.name == provider_name:
return well_known_llm
raise HTTPException(status_code=404, detail=f"Provider {provider_name} not found")
raise OnyxError(OnyxErrorCode.NOT_FOUND, f"Provider {provider_name} not found")
@admin_router.post("/test")
@@ -281,7 +282,7 @@ def test_llm_configuration(
error_msg = test_llm(llm)
if error_msg:
raise HTTPException(status_code=400, detail=error_msg)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_msg)
@admin_router.post("/test/default")
@@ -292,11 +293,11 @@ def test_default_provider(
llm = get_default_llm()
except ValueError:
logger.exception("Failed to fetch default LLM Provider")
raise HTTPException(status_code=400, detail="No LLM Provider setup")
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "No LLM Provider setup")
error = test_llm(llm)
if error:
raise HTTPException(status_code=400, detail=str(error))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(error))
@admin_router.get("/provider")
@@ -362,35 +363,31 @@ def put_llm_provider(
# Check name constraints
# TODO: Once port from name to id is complete, unique name will no longer be required
if existing_provider and llm_provider_upsert_request.name != existing_provider.name:
raise HTTPException(
status_code=400,
detail="Renaming providers is not currently supported",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Renaming providers is not currently supported",
)
found_provider = fetch_existing_llm_provider(
name=llm_provider_upsert_request.name, db_session=db_session
)
if found_provider is not None and found_provider is not existing_provider:
raise HTTPException(
status_code=400,
detail=f"Provider with name={llm_provider_upsert_request.name} already exists",
raise OnyxError(
OnyxErrorCode.DUPLICATE_RESOURCE,
f"Provider with name={llm_provider_upsert_request.name} already exists",
)
if existing_provider and is_creation:
raise HTTPException(
status_code=400,
detail=(
f"LLM Provider with name {llm_provider_upsert_request.name} and "
f"id={llm_provider_upsert_request.id} already exists"
),
raise OnyxError(
OnyxErrorCode.DUPLICATE_RESOURCE,
f"LLM Provider with name {llm_provider_upsert_request.name} and "
f"id={llm_provider_upsert_request.id} already exists",
)
elif not existing_provider and not is_creation:
raise HTTPException(
status_code=400,
detail=(
f"LLM Provider with name {llm_provider_upsert_request.name} and "
f"id={llm_provider_upsert_request.id} does not exist"
),
raise OnyxError(
OnyxErrorCode.NOT_FOUND,
f"LLM Provider with name {llm_provider_upsert_request.name} and "
f"id={llm_provider_upsert_request.id} does not exist",
)
# SSRF Protection: Validate api_base and custom_config match stored values
@@ -415,9 +412,9 @@ def put_llm_provider(
db_session, persona_ids
)
if missing_personas:
raise HTTPException(
status_code=400,
detail=f"Invalid persona IDs: {', '.join(map(str, missing_personas))}",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
f"Invalid persona IDs: {', '.join(map(str, missing_personas))}",
)
# Remove duplicates while preserving order
seen: set[int] = set()
@@ -473,7 +470,7 @@ def put_llm_provider(
return result
except ValueError as e:
logger.exception("Failed to upsert LLM Provider")
raise HTTPException(status_code=400, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@admin_router.delete("/provider/{provider_id}")
@@ -483,19 +480,19 @@ def delete_llm_provider(
_: User = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
if not force:
model = fetch_default_llm_model(db_session)
if model and model.llm_provider_id == provider_id:
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Cannot delete the default LLM provider",
)
try:
if not force:
model = fetch_default_llm_model(db_session)
if model and model.llm_provider_id == provider_id:
raise HTTPException(
status_code=400,
detail="Cannot delete the default LLM provider",
)
remove_llm_provider(db_session, provider_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
raise OnyxError(OnyxErrorCode.NOT_FOUND, str(e))
@admin_router.post("/default")
@@ -535,9 +532,9 @@ def get_auto_config(
"""
config = fetch_llm_recommendations_from_github()
if not config:
raise HTTPException(
status_code=502,
detail="Failed to fetch configuration from GitHub",
raise OnyxError(
OnyxErrorCode.BAD_GATEWAY,
"Failed to fetch configuration from GitHub",
)
return config.model_dump()
@@ -694,13 +691,13 @@ def list_llm_providers_for_persona(
persona = fetch_persona_with_groups(db_session, persona_id)
if not persona:
raise HTTPException(status_code=404, detail="Persona not found")
raise OnyxError(OnyxErrorCode.PERSONA_NOT_FOUND, "Persona not found")
# Verify user has access to this persona
if not user_can_access_persona(db_session, persona_id, user, get_editable=False):
raise HTTPException(
status_code=403,
detail="You don't have access to this assistant",
raise OnyxError(
OnyxErrorCode.INSUFFICIENT_PERMISSIONS,
"You don't have access to this assistant",
)
is_admin = user.role == UserRole.ADMIN
@@ -854,9 +851,9 @@ def get_bedrock_available_models(
try:
bedrock = session.client("bedrock")
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"Failed to create Bedrock client: {e}. Check AWS credentials and region.",
raise OnyxError(
OnyxErrorCode.CREDENTIAL_INVALID,
f"Failed to create Bedrock client: {e}. Check AWS credentials and region.",
)
# Build model info dict from foundation models (modelId -> metadata)
@@ -975,14 +972,14 @@ def get_bedrock_available_models(
return results
except (ClientError, NoCredentialsError, BotoCoreError) as e:
raise HTTPException(
status_code=400,
detail=f"Failed to connect to AWS Bedrock: {e}",
raise OnyxError(
OnyxErrorCode.CREDENTIAL_INVALID,
f"Failed to connect to AWS Bedrock: {e}",
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Unexpected error fetching Bedrock models: {e}",
raise OnyxError(
OnyxErrorCode.INTERNAL_ERROR,
f"Unexpected error fetching Bedrock models: {e}",
)
@@ -994,9 +991,9 @@ def _get_ollama_available_model_names(api_base: str) -> set[str]:
response.raise_for_status()
response_json = response.json()
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"Failed to fetch Ollama models: {e}",
raise OnyxError(
OnyxErrorCode.BAD_GATEWAY,
f"Failed to fetch Ollama models: {e}",
)
models = response_json.get("models", [])
@@ -1013,9 +1010,9 @@ def get_ollama_available_models(
cleaned_api_base = request.api_base.strip().rstrip("/")
if not cleaned_api_base:
raise HTTPException(
status_code=400,
detail="API base URL is required to fetch Ollama models.",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"API base URL is required to fetch Ollama models.",
)
# NOTE: most people run Ollama locally, so we don't disallow internal URLs
@@ -1024,9 +1021,9 @@ def get_ollama_available_models(
# with the same response format
model_names = _get_ollama_available_model_names(cleaned_api_base)
if not model_names:
raise HTTPException(
status_code=400,
detail="No models found from your Ollama server",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No models found from your Ollama server",
)
all_models_with_context_size_and_vision: list[OllamaFinalModelResponse] = []
@@ -1128,9 +1125,9 @@ def _get_openrouter_models_response(api_base: str, api_key: str) -> dict:
response.raise_for_status()
return response.json()
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"Failed to fetch OpenRouter models: {e}",
raise OnyxError(
OnyxErrorCode.BAD_GATEWAY,
f"Failed to fetch OpenRouter models: {e}",
)
@@ -1151,9 +1148,9 @@ def get_openrouter_available_models(
data = response_json.get("data", [])
if not isinstance(data, list) or len(data) == 0:
raise HTTPException(
status_code=400,
detail="No models found from your OpenRouter endpoint",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No models found from your OpenRouter endpoint",
)
results: list[OpenRouterFinalModelResponse] = []
@@ -1188,8 +1185,9 @@ def get_openrouter_available_models(
)
if not results:
raise HTTPException(
status_code=400, detail="No compatible models found from OpenRouter"
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"No compatible models found from OpenRouter",
)
sorted_results = sorted(results, key=lambda m: m.name.lower())

View File

@@ -1,7 +1,5 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import status
from sqlalchemy.orm import Session
from onyx.auth.users import current_admin_user
@@ -21,6 +19,8 @@ from onyx.db.search_settings import get_secondary_search_settings
from onyx.db.search_settings import update_current_search_settings
from onyx.db.search_settings import update_search_settings_status
from onyx.document_index.factory import get_default_document_index
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.file_processing.unstructured import delete_unstructured_api_key
from onyx.file_processing.unstructured import get_unstructured_api_key
from onyx.file_processing.unstructured import update_unstructured_api_key
@@ -48,9 +48,9 @@ def set_new_search_settings(
# NOTE Enable integration external dependency tests in test_search_settings.py
# when this is reenabled. They are currently skipped
logger.error("Setting new search settings is temporarily disabled.")
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Setting new search settings is temporarily disabled.",
raise OnyxError(
OnyxErrorCode.NOT_IMPLEMENTED,
"Setting new search settings is temporarily disabled.",
)
# if search_settings_new.index_name:
# logger.warning("Index name was specified by request, this is not suggested")
@@ -191,7 +191,7 @@ def delete_search_settings_endpoint(
search_settings_id=deletion_request.search_settings_id,
)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, str(e))
@router.get("/get-current-search-settings")
@@ -241,9 +241,9 @@ def update_saved_search_settings(
) -> None:
# Disallow contextual RAG for cloud deployments
if MULTI_TENANT and search_settings.enable_contextual_rag:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Contextual RAG disabled in Onyx Cloud",
raise OnyxError(
OnyxErrorCode.VALIDATION_ERROR,
"Contextual RAG disabled in Onyx Cloud",
)
validate_contextual_rag_model(
@@ -297,7 +297,7 @@ def validate_contextual_rag_model(
model_name=model_name,
db_session=db_session,
):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error_msg)
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_msg)
def _validate_contextual_rag_model(

View File

@@ -11,7 +11,6 @@ from unittest.mock import patch
from uuid import uuid4
import pytest
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.db.enums import LLMModelFlowType
@@ -20,6 +19,8 @@ from onyx.db.llm import remove_llm_provider
from onyx.db.llm import update_default_provider
from onyx.db.llm import upsert_llm_provider
from onyx.db.models import UserRole
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.llm.constants import LlmProviderNames
from onyx.llm.interfaces import LLM
from onyx.server.manage.llm.api import (
@@ -122,16 +123,16 @@ class TestLLMConfigurationEndpoint:
finally:
db_session.rollback()
def test_failed_llm_test_raises_http_exception(
def test_failed_llm_test_raises_onyx_error(
self,
db_session: Session,
provider_name: str, # noqa: ARG002
) -> None:
"""
Test that a failed LLM test raises an HTTPException with status 400.
Test that a failed LLM test raises an OnyxError with VALIDATION_ERROR.
When test_llm returns an error message, the endpoint should raise
an HTTPException with the error details.
an OnyxError with the error details.
"""
error_message = "Invalid API key: Authentication failed"
@@ -143,7 +144,7 @@ class TestLLMConfigurationEndpoint:
with patch(
"onyx.server.manage.llm.api.test_llm", side_effect=mock_test_llm_failure
):
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
run_test_llm_configuration(
test_llm_request=LLMTestRequest(
provider=LlmProviderNames.OPENAI,
@@ -156,9 +157,8 @@ class TestLLMConfigurationEndpoint:
db_session=db_session,
)
# Verify the exception details
assert exc_info.value.status_code == 400
assert exc_info.value.detail == error_message
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert exc_info.value.message == error_message
finally:
db_session.rollback()
@@ -536,11 +536,11 @@ class TestDefaultProviderEndpoint:
remove_llm_provider(db_session, provider.id)
# Now run_test_default_provider should fail
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
run_test_default_provider(_=_create_mock_admin())
assert exc_info.value.status_code == 400
assert "No LLM Provider setup" in exc_info.value.detail
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert "No LLM Provider setup" in exc_info.value.message
finally:
db_session.rollback()
@@ -581,11 +581,11 @@ class TestDefaultProviderEndpoint:
with patch(
"onyx.server.manage.llm.api.test_llm", side_effect=mock_test_llm_failure
):
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
run_test_default_provider(_=_create_mock_admin())
assert exc_info.value.status_code == 400
assert exc_info.value.detail == error_message
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert exc_info.value.message == error_message
finally:
db_session.rollback()

View File

@@ -16,13 +16,14 @@ from unittest.mock import patch
from uuid import uuid4
import pytest
from fastapi import HTTPException
from sqlalchemy.orm import Session
from onyx.db.llm import fetch_existing_llm_provider
from onyx.db.llm import remove_llm_provider
from onyx.db.llm import upsert_llm_provider
from onyx.db.models import UserRole
from onyx.error_handling.error_codes import OnyxErrorCode
from onyx.error_handling.exceptions import OnyxError
from onyx.llm.constants import LlmProviderNames
from onyx.server.manage.llm.api import _mask_string
from onyx.server.manage.llm.api import put_llm_provider
@@ -100,7 +101,7 @@ class TestLLMProviderChanges:
api_base="https://attacker.example.com",
)
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
put_llm_provider(
llm_provider_upsert_request=update_request,
is_creation=False,
@@ -108,9 +109,9 @@ class TestLLMProviderChanges:
db_session=db_session,
)
assert exc_info.value.status_code == 400
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert "cannot be changed without changing the API key" in str(
exc_info.value.detail
exc_info.value.message
)
finally:
_cleanup_provider(db_session, provider_name)
@@ -236,7 +237,7 @@ class TestLLMProviderChanges:
api_base=None,
)
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
put_llm_provider(
llm_provider_upsert_request=update_request,
is_creation=False,
@@ -244,9 +245,9 @@ class TestLLMProviderChanges:
db_session=db_session,
)
assert exc_info.value.status_code == 400
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert "cannot be changed without changing the API key" in str(
exc_info.value.detail
exc_info.value.message
)
finally:
_cleanup_provider(db_session, provider_name)
@@ -339,7 +340,7 @@ class TestLLMProviderChanges:
custom_config_changed=True,
)
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
put_llm_provider(
llm_provider_upsert_request=update_request,
is_creation=False,
@@ -347,9 +348,9 @@ class TestLLMProviderChanges:
db_session=db_session,
)
assert exc_info.value.status_code == 400
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert "cannot be changed without changing the API key" in str(
exc_info.value.detail
exc_info.value.message
)
finally:
_cleanup_provider(db_session, provider_name)
@@ -375,7 +376,7 @@ class TestLLMProviderChanges:
custom_config_changed=True,
)
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(OnyxError) as exc_info:
put_llm_provider(
llm_provider_upsert_request=update_request,
is_creation=False,
@@ -383,9 +384,9 @@ class TestLLMProviderChanges:
db_session=db_session,
)
assert exc_info.value.status_code == 400
assert exc_info.value.error_code == OnyxErrorCode.VALIDATION_ERROR
assert "cannot be changed without changing the API key" in str(
exc_info.value.detail
exc_info.value.message
)
finally:
_cleanup_provider(db_session, provider_name)

View File

@@ -114,8 +114,8 @@ def test_create_duplicate_config_fails(
headers=admin_user.headers,
)
assert response.status_code == 400
assert "already exists" in response.json()["detail"]
assert response.status_code == 409
assert "already exists" in response.json()["message"]
def test_get_all_configs(
@@ -292,7 +292,7 @@ def test_update_config_source_provider_not_found(
)
assert response.status_code == 404
assert "not found" in response.json()["detail"]
assert "not found" in response.json()["message"]
def test_delete_config(
@@ -468,7 +468,7 @@ def test_create_config_missing_credentials(
)
assert response.status_code == 400
assert "No provider or source llm provided" in response.json()["detail"]
assert "No provider or source llm provided" in response.json()["message"]
def test_create_config_source_provider_not_found(
@@ -488,4 +488,4 @@ def test_create_config_source_provider_not_found(
)
assert response.status_code == 404
assert "not found" in response.json()["detail"]
assert "not found" in response.json()["message"]

View File

@@ -427,7 +427,7 @@ def test_delete_default_llm_provider_rejected(reset: None) -> None: # noqa: ARG
headers=admin_user.headers,
)
assert delete_response.status_code == 400
assert "Cannot delete the default LLM provider" in delete_response.json()["detail"]
assert "Cannot delete the default LLM provider" in delete_response.json()["message"]
# Verify provider still exists
provider_data = _get_provider_by_id(admin_user, created_provider["id"])
@@ -673,8 +673,8 @@ def test_duplicate_provider_name_rejected(reset: None) -> None: # noqa: ARG001
headers=admin_user.headers,
json=base_payload,
)
assert response.status_code == 400
assert "already exists" in response.json()["detail"]
assert response.status_code == 409
assert "already exists" in response.json()["message"]
def test_rename_provider_rejected(reset: None) -> None: # noqa: ARG001
@@ -711,7 +711,7 @@ def test_rename_provider_rejected(reset: None) -> None: # noqa: ARG001
json=update_payload,
)
assert response.status_code == 400
assert "not currently supported" in response.json()["detail"]
assert "not currently supported" in response.json()["message"]
# Verify no duplicate was created — only the original provider should exist
provider = _get_provider_by_id(admin_user, provider_id)

View File

@@ -69,7 +69,7 @@ def test_unauthorized_persona_access_returns_403(
# Should return 403 Forbidden
assert response.status_code == 403
assert "don't have access to this assistant" in response.json()["detail"]
assert "don't have access to this assistant" in response.json()["message"]
def test_authorized_persona_access_returns_filtered_providers(
@@ -245,4 +245,4 @@ def test_nonexistent_persona_returns_404(
# Should return 404
assert response.status_code == 404
assert "Persona not found" in response.json()["detail"]
assert "Persona not found" in response.json()["message"]

View File

@@ -300,7 +300,7 @@ def test_update_contextual_rag_nonexistent_provider(
headers=admin_user.headers,
)
assert response.status_code == 400
assert "Provider nonexistent-provider not found" in response.json()["detail"]
assert "Provider nonexistent-provider not found" in response.json()["message"]
def test_update_contextual_rag_nonexistent_model(
@@ -322,7 +322,7 @@ def test_update_contextual_rag_nonexistent_model(
assert response.status_code == 400
assert (
f"Model nonexistent-model not found in provider {llm_provider.name}"
in response.json()["detail"]
in response.json()["message"]
)
@@ -342,7 +342,7 @@ def test_update_contextual_rag_missing_provider_name(
headers=admin_user.headers,
)
assert response.status_code == 400
assert "Provider name and model name are required" in response.json()["detail"]
assert "Provider name and model name are required" in response.json()["message"]
def test_update_contextual_rag_missing_model_name(
@@ -362,7 +362,7 @@ def test_update_contextual_rag_missing_model_name(
headers=admin_user.headers,
)
assert response.status_code == 400
assert "Provider name and model name are required" in response.json()["detail"]
assert "Provider name and model name are required" in response.json()["message"]
@pytest.mark.skip(reason="Set new search settings is temporarily disabled.")