feat(email): Add email sender tool and update image generator tool (#2579)

This pull request adds a new email sender tool and updates the image
generator tool.
This commit is contained in:
Stan Girard 2024-05-10 16:56:51 +02:00 committed by GitHub
parent 105a2b8ecc
commit 01c6e7b3bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 110 additions and 11 deletions

View File

@ -9,13 +9,13 @@ from typing import List, Optional
from fastapi import UploadFile
from logger import get_logger
from modules.user.service.user_usage import UserUsage
from modules.assistant.dto.inputs import InputAssistant
from modules.assistant.ito.utils.pdf_generator import PDFGenerator, PDFModel
from modules.chat.controller.chat.utils import update_user_usage
from modules.contact_support.controller.settings import ContactsSettings
from modules.upload.controller.upload_routes import upload_file
from modules.user.entity.user_identity import UserIdentity
from modules.user.service.user_usage import UserUsage
from packages.emails.send_email import send_email
from pydantic import BaseModel
from unidecode import unidecode
@ -111,8 +111,8 @@ class ITO(BaseModel):
</div>
"""
params = {
"from": mail_from,
"to": mail_to,
"sender": mail_from,
"to": [mail_to],
"subject": "Quivr Ingestion Processed",
"reply_to": "no-reply@quivr.app",
"html": body,

View File

@ -1,6 +1,6 @@
import json
import operator
from typing import Annotated, AsyncIterable, List, Sequence, TypedDict
from typing import Annotated, AsyncIterable, List, Optional, Sequence, TypedDict
from uuid import UUID
from langchain.tools import BaseTool
@ -15,7 +15,12 @@ from modules.brain.knowledge_brain_qa import KnowledgeBrainQA
from modules.chat.dto.chats import ChatQuestion
from modules.chat.dto.outputs import GetChatHistoryOutput
from modules.chat.service.chat_service import ChatService
from modules.tools import ImageGeneratorTool, URLReaderTool, WebSearchTool
from modules.tools import (
EmailSenderTool,
ImageGeneratorTool,
URLReaderTool,
WebSearchTool,
)
class AgentState(TypedDict):
@ -37,8 +42,8 @@ class GPT4Brain(KnowledgeBrainQA):
KnowledgeBrainQA (_type_): A brain that store the knowledge internaly
"""
tools: List[BaseTool] = [WebSearchTool(), ImageGeneratorTool(), URLReaderTool()]
tool_executor: ToolExecutor = ToolExecutor(tools)
tools: Optional[List[BaseTool]] = None
tool_executor: Optional[ToolExecutor] = None
model_function: ChatOpenAI = None
def __init__(
@ -48,6 +53,13 @@ class GPT4Brain(KnowledgeBrainQA):
super().__init__(
**kwargs,
)
self.tools = [
WebSearchTool(),
ImageGeneratorTool(),
URLReaderTool(),
EmailSenderTool(user_email=self.user_email),
]
self.tool_executor = ToolExecutor(tools=self.tools)
def calculate_pricing(self):
return 3
@ -164,7 +176,7 @@ class GPT4Brain(KnowledgeBrainQA):
transformed_history, streamed_chat_history = (
self.initialize_streamed_chat_history(chat_id, question)
)
filtered_history = self.filter_history(transformed_history, 20, 2000)
filtered_history = self.filter_history(transformed_history, 40, 2000)
response_tokens = []
config = {"metadata": {"conversation_id": str(chat_id)}}
@ -232,7 +244,7 @@ class GPT4Brain(KnowledgeBrainQA):
transformed_history, _ = self.initialize_streamed_chat_history(
chat_id, question
)
filtered_history = self.filter_history(transformed_history, 20, 2000)
filtered_history = self.filter_history(transformed_history, 40, 2000)
config = {"metadata": {"conversation_id": str(chat_id)}}
prompt = ChatPromptTemplate.from_messages(

View File

@ -1,4 +1,4 @@
from .image_generator import ImageGeneratorTool
from .web_search import WebSearchTool
from .url_reader import URLReaderTool
from .email_sender import EmailSenderTool

View File

@ -0,0 +1,81 @@
# Extract and combine content recursively
from typing import Dict, Optional, Type
from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain.pydantic_v1 import BaseModel as BaseModelV1
from langchain.pydantic_v1 import Field as FieldV1
from langchain_community.document_loaders import PlaywrightURLLoader
from langchain_core.tools import BaseTool
from logger import get_logger
from models import BrainSettings
from modules.contact_support.controller.settings import ContactsSettings
from packages.emails.send_email import send_email
from pydantic import BaseModel
logger = get_logger(__name__)
class EmailInput(BaseModelV1):
text: str = FieldV1(
...,
title="text",
description="text to send in HTML email format. Use pretty formating, use bold, italic, next line, etc...",
)
class EmailSenderTool(BaseTool):
user_email: str
name = "email-sender"
description = "useful for when you need to send an email."
args_schema: Type[BaseModel] = EmailInput
brain_settings: BrainSettings = BrainSettings()
contact_settings: ContactsSettings = ContactsSettings()
def _run(
self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> Dict:
html_body = f"""
<div style="text-align: center;">
<img src="https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png" alt="Quivr Logo" style="width: 100px; height: 100px; border-radius: 50%; margin: 0 auto; display: block;">
<br />
</div>
"""
html_body += f"""
{text}
"""
logger.debug(f"Email body: {html_body}")
logger.debug(f"Email to: {self.user_email}")
logger.debug(f"Email from: {self.contact_settings.resend_contact_sales_from}")
try:
r = send_email(
{
"sender": self.contact_settings.resend_contact_sales_from,
"to": self.user_email,
"reply_to": "no-reply@quivr.app",
"subject": "Email from your assistant",
"html": html_body,
}
)
logger.info("Resend response", r)
except Exception as e:
logger.error(f"Error sending email: {e}")
return {"content": "Error sending email because of error: " + str(e)}
return {"content": "Email sent"}
async def _arun(
self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> Dict:
"""Run the tool asynchronously."""
loader = PlaywrightURLLoader(urls=[url], remove_selectors=["header", "footer"])
data = loader.load()
extracted_content = ""
for page in data:
extracted_content += page.page_content
return {"content": extracted_content}

View File

@ -57,5 +57,6 @@ class ImageGeneratorTool(BaseTool):
n=1,
)
image_url = response.data[0].url
revised_prompt = response.data[0].revised_prompt
# Make the url a markdown image
return f"![Generated Image]({image_url})"
return f"{revised_prompt} \n ![Generated Image]({image_url}) "

View File

@ -78,3 +78,8 @@ class WebSearchTool(BaseTool):
def _format_result(self, title: str, description: str, url: str) -> str:
return f"**{title}**\n{description}\n{url}"
if __name__ == "__main__":
tool = WebSearchTool()
print(tool.run("python"))