Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update auth for litellm proxy #2316

Merged
merged 9 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
27 changes: 27 additions & 0 deletions backend/danswer/db/search_settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from sqlalchemy import and_
from sqlalchemy import delete
from sqlalchemy import select
from sqlalchemy.orm import Session

Expand All @@ -13,6 +15,7 @@
from danswer.db.engine import get_sqlalchemy_engine
from danswer.db.llm import fetch_embedding_provider
from danswer.db.models import CloudEmbeddingProvider
from danswer.db.models import IndexAttempt
from danswer.db.models import IndexModelStatus
from danswer.db.models import SearchSettings
from danswer.indexing.models import IndexingSetting
Expand Down Expand Up @@ -89,6 +92,30 @@ def get_current_db_embedding_provider(
return current_embedding_provider


def delete_search_settings(db_session: Session, search_settings_id: int) -> None:
current_settings = get_current_search_settings(db_session)

if current_settings.id == search_settings_id:
raise ValueError("Cannot delete currently active search settings")

# First, delete associated index attempts
index_attempts_query = delete(IndexAttempt).where(
IndexAttempt.search_settings_id == search_settings_id
)
db_session.execute(index_attempts_query)

# Then, delete the search settings
search_settings_query = delete(SearchSettings).where(
and_(
SearchSettings.id == search_settings_id,
SearchSettings.status != IndexModelStatus.PRESENT,
)
)

db_session.execute(search_settings_query)
db_session.commit()


def get_current_search_settings(db_session: Session) -> SearchSettings:
query = (
select(SearchSettings)
Expand Down
2 changes: 2 additions & 0 deletions backend/danswer/indexing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def from_index_chunk(


class EmbeddingModelDetail(BaseModel):
id: int | None = None
model_name: str
normalize: bool
query_prefix: str | None
Expand All @@ -112,6 +113,7 @@ def from_db_model(
search_settings: "SearchSettings",
) -> "EmbeddingModelDetail":
return cls(
id=search_settings.id,
model_name=search_settings.model_name,
normalize=search_settings.normalize,
query_prefix=search_settings.query_prefix,
Expand Down
2 changes: 1 addition & 1 deletion backend/danswer/server/manage/embedding/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ def test_embedding_configuration(
api_key=test_llm_request.api_key,
api_url=test_llm_request.api_url,
provider_type=test_llm_request.provider_type,
model_name=test_llm_request.model_name,
normalize=False,
query_prefix=None,
passage_prefix=None,
model_name=None,
)
test_model.encode(["Testing Embedding"], text_type=EmbedTextType.QUERY)

Expand Down
5 changes: 5 additions & 0 deletions backend/danswer/server/manage/embedding/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
from danswer.db.models import CloudEmbeddingProvider as CloudEmbeddingProviderModel


class SearchSettingsDeleteRequest(BaseModel):
search_settings_id: int


class TestEmbeddingRequest(BaseModel):
provider_type: EmbeddingProvider
api_key: str | None = None
api_url: str | None = None
model_name: str | None = None


class CloudEmbeddingProvider(BaseModel):
Expand Down
18 changes: 18 additions & 0 deletions backend/danswer/server/manage/search_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from danswer.db.models import IndexModelStatus
from danswer.db.models import User
from danswer.db.search_settings import create_search_settings
from danswer.db.search_settings import delete_search_settings
from danswer.db.search_settings import get_current_search_settings
from danswer.db.search_settings import get_embedding_provider_from_provider_type
from danswer.db.search_settings import get_secondary_search_settings
Expand All @@ -23,6 +24,7 @@
from danswer.natural_language_processing.search_nlp_models import clean_model_name
from danswer.search.models import SavedSearchSettings
from danswer.search.models import SearchSettingsCreationRequest
from danswer.server.manage.embedding.models import SearchSettingsDeleteRequest
from danswer.server.manage.models import FullModelVersionResponse
from danswer.server.models import IdReturn
from danswer.utils.logger import setup_logger
Expand Down Expand Up @@ -97,6 +99,7 @@ def set_new_search_settings(
primary_index_name=search_settings.index_name,
secondary_index_name=new_search_settings.index_name,
)

document_index.ensure_indices_exist(
index_embedding_dim=search_settings.model_dim,
secondary_index_embedding_dim=new_search_settings.model_dim,
Expand Down Expand Up @@ -132,6 +135,21 @@ def cancel_new_embedding(
)


@router.delete("/delete-search-settings")
def delete_search_settings_endpoint(
deletion_request: SearchSettingsDeleteRequest,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
try:
delete_search_settings(
db_session=db_session,
search_settings_id=deletion_request.search_settings_id,
)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))


@router.get("/get-current-search-settings")
def get_current_search_settings_endpoint(
_: User | None = Depends(current_user),
Expand Down
14 changes: 11 additions & 3 deletions backend/model_server/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,18 @@ def get_local_reranking_model(


def embed_with_litellm_proxy(
texts: list[str], api_url: str, model: str
texts: list[str], api_url: str, model_name: str, api_key: str | None
) -> list[Embedding]:
headers = {} if not api_key else {"Authorization": f"Bearer {api_key}"}

with httpx.Client() as client:
response = client.post(
api_url,
json={
"model": model,
"model": model_name,
"input": texts,
},
headers=headers,
)
response.raise_for_status()
result = response.json()
Expand Down Expand Up @@ -280,7 +283,12 @@ def embed_text(
logger.error("API URL not provided for LiteLLM proxy")
raise ValueError("API URL is required for LiteLLM proxy embedding.")
try:
return embed_with_litellm_proxy(texts, api_url, model_name or "")
return embed_with_litellm_proxy(
texts=texts,
api_url=api_url,
model_name=model_name or "",
api_key=api_key,
)
except Exception as e:
logger.exception(f"Error during LiteLLM proxy embedding: {str(e)}")
raise
Expand Down
1 change: 1 addition & 0 deletions backend/shared_configs/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

# Fields which should only be set on new search setting
PRESERVED_SEARCH_FIELDS = [
"id",
"provider_type",
"api_key",
"model_name",
Expand Down
180 changes: 119 additions & 61 deletions web/src/app/admin/embeddings/modals/ChangeCredentialsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,34 +153,22 @@ export function ChangeCredentialsModal({
title={`Modify your ${provider.provider_type} ${isProxy ? "URL" : "key"}`}
onOutsideClick={onCancel}
>
<div className="mb-4">
<Subtitle className="font-bold text-lg">
Want to swap out your {isProxy ? "URL" : "key"}?
</Subtitle>
<a
href={provider.apiLink}
target="_blank"
rel="noopener noreferrer"
className="underline cursor-pointer mt-2 mb-4"
>
Visit API
</a>

<div className="flex flex-col mt-4 gap-y-2">
{useFileUpload ? (
<>
<Label>Upload JSON File</Label>
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleFileUpload}
className="text-lg w-full p-1"
/>
{fileName && <p>Uploaded file: {fileName}</p>}
</>
) : (
<>
<>
{isProxy && (
<div className="mb-4">
<Subtitle className="font-bold text-lg">
Want to swap out your URL?
</Subtitle>
<a
href={provider.apiLink}
target="_blank"
rel="noopener noreferrer"
className="underline cursor-pointer mt-2 mb-4"
>
Visit API
</a>

<div className="flex flex-col mt-4 gap-y-2">
<input
className={`
border
Expand All @@ -193,46 +181,116 @@ export function ChangeCredentialsModal({
`}
value={apiKeyOrUrl}
onChange={(e: any) => setApiKeyOrUrl(e.target.value)}
placeholder={`Paste your ${isProxy ? "API URL" : "API key"} here`}
placeholder="Paste your API URL here"
/>
</>
)}
</div>
</div>

{testError && (
<Callout title="Error" color="red" className="mt-4">
{testError}
</Callout>
)}

{testError && (
<Callout title="Error" color="red" className="mt-4">
{testError}
</Callout>
<div className="flex mt-4 justify-between">
<Button
color="blue"
onClick={() => handleSubmit()}
disabled={!apiKeyOrUrl}
>
Swap URL
</Button>
</div>

{deletionError && (
<Callout title="Error" color="red" className="mt-4">
{deletionError}
</Callout>
)}
<Divider />
</div>
)}

<div className="flex mt-4 justify-between">
<Button
color="blue"
onClick={() => handleSubmit()}
disabled={!apiKeyOrUrl}
<div className="mb-4">
<Subtitle className="font-bold text-lg">
Want to swap out your key?
</Subtitle>
<a
href={provider.apiLink}
target="_blank"
rel="noopener noreferrer"
className="underline cursor-pointer mt-2 mb-4"
>
Swap {isProxy ? "URL" : "Key"}
Visit API
</a>

<div className="flex flex-col mt-4 gap-y-2">
{useFileUpload ? (
<>
<Label>Upload JSON File</Label>
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleFileUpload}
className="text-lg w-full p-1"
/>
{fileName && <p>Uploaded file: {fileName}</p>}
</>
) : (
<>
<input
className={`
border
border-border
rounded
w-full
py-2
px-3
bg-background-emphasis
`}
value={apiKeyOrUrl}
onChange={(e: any) => setApiKeyOrUrl(e.target.value)}
placeholder="Paste your API key here"
/>
</>
)}
</div>

{testError && (
<Callout title="Error" color="red" className="mt-4">
{testError}
</Callout>
)}

<div className="flex mt-4 justify-between">
<Button
color="blue"
onClick={() => handleSubmit()}
disabled={!apiKeyOrUrl}
>
Swap Key
</Button>
</div>
<Divider />

<Subtitle className="mt-4 font-bold text-lg mb-2">
You can also delete your configuration.
</Subtitle>
<Text className="mb-2">
This is only possible if you have already switched to a different
embedding type!
</Text>

<Button onClick={handleDelete} color="red">
Delete Configuration
</Button>
{deletionError && (
<Callout title="Error" color="red" className="mt-4">
{deletionError}
</Callout>
)}
</div>
<Divider />

<Subtitle className="mt-4 font-bold text-lg mb-2">
You can also delete your {isProxy ? "URL" : "key"}.
</Subtitle>
<Text className="mb-2">
This is only possible if you have already switched to a different
embedding type!
</Text>

<Button onClick={handleDelete} color="red">
Delete {isProxy ? "URL" : "key"}
</Button>
{deletionError && (
<Callout title="Error" color="red" className="mt-4">
{deletionError}
</Callout>
)}
</div>
</>
</Modal>
);
}
Loading
Loading