ソースを参照

feat: add builtin tools for send email (#10493)

wakaka6 5 ヶ月 前
コミット
9c7edb9242

ファイルの差分が大きいため隠しています
+ 0 - 0
api/core/tools/provider/builtin/email/_assets/icon.svg


+ 7 - 0
api/core/tools/provider/builtin/email/email.py

@@ -0,0 +1,7 @@
+from core.tools.provider.builtin.email.tools.send_mail import SendMailTool
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class SmtpProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict) -> None:
+        SendMailTool()

+ 83 - 0
api/core/tools/provider/builtin/email/email.yaml

@@ -0,0 +1,83 @@
+identity:
+  author: wakaka6
+  name: email
+  label:
+    en_US: email
+    zh_Hans: 电子邮件
+  description:
+    en_US: send email through smtp protocol
+    zh_Hans: 通过smtp协议发送电子邮件
+  icon: icon.svg
+  tags:
+    - utilities
+credentials_for_provider:
+  email_account:
+    type: text-input
+    required: true
+    label:
+      en_US: email account
+      zh_Hans: 邮件账号
+    placeholder:
+      en_US: input you email account
+      zh_Hans: 输入你的邮箱账号
+    help:
+      en_US: email account
+      zh_Hans: 邮件账号
+  email_password:
+    type: secret-input
+    required: true
+    label:
+      en_US: email password
+      zh_Hans: 邮件密码
+    placeholder:
+      en_US: email password
+      zh_Hans: 邮件密码
+    help:
+      en_US: email password
+      zh_Hans: 邮件密码
+  smtp_server:
+    type: text-input
+    required: true
+    label:
+      en_US: smtp server
+      zh_Hans: 发信smtp服务器地址
+    placeholder:
+      en_US: smtp server
+      zh_Hans: 发信smtp服务器地址
+    help:
+      en_US: smtp server
+      zh_Hans: 发信smtp服务器地址
+  smtp_port:
+    type: text-input
+    required: true
+    label:
+      en_US: smtp server port
+      zh_Hans: 发信smtp服务器端口
+    placeholder:
+      en_US: smtp server port
+      zh_Hans: 发信smtp服务器端口
+    help:
+      en_US: smtp server port
+      zh_Hans: 发信smtp服务器端口
+  encrypt_method:
+    type: select
+    required: true
+    options:
+      - value: NONE
+        label:
+          en_US: NONE
+          zh_Hans: 无加密
+      - value: SSL
+        label:
+          en_US: SSL
+          zh_Hans: SSL加密
+      - value: TLS
+        label:
+          en_US: START TLS
+          zh_Hans: START TLS加密
+    label:
+      en_US: encrypt method
+      zh_Hans: 加密方式
+    help:
+      en_US: smtp server encrypt method
+      zh_Hans: 发信smtp服务器加密方式

+ 53 - 0
api/core/tools/provider/builtin/email/tools/send.py

@@ -0,0 +1,53 @@
+import logging
+import smtplib
+import ssl
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+
+from pydantic import BaseModel
+
+
+class SendEmailToolParameters(BaseModel):
+    smtp_server: str
+    smtp_port: int
+
+    email_account: str
+    email_password: str
+
+    sender_to: str
+    subject: str
+    email_content: str
+    encrypt_method: str
+
+
+def send_mail(parmas: SendEmailToolParameters):
+    timeout = 60
+    msg = MIMEMultipart("alternative")
+    msg["From"] = parmas.email_account
+    msg["To"] = parmas.sender_to
+    msg["Subject"] = parmas.subject
+    msg.attach(MIMEText(parmas.email_content, "plain"))
+    msg.attach(MIMEText(parmas.email_content, "html"))
+
+    ctx = ssl.create_default_context()
+
+    if parmas.encrypt_method.upper() == "SSL":
+        try:
+            with smtplib.SMTP_SSL(parmas.smtp_server, parmas.smtp_port, context=ctx, timeout=timeout) as server:
+                server.login(parmas.email_account, parmas.email_password)
+                server.sendmail(parmas.email_account, parmas.sender_to, msg.as_string())
+                return True
+        except Exception as e:
+            logging.exception("send email failed: %s", e)
+            return False
+    else:  # NONE or TLS
+        try:
+            with smtplib.SMTP(parmas.smtp_server, parmas.smtp_port, timeout=timeout) as server:
+                if parmas.encrypt_method.upper() == "TLS":
+                    server.starttls(context=ctx)
+                server.login(parmas.email_account, parmas.email_password)
+                server.sendmail(parmas.email_account, parmas.sender_to, msg.as_string())
+                return True
+        except Exception as e:
+            logging.exception("send email failed: %s", e)
+            return False

