mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 01:21:48 +03:00
feat(lcel): migrated to lcel and pydantic (#2185)
# 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): --------- Co-authored-by: Antoine Dewez <44063631+Zewed@users.noreply.github.com>
This commit is contained in:
parent
2ba3bc1f07
commit
08e015af6c
29
Pipfile
29
Pipfile
@ -6,46 +6,49 @@ name = "pypi"
|
||||
[packages]
|
||||
langchain = "*"
|
||||
litellm = "*"
|
||||
openai = "==1.1.1"
|
||||
openai = "*"
|
||||
gitpython = "==3.1.36"
|
||||
pdf2image = "==1.16.3"
|
||||
nest-asyncio = "==1.5.6"
|
||||
pypdf = "==3.9.0"
|
||||
supabase = "==1.1.0"
|
||||
tiktoken = "==0.4.0"
|
||||
fastapi = "==0.95.2"
|
||||
supabase = "*"
|
||||
tiktoken = "*"
|
||||
fastapi = "*"
|
||||
python-multipart = "==0.0.6"
|
||||
uvicorn = "==0.22.0"
|
||||
pypandoc = "==1.11"
|
||||
docx2txt = "==0.8"
|
||||
python-jose = "==3.3.0"
|
||||
asyncpg = "==0.27.0"
|
||||
flake8 = "==6.0.0"
|
||||
flake8-black = "==0.3.6"
|
||||
flake8 = "*"
|
||||
flake8-black = "*"
|
||||
sentry-sdk = {extras = ["fastapi"] }
|
||||
pyright = "==1.1.316"
|
||||
resend = "==0.5.1"
|
||||
resend = "*"
|
||||
html5lib = "==1.1"
|
||||
beautifulsoup4 = "*"
|
||||
newspaper3k = "*"
|
||||
xlrd = "==1.0.0"
|
||||
redis = "==4.5.4"
|
||||
xlrd = "*"
|
||||
redis = "*"
|
||||
flower = "*"
|
||||
boto3 = "==1.33.7"
|
||||
botocore = "==1.33.7"
|
||||
boto3 = "*"
|
||||
botocore = "*"
|
||||
celery = {extras = ["sqs"] }
|
||||
python-dotenv = "*"
|
||||
pytest-mock = "*"
|
||||
pytest-celery = "*"
|
||||
pytesseract = "==0.3.10"
|
||||
pytesseract = "*"
|
||||
async-generator = "*"
|
||||
posthog = "==3.1.0"
|
||||
posthog = "*"
|
||||
jq = "==1.6.0"
|
||||
pytest = "*"
|
||||
ddtrace = "*"
|
||||
watchdog = "*"
|
||||
langchain-community = "*"
|
||||
langchain-openai = "*"
|
||||
pydantic-settings = "*"
|
||||
unstructured = {extras = ["all-docs"], version = "*"}
|
||||
langfuse = "*"
|
||||
|
||||
[dev-packages]
|
||||
black = "*"
|
||||
|
803
Pipfile.lock
generated
803
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@ -40,9 +40,11 @@ RUN pip install --no-cache-dir -r requirements.txt --timeout 200
|
||||
|
||||
RUN if [ "$DEV_MODE" = "true" ]; then pip install --no-cache debugpy --timeout 200; fi
|
||||
|
||||
WORKDIR /code
|
||||
# Copy the rest of the application
|
||||
COPY . .
|
||||
|
||||
|
||||
EXPOSE 5050
|
||||
|
||||
CMD ["uvicorn", "main:app","--reload", "--host", "0.0.0.0", "--port", "5050", "--workers", "6"]
|
@ -1,12 +1,18 @@
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
|
||||
from modules.chat.dto.outputs import GetChatHistoryOutput
|
||||
|
||||
|
||||
def format_chat_history(history) -> List[Tuple[str, str]]:
|
||||
"""Format the chat history into a list of tuples (human, ai)"""
|
||||
|
||||
return [(chat.user_message, chat.assistant) for chat in history]
|
||||
def format_chat_history(
|
||||
history: List[GetChatHistoryOutput],
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Format the chat history into a list of HumanMessage and AIMessage"""
|
||||
formatted_history = []
|
||||
for chat in history:
|
||||
formatted_history.append(HumanMessage(chat.user_message))
|
||||
formatted_history.append(AIMessage(chat.assistant))
|
||||
return formatted_history
|
||||
|
||||
|
||||
def format_history_to_openai_mesages(
|
||||
|
@ -2,15 +2,12 @@ from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from llm.utils.get_prompt_to_use_id import get_prompt_to_use_id
|
||||
from modules.prompt.entity.prompt import Prompt
|
||||
from modules.prompt.service import PromptService
|
||||
|
||||
promptService = PromptService()
|
||||
|
||||
|
||||
def get_prompt_to_use(
|
||||
brain_id: Optional[UUID], prompt_id: Optional[UUID]
|
||||
) -> Optional[Prompt]:
|
||||
def get_prompt_to_use(brain_id: Optional[UUID], prompt_id: Optional[UUID]) -> str:
|
||||
prompt_to_use_id = get_prompt_to_use_id(brain_id, prompt_id)
|
||||
if prompt_to_use_id is None:
|
||||
return None
|
||||
|
@ -1,7 +1,7 @@
|
||||
from uuid import UUID
|
||||
|
||||
from logger import get_logger
|
||||
from pydantic import BaseModel
|
||||
from pydantic import ConfigDict, BaseModel
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@ -10,6 +10,4 @@ class BrainSubscription(BaseModel):
|
||||
brain_id: UUID
|
||||
email: str
|
||||
rights: str = "Viewer"
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
@ -18,7 +18,7 @@ logger = get_logger(__name__)
|
||||
|
||||
class File(BaseModel):
|
||||
id: Optional[UUID] = None
|
||||
file: Optional[UploadFile]
|
||||
file: Optional[UploadFile] = None
|
||||
file_name: Optional[str] = ""
|
||||
file_size: Optional[int] = None
|
||||
file_sha1: Optional[str] = ""
|
||||
|
@ -5,7 +5,7 @@ from langchain.embeddings.openai import OpenAIEmbeddings
|
||||
from logger import get_logger
|
||||
from models.databases.supabase.supabase import SupabaseDB
|
||||
from posthog import Posthog
|
||||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from supabase.client import Client, create_client
|
||||
from vectorstore.supabase import SupabaseVectorStore
|
||||
|
||||
@ -13,11 +13,13 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class BrainRateLimiting(BaseSettings):
|
||||
model_config = SettingsConfigDict(validate_default=False)
|
||||
max_brain_per_user: int = 5
|
||||
|
||||
|
||||
# The `PostHogSettings` class is used to initialize and interact with the PostHog analytics service.
|
||||
class PostHogSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(validate_default=False)
|
||||
posthog_api_key: str = None
|
||||
posthog_api_url: str = None
|
||||
posthog: Posthog = None
|
||||
@ -102,15 +104,19 @@ class PostHogSettings(BaseSettings):
|
||||
|
||||
|
||||
class BrainSettings(BaseSettings):
|
||||
openai_api_key: str
|
||||
supabase_url: str
|
||||
supabase_service_key: str
|
||||
model_config = SettingsConfigDict(validate_default=False)
|
||||
openai_api_key: str = ""
|
||||
supabase_url: str = ""
|
||||
supabase_service_key: str = ""
|
||||
resend_api_key: str = "null"
|
||||
resend_email_address: str = "brain@mail.quivr.app"
|
||||
ollama_api_base_url: str = None
|
||||
langfuse_public_key: str = None
|
||||
langfuse_secret_key: str = None
|
||||
|
||||
|
||||
class ResendSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(validate_default=False)
|
||||
resend_api_key: str = "null"
|
||||
|
||||
|
||||
|
@ -6,7 +6,6 @@ from modules.api_key.repository.api_key_interface import ApiKeysInterface
|
||||
from modules.api_key.repository.api_keys import ApiKeys
|
||||
from modules.user.entity.user_identity import UserIdentity
|
||||
from modules.user.service.user_service import UserService
|
||||
from pydantic import DateError
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@ -37,7 +36,8 @@ class ApiKeyService:
|
||||
if api_key_creation_date.year == current_date.year:
|
||||
return True
|
||||
return False
|
||||
except DateError:
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying API key: {e}")
|
||||
return False
|
||||
|
||||
async def get_user_from_api_key(
|
||||
|
@ -43,12 +43,12 @@ class CreateBrainProperties(BaseModel, extra=Extra.forbid):
|
||||
name: Optional[str] = "Default brain"
|
||||
description: str = "This is a description"
|
||||
status: Optional[str] = "private"
|
||||
model: Optional[str]
|
||||
model: Optional[str] = None
|
||||
temperature: Optional[float] = 0.0
|
||||
max_tokens: Optional[int] = 2000
|
||||
prompt_id: Optional[UUID] = None
|
||||
brain_type: Optional[BrainType] = BrainType.DOC
|
||||
brain_definition: Optional[CreateApiBrainDefinition]
|
||||
brain_definition: Optional[CreateApiBrainDefinition] = None
|
||||
brain_secrets_values: Optional[dict] = {}
|
||||
connected_brains_ids: Optional[list[UUID]] = []
|
||||
integration: Optional[BrainIntegrationSettings] = None
|
||||
@ -61,14 +61,14 @@ class CreateBrainProperties(BaseModel, extra=Extra.forbid):
|
||||
|
||||
|
||||
class BrainUpdatableProperties(BaseModel):
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
temperature: Optional[float]
|
||||
model: Optional[str]
|
||||
max_tokens: Optional[int]
|
||||
status: Optional[str]
|
||||
prompt_id: Optional[UUID]
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity]
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
model: Optional[str] = None
|
||||
max_tokens: Optional[int] = None
|
||||
status: Optional[str] = None
|
||||
prompt_id: Optional[UUID] = None
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity] = None
|
||||
connected_brains_ids: Optional[list[UUID]] = []
|
||||
integration: Optional[BrainIntegrationUpdateSettings] = None
|
||||
|
||||
|
@ -8,7 +8,7 @@ from pydantic import BaseModel, Extra
|
||||
class ApiBrainDefinitionSchemaProperty(BaseModel, extra=Extra.forbid):
|
||||
type: str
|
||||
description: str
|
||||
enum: Optional[list]
|
||||
enum: Optional[list] = None
|
||||
name: str
|
||||
|
||||
def dict(self, **kwargs):
|
||||
@ -26,7 +26,7 @@ class ApiBrainDefinitionSchema(BaseModel, extra=Extra.forbid):
|
||||
class ApiBrainDefinitionSecret(BaseModel, extra=Extra.forbid):
|
||||
name: str
|
||||
type: str
|
||||
description: Optional[str]
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class ApiBrainAllowedMethods(str, Enum):
|
||||
|
@ -16,18 +16,18 @@ class BrainType(str, Enum):
|
||||
class BrainEntity(BaseModel):
|
||||
brain_id: UUID
|
||||
name: str
|
||||
description: Optional[str]
|
||||
temperature: Optional[float]
|
||||
model: Optional[str]
|
||||
max_tokens: Optional[int]
|
||||
status: Optional[str]
|
||||
prompt_id: Optional[UUID]
|
||||
description: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
model: Optional[str] = None
|
||||
max_tokens: Optional[int] = None
|
||||
status: Optional[str] = None
|
||||
prompt_id: Optional[UUID] = None
|
||||
last_update: str
|
||||
brain_type: BrainType
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity]
|
||||
connected_brains_ids: Optional[List[UUID]]
|
||||
raw: Optional[bool]
|
||||
jq_instructions: Optional[str]
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity] = None
|
||||
connected_brains_ids: Optional[List[UUID]] = None
|
||||
raw: Optional[bool] = None
|
||||
jq_instructions: Optional[str] = None
|
||||
|
||||
@property
|
||||
def id(self) -> UUID:
|
||||
@ -44,11 +44,11 @@ class BrainEntity(BaseModel):
|
||||
class PublicBrain(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
description: Optional[str]
|
||||
description: Optional[str] = None
|
||||
number_of_subscribers: int = 0
|
||||
last_update: str
|
||||
brain_type: BrainType
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity]
|
||||
brain_definition: Optional[ApiBrainDefinitionEntity] = None
|
||||
|
||||
|
||||
class RoleEnum(str, Enum):
|
||||
|
@ -7,8 +7,8 @@ from pydantic import BaseModel
|
||||
class IntegrationDescriptionEntity(BaseModel):
|
||||
id: UUID
|
||||
integration_name: str
|
||||
integration_logo_url: Optional[str]
|
||||
connection_settings: Optional[dict]
|
||||
integration_logo_url: Optional[str] = None
|
||||
connection_settings: Optional[dict] = None
|
||||
|
||||
|
||||
class IntegrationEntity(BaseModel):
|
||||
@ -16,5 +16,5 @@ class IntegrationEntity(BaseModel):
|
||||
user_id: str
|
||||
brain_id: str
|
||||
integration_id: str
|
||||
settings: Optional[dict]
|
||||
credentials: Optional[dict]
|
||||
settings: Optional[dict] = None
|
||||
credentials: Optional[dict] = None
|
||||
|
@ -33,7 +33,7 @@ class NotionSearchResponse(BaseModel):
|
||||
"""Represents the response from the Notion Search API"""
|
||||
|
||||
results: list[dict[str, Any]]
|
||||
next_cursor: Optional[str]
|
||||
next_cursor: Optional[str] = None
|
||||
has_more: bool = False
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import AsyncIterable, Awaitable, List, Optional
|
||||
from typing import AsyncIterable, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from langchain.callbacks.streaming_aiter import AsyncIteratorCallbackHandler
|
||||
@ -18,7 +17,8 @@ from modules.chat.dto.chats import ChatQuestion, Sources
|
||||
from modules.chat.dto.inputs import CreateChatHistory
|
||||
from modules.chat.dto.outputs import GetChatHistoryOutput
|
||||
from modules.chat.service.chat_service import ChatService
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic_settings import BaseSettings
|
||||
from repository.files.generate_file_signed_url import generate_file_signed_url
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@ -111,30 +111,27 @@ class KnowledgeBrainQA(BaseModel, QAInterface):
|
||||
Each have the same prompt template, which is defined in the `prompt_template` property.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
"""Configuration of the Pydantic Object"""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
# Instantiate settings
|
||||
brain_settings = BrainSettings() # type: ignore other parameters are optional
|
||||
brain_settings: BaseSettings = BrainSettings()
|
||||
|
||||
# Default class attributes
|
||||
model: str = None # pyright: ignore reportPrivateUsage=none
|
||||
temperature: float = 0.1
|
||||
chat_id: str = None # pyright: ignore reportPrivateUsage=none
|
||||
brain_id: str # pyright: ignore reportPrivateUsage=none
|
||||
brain_id: str = None # pyright: ignore reportPrivateUsage=none
|
||||
max_tokens: int = 2000
|
||||
max_input: int = 2000
|
||||
streaming: bool = False
|
||||
knowledge_qa: Optional[RAGInterface]
|
||||
knowledge_qa: Optional[RAGInterface] = None
|
||||
metadata: Optional[dict] = None
|
||||
|
||||
callbacks: List[AsyncIteratorCallbackHandler] = (
|
||||
None # pyright: ignore reportPrivateUsage=none
|
||||
)
|
||||
|
||||
prompt_id: Optional[UUID]
|
||||
prompt_id: Optional[UUID] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -263,123 +260,61 @@ class KnowledgeBrainQA(BaseModel, QAInterface):
|
||||
self, chat_id: UUID, question: ChatQuestion, save_answer: bool = True
|
||||
) -> AsyncIterable:
|
||||
history = chat_service.get_chat_history(self.chat_id)
|
||||
callback = AsyncIteratorCallbackHandler()
|
||||
self.callbacks = [callback]
|
||||
|
||||
# The Chain that combines the question and answer
|
||||
qa = ConversationalRetrievalChain(
|
||||
retriever=self.knowledge_qa.get_retriever(),
|
||||
combine_docs_chain=self.knowledge_qa.get_doc_chain(
|
||||
callbacks=self.callbacks,
|
||||
streaming=True,
|
||||
),
|
||||
question_generator=self.knowledge_qa.get_question_generation_llm(),
|
||||
verbose=False,
|
||||
rephrase_question=False,
|
||||
return_source_documents=True,
|
||||
)
|
||||
conversational_qa_chain = self.knowledge_qa.get_chain()
|
||||
|
||||
transformed_history = format_chat_history(history)
|
||||
|
||||
response_tokens = []
|
||||
|
||||
async def wrap_done(fn: Awaitable, event: asyncio.Event):
|
||||
try:
|
||||
return await fn
|
||||
except Exception as e:
|
||||
logger.error(f"Caught exception: {e}")
|
||||
return None # Or some sentinel value that indicates failure
|
||||
finally:
|
||||
event.set()
|
||||
brain = brain_service.get_brain_by_id(self.brain_id)
|
||||
|
||||
prompt_content = self.prompt_to_use.content if self.prompt_to_use else None
|
||||
run = asyncio.create_task(
|
||||
wrap_done(
|
||||
qa.acall(
|
||||
{
|
||||
"question": question.question,
|
||||
"chat_history": transformed_history,
|
||||
"custom_personality": prompt_content,
|
||||
}
|
||||
),
|
||||
callback.done,
|
||||
streamed_chat_history = chat_service.update_chat_history(
|
||||
CreateChatHistory(
|
||||
**{
|
||||
"chat_id": chat_id,
|
||||
"user_message": question.question,
|
||||
"assistant": "",
|
||||
"brain_id": brain.brain_id,
|
||||
"prompt_id": self.prompt_to_use_id,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
brain = brain_service.get_brain_by_id(self.brain_id)
|
||||
|
||||
if save_answer:
|
||||
streamed_chat_history = chat_service.update_chat_history(
|
||||
CreateChatHistory(
|
||||
**{
|
||||
"chat_id": chat_id,
|
||||
"user_message": question.question,
|
||||
"assistant": "",
|
||||
"brain_id": brain.brain_id,
|
||||
"prompt_id": self.prompt_to_use_id,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
streamed_chat_history = GetChatHistoryOutput(
|
||||
**{
|
||||
"chat_id": str(chat_id),
|
||||
"message_id": streamed_chat_history.message_id,
|
||||
"message_time": streamed_chat_history.message_time,
|
||||
"user_message": question.question,
|
||||
"assistant": "",
|
||||
"prompt_title": (
|
||||
self.prompt_to_use.title if self.prompt_to_use else None
|
||||
),
|
||||
"brain_name": brain.name if brain else None,
|
||||
"brain_id": str(brain.brain_id) if brain else None,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
)
|
||||
else:
|
||||
streamed_chat_history = GetChatHistoryOutput(
|
||||
**{
|
||||
"chat_id": str(chat_id),
|
||||
"message_id": None,
|
||||
"message_time": None,
|
||||
"user_message": question.question,
|
||||
"assistant": "",
|
||||
"prompt_title": (
|
||||
self.prompt_to_use.title if self.prompt_to_use else None
|
||||
),
|
||||
"brain_name": brain.name if brain else None,
|
||||
"brain_id": str(brain.brain_id) if brain else None,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
)
|
||||
streamed_chat_history = GetChatHistoryOutput(
|
||||
**{
|
||||
"chat_id": str(chat_id),
|
||||
"message_id": streamed_chat_history.message_id,
|
||||
"message_time": streamed_chat_history.message_time,
|
||||
"user_message": question.question,
|
||||
"assistant": "",
|
||||
"prompt_title": (
|
||||
self.prompt_to_use.title if self.prompt_to_use else None
|
||||
),
|
||||
"brain_name": brain.name if brain else None,
|
||||
"brain_id": str(brain.brain_id) if brain else None,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
async for token in callback.aiter():
|
||||
logger.debug("Token: %s", token)
|
||||
response_tokens.append(token)
|
||||
streamed_chat_history.assistant = token
|
||||
|
||||
async for chunk in conversational_qa_chain.astream(
|
||||
{
|
||||
"question": question.question,
|
||||
"chat_history": transformed_history,
|
||||
"custom_personality": (
|
||||
self.prompt_to_use.content if self.prompt_to_use else None
|
||||
),
|
||||
}
|
||||
):
|
||||
response_tokens.append(chunk.content)
|
||||
streamed_chat_history.assistant = chunk.content
|
||||
yield f"data: {json.dumps(streamed_chat_history.dict())}"
|
||||
except Exception as e:
|
||||
logger.error("Error during streaming tokens: %s", e)
|
||||
try:
|
||||
# Python
|
||||
|
||||
# Await the run
|
||||
result = await run
|
||||
|
||||
sources_list = generate_source(result, brain)
|
||||
# Create metadata if it doesn't exist
|
||||
if not streamed_chat_history.metadata:
|
||||
streamed_chat_history.metadata = {}
|
||||
# Serialize the sources list
|
||||
serialized_sources_list = [source.dict() for source in sources_list]
|
||||
streamed_chat_history.metadata["sources"] = serialized_sources_list
|
||||
yield f"data: {json.dumps(streamed_chat_history.dict())}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error processing source documents: %s", e)
|
||||
logger.error("Error generating stream: %s", e)
|
||||
|
||||
# Combine all response tokens to form the final assistant message
|
||||
assistant = "".join(response_tokens)
|
||||
|
||||
try:
|
||||
|
@ -21,8 +21,7 @@ from modules.chat.dto.chats import ChatQuestion
|
||||
from modules.chat.dto.inputs import CreateChatHistory
|
||||
from modules.chat.dto.outputs import GetChatHistoryOutput
|
||||
from modules.chat.service.chat_service import ChatService
|
||||
from modules.prompt.entity.prompt import Prompt
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
logger = get_logger(__name__)
|
||||
SYSTEM_MESSAGE = "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.When answering use markdown or any other techniques to display the content in a nice and aerated way."
|
||||
@ -58,7 +57,7 @@ class HeadlessQA(BaseModel, QAInterface):
|
||||
self.callbacks = self._determine_callback_array(self.streaming)
|
||||
|
||||
@property
|
||||
def prompt_to_use(self) -> Optional[Prompt]:
|
||||
def prompt_to_use(self) -> str:
|
||||
return get_prompt_to_use(None, self.prompt_id)
|
||||
|
||||
@property
|
||||
@ -260,5 +259,4 @@ class HeadlessQA(BaseModel, QAInterface):
|
||||
assistant=assistant,
|
||||
)
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
210
backend/modules/brain/rags/new_quivr_rag.py
Normal file
210
backend/modules/brain/rags/new_quivr_rag.py
Normal file
@ -0,0 +1,210 @@
|
||||
from operator import itemgetter
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from langchain.chains import ConversationalRetrievalChain
|
||||
from langchain.embeddings.ollama import OllamaEmbeddings
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.schema import format_document
|
||||
from langchain_community.chat_models import ChatLiteLLM
|
||||
from langchain_core.messages import get_buffer_string
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
|
||||
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
|
||||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||||
from llm.utils.get_prompt_to_use import get_prompt_to_use
|
||||
from logger import get_logger
|
||||
from models import BrainSettings # Importing settings related to the 'brain'
|
||||
from modules.brain.service.brain_service import BrainService
|
||||
from modules.chat.service.chat_service import ChatService
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from supabase.client import Client, create_client
|
||||
from vectorstore.supabase import CustomSupabaseVectorStore
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
# First step is to create the Rephrasing Prompt
|
||||
_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
|
||||
|
||||
Chat History:
|
||||
{chat_history}
|
||||
Follow Up Input: {question}
|
||||
Standalone question:"""
|
||||
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)
|
||||
|
||||
# Next is the answering prompt
|
||||
|
||||
template = """Answer the question based only on the following context from files:
|
||||
{context}
|
||||
|
||||
Question: {question}
|
||||
"""
|
||||
ANSWER_PROMPT = ChatPromptTemplate.from_template(template)
|
||||
|
||||
# How we format documents
|
||||
|
||||
DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(
|
||||
template="File {file_name}: {page_content}"
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class QuivrRAG(BaseModel):
|
||||
"""
|
||||
Quivr implementation of the RAGInterface.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
# Instantiate settings
|
||||
brain_settings = BrainSettings() # type: ignore other parameters are optional
|
||||
|
||||
# Default class attributes
|
||||
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
|
||||
max_tokens: int = 2000 # Output length
|
||||
max_input: int = 2000
|
||||
streaming: bool = False
|
||||
|
||||
@property
|
||||
def embeddings(self):
|
||||
if self.brain_settings.ollama_api_base_url:
|
||||
return OllamaEmbeddings(
|
||||
base_url=self.brain_settings.ollama_api_base_url
|
||||
) # pyright: ignore reportPrivateUsage=none
|
||||
else:
|
||||
return OpenAIEmbeddings()
|
||||
|
||||
@property
|
||||
def prompt_to_use(self):
|
||||
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
|
||||
qa: Optional[ConversationalRetrievalChain] = None
|
||||
prompt_id: Optional[UUID]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
brain_id: str,
|
||||
chat_id: str,
|
||||
streaming: bool = False,
|
||||
prompt_id: Optional[UUID] = None,
|
||||
max_tokens: int = 2000,
|
||||
max_input: int = 2000,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
model=model,
|
||||
brain_id=brain_id,
|
||||
chat_id=chat_id,
|
||||
streaming=streaming,
|
||||
max_tokens=max_tokens,
|
||||
max_input=max_input,
|
||||
**kwargs,
|
||||
)
|
||||
self.supabase_client = self._create_supabase_client()
|
||||
self.vector_store = self._create_vector_store()
|
||||
self.prompt_id = prompt_id
|
||||
self.max_tokens = max_tokens
|
||||
self.max_input = max_input
|
||||
self.model = model
|
||||
self.brain_id = brain_id
|
||||
self.chat_id = chat_id
|
||||
self.streaming = streaming
|
||||
|
||||
logger.info(f"QuivrRAG initialized with model {model} and brain {brain_id}")
|
||||
logger.info("Max input length: " + str(self.max_input))
|
||||
|
||||
def _create_supabase_client(self) -> Client:
|
||||
return create_client(
|
||||
self.brain_settings.supabase_url, self.brain_settings.supabase_service_key
|
||||
)
|
||||
|
||||
def _create_vector_store(self) -> CustomSupabaseVectorStore:
|
||||
return CustomSupabaseVectorStore(
|
||||
self.supabase_client,
|
||||
self.embeddings,
|
||||
table_name="vectors",
|
||||
brain_id=self.brain_id,
|
||||
max_input=self.max_input,
|
||||
)
|
||||
|
||||
def _create_llm(
|
||||
self,
|
||||
callbacks,
|
||||
model,
|
||||
streaming=False,
|
||||
temperature=0,
|
||||
) -> BaseLLM:
|
||||
"""
|
||||
Create a LLM with the given parameters
|
||||
"""
|
||||
if streaming and callbacks is None:
|
||||
raise ValueError(
|
||||
"Callbacks must be provided when using streaming language models"
|
||||
)
|
||||
|
||||
api_base = None
|
||||
if self.brain_settings.ollama_api_base_url and model.startswith("ollama"):
|
||||
api_base = self.brain_settings.ollama_api_base_url
|
||||
|
||||
return ChatLiteLLM(
|
||||
temperature=temperature,
|
||||
max_tokens=self.max_tokens,
|
||||
model=model,
|
||||
streaming=streaming,
|
||||
verbose=False,
|
||||
callbacks=callbacks,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
def _combine_documents(
|
||||
docs, document_prompt=DEFAULT_DOCUMENT_PROMPT, document_separator="\n\n"
|
||||
):
|
||||
doc_strings = [format_document(doc, document_prompt) for doc in docs]
|
||||
return document_separator.join(doc_strings)
|
||||
|
||||
def get_retriever(self):
|
||||
return self.vector_store.as_retriever()
|
||||
|
||||
def get_chain(self):
|
||||
retriever = self.get_retriever()
|
||||
|
||||
_inputs = RunnableParallel(
|
||||
standalone_question=RunnablePassthrough.assign(
|
||||
chat_history=lambda x: get_buffer_string(x["chat_history"])
|
||||
)
|
||||
| CONDENSE_QUESTION_PROMPT
|
||||
| ChatOpenAI(temperature=0)
|
||||
| StrOutputParser(),
|
||||
)
|
||||
|
||||
_context = {
|
||||
"context": itemgetter("standalone_question")
|
||||
| retriever
|
||||
| self._combine_documents,
|
||||
"question": lambda x: x["standalone_question"],
|
||||
}
|
||||
conversational_qa_chain = _inputs | _context | ANSWER_PROMPT | ChatOpenAI()
|
||||
|
||||
return conversational_qa_chain
|
@ -1,30 +1,69 @@
|
||||
from operator import itemgetter
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from langchain.chains import ConversationalRetrievalChain, LLMChain
|
||||
from langchain.chains.question_answering import load_qa_chain
|
||||
from langchain.chains import ConversationalRetrievalChain
|
||||
from langchain.embeddings.ollama import OllamaEmbeddings
|
||||
from langchain.embeddings.openai import OpenAIEmbeddings
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.prompts.chat import (
|
||||
ChatPromptTemplate,
|
||||
HumanMessagePromptTemplate,
|
||||
SystemMessagePromptTemplate,
|
||||
)
|
||||
from langchain.prompts import HumanMessagePromptTemplate
|
||||
from langchain.schema import format_document
|
||||
from langchain_community.chat_models import ChatLiteLLM
|
||||
from llm.prompts.CONDENSE_PROMPT import CONDENSE_QUESTION_PROMPT
|
||||
from langchain_core.messages import SystemMessage, get_buffer_string
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
|
||||
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
|
||||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||||
from llm.utils.get_prompt_to_use import get_prompt_to_use
|
||||
from logger import get_logger
|
||||
from models import BrainSettings # Importing settings related to the 'brain'
|
||||
from modules.brain.rags.rag_interface import RAGInterface
|
||||
from modules.brain.service.brain_service import BrainService
|
||||
from modules.chat.service.chat_service import ChatService
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic_settings import BaseSettings
|
||||
from supabase.client import Client, create_client
|
||||
from vectorstore.supabase import CustomSupabaseVectorStore
|
||||
|
||||
logger = get_logger(__name__)
|
||||
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."
|
||||
|
||||
|
||||
# First step is to create the Rephrasing Prompt
|
||||
_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
|
||||
|
||||
Chat History:
|
||||
{chat_history}
|
||||
Follow Up Input: {question}
|
||||
Standalone question:"""
|
||||
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)
|
||||
|
||||
# Next is the answering prompt
|
||||
|
||||
template_answer = """
|
||||
Context:
|
||||
{context}
|
||||
|
||||
User Instructions to follow when answering, default to none: {custom_instructions}
|
||||
User Question: {question}
|
||||
Answer:
|
||||
"""
|
||||
ANSWER_PROMPT = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
SystemMessage(
|
||||
content=(
|
||||
"When answering use markdown or any other techniques to display the content in a nice and aerated way. Use the following pieces of context from files provided by the user to answer the users question in the same language as the user question. Your name is Quivr. You're a helpful assistant. If you don't know the answer with the context provided from the files, just say that you don't know, don't try to make up an answer."
|
||||
)
|
||||
),
|
||||
HumanMessagePromptTemplate.from_template(template_answer),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
ChatPromptTemplate.from_template(template_answer)
|
||||
|
||||
# How we format documents
|
||||
|
||||
DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(
|
||||
template="File: {file_name} Content: {page_content}"
|
||||
)
|
||||
|
||||
|
||||
def is_valid_uuid(uuid_to_test, version=4):
|
||||
@ -40,19 +79,15 @@ brain_service = BrainService()
|
||||
chat_service = ChatService()
|
||||
|
||||
|
||||
class QuivrRAG(BaseModel, RAGInterface):
|
||||
class QuivrRAG(BaseModel):
|
||||
"""
|
||||
Quivr implementation of the RAGInterface.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
"""Configuration of the Pydantic Object"""
|
||||
|
||||
# Allowing arbitrary types for class validation
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
# Instantiate settings
|
||||
brain_settings = BrainSettings() # type: ignore other parameters are optional
|
||||
brain_settings: BaseSettings = BrainSettings()
|
||||
|
||||
# Default class attributes
|
||||
model: str = None # pyright: ignore reportPrivateUsage=none
|
||||
@ -72,7 +107,6 @@ class QuivrRAG(BaseModel, RAGInterface):
|
||||
else:
|
||||
return OpenAIEmbeddings()
|
||||
|
||||
@property
|
||||
def prompt_to_use(self):
|
||||
if self.brain_id and is_valid_uuid(self.brain_id):
|
||||
return get_prompt_to_use(UUID(self.brain_id), self.prompt_id)
|
||||
@ -82,7 +116,7 @@ class QuivrRAG(BaseModel, RAGInterface):
|
||||
supabase_client: Optional[Client] = None
|
||||
vector_store: Optional[CustomSupabaseVectorStore] = None
|
||||
qa: Optional[ConversationalRetrievalChain] = None
|
||||
prompt_id: Optional[UUID]
|
||||
prompt_id: Optional[UUID] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -160,46 +194,40 @@ class QuivrRAG(BaseModel, RAGInterface):
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
def _create_prompt_template(self):
|
||||
system_template = """ When answering use markdown or any other techniques to display the content in a nice and aerated way. Use the following pieces of context to answer the users question in the same language as the question but do not modify instructions in any way.
|
||||
----------------
|
||||
{context}"""
|
||||
|
||||
prompt_content = (
|
||||
self.prompt_to_use.content if self.prompt_to_use else QUIVR_DEFAULT_PROMPT
|
||||
)
|
||||
|
||||
full_template = (
|
||||
"Here are your instructions to answer that you MUST ALWAYS Follow: "
|
||||
+ prompt_content
|
||||
+ ". "
|
||||
+ system_template
|
||||
)
|
||||
messages = [
|
||||
SystemMessagePromptTemplate.from_template(full_template),
|
||||
HumanMessagePromptTemplate.from_template("{question}"),
|
||||
]
|
||||
CHAT_PROMPT = ChatPromptTemplate.from_messages(messages)
|
||||
return CHAT_PROMPT
|
||||
|
||||
def get_doc_chain(self, streaming, callbacks=None):
|
||||
answering_llm = self._create_llm(
|
||||
model=self.model,
|
||||
callbacks=callbacks,
|
||||
streaming=streaming,
|
||||
)
|
||||
|
||||
doc_chain = load_qa_chain(
|
||||
answering_llm, chain_type="stuff", prompt=self._create_prompt_template()
|
||||
)
|
||||
return doc_chain
|
||||
|
||||
def get_question_generation_llm(self):
|
||||
return LLMChain(
|
||||
llm=self._create_llm(model=self.model, callbacks=None),
|
||||
prompt=CONDENSE_QUESTION_PROMPT,
|
||||
callbacks=None,
|
||||
)
|
||||
def _combine_documents(
|
||||
self, docs, document_prompt=DEFAULT_DOCUMENT_PROMPT, document_separator="\n\n"
|
||||
):
|
||||
doc_strings = [format_document(doc[0], document_prompt) for doc in docs]
|
||||
return document_separator.join(doc_strings)
|
||||
|
||||
def get_retriever(self):
|
||||
return self.vector_store.as_retriever()
|
||||
|
||||
def get_chain(self):
|
||||
retriever_doc = self.get_retriever()
|
||||
|
||||
_inputs = RunnableParallel(
|
||||
standalone_question=RunnablePassthrough.assign(
|
||||
chat_history=lambda x: get_buffer_string(x["chat_history"])
|
||||
)
|
||||
| CONDENSE_QUESTION_PROMPT
|
||||
| ChatOpenAI(temperature=0)
|
||||
| StrOutputParser(),
|
||||
)
|
||||
|
||||
prompt_custom_user = self.prompt_to_use()
|
||||
prompt_to_use = "None"
|
||||
if prompt_custom_user:
|
||||
prompt_to_use = prompt_custom_user.content
|
||||
logger.info(f"Prompt to use: {prompt_custom_user}")
|
||||
_context = {
|
||||
"context": itemgetter("standalone_question")
|
||||
| retriever_doc
|
||||
| self._combine_documents,
|
||||
"question": lambda x: x["standalone_question"],
|
||||
"custom_instructions": lambda x: prompt_to_use,
|
||||
}
|
||||
|
||||
conversational_qa_chain = _inputs | _context | ANSWER_PROMPT | ChatOpenAI()
|
||||
|
||||
return conversational_qa_chain
|
||||
|
@ -19,7 +19,8 @@ class NullableUUID(UUID):
|
||||
yield cls.validate
|
||||
|
||||
@classmethod
|
||||
def validate(cls, v) -> UUID | None:
|
||||
def validate(v, values, **kwargs):
|
||||
logger.info(f"Validating UUID: {v}")
|
||||
if v == "":
|
||||
return None
|
||||
try:
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import List, Optional
|
||||
from typing import Annotated, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
@ -12,7 +12,6 @@ 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.utils import (
|
||||
NullableUUID,
|
||||
check_user_requests_limit,
|
||||
find_model_and_generate_metadata,
|
||||
)
|
||||
@ -88,6 +87,8 @@ def get_answer_generator(
|
||||
brain_id, chat_question.question, current_user, chat_id, history, vector_store
|
||||
)
|
||||
|
||||
logger.info(f"Brain: {brain}")
|
||||
|
||||
model_to_use, metadata = find_model_and_generate_metadata(
|
||||
chat_id,
|
||||
brain,
|
||||
@ -208,12 +209,13 @@ async def create_question_handler(
|
||||
request: Request,
|
||||
chat_question: ChatQuestion,
|
||||
chat_id: UUID,
|
||||
brain_id: NullableUUID | UUID | None = Query(
|
||||
..., description="The ID of the brain"
|
||||
),
|
||||
brain_id: Annotated[UUID | None, Query()] = None,
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
):
|
||||
try:
|
||||
logger.info(
|
||||
f"Creating question for chat {chat_id} with brain {brain_id} of type {type(brain_id)}"
|
||||
)
|
||||
gpt_answer_generator = get_answer_generator(
|
||||
chat_id, chat_question, brain_id, current_user
|
||||
)
|
||||
@ -241,11 +243,10 @@ async def create_stream_question_handler(
|
||||
request: Request,
|
||||
chat_question: ChatQuestion,
|
||||
chat_id: UUID,
|
||||
brain_id: NullableUUID | UUID | None = Query(
|
||||
..., description="The ID of the brain"
|
||||
),
|
||||
brain_id: Annotated[UUID | None, Query()] = None,
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
) -> StreamingResponse:
|
||||
|
||||
chat_instance = BrainfulChat()
|
||||
chat_instance.validate_authorization(user_id=current_user.id, brain_id=brain_id)
|
||||
|
||||
@ -253,10 +254,17 @@ async def create_stream_question_handler(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Creating question for chat {chat_id} with brain {brain_id} of type {type(brain_id)}"
|
||||
)
|
||||
|
||||
gpt_answer_generator = get_answer_generator(
|
||||
chat_id, chat_question, brain_id, current_user
|
||||
)
|
||||
|
||||
logger.info(gpt_answer_generator)
|
||||
|
||||
try:
|
||||
return StreamingResponse(
|
||||
gpt_answer_generator.generate_stream(
|
||||
|
@ -4,7 +4,7 @@ from uuid import UUID
|
||||
|
||||
from modules.chat.dto.outputs import GetChatHistoryOutput
|
||||
from modules.notification.entity.notification import Notification
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
@ -21,11 +21,11 @@ class ChatMessage(BaseModel):
|
||||
|
||||
class ChatQuestion(BaseModel):
|
||||
question: str
|
||||
model: Optional[str]
|
||||
temperature: Optional[float]
|
||||
max_tokens: Optional[int]
|
||||
brain_id: Optional[UUID]
|
||||
prompt_id: Optional[UUID]
|
||||
model: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
max_tokens: Optional[int] = None
|
||||
brain_id: Optional[UUID] = None
|
||||
prompt_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class Sources(BaseModel):
|
||||
@ -34,12 +34,6 @@ class Sources(BaseModel):
|
||||
type: str
|
||||
original_file_name: str
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
**BaseModel.Config.json_encoders,
|
||||
UUID: lambda v: str(v),
|
||||
}
|
||||
|
||||
|
||||
class ChatItemType(Enum):
|
||||
MESSAGE = "MESSAGE"
|
||||
|
@ -9,8 +9,8 @@ class CreateChatHistory(BaseModel):
|
||||
chat_id: UUID
|
||||
user_message: str
|
||||
assistant: str
|
||||
prompt_id: Optional[UUID]
|
||||
brain_id: Optional[UUID]
|
||||
prompt_id: Optional[UUID] = None
|
||||
brain_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class QuestionAndAnswer(BaseModel):
|
||||
|
@ -9,11 +9,13 @@ class GetChatHistoryOutput(BaseModel):
|
||||
message_id: Optional[UUID] | str
|
||||
user_message: str
|
||||
assistant: str
|
||||
message_time: Optional[str]
|
||||
prompt_title: Optional[str] | None
|
||||
brain_name: Optional[str] | None
|
||||
brain_id: Optional[str] | None # string because UUID is not JSON serializable
|
||||
metadata: Optional[dict] | None
|
||||
message_time: Optional[str] = None
|
||||
prompt_title: Optional[str] | None = None
|
||||
brain_name: Optional[str] | None = None
|
||||
brain_id: Optional[str] | None = (
|
||||
None # string because UUID is not JSON serializable
|
||||
)
|
||||
metadata: Optional[dict] | None = None
|
||||
|
||||
def dict(self, *args, **kwargs):
|
||||
chat_history = super().dict(*args, **kwargs)
|
||||
@ -37,8 +39,8 @@ class ChatCompletionMessageToolCall(BaseModel):
|
||||
class CompletionMessage(BaseModel):
|
||||
# = "assistant" | "user" | "system" | "tool"
|
||||
role: str
|
||||
content: str | None
|
||||
tool_calls: Optional[List[ChatCompletionMessageToolCall]]
|
||||
content: str | None = None
|
||||
tool_calls: Optional[List[ChatCompletionMessageToolCall]] = None
|
||||
|
||||
|
||||
class CompletionResponse(BaseModel):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class ContactsSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(validate_default=False)
|
||||
resend_contact_sales_from: str = "null"
|
||||
resend_contact_sales_to: str = "null"
|
||||
|
@ -23,5 +23,5 @@ class CreateNotificationProperties(BaseModel):
|
||||
class NotificationUpdatableProperties(BaseModel):
|
||||
"""Properties that can be received on notification update"""
|
||||
|
||||
message: Optional[str]
|
||||
message: Optional[str] = None
|
||||
status: Optional[NotificationsStatusEnum] = NotificationsStatusEnum.Done
|
||||
|
@ -13,8 +13,8 @@ class NotificationsStatusEnum(str, Enum):
|
||||
class Notification(BaseModel):
|
||||
id: UUID
|
||||
datetime: str
|
||||
chat_id: Optional[UUID]
|
||||
message: Optional[str]
|
||||
chat_id: Optional[UUID] = None
|
||||
message: Optional[str] = None
|
||||
action: str
|
||||
status: NotificationsStatusEnum
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import ConfigDict, BaseModel
|
||||
|
||||
|
||||
class OnboardingUpdatableProperties(BaseModel):
|
||||
|
||||
"""Properties that can be received on onboarding update"""
|
||||
|
||||
onboarding_a: Optional[bool]
|
||||
onboarding_b1: Optional[bool]
|
||||
onboarding_b2: Optional[bool]
|
||||
onboarding_b3: Optional[bool]
|
||||
|
||||
class Config:
|
||||
extra = "forbid"
|
||||
onboarding_a: Optional[bool] = None
|
||||
onboarding_b1: Optional[bool] = None
|
||||
onboarding_b2: Optional[bool] = None
|
||||
onboarding_b3: Optional[bool] = None
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
@ -28,9 +28,9 @@ class CreatePromptProperties(BaseModel):
|
||||
class PromptUpdatableProperties(BaseModel):
|
||||
"""Properties that can be received on prompt update"""
|
||||
|
||||
title: Optional[str]
|
||||
content: Optional[str]
|
||||
status: Optional[PromptStatusEnum]
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
status: Optional[PromptStatusEnum] = None
|
||||
|
||||
|
||||
class DeletePromptResponse(BaseModel):
|
||||
|
@ -2,18 +2,18 @@
|
||||
aiohttp==3.9.3; python_version >= '3.8'
|
||||
aiosignal==1.3.1; python_version >= '3.7'
|
||||
amqp==5.2.0; python_version >= '3.6'
|
||||
annotated-types==0.6.0; python_version >= '3.8'
|
||||
antlr4-python3-runtime==4.9.3
|
||||
anyio==3.7.1; python_version >= '3.7'
|
||||
anyio==4.2.0; python_version >= '3.8'
|
||||
async-generator==1.10; python_version >= '3.5'
|
||||
async-timeout==4.0.3; python_full_version <= '3.11.2'
|
||||
asyncpg==0.27.0; python_full_version >= '3.7.0'
|
||||
attrs==23.2.0; python_version >= '3.7'
|
||||
backoff==2.2.1; python_version >= '3.7' and python_version < '4.0'
|
||||
beautifulsoup4==4.12.3; python_full_version >= '3.6.0'
|
||||
billiard==4.2.0; python_version >= '3.7'
|
||||
black==24.2.0; python_version >= '3.8'
|
||||
boto3==1.33.7; python_version >= '3.7'
|
||||
botocore==1.33.7; python_version >= '3.7'
|
||||
boto3==1.34.41; python_version >= '3.8'
|
||||
botocore==1.34.41; python_version >= '3.8'
|
||||
bytecode==0.15.1; python_version >= '3.8'
|
||||
cattrs==23.2.3; python_version >= '3.8'
|
||||
celery[sqs]==5.3.6; python_version >= '3.8'
|
||||
@ -21,6 +21,7 @@ certifi==2024.2.2; python_version >= '3.6'
|
||||
cffi==1.16.0; platform_python_implementation != 'PyPy'
|
||||
chardet==5.2.0; python_version >= '3.7'
|
||||
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
||||
chevron==0.14.0
|
||||
click==8.1.7; python_version >= '3.7'
|
||||
click-didyoumean==0.3.0; python_full_version >= '3.6.2' and python_full_version < '4.0.0'
|
||||
click-plugins==1.1.1
|
||||
@ -33,22 +34,22 @@ cycler==0.12.1; python_version >= '3.8'
|
||||
dataclasses-json==0.6.4; python_version >= '3.7' and python_version < '4.0'
|
||||
dataclasses-json-speakeasy==0.5.11; python_version >= '3.7' and python_version < '4.0'
|
||||
ddsketch==2.0.4; python_version >= '2.7'
|
||||
ddtrace==2.6.0; python_version >= '3.7'
|
||||
ddtrace==2.6.1; python_version >= '3.7'
|
||||
deprecated==1.2.14; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
deprecation==2.1.0
|
||||
distro==1.9.0; python_version >= '3.6'
|
||||
docx2txt==0.8
|
||||
ecdsa==0.18.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
ecdsa==0.18.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
|
||||
effdet==0.4.1
|
||||
emoji==2.10.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
envier==0.5.1; python_version >= '3.7'
|
||||
et-xmlfile==1.1.0; python_version >= '3.6'
|
||||
fastapi==0.95.2; python_version >= '3.7'
|
||||
fastapi==0.109.2; python_version >= '3.8'
|
||||
feedfinder2==0.0.4
|
||||
feedparser==6.0.11; python_version >= '3.6'
|
||||
filelock==3.13.1; python_version >= '3.8'
|
||||
filetype==1.2.0
|
||||
flake8==6.0.0; python_full_version >= '3.8.1'
|
||||
flake8==7.0.0; python_full_version >= '3.8.1'
|
||||
flake8-black==0.3.6; python_version >= '3.7'
|
||||
flatbuffers==23.5.26
|
||||
flower==2.0.1; python_version >= '3.7'
|
||||
@ -57,11 +58,11 @@ frozenlist==1.4.1; python_version >= '3.8'
|
||||
fsspec==2024.2.0; python_version >= '3.8'
|
||||
gitdb==4.0.11; python_version >= '3.7'
|
||||
gitpython==3.1.36; python_version >= '3.7'
|
||||
gotrue==1.3.1; python_version >= '3.8' and python_version < '4.0'
|
||||
gotrue==2.1.0; python_version >= '3.8' and python_version < '4.0'
|
||||
h11==0.14.0; python_version >= '3.7'
|
||||
html5lib==1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
httpcore==0.17.3; python_version >= '3.7'
|
||||
httpx==0.24.1; python_version >= '3.7'
|
||||
httpcore==1.0.2; python_version >= '3.8'
|
||||
httpx==0.25.2; python_version >= '3.8'
|
||||
huggingface-hub==0.20.3; python_full_version >= '3.8.0'
|
||||
humanfriendly==10.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
humanize==4.9.0; python_version >= '3.8'
|
||||
@ -79,22 +80,24 @@ jsonpath-python==1.0.6; python_version >= '3.6'
|
||||
jsonpointer==2.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'
|
||||
kiwisolver==1.4.5; python_version >= '3.7'
|
||||
kombu[sqs]==5.3.5; python_version >= '3.8'
|
||||
langchain==0.1.6; python_version < '4.0' and python_full_version >= '3.8.1'
|
||||
langchain-community==0.0.19; python_version < '4.0' and python_full_version >= '3.8.1'
|
||||
langchain-core==0.1.22; python_version < '4.0' and python_full_version >= '3.8.1'
|
||||
langchain==0.1.7; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
langchain-community==0.0.20; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
langchain-core==0.1.23; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
langchain-openai==0.0.6; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
langdetect==1.0.9
|
||||
langsmith==0.0.87; python_version < '4.0' and python_full_version >= '3.8.1'
|
||||
langfuse==2.13.2; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
langsmith==0.0.87; python_full_version >= '3.8.1' and python_version < '4.0'
|
||||
layoutparser[layoutmodels,tesseract]==0.3.4; python_version >= '3.6'
|
||||
litellm==1.23.10; python_version not in '2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7' and python_version >= '3.8'
|
||||
litellm==1.23.14; python_version not in '2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7' and python_version >= '3.8'
|
||||
lxml==5.1.0; python_version >= '3.6'
|
||||
markdown==3.5.2; python_version >= '3.8'
|
||||
markdown==3.5.2
|
||||
markupsafe==2.1.5; python_version >= '3.7'
|
||||
marshmallow==3.20.2; python_version >= '3.8'
|
||||
matplotlib==3.8.2; python_version >= '3.9'
|
||||
mccabe==0.7.0; python_version >= '3.6'
|
||||
monotonic==1.6
|
||||
mpmath==1.3.0
|
||||
msg-parser==1.2.0; python_version >= '3.4'
|
||||
msg-parser==1.2.0
|
||||
multidict==6.0.5; python_version >= '3.7'
|
||||
mypy-extensions==1.0.0; python_version >= '3.5'
|
||||
nest-asyncio==1.5.6; python_version >= '3.5'
|
||||
@ -107,15 +110,15 @@ olefile==0.47; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2,
|
||||
omegaconf==2.3.0; python_version >= '3.6'
|
||||
onnx==1.15.0
|
||||
onnxruntime==1.15.1
|
||||
openai==1.1.1; python_full_version >= '3.7.1'
|
||||
openai==1.12.0; python_full_version >= '3.7.1'
|
||||
opencv-python==4.9.0.80; python_version >= '3.6'
|
||||
openpyxl==3.1.2; python_version >= '3.6'
|
||||
openpyxl==3.1.2
|
||||
opentelemetry-api==1.22.0; python_version >= '3.7'
|
||||
packaging==23.2; python_version >= '3.7'
|
||||
pandas==2.2.0; python_version >= '3.8'
|
||||
pandas==2.2.0
|
||||
pathspec==0.12.1; python_version >= '3.8'
|
||||
pdf2image==1.16.3
|
||||
pdfminer.six==20221105; python_version >= '3.6'
|
||||
pdfminer.six==20221105
|
||||
pdfplumber==0.10.4; python_version >= '3.8'
|
||||
pikepdf==8.12.0
|
||||
pillow==10.2.0; python_version >= '3.8'
|
||||
@ -123,18 +126,20 @@ pillow-heif==0.15.0
|
||||
platformdirs==4.2.0; python_version >= '3.8'
|
||||
pluggy==1.4.0; python_version >= '3.8'
|
||||
portalocker==2.8.2; python_version >= '3.8'
|
||||
postgrest==0.11.0; python_version >= '3.8' and python_version < '4.0'
|
||||
posthog==3.1.0
|
||||
postgrest==0.15.0; python_version >= '3.8' and python_version < '4.0'
|
||||
posthog==3.4.1
|
||||
prometheus-client==0.19.0; python_version >= '3.8'
|
||||
prompt-toolkit==3.0.43; python_full_version >= '3.7.0'
|
||||
protobuf==4.25.2; python_version >= '3.8'
|
||||
pyasn1==0.5.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
pycocotools==2.0.7; python_version >= '3.5'
|
||||
pycodestyle==2.10.0; python_version >= '3.6'
|
||||
pycodestyle==2.11.1; python_version >= '3.8'
|
||||
pycparser==2.21
|
||||
pycurl==7.45.2
|
||||
pydantic==1.10.14; python_version >= '3.7'
|
||||
pyflakes==3.0.1; python_version >= '3.6'
|
||||
pydantic==2.6.1; python_version >= '3.8'
|
||||
pydantic-core==2.16.2; python_version >= '3.8'
|
||||
pydantic-settings==2.1.0; python_version >= '3.8'
|
||||
pyflakes==3.2.0; python_version >= '3.8'
|
||||
pypandoc==1.11; python_version >= '3.6'
|
||||
pyparsing==3.1.1; python_full_version >= '3.6.8'
|
||||
pypdf==3.9.0; python_version >= '3.6'
|
||||
@ -144,8 +149,8 @@ pytesseract==0.3.10; python_version >= '3.7'
|
||||
pytest==8.0.0; python_version >= '3.8'
|
||||
pytest-celery==0.0.0
|
||||
pytest-mock==3.12.0; python_version >= '3.8'
|
||||
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
python-docx==1.1.0; python_version >= '3.7'
|
||||
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
||||
python-docx==1.1.0
|
||||
python-dotenv==1.0.1; python_version >= '3.8'
|
||||
python-iso639==2024.2.7; python_version >= '3.8'
|
||||
python-jose==3.3.0
|
||||
@ -156,32 +161,32 @@ pytz==2024.1
|
||||
pyyaml==6.0.1; python_version >= '3.6'
|
||||
rapidfuzz==3.6.1; python_version >= '3.8'
|
||||
realtime==1.0.2; python_version >= '3.8' and python_version < '4.0'
|
||||
redis==4.5.4; python_version >= '3.7'
|
||||
redis==5.0.1; python_version >= '3.7'
|
||||
regex==2023.12.25; python_version >= '3.7'
|
||||
requests==2.31.0; python_version >= '3.7'
|
||||
requests-file==2.0.0
|
||||
resend==0.5.1; python_version >= '3.7'
|
||||
resend==0.7.2; python_version >= '3.7'
|
||||
rsa==4.9; python_version >= '3.6' and python_version < '4'
|
||||
s3transfer==0.8.2; python_version >= '3.7'
|
||||
s3transfer==0.10.0; python_version >= '3.8'
|
||||
safetensors==0.4.2; python_version >= '3.7'
|
||||
scipy==1.12.0; python_version >= '3.9'
|
||||
sentry-sdk[fastapi]==1.40.3
|
||||
sentry-sdk[fastapi]==1.40.4
|
||||
setuptools==69.1.0; python_version >= '3.8'
|
||||
sgmllib3k==1.0.0
|
||||
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
||||
smmap==5.0.1; python_version >= '3.7'
|
||||
sniffio==1.3.0; python_version >= '3.7'
|
||||
soupsieve==2.5; python_version >= '3.8'
|
||||
sqlalchemy==2.0.26; python_version >= '3.7'
|
||||
starlette==0.27.0; python_version >= '3.7'
|
||||
storage3==0.6.1; python_version >= '3.8' and python_version < '4.0'
|
||||
sqlalchemy==2.0.27; python_version >= '3.7'
|
||||
starlette==0.36.3; python_version >= '3.8'
|
||||
storage3==0.7.0; python_version >= '3.8' and python_version < '4.0'
|
||||
strenum==0.4.15
|
||||
supabase==1.1.0; python_version >= '3.8' and python_version < '4.0'
|
||||
supafunc==0.2.3; python_version >= '3.8' and python_version < '4.0'
|
||||
supabase==2.3.4; python_version >= '3.8' and python_version < '4.0'
|
||||
supafunc==0.3.3; python_version >= '3.8' and python_version < '4.0'
|
||||
sympy==1.12; python_version >= '3.8'
|
||||
tabulate==0.9.0; python_version >= '3.7'
|
||||
tenacity==8.2.3; python_version >= '3.7'
|
||||
tiktoken==0.4.0; python_version >= '3.8'
|
||||
tiktoken==0.6.0; python_version >= '3.8'
|
||||
timm==0.9.12; python_version >= '3.7'
|
||||
tinysegmenter==0.3
|
||||
tldextract==5.1.1; python_version >= '3.8'
|
||||
@ -205,8 +210,8 @@ watchdog==4.0.0; python_version >= '3.8'
|
||||
wcwidth==0.2.13
|
||||
webencodings==0.5.1
|
||||
websockets==11.0.3; python_version >= '3.7'
|
||||
wrapt==1.16.0; python_version >= '3.6'
|
||||
xlrd==1.0.0
|
||||
wrapt==1.14.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
xlrd==2.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
xlsxwriter==3.1.9; python_version >= '3.6'
|
||||
xmltodict==0.13.0; python_version >= '3.4'
|
||||
yarl==1.9.4; python_version >= '3.7'
|
||||
|
@ -292,7 +292,7 @@ async def decline_invitation(
|
||||
|
||||
|
||||
class BrainSubscriptionUpdatableProperties(BaseModel):
|
||||
rights: str | None
|
||||
rights: str | None = None
|
||||
email: str
|
||||
|
||||
|
||||
|
@ -100,16 +100,4 @@ class CustomSupabaseVectorStore(SupabaseVectorStore):
|
||||
if search.get("content")
|
||||
]
|
||||
|
||||
documents = [doc for doc, _ in match_result]
|
||||
max_tokens_input = self.max_input
|
||||
documents_to_return = []
|
||||
|
||||
# Limits to max_tokens_input with metadata chunk_size
|
||||
for doc in documents:
|
||||
if doc.metadata["chunk_size"] <= max_tokens_input:
|
||||
documents_to_return.append(doc)
|
||||
max_tokens_input -= doc.metadata["chunk_size"]
|
||||
else:
|
||||
break
|
||||
|
||||
return documents_to_return
|
||||
return match_result
|
||||
|
@ -44,14 +44,11 @@ services:
|
||||
- .env
|
||||
build:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: worker
|
||||
# volumes:
|
||||
# - ./backend/:/code/
|
||||
command: >
|
||||
/bin/sh -c "
|
||||
watchmedo auto-restart -d . -p '*.py' --recursive -- celery -A celery_worker worker -l info
|
||||
"
|
||||
command: celery -A celery_worker worker -l info
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
@ -63,13 +60,10 @@ services:
|
||||
- .env
|
||||
build:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
|
||||
container_name: beat
|
||||
command: >
|
||||
/bin/sh -c "
|
||||
watchmedo auto-restart -d . -p '*.py' --recursive -- celery -A celery_worker beat -l info
|
||||
"
|
||||
command: celery -A celery_worker beat -l info
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
@ -81,7 +75,7 @@ services:
|
||||
- .env
|
||||
build:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
|
||||
container_name: flower
|
||||
command: celery -A celery_worker flower -l info --port=5555
|
||||
|
@ -60,11 +60,12 @@ export const useQuestion = (): UseChatService => {
|
||||
const body = JSON.stringify(chatQuestion);
|
||||
|
||||
try {
|
||||
const response = await fetchInstance.post(
|
||||
`/chat/${chatId}/question/stream?brain_id=${currentBrain?.id ?? ""}`,
|
||||
body,
|
||||
headers
|
||||
);
|
||||
let url = `/chat/${chatId}/question/stream`;
|
||||
console.log("currentBrain", currentBrain);
|
||||
if (currentBrain?.id) {
|
||||
url += `?brain_id=${currentBrain.id}`;
|
||||
}
|
||||
const response = await fetchInstance.post(url, body, headers);
|
||||
if (!response.ok) {
|
||||
void handleFetchError(response);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user