Skip to content

Commit

Permalink
feat: 🎸 backend (#2025)
Browse files Browse the repository at this point in the history
added search

# Description

Please include a summary of the changes and the related issue. Please
also include relevant motivation and context.

## Checklist before requesting a review

Please delete options that are not relevant.

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that
my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

## Screenshots (if appropriate):
  • Loading branch information
StanGirard authored Jan 19, 2024
1 parent 46a3fd6 commit ba3d77a
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 56 deletions.
36 changes: 22 additions & 14 deletions backend/llm/knowledge_brain_qa.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
chat_service = ChatService()


def is_valid_uuid(uuid_to_test, version=4):
try:
uuid_obj = UUID(uuid_to_test, version=version)
except ValueError:
return False

return str(uuid_obj) == uuid_to_test


class KnowledgeBrainQA(BaseModel, QAInterface):
"""
Main class for the Brain Picking functionality.
Expand All @@ -50,7 +59,7 @@ class Config:
model: str = None # pyright: ignore reportPrivateUsage=none
temperature: float = 0.1
chat_id: str = None # pyright: ignore reportPrivateUsage=none
brain_id: str = None # pyright: ignore reportPrivateUsage=none
brain_id: str # pyright: ignore reportPrivateUsage=none
max_tokens: int = 256
streaming: bool = False
knowledge_qa: Optional[RAGInterface]
Expand Down Expand Up @@ -88,13 +97,18 @@ def __init__(

@property
def prompt_to_use(self):
# TODO: move to prompt service or instruction or something
return get_prompt_to_use(UUID(self.brain_id), self.prompt_id)
if self.brain_id and is_valid_uuid(self.brain_id):
return get_prompt_to_use(UUID(self.brain_id), self.prompt_id)
else:
return None

@property
def prompt_to_use_id(self) -> Optional[UUID]:
# TODO: move to prompt service or instruction or something
return get_prompt_to_use_id(UUID(self.brain_id), self.prompt_id)
if self.brain_id and is_valid_uuid(self.brain_id):
return get_prompt_to_use_id(UUID(self.brain_id), self.prompt_id)
else:
return None

def generate_answer(
self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True
Expand Down Expand Up @@ -129,10 +143,7 @@ def generate_answer(

answer = model_response["answer"]

brain = None

if question.brain_id:
brain = brain_service.get_brain_by_id(question.brain_id)
brain = brain_service.get_brain_by_id(self.brain_id)

if save_answer:
# save the answer to the database or not -> add a variable
Expand All @@ -142,7 +153,7 @@ def generate_answer(
"chat_id": chat_id,
"user_message": question.question,
"assistant": answer,
"brain_id": question.brain_id,
"brain_id": brain.brain_id,
"prompt_id": self.prompt_to_use_id,
}
)
Expand Down Expand Up @@ -223,10 +234,7 @@ async def wrap_done(fn: Awaitable, event: asyncio.Event):
)
)

brain = None

if question.brain_id:
brain = brain_service.get_brain_by_id(question.brain_id)
brain = brain_service.get_brain_by_id(self.brain_id)

if save_answer:
streamed_chat_history = chat_service.update_chat_history(
Expand All @@ -235,7 +243,7 @@ async def wrap_done(fn: Awaitable, event: asyncio.Event):
"chat_id": chat_id,
"user_message": question.question,
"assistant": "",
"brain_id": question.brain_id,
"brain_id": brain.brain_id,
"prompt_id": self.prompt_to_use_id,
}
)
Expand Down
15 changes: 14 additions & 1 deletion backend/llm/rags/quivr_rag.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
QUIVR_DEFAULT_PROMPT = "Your name is Quivr. You're a helpful assistant. If you don't know the answer, just say that you don't know, don't try to make up an answer."


def is_valid_uuid(uuid_to_test, version=4):
try:
uuid_obj = UUID(uuid_to_test, version=version)
except ValueError:
return False

return str(uuid_obj) == uuid_to_test


brain_service = BrainService()
chat_service = ChatService()

Expand Down Expand Up @@ -65,7 +74,10 @@ def embeddings(self):

@property
def prompt_to_use(self):
return get_prompt_to_use(UUID(self.brain_id), self.prompt_id)
if self.brain_id and is_valid_uuid(self.brain_id):
return get_prompt_to_use(UUID(self.brain_id), self.prompt_id)
else:
return None

supabase_client: Optional[Client] = None
vector_store: Optional[CustomSupabaseVectorStore] = None
Expand Down Expand Up @@ -179,4 +191,5 @@ def get_question_generation_llm(self):
def get_retriever(self):
return self.vector_store.as_retriever()

# Some other methods can be added such as on_stream, on_end,... to abstract history management (each answer should be saved or not) # Some other methods can be added such as on_stream, on_end,... to abstract history management (each answer should be saved or not)
# Some other methods can be added such as on_stream, on_end,... to abstract history management (each answer should be saved or not)
3 changes: 0 additions & 3 deletions backend/models/databases/supabase/user_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,6 @@ def check_if_is_premium_user(self, user_id: UUID):
matching_customers = None
try:
user_is_customer, user_customer_id = self.check_user_is_customer(user_id)
logger.info("🔥🔥🔥")
logger.info(user_is_customer)
logger.info(user_customer_id)

if user_is_customer:
self.db.table("user_settings").update({"is_premium": True}).match(
Expand Down
4 changes: 2 additions & 2 deletions backend/modules/brain/dto/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class CreateApiBrainDefinition(BaseModel, extra=Extra.forbid):

class CreateBrainProperties(BaseModel, extra=Extra.forbid):
name: Optional[str] = "Default brain"
description: Optional[str] = "This is a description"
status: Optional[str] = "private"
description: str = "This is a description"
status: Optional[str] = "public"
model: Optional[str]
temperature: Optional[float] = 0.0
max_tokens: Optional[int] = 256
Expand Down
35 changes: 21 additions & 14 deletions backend/modules/brain/repository/brains.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from uuid import UUID

from logger import get_logger
from models.settings import get_supabase_client
from models.settings import get_embeddings, get_supabase_client
from modules.brain.dto.inputs import BrainUpdatableProperties
from modules.brain.entity.brain_entity import BrainEntity, PublicBrain
from modules.brain.repository.interfaces.brains_interface import BrainsInterface
from modules.brain.repository.interfaces.brains_interface import \
BrainsInterface

logger = get_logger(__name__)

Expand All @@ -15,17 +16,18 @@ def __init__(self):
self.db = supabase_client

def create_brain(self, brain):
response = (
self.db.table("brains").insert(
brain.dict(
exclude={
"brain_definition",
"brain_secrets_values",
"connected_brains_ids",
}
)
)
).execute()
embeddings = get_embeddings()
string_to_embed = f"Name: {brain.name} Description: {brain.description}"
brain_meaning = embeddings.embed_query(string_to_embed)
brain_dict = brain.dict(
exclude={
"brain_definition",
"brain_secrets_values",
"connected_brains_ids",
}
)
brain_dict["meaning"] = brain_meaning
response = (self.db.table("brains").insert(brain_dict)).execute()

return BrainEntity(**response.data[0])

Expand Down Expand Up @@ -80,9 +82,14 @@ def delete_brain(self, brain_id: str):
def update_brain_by_id(
self, brain_id: UUID, brain: BrainUpdatableProperties
) -> BrainEntity | None:
embeddings = get_embeddings()
string_to_embed = f"Name: {brain.name} Description: {brain.description}"
brain_meaning = embeddings.embed_query(string_to_embed)
brain_dict = brain.dict(exclude_unset=True)
brain_dict["meaning"] = brain_meaning
update_brain_response = (
self.db.table("brains")
.update(brain.dict(exclude_unset=True))
.update(brain_dict)
.match({"brain_id": brain_id})
.execute()
).data
Expand Down
74 changes: 56 additions & 18 deletions backend/modules/chat/controller/chat/brainful_chat.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
from fastapi import HTTPException
from langchain.embeddings.ollama import OllamaEmbeddings
from langchain.embeddings.openai import OpenAIEmbeddings
from llm.api_brain_qa import APIBrainQA
from llm.composite_brain_qa import CompositeBrainQA
from llm.knowledge_brain_qa import KnowledgeBrainQA
from logger import get_logger
from models.settings import BrainSettings, get_supabase_client
from modules.brain.entity.brain_entity import BrainType, RoleEnum
from modules.brain.service.brain_authorization_service import (
validate_brain_authorization,
)
from modules.brain.service.brain_service import BrainService
from modules.chat.controller.chat.interface import ChatInterface
from modules.chat.service.chat_service import ChatService
from vectorstore.supabase import CustomSupabaseVectorStore

chat_service = ChatService()

logger = get_logger(__name__)

models_supporting_function_calls = [
"gpt-4",
Expand Down Expand Up @@ -40,22 +49,50 @@ def get_answer_generator(
streaming,
prompt_id,
user_id,
chat_question,
):
brain = brain_service.get_brain_by_id(brain_id)

if not brain:
raise HTTPException(status_code=404, detail="Brain not found")
brain_id_to_use = brain_id
if not brain_id:
brain_settings = BrainSettings()
supabase_client = get_supabase_client()
embeddings = None
if brain_settings.ollama_api_base_url:
embeddings = OllamaEmbeddings(
base_url=brain_settings.ollama_api_base_url
) # pyright: ignore reportPrivateUsage=none
else:
embeddings = OpenAIEmbeddings()
vector_store = CustomSupabaseVectorStore(
supabase_client, embeddings, table_name="vectors"
)
# Get the first question from the chat_question
logger.info(f"Finding brain closest to {chat_question}")
logger.info("🔥🔥🔥🔥🔥")
question = chat_question.question
logger.info(f"Question is {question}")
history = chat_service.get_chat_history(chat_id)
if history:
question = history[0].user_message
logger.info(f"Question is {question}")
brain_id_to_use = vector_store.find_brain_closest_query(question)
logger.info(f"Found brain {brain_id_to_use}")
logger.info("🧠🧠🧠")

brain = brain_service.get_brain_by_id(brain_id_to_use)
logger.info(f"Brain type: {brain.brain_type}")
logger.info(f"Id is {brain.brain_id}")
logger.info(f"Type of brain_id is {type(brain.brain_id)}")
if (
brain.brain_type == BrainType.DOC
brain
and brain.brain_type == BrainType.DOC
or model not in models_supporting_function_calls
):
return KnowledgeBrainQA(
chat_id=chat_id,
model=model,
max_tokens=max_tokens,
temperature=temperature,
brain_id=brain_id,
brain_id=str(brain.brain_id),
streaming=streaming,
prompt_id=prompt_id,
)
Expand All @@ -65,19 +102,20 @@ def get_answer_generator(
model=model,
max_tokens=max_tokens,
temperature=temperature,
brain_id=brain_id,
brain_id=str(brain.brain_id),
streaming=streaming,
prompt_id=prompt_id,
user_id=user_id,
)

return APIBrainQA(
chat_id=chat_id,
model=model,
max_tokens=max_tokens,
temperature=temperature,
brain_id=brain_id,
streaming=streaming,
prompt_id=prompt_id,
user_id=user_id,
)
if brain.brain_type == BrainType.API:
return APIBrainQA(
chat_id=chat_id,
model=model,
max_tokens=max_tokens,
temperature=temperature,
brain_id=str(brain.brain_id),
streaming=streaming,
prompt_id=prompt_id,
user_id=user_id,
)
1 change: 1 addition & 0 deletions backend/modules/chat/controller/chat/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ def get_answer_generator(
streaming,
prompt_id,
user_id,
chat_question,
):
pass
8 changes: 5 additions & 3 deletions backend/modules/chat/controller/chat_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from middlewares.auth import AuthBearer, get_current_user
from models.user_usage import UserUsage
from modules.brain.service.brain_service import BrainService
from modules.chat.controller.chat.brainful_chat import BrainfulChat
from modules.chat.controller.chat.factory import get_chat_strategy
from modules.chat.controller.chat.utils import NullableUUID, check_user_requests_limit
from modules.chat.dto.chats import ChatItem, ChatQuestion
Expand Down Expand Up @@ -166,6 +167,7 @@ async def create_question_handler(
streaming=False,
prompt_id=chat_question.prompt_id,
user_id=current_user.id,
chat_question=chat_question,
)

chat_answer = gpt_answer_generator.generate_answer(
Expand Down Expand Up @@ -196,7 +198,7 @@ async def create_stream_question_handler(
| None = Query(..., description="The ID of the brain"),
current_user: UserIdentity = Depends(get_current_user),
) -> StreamingResponse:
chat_instance = get_chat_strategy(brain_id)
chat_instance = BrainfulChat()
chat_instance.validate_authorization(user_id=current_user.id, brain_id=brain_id)

user_daily_usage = UserUsage(
Expand All @@ -215,7 +217,6 @@ async def create_stream_question_handler(
fallback_model = "gpt-3.5-turbo-1106"
fallback_temperature = 0
fallback_max_tokens = 256

if brain_id:
brain = brain_service.get_brain_by_id(brain_id)
if brain:
Expand All @@ -240,8 +241,9 @@ async def create_stream_question_handler(
temperature=chat_question.temperature, # type: ignore
streaming=True,
prompt_id=chat_question.prompt_id,
brain_id=str(brain_id),
brain_id=brain_id,
user_id=current_user.id,
chat_question=chat_question,
)

return StreamingResponse(
Expand Down
Loading

0 comments on commit ba3d77a

Please sign in to comment.