+ 66 - 0
api/core/tools/provider/builtin/email/tools/send_mail.py

@@ -0,0 +1,66 @@
+import re
+from typing import Any, Union
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.provider.builtin.email.tools.send import (
+    SendEmailToolParameters,
+    send_mail,
+)
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class SendMailTool(BuiltinTool):
+    def _invoke(
+        self, user_id: str, tool_parameters: dict[str, Any]
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        invoke tools
+        """
+        sender = self.runtime.credentials.get("email_account", "")
+        email_rgx = re.compile(r"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$")
+        password = self.runtime.credentials.get("email_password", "")
+        smtp_server = self.runtime.credentials.get("smtp_server", "")
+        if not smtp_server:
+            return self.create_text_message("please input smtp server")
+        smtp_port = self.runtime.credentials.get("smtp_port", "")
+        try:
+            smtp_port = int(smtp_port)
+        except ValueError:
+            return self.create_text_message("Invalid parameter smtp_port(should be int)")
+
+        if not sender:
+            return self.create_text_message("please input sender")
+        if not email_rgx.match(sender):
+            return self.create_text_message("Invalid parameter userid, the sender is not a mailbox")
+
+        receiver_email = tool_parameters["send_to"]
+        if not receiver_email:
+            return self.create_text_message("please input receiver email")
+        if not email_rgx.match(receiver_email):
+            return self.create_text_message("Invalid parameter receiver email, the receiver email is not a mailbox")
+        email_content = tool_parameters.get("email_content", "")
+
+        if not email_content:
+            return self.create_text_message("please input email content")
+
+        subject = tool_parameters.get("subject", "")
+        if not subject:
+            return self.create_text_message("please input email subject")
+
+        encrypt_method = self.runtime.credentials.get("encrypt_method", "")
+        if not encrypt_method:
+            return self.create_text_message("please input encrypt method")
+
+        send_email_params = SendEmailToolParameters(
+            smtp_server=smtp_server,
+            smtp_port=smtp_port,
+            email_account=sender,
+            email_password=password,
+            sender_to=receiver_email,
+            subject=subject,
+            email_content=email_content,
+            encrypt_method=encrypt_method,
+        )
+        if send_mail(send_email_params):
+            return self.create_text_message("send email success")
+        return self.create_text_message("send email failed")

+ 46 - 0
api/core/tools/provider/builtin/email/tools/send_mail.yaml

@@ -0,0 +1,46 @@
+identity:
+  name: send_mail
+  author: wakaka6
+  label:
+    en_US: send email
+    zh_Hans: 发送邮件
+  icon: icon.svg
+description:
+  human:
+    en_US: A tool for sending email
+    zh_Hans: 用于发送邮件
+  llm: A tool for sending email
+parameters:
+  - name: send_to
+    type: string
+    required: true
+    label:
+      en_US: Recipient email account
+      zh_Hans: 收件人邮箱账号
+    human_description:
+      en_US: Recipient email account
+      zh_Hans: 收件人邮箱账号
+    llm_description: Recipient email account
+    form: llm
+  - name: subject
+    type: string
+    required: true
+    label:
+      en_US: email subject
+      zh_Hans: 邮件主题
+    human_description:
+      en_US: email subject
+      zh_Hans: 邮件主题
+    llm_description: email subject
+    form: llm
+  - name: email_content
+    type: string
+    required: true
+    label:
+      en_US: email content
+      zh_Hans: 邮件内容
+    human_description:
+      en_US: email content
+      zh_Hans: 邮件内容
+    llm_description: email content
+    form: llm

+ 75 - 0
api/core/tools/provider/builtin/email/tools/send_mail_batch.py

@@ -0,0 +1,75 @@
+import json
+import re
+from typing import Any, Union
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.provider.builtin.email.tools.send import (
+    SendEmailToolParameters,
+    send_mail,
+)
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class SendMailTool(BuiltinTool):
+    def _invoke(
+        self, user_id: str, tool_parameters: dict[str, Any]
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        invoke tools
+        """
+        sender = self.runtime.credentials.get("email_account", "")
+        email_rgx = re.compile(r"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$")
+        password = self.runtime.credentials.get("email_password", "")
+        smtp_server = self.runtime.credentials.get("smtp_server", "")
+        if not smtp_server:
+            return self.create_text_message("please input smtp server")
+        smtp_port = self.runtime.credentials.get("smtp_port", "")
+        try:
+            smtp_port = int(smtp_port)
+        except ValueError:
+            return self.create_text_message("Invalid parameter smtp_port(should be int)")
+
+        if not sender:
+            return self.create_text_message("please input sender")
+        if not email_rgx.match(sender):
+            return self.create_text_message("Invalid parameter userid, the sender is not a mailbox")
+
+        receivers_email = tool_parameters["send_to"]
+        if not receivers_email:
+            return self.create_text_message("please input receiver email")
+        receivers_email = json.loads(receivers_email)
+        for receiver in receivers_email:
+            if not email_rgx.match(receiver):
+                return self.create_text_message(
+                    f"Invalid parameter receiver email, the receiver email({receiver}) is not a mailbox"
+                )
+        email_content = tool_parameters.get("email_content", "")
+
+        if not email_content:
+            return self.create_text_message("please input email content")
+
+        subject = tool_parameters.get("subject", "")
+        if not subject:
+            return self.create_text_message("please input email subject")
+
+        encrypt_method = self.runtime.credentials.get("encrypt_method", "")
+        if not encrypt_method:
+            return self.create_text_message("please input encrypt method")
+
+        msg = {}
+        for receiver in receivers_email:
+            send_email_params = SendEmailToolParameters(
+                smtp_server=smtp_server,
+                smtp_port=smtp_port,
+                email_account=sender,
+                email_password=password,
+                sender_to=receiver,
+                subject=subject,
+                email_content=email_content,
+                encrypt_method=encrypt_method,
+            )
+            if send_mail(send_email_params):
+                msg[receiver] = "send email success"
+            else:
+                msg[receiver] = "send email failed"
+        return self.create_text_message(json.dumps(msg))

+ 46 - 0
api/core/tools/provider/builtin/email/tools/send_mail_batch.yaml

@@ -0,0 +1,46 @@
+identity:
+  name: send_mail_batch
+  author: wakaka6
+  label:
+    en_US: send email to multiple recipients
+    zh_Hans: 发送邮件给多个收件人
+  icon: icon.svg
+description:
+  human:
+    en_US: A tool for sending email to multiple recipients
+    zh_Hans: 用于发送邮件给多个收件人的工具
+  llm: A tool for sending email to multiple recipients
+parameters:
+  - name: send_to
+    type: string
+    required: true
+    label:
+      en_US: Recipient email account(json list)
+      zh_Hans: 收件人邮箱账号(json list)
+    human_description:
+      en_US: Recipient email account
+      zh_Hans: 收件人邮箱账号
+    llm_description: A list of recipient email account(json format)
+    form: llm
+  - name: subject
+    type: string
+    required: true
+    label:
+      en_US: email subject
+      zh_Hans: 邮件主题
+    human_description:
+      en_US: email subject
+      zh_Hans: 邮件主题
+    llm_description: email subject
+    form: llm
+  - name: email_content
+    type: string
+    required: true
+    label:
+      en_US: email content
+      zh_Hans: 邮件内容
+    human_description:
+      en_US: email content
+      zh_Hans: 邮件内容
+    llm_description: email content
+    form: llm

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません