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

feat: 🎸 backend #2025

Merged
merged 2 commits into from
Jan 19, 2024
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
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