Skip to content

Commit

Permalink
add text embedding
Browse files Browse the repository at this point in the history
  • Loading branch information
pablonyx committed Dec 2, 2024
1 parent edeaee9 commit d193c4f
Show file tree
Hide file tree
Showing 23 changed files with 960 additions and 127 deletions.
10 changes: 10 additions & 0 deletions '
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
basic tests
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch text_view
# Changes to be committed:
# modified: backend/danswer/server/query_and_chat/query_backend.py
# modified: backend/tests/integration/common_utils/managers/file.py
# modified: backend/tests/integration/tests/file/test_file_detection.py
#
1 change: 1 addition & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/* && \
rm -f /usr/local/lib/python3.11/site-packages/tornado/test/test.key


# Pre-downloading models for setups with limited egress
RUN python -c "from tokenizers import Tokenizer; \
Tokenizer.from_pretrained('nomic-ai/nomic-embed-text-v1')"
Expand Down
2 changes: 1 addition & 1 deletion backend/danswer/auth/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def verify_email_is_invited(email: str) -> None:
whitelist = get_invited_users()
if not whitelist:
return

print("whitelist is", whitelist)
if not email:
raise PermissionError("Email must be specified")

Expand Down
3 changes: 3 additions & 0 deletions backend/danswer/document_index/vespa/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ def register_multitenant_indices(

zip_file = in_memory_zip_from_file_bytes(zip_dict)

# Reset the file pointer to the beginning for the subsequent POST request
zip_file.seek(0)

headers = {"Content-Type": "application/zip"}
response = requests.post(deploy_url, headers=headers, data=zip_file)

Expand Down
6 changes: 6 additions & 0 deletions backend/danswer/file_store/file_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def read_file(
Contents of the file and metadata dict
"""

@abstractmethod
def read_file_record(self, file_name: str) -> PGFileStore:
"""
Read the file record by the name
"""

@abstractmethod
def delete_file(self, file_name: str) -> None:
"""
Expand Down
19 changes: 15 additions & 4 deletions backend/danswer/server/query_and_chat/chat_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import io
import json
import mimetypes
import uuid
from collections.abc import Callable
from collections.abc import Generator
Expand Down Expand Up @@ -707,14 +708,24 @@ def upload_files_for_chat(
}


@router.get("/file/{file_id}")
@router.get("/file/{file_id:path}")
def fetch_chat_file(
file_id: str,
db_session: Session = Depends(get_session),
_: User | None = Depends(current_user),
) -> Response:
file_store = get_default_file_store(db_session)
file_record = file_store.read_file_record(file_id)
if not file_record:
raise HTTPException(status_code=404, detail="File not found")

media_type: str | None = None
if file_record.file_type:
media_type = file_record.file_type
else:
media_type, __ = mimetypes.guess_type(file_record.display_name)
if not media_type:
media_type = "application/octet-stream"

file_io = file_store.read_file(file_id, mode="b")
# NOTE: specifying "image/jpeg" here, but it still works for pngs
# TODO: do this properly
return Response(content=file_io.read(), media_type="image/jpeg")
return StreamingResponse(file_io, media_type=media_type)
45 changes: 45 additions & 0 deletions backend/shared_configs/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,47 +163,92 @@ async def async_return_default_schema(*args: Any, **kwargs: Any) -> str:
dim=1024,
index_name="danswer_chunk_cohere_embed_english_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-v3.0",
dim=1024,
index_name="danswer_chunk_embed_english_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-light-v3.0",
dim=384,
index_name="danswer_chunk_cohere_embed_english_light_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-light-v3.0",
dim=384,
index_name="danswer_chunk_embed_english_light_v3_0",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-large",
dim=3072,
index_name="danswer_chunk_openai_text_embedding_3_large",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-large",
dim=3072,
index_name="danswer_chunk_text_embedding_3_large",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-small",
dim=1536,
index_name="danswer_chunk_openai_text_embedding_3_small",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-small",
dim=1536,
index_name="danswer_chunk_text_embedding_3_small",
),
SupportedEmbeddingModel(
name="google/text-embedding-004",
dim=768,
index_name="danswer_chunk_google_text_embedding_004",
),
SupportedEmbeddingModel(
name="google/text-embedding-004",
dim=768,
index_name="danswer_chunk_text_embedding_004",
),
SupportedEmbeddingModel(
name="google/textembedding-gecko@003",
dim=768,
index_name="danswer_chunk_google_textembedding_gecko_003",
),
SupportedEmbeddingModel(
name="google/textembedding-gecko@003",
dim=768,
index_name="danswer_chunk_textembedding_gecko_003",
),
SupportedEmbeddingModel(
name="voyage/voyage-large-2-instruct",
dim=1024,
index_name="danswer_chunk_voyage_large_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-large-2-instruct",
dim=1024,
index_name="danswer_chunk_large_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-light-2-instruct",
dim=384,
index_name="danswer_chunk_voyage_light_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-light-2-instruct",
dim=384,
index_name="danswer_chunk_light_2_instruct",
),
# Self-hosted models
SupportedEmbeddingModel(
name="nomic-ai/nomic-embed-text-v1",
dim=768,
index_name="danswer_chunk_nomic_ai_nomic_embed_text_v1",
),
SupportedEmbeddingModel(
name="nomic-ai/nomic-embed-text-v1",
dim=768,
index_name="danswer_chunk_nomic_embed_text_v1",
),
SupportedEmbeddingModel(
name="intfloat/e5-base-v2",
dim=768,
Expand Down
62 changes: 62 additions & 0 deletions backend/tests/integration/common_utils/managers/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import mimetypes
from typing import cast
from typing import IO
from typing import List
from typing import Tuple

import requests

from danswer.file_store.models import FileDescriptor
from tests.integration.common_utils.constants import API_SERVER_URL
from tests.integration.common_utils.constants import GENERAL_HEADERS
from tests.integration.common_utils.test_models import DATestUser


class FileManager:
@staticmethod
def upload_files(
files: List[Tuple[str, IO]],
user_performing_action: DATestUser | None = None,
) -> Tuple[List[FileDescriptor], str]:
headers = (
user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS
)
headers.pop("Content-Type", None)

files_param = []
for filename, file_obj in files:
mime_type, _ = mimetypes.guess_type(filename)
if mime_type is None:
mime_type = "application/octet-stream"
files_param.append(("files", (filename, file_obj, mime_type)))

response = requests.post(
f"{API_SERVER_URL}/chat/file",
files=files_param,
headers=headers,
)

if not response.ok:
return (
cast(List[FileDescriptor], []),
f"Failed to upload files - {response.json().get('detail', 'Unknown error')}",
)

response_json = response.json()
return response_json.get("files", cast(List[FileDescriptor], [])), ""

@staticmethod
def fetch_uploaded_file(
file_id: str,
user_performing_action: DATestUser | None = None,
) -> bytes:
response = requests.get(
f"{API_SERVER_URL}/chat/file/{file_id}",
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
return response.content
6 changes: 6 additions & 0 deletions node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
56 changes: 43 additions & 13 deletions web/src/app/chat/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ import { NoAssistantModal } from "@/components/modals/NoAssistantModal";
import { useAssistants } from "@/components/context/AssistantsContext";
import { Separator } from "@/components/ui/separator";
import AssistantBanner from "../../components/assistants/AssistantBanner";
import TextView from "@/components/chat_search/TextView";
import AssistantSelector from "@/components/chat_search/AssistantSelector";
import { Modal } from "@/components/Modal";
import { DocumentSidebar } from "./documentSidebar/DocumentSidebar";

const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
Expand Down Expand Up @@ -241,10 +243,10 @@ export function ChatPage({
(assistant) => assistant.id === existingChatSessionAssistantId
)
: defaultAssistantId !== undefined
? availableAssistants.find(
(assistant) => assistant.id === defaultAssistantId
)
: undefined
? availableAssistants.find(
(assistant) => assistant.id === defaultAssistantId
)
: undefined
);
// Gather default temperature settings
const search_param_temperature = searchParams.get(
Expand All @@ -254,12 +256,12 @@ export function ChatPage({
const defaultTemperature = search_param_temperature
? parseFloat(search_param_temperature)
: selectedAssistant?.tools.some(
(tool) =>
tool.in_code_tool_id === "SearchTool" ||
tool.in_code_tool_id === "InternetSearchTool"
)
? 0
: 0.7;
(tool) =>
tool.in_code_tool_id === "SearchTool" ||
tool.in_code_tool_id === "InternetSearchTool"
)
? 0
: 0.7;

const setSelectedAssistantFromId = (assistantId: number) => {
// NOTE: also intentionally look through available assistants here, so that
Expand All @@ -279,6 +281,9 @@ export function ChatPage({
const [alternativeAssistant, setAlternativeAssistant] =
useState<Persona | null>(null);

const [presentingDocument, setPresentingDocument] =
useState<DanswerDocument | null>(null);

const {
visibleAssistants: assistants,
recentAssistants,
Expand Down Expand Up @@ -490,6 +495,7 @@ export function ChatPage({
clientScrollToBottom(true);
}
}

setIsFetchingChatMessages(false);

// if this is a seeded chat, then kick off the AI message generation
Expand Down Expand Up @@ -1152,8 +1158,8 @@ export function ChatPage({
const currentAssistantId = alternativeAssistantOverride
? alternativeAssistantOverride.id
: alternativeAssistant
? alternativeAssistant.id
: liveAssistant.id;
? alternativeAssistant.id
: liveAssistant.id;

resetInputBar();
let messageUpdates: Message[] | null = null;
Expand Down Expand Up @@ -1649,7 +1655,6 @@ export function ChatPage({
scrollDist,
endDivRef,
debounceNumber,
waitForScrollRef,
mobile: settings?.isMobile,
enableAutoScroll: autoScrollEnabled,
});
Expand Down Expand Up @@ -1946,6 +1951,7 @@ export function ChatPage({
{popup}

<ChatPopup />

{currentFeedback && (
<FeedbackModal
feedbackType={currentFeedback[0]}
Expand Down Expand Up @@ -2024,6 +2030,13 @@ export function ChatPage({
/>
)}

{presentingDocument && (
<TextView
presentingDocument={presentingDocument}
onClose={() => setPresentingDocument(null)}
/>
)}

{stackTraceModalContent && (
<ExceptionTraceModal
onOutsideClick={() => setStackTraceModalContent(null)}
Expand Down Expand Up @@ -2424,6 +2437,9 @@ export function ChatPage({
}
>
<AIMessage
setPresentingDocument={
setPresentingDocument
}
index={i}
selectedMessageForDocDisplay={
selectedMessageForDocDisplay
Expand Down Expand Up @@ -2811,6 +2827,20 @@ export function ChatPage({
</div>
{/* Right Sidebar - DocumentSidebar */}
</div>
<DocumentSidebar
initialWidth={350}
ref={innerSidebarElementRef}
closeSidebar={() => setDocumentSelection(false)}
selectedMessage={aiMessage}
selectedDocuments={selectedDocuments}
toggleDocumentSelection={toggleDocumentSelection}
clearSelectedDocuments={clearSelectedDocuments}
selectedDocumentTokens={selectedDocumentTokens}
maxTokens={maxTokens}
isLoading={isFetchingChatMessages}
isOpen={documentSelection}
setPresentingDocument={setPresentingDocument}
/>
</>
);
}
Loading

0 comments on commit d193c4f

Please sign in to comment.