Browse Source

feat(Tools): add feishu document and message plugins (#6435)

Co-authored-by: 黎斌 <libin.23@bytedance.com>
走在修行的大街上 8 months ago
parent
commit
92cab33b73
20 changed files with 707 additions and 0 deletions
  1. 2 0
      api/core/tools/provider/_position.yaml
  2. 9 0
      api/core/tools/provider/builtin/feishu_document/_assets/icon.svg
  3. 15 0
      api/core/tools/provider/builtin/feishu_document/feishu_document.py
  4. 34 0
      api/core/tools/provider/builtin/feishu_document/feishu_document.yaml
  5. 19 0
      api/core/tools/provider/builtin/feishu_document/tools/create_document.py
  6. 47 0
      api/core/tools/provider/builtin/feishu_document/tools/create_document.yaml
  7. 17 0
      api/core/tools/provider/builtin/feishu_document/tools/get_document_raw_content.py
  8. 23 0
      api/core/tools/provider/builtin/feishu_document/tools/get_document_raw_content.yaml
  9. 19 0
      api/core/tools/provider/builtin/feishu_document/tools/list_document_block.py
  10. 48 0
      api/core/tools/provider/builtin/feishu_document/tools/list_document_block.yaml
  11. 19 0
      api/core/tools/provider/builtin/feishu_document/tools/write_document.py
  12. 56 0
      api/core/tools/provider/builtin/feishu_document/tools/write_document.yaml
  13. 19 0
      api/core/tools/provider/builtin/feishu_message/_assets/icon.svg
  14. 15 0
      api/core/tools/provider/builtin/feishu_message/feishu_message.py
  15. 34 0
      api/core/tools/provider/builtin/feishu_message/feishu_message.yaml
  16. 20 0
      api/core/tools/provider/builtin/feishu_message/tools/send_bot_message.py
  17. 91 0
      api/core/tools/provider/builtin/feishu_message/tools/send_bot_message.yaml
  18. 19 0
      api/core/tools/provider/builtin/feishu_message/tools/send_webhook_message.py
  19. 58 0
      api/core/tools/provider/builtin/feishu_message/tools/send_webhook_message.yaml
  20. 143 0
      api/core/tools/utils/feishu_api_utils.py

+ 2 - 0
api/core/tools/provider/_position.yaml

@@ -30,5 +30,7 @@
 - dingtalk
 - feishu
 - feishu_base
+- feishu_document
+- feishu_message
 - slack
 - tianditu

+ 9 - 0
api/core/tools/provider/builtin/feishu_document/_assets/icon.svg

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64px" height="64px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g><path style="opacity:1" fill="#fefefe" d="M -0.5,-0.5 C 20.8333,-0.5 42.1667,-0.5 63.5,-0.5C 63.5,20.8333 63.5,42.1667 63.5,63.5C 42.1667,63.5 20.8333,63.5 -0.5,63.5C -0.5,42.1667 -0.5,20.8333 -0.5,-0.5 Z"/></g>
+<g><path style="opacity:1" fill="#346df3" d="M 47.5,33.5 C 43.3272,29.8779 38.9939,29.7112 34.5,33C 32.682,35.4897 30.3487,37.3231 27.5,38.5C 23.5003,43.5136 24.167,47.847 29.5,51.5C 24.1563,51.666 18.8229,51.4994 13.5,51C 13,50.5 12.5,50 12,49.5C 11.3333,36.8333 11.3333,24.1667 12,11.5C 12.5,11 13,10.5 13.5,10C 24.1667,9.33333 34.8333,9.33333 45.5,10C 46,10.5 46.5,11 47,11.5C 47.4997,18.8258 47.6663,26.1591 47.5,33.5 Z"/></g>
+<g><path style="opacity:1" fill="#f9fafe" d="M 20.5,19.5 C 25.1785,19.3342 29.8452,19.5008 34.5,20C 35.8333,21 35.8333,22 34.5,23C 29.8333,23.6667 25.1667,23.6667 20.5,23C 19.3157,21.8545 19.3157,20.6879 20.5,19.5 Z"/></g>
+<g><path style="opacity:1" fill="#f3f6fe" d="M 20.5,27.5 C 22.5273,27.3379 24.5273,27.5045 26.5,28C 27.8333,29 27.8333,30 26.5,31C 24.5,31.6667 22.5,31.6667 20.5,31C 19.3157,29.8545 19.3157,28.6879 20.5,27.5 Z"/></g>
+<g><path style="opacity:1" fill="#36d4c1" d="M 47.5,33.5 C 48.7298,35.2972 49.3964,37.2972 49.5,39.5C 51.3904,39.2965 52.8904,39.9632 54,41.5C 55.1825,45.2739 54.3492,48.4406 51.5,51C 44.1742,51.4997 36.8409,51.6663 29.5,51.5C 24.167,47.847 23.5003,43.5136 27.5,38.5C 30.3487,37.3231 32.682,35.4897 34.5,33C 38.9939,29.7112 43.3272,29.8779 47.5,33.5 Z"/></g>
+</svg>

+ 15 - 0
api/core/tools/provider/builtin/feishu_document/feishu_document.py

@@ -0,0 +1,15 @@
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class FeishuDocumentProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict) -> None:
+        app_id = credentials.get('app_id')
+        app_secret = credentials.get('app_secret')
+        if not app_id or not app_secret:
+            raise ToolProviderCredentialValidationError("app_id and app_secret is required")
+        try:
+            assert FeishuRequest(app_id, app_secret).tenant_access_token is not None
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

+ 34 - 0
api/core/tools/provider/builtin/feishu_document/feishu_document.yaml

@@ -0,0 +1,34 @@
+identity:
+  author: Doug Lea
+  name: feishu_document
+  label:
+    en_US: Lark Cloud Document
+    zh_Hans: 飞书云文档
+  description:
+    en_US: Lark Cloud Document
+    zh_Hans: 飞书云文档
+  icon: icon.svg
+  tags:
+    - social
+    - productivity
+credentials_for_provider:
+  app_id:
+    type: text-input
+    required: true
+    label:
+      en_US: APP ID
+    placeholder:
+      en_US: Please input your feishu app id
+      zh_Hans: 请输入你的飞书 app id
+    help:
+      en_US: Get your app_id and app_secret from Feishu
+      zh_Hans: 从飞书获取您的 app_id 和 app_secret
+    url: https://open.feishu.cn
+  app_secret:
+    type: secret-input
+    required: true
+    label:
+      en_US: APP Secret
+    placeholder:
+      en_US: Please input your app secret
+      zh_Hans: 请输入你的飞书 app secret

+ 19 - 0
api/core/tools/provider/builtin/feishu_document/tools/create_document.py

@@ -0,0 +1,19 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class CreateDocumentTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        title = tool_parameters.get('title')
+        content = tool_parameters.get('content')
+        folder_token = tool_parameters.get('folder_token')
+
+        res = client.create_document(title, content, folder_token)
+        return self.create_json_message(res)

+ 47 - 0
api/core/tools/provider/builtin/feishu_document/tools/create_document.yaml

@@ -0,0 +1,47 @@
+identity:
+  name: create_document
+  author: Doug Lea
+  label:
+    en_US: Create Lark document
+    zh_Hans: 创建飞书文档
+description:
+  human:
+    en_US: Create Lark document
+    zh_Hans: 创建飞书文档,支持创建空文档和带内容的文档,支持 markdown 语法创建。
+  llm: A tool for creating Feishu documents.
+parameters:
+  - name: title
+    type: string
+    required: false
+    label:
+      en_US: Document title
+      zh_Hans: 文档标题
+    human_description:
+      en_US: Document title, only supports plain text content.
+      zh_Hans: 文档标题,只支持纯文本内容。
+    llm_description: 文档标题,只支持纯文本内容,可以为空。
+    form: llm
+
+  - name: content
+    type: string
+    required: false
+    label:
+      en_US: Document content
+      zh_Hans: 文档内容
+    human_description:
+      en_US: Document content, supports markdown syntax, can be empty.
+      zh_Hans: 文档内容,支持 markdown 语法,可以为空。
+    llm_description: 文档内容,支持 markdown 语法,可以为空。
+    form: llm
+
+  - name: folder_token
+    type: string
+    required: false
+    label:
+      en_US: folder_token
+      zh_Hans: 文档所在文件夹的 Token
+    human_description:
+      en_US: The token of the folder where the document is located. If it is not passed or is empty, it means the root directory.
+      zh_Hans: 文档所在文件夹的 Token,不传或传空表示根目录。
+    llm_description: 文档所在文件夹的 Token,不传或传空表示根目录。
+    form: llm

+ 17 - 0
api/core/tools/provider/builtin/feishu_document/tools/get_document_raw_content.py

@@ -0,0 +1,17 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class GetDocumentRawContentTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        document_id = tool_parameters.get('document_id')
+
+        res = client.get_document_raw_content(document_id)
+        return self.create_json_message(res)

+ 23 - 0
api/core/tools/provider/builtin/feishu_document/tools/get_document_raw_content.yaml

@@ -0,0 +1,23 @@
+identity:
+  name: get_document_raw_content
+  author: Doug Lea
+  label:
+    en_US: Get Document Raw Content
+    zh_Hans: 获取文档纯文本内容
+description:
+  human:
+    en_US: Get document raw content
+    zh_Hans: 获取文档纯文本内容
+  llm: A tool for getting the plain text content of Feishu documents
+parameters:
+  - name: document_id
+    type: string
+    required: true
+    label:
+      en_US: document_id
+      zh_Hans: 飞书文档的唯一标识
+    human_description:
+      en_US: Unique ID of Feishu document document_id
+      zh_Hans: 飞书文档的唯一标识 document_id
+    llm_description: 飞书文档的唯一标识 document_id
+    form: llm

+ 19 - 0
api/core/tools/provider/builtin/feishu_document/tools/list_document_block.py

@@ -0,0 +1,19 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class ListDocumentBlockTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        document_id = tool_parameters.get('document_id')
+        page_size = tool_parameters.get('page_size', 500)
+        page_token = tool_parameters.get('page_token', '')
+
+        res = client.list_document_block(document_id, page_token, page_size)
+        return self.create_json_message(res)

+ 48 - 0
api/core/tools/provider/builtin/feishu_document/tools/list_document_block.yaml

@@ -0,0 +1,48 @@
+identity:
+  name: list_document_block
+  author: Doug Lea
+  label:
+    en_US: List Document Block
+    zh_Hans: 获取飞书文档所有块
+description:
+  human:
+    en_US: List document block
+    zh_Hans: 获取飞书文档所有块的富文本内容并分页返回。
+  llm: A tool to get all blocks of Feishu documents
+parameters:
+  - name: document_id
+    type: string
+    required: true
+    label:
+      en_US: document_id
+      zh_Hans: 飞书文档的唯一标识
+    human_description:
+      en_US: Unique ID of Feishu document document_id
+      zh_Hans: 飞书文档的唯一标识 document_id
+    llm_description: 飞书文档的唯一标识 document_id
+    form: llm
+
+  - name: page_size
+    type: number
+    required: false
+    default: 500
+    label:
+      en_US: page_size
+      zh_Hans: 分页大小
+    human_description:
+      en_US: Paging size, the default and maximum value is 500.
+      zh_Hans: 分页大小, 默认值和最大值为 500。
+    llm_description: 分页大小, 表示一次请求最多返回多少条数据,默认值和最大值为 500。
+    form: llm
+
+  - name: page_token
+    type: string
+    required: false
+    label:
+      en_US: page_token
+      zh_Hans: 分页标记
+    human_description:
+      en_US: Pagination tag, used to paginate query results so that more items can be obtained in the next traversal.
+      zh_Hans: 分页标记,用于分页查询结果,以便下次遍历时获取更多项。
+    llm_description: 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果。
+    form: llm

+ 19 - 0
api/core/tools/provider/builtin/feishu_document/tools/write_document.py

@@ -0,0 +1,19 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class CreateDocumentTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        document_id = tool_parameters.get('document_id')
+        content = tool_parameters.get('content')
+        position = tool_parameters.get('position')
+
+        res = client.write_document(document_id, content, position)
+        return self.create_json_message(res)

+ 56 - 0
api/core/tools/provider/builtin/feishu_document/tools/write_document.yaml

@@ -0,0 +1,56 @@
+identity:
+  name: write_document
+  author: Doug Lea
+  label:
+    en_US: Write Document
+    zh_Hans: 在飞书文档中新增内容
+description:
+  human:
+    en_US: Adding new content to Lark documents
+    zh_Hans: 在飞书文档中新增内容
+  llm: A tool for adding new content to Lark documents.
+parameters:
+  - name: document_id
+    type: string
+    required: true
+    label:
+      en_US: document_id
+      zh_Hans: 飞书文档的唯一标识
+    human_description:
+      en_US: Unique ID of Feishu document document_id
+      zh_Hans: 飞书文档的唯一标识 document_id
+    llm_description: 飞书文档的唯一标识 document_id
+    form: llm
+
+  - name: content
+    type: string
+    required: true
+    label:
+      en_US: document content
+      zh_Hans: 文档内容
+    human_description:
+      en_US: Document content, supports markdown syntax, can be empty.
+      zh_Hans: 文档内容,支持 markdown 语法,可以为空。
+    llm_description:
+    form: llm
+
+  - name: position
+    type: select
+    required: true
+    default: start
+    label:
+      en_US: Choose where to add content
+      zh_Hans: 选择添加内容的位置
+    human_description:
+      en_US: Please fill in start or end to add content at the beginning or end of the document respectively.
+      zh_Hans: 请填入 start 或 end, 分别表示在文档开头(start)或结尾(end)添加内容。
+    form: llm
+    options:
+      - value: start
+        label:
+          en_US: start
+          zh_Hans: 在文档开头添加内容
+      - value: end
+        label:
+          en_US: end
+          zh_Hans: 在文档结尾添加内容

+ 19 - 0
api/core/tools/provider/builtin/feishu_message/_assets/icon.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">  <image id="image0" width="64" height="64" x="0" y="0"
+    xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
+AAB1MAAA6mAAADqYAAAXcJy6UTwAAAC9UExURf///////+bs/vL2/qa/+n+j+E1/9TNt9FmI9nOa
++Obt/sza/GaR97PI+9nk/aa/+5m2+oCk+Iyt+Yys+eXt/oCj+L/R+4yt+HOb+Ex/9TOA6jOi2jO8
+zTPJxzPWwDOa3eb69zN67X/l2DOb3TPPw0DZxLPv55nq4LPw6DOB6vL9+0B29TOo16bt4zPCynPj
+00zbyDN08WbgzzOH50DYxFmI9bLI+5nr34zn3OX699n384zo21ndyzTWwJnq37nAcdIAAAABdFJO
+U/4a4wd9AAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAAd0SU1FB+gHEggfEk4D
+XiUAAAFOSURBVFjD7dVZU8IwFAXgpq2NtFFRUVTKtYC4gCvu6///WcCMI9Cc3CR2fLLn/XyT3KRp
+IComqIEa+GMgDMNfA1G8lsh51htx6g9kSi5HbfgBm6v1eZLUA9iSKE1nYFviqMgNMPVn44xcgB1p
+jnIAmpLLrhVoST6ZDdizAMoCZNKWjAdsC8BLWACRtS9lygH7DkDMAW0H4IADlANwyAEJUzzq5F2i
+bn5cMIC53svpJ/3CHxic0FKGp75Ah0o585uB1ic69zmFnt6nYQEBfA9yAFDf/SZeEMwIfgtjAFxi
+4AoBcA/XGLiBAHoPcJ9uISAaWv/OABAGWuOKgIgrbgHM0TDEiQnQHnavY0Tfwz0GCgMA/kweVxm/
+y2gJD4UJQJd5wE6gfIxlIXlsPz1rwIsRwNGFkR8gXicVASHe3j++u5+zfHlugU8N1MD/AQI2U2Cm
+Yux2lsz2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTA3LTE4VDA4OjMxOjE4KzAwOjAwPdC6HgAA
+ACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wNy0xOFQwODozMToxOCswMDowMEyNAqIAAAAodEVYdGRh
+dGU6dGltZXN0YW1wADIwMjQtMDctMThUMDg6MzE6MTgrMDA6MDAbmCN9AAAAAElFTkSuQmCC" />
+</svg>

+ 15 - 0
api/core/tools/provider/builtin/feishu_message/feishu_message.py

@@ -0,0 +1,15 @@
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class FeishuMessageProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict) -> None:
+        app_id = credentials.get('app_id')
+        app_secret = credentials.get('app_secret')
+        if not app_id or not app_secret:
+            raise ToolProviderCredentialValidationError("app_id and app_secret is required")
+        try:
+            assert FeishuRequest(app_id, app_secret).tenant_access_token is not None
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

+ 34 - 0
api/core/tools/provider/builtin/feishu_message/feishu_message.yaml

@@ -0,0 +1,34 @@
+identity:
+  author: Doug Lea
+  name: feishu_message
+  label:
+    en_US: Lark Message
+    zh_Hans: 飞书消息
+  description:
+    en_US: Lark Message
+    zh_Hans: 飞书消息
+  icon: icon.svg
+  tags:
+    - social
+    - productivity
+credentials_for_provider:
+  app_id:
+    type: text-input
+    required: true
+    label:
+      en_US: APP ID
+    placeholder:
+      en_US: Please input your feishu app id
+      zh_Hans: 请输入你的飞书 app id
+    help:
+      en_US: Get your app_id and app_secret from Feishu
+      zh_Hans: 从飞书获取您的 app_id 和 app_secret
+    url: https://open.feishu.cn
+  app_secret:
+    type: secret-input
+    required: true
+    label:
+      en_US: APP Secret
+    placeholder:
+      en_US: Please input your app secret
+      zh_Hans: 请输入你的飞书 app secret

+ 20 - 0
api/core/tools/provider/builtin/feishu_message/tools/send_bot_message.py

@@ -0,0 +1,20 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class SendBotMessageTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        receive_id_type = tool_parameters.get('receive_id_type')
+        receive_id = tool_parameters.get('receive_id')
+        msg_type = tool_parameters.get('msg_type')
+        content = tool_parameters.get('content')
+
+        res = client.send_bot_message(receive_id_type, receive_id, msg_type, content)
+        return self.create_json_message(res)

+ 91 - 0
api/core/tools/provider/builtin/feishu_message/tools/send_bot_message.yaml

@@ -0,0 +1,91 @@
+identity:
+  name: send_bot_message
+  author: Doug Lea
+  label:
+    en_US: Send Bot Message
+    zh_Hans: 发送飞书应用消息
+description:
+  human:
+    en_US: Send bot message
+    zh_Hans: 发送飞书应用消息
+  llm: A tool for sending Feishu application messages.
+parameters:
+  - name: receive_id_type
+    type: select
+    required: true
+    options:
+      - value: open_id
+        label:
+          en_US: open id
+          zh_Hans: open id
+      - value: union_id
+        label:
+          en_US: union id
+          zh_Hans: union id
+      - value: user_id
+        label:
+          en_US: user id
+          zh_Hans: user id
+      - value: email
+        label:
+          en_US: email
+          zh_Hans: email
+      - value: chat_id
+        label:
+          en_US: chat id
+          zh_Hans: chat id
+    label:
+      en_US: User ID Type
+      zh_Hans: 用户 ID 类型
+    human_description:
+      en_US: User ID Type
+      zh_Hans: 用户 ID 类型,可选值有 open_id、union_id、user_id、email、chat_id。
+    llm_description: 用户 ID 类型,可选值有 open_id、union_id、user_id、email、chat_id。
+    form: llm
+
+  - name: receive_id
+    type: string
+    required: true
+    label:
+      en_US: Receive Id
+      zh_Hans: 消息接收者的 ID
+    human_description:
+      en_US: The ID of the message receiver. The ID type should correspond to the query parameter receive_id_type.
+      zh_Hans: 消息接收者的 ID,ID 类型应与查询参数 receive_id_type 对应。
+    llm_description: 消息接收者的 ID,ID 类型应与查询参数 receive_id_type 对应。
+    form: llm
+
+  - name: msg_type
+    type: string
+    required: true
+    options:
+      - value: text
+        label:
+          en_US: text
+          zh_Hans: 文本
+      - value: interactive
+        label:
+          en_US: message card
+          zh_Hans: 消息卡片
+    label:
+      en_US: Message type
+      zh_Hans: 消息类型
+    human_description:
+      en_US: Message type, optional values are, text (text), interactive (message card).
+      zh_Hans: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
+    llm_description: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
+    form: llm
+
+  - name: content
+    type: string
+    required: true
+    label:
+      en_US: Message content
+      zh_Hans: 消息内容
+    human_description:
+      en_US: Message content
+      zh_Hans: |
+        消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容,
+        具体格式说明参考:https://open.larkoffice.com/document/server-docs/im-v1/message-content-description/create_json
+    llm_description: 消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容。
+    form: llm

+ 19 - 0
api/core/tools/provider/builtin/feishu_message/tools/send_webhook_message.py

@@ -0,0 +1,19 @@
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+from core.tools.utils.feishu_api_utils import FeishuRequest
+
+
+class SendWebhookMessageTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) ->ToolInvokeMessage:
+        app_id = self.runtime.credentials.get('app_id')
+        app_secret = self.runtime.credentials.get('app_secret')
+        client = FeishuRequest(app_id, app_secret)
+
+        webhook = tool_parameters.get('webhook')
+        msg_type = tool_parameters.get('msg_type')
+        content = tool_parameters.get('content')
+
+        res = client.send_webhook_message(webhook, msg_type, content)
+        return self.create_json_message(res)

+ 58 - 0
api/core/tools/provider/builtin/feishu_message/tools/send_webhook_message.yaml

@@ -0,0 +1,58 @@
+identity:
+  name: send_webhook_message
+  author: Doug Lea
+  label:
+    en_US: Send Webhook Message
+    zh_Hans: 使用自定义机器人发送飞书消息
+description:
+  human:
+    en_US: Send webhook message
+    zh_Hans: 使用自定义机器人发送飞书消息
+  llm: A tool for sending Lark messages using a custom robot.
+parameters:
+  - name: webhook
+    type: string
+    required: true
+    label:
+      en_US: webhook
+      zh_Hans: webhook 的地址
+    human_description:
+      en_US: The address of the webhook
+      zh_Hans: webhook 的地址
+    llm_description: webhook 的地址
+    form: llm
+
+  - name: msg_type
+    type: string
+    required: true
+    options:
+      - value: text
+        label:
+          en_US: text
+          zh_Hans: 文本
+      - value: interactive
+        label:
+          en_US: message card
+          zh_Hans: 消息卡片
+    label:
+      en_US: Message type
+      zh_Hans: 消息类型
+    human_description:
+      en_US: Message type, optional values are, text (text), interactive (message card).
+      zh_Hans: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
+    llm_description: 消息类型,可选值有:text(文本)、interactive(消息卡片)。
+    form: llm
+
+  - name: content
+    type: string
+    required: true
+    label:
+      en_US: Message content
+      zh_Hans: 消息内容
+    human_description:
+      en_US: Message content
+      zh_Hans: |
+        消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容,
+        具体格式说明参考:https://open.larkoffice.com/document/server-docs/im-v1/message-content-description/create_json
+    llm_description: 消息内容,JSON 结构序列化后的字符串。不同 msg_type 对应不同内容。
+    form: llm

+ 143 - 0
api/core/tools/utils/feishu_api_utils.py

@@ -0,0 +1,143 @@
+import httpx
+
+from extensions.ext_redis import redis_client
+
+
+class FeishuRequest:
+    def __init__(self, app_id: str, app_secret: str):
+        self.app_id = app_id
+        self.app_secret = app_secret
+
+    @property
+    def tenant_access_token(self):
+        feishu_tenant_access_token = f"tools:{self.app_id}:feishu_tenant_access_token"
+        if redis_client.exists(feishu_tenant_access_token):
+            return redis_client.get(feishu_tenant_access_token).decode()
+        res = self.get_tenant_access_token(self.app_id, self.app_secret)
+        redis_client.setex(feishu_tenant_access_token, res.get("expire"), res.get("tenant_access_token"))
+        return res.get("tenant_access_token")
+
+    def _send_request(self, url: str, method: str = "post", require_token: bool = True, payload: dict = None,
+                      params: dict = None):
+        headers = {
+            "Content-Type": "application/json",
+            "user-agent": "Dify",
+        }
+        if require_token:
+            headers["tenant-access-token"] = f"{self.tenant_access_token}"
+        res = httpx.request(method=method, url=url, headers=headers, json=payload, params=params, timeout=30).json()
+        if res.get("code") != 0:
+            raise Exception(res)
+        return res
+
+    def get_tenant_access_token(self, app_id: str, app_secret: str) -> dict:
+        """
+        API url: https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal
+        Example Response:
+        {
+            "code": 0,
+            "msg": "ok",
+            "tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
+            "expire": 7200
+        }
+        """
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/access_token/get_tenant_access_token"
+        payload = {
+            "app_id": app_id,
+            "app_secret": app_secret
+        }
+        res = self._send_request(url, require_token=False, payload=payload)
+        return res
+
+    def create_document(self, title: str, content: str, folder_token: str) -> dict:
+        """
+        API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/create
+        Example Response:
+        {
+            "data": {
+                "title": "title",
+                "url": "https://svi136aogf123.feishu.cn/docx/VWbvd4fEdoW0WSxaY1McQTz8n7d",
+                "type": "docx",
+                "token": "VWbvd4fEdoW0WSxaY1McQTz8n7d"
+            },
+            "log_id": "021721281231575fdbddc0200ff00060a9258ec0000103df61b5d",
+            "code": 0,
+            "msg": "创建飞书文档成功,请查看"
+        }
+        """
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/document/create_document"
+        payload = {
+            "title": title,
+            "content": content,
+            "folder_token": folder_token,
+        }
+        res = self._send_request(url, payload=payload)
+        return res.get("data")
+
+    def write_document(self, document_id: str, content: str, position: str = "start") -> dict:
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/document/write_document"
+        payload = {
+            "document_id": document_id,
+            "content": content,
+            "position": position
+        }
+        res = self._send_request(url, payload=payload)
+        return res.get("data")
+
+    def get_document_raw_content(self, document_id: str) -> dict:
+        """
+        API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/raw_content
+        Example Response:
+        {
+            "code": 0,
+            "msg": "success",
+            "data": {
+                "content": "云文档\n多人实时协同,插入一切元素。不仅是在线文档,更是强大的创作和互动工具\n云文档:专为协作而生\n"
+            }
+        }
+        """
+        params = {
+            "document_id": document_id,
+        }
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/document/get_document_raw_content"
+        res = self._send_request(url, method="get", params=params)
+        return res.get("data").get("content")
+
+    def list_document_block(self, document_id: str, page_token: str, page_size: int = 500) -> dict:
+        """
+        API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/list
+        """
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/document/list_document_block"
+        params = {
+            "document_id": document_id,
+            "page_size": page_size,
+            "page_token": page_token,
+        }
+        res = self._send_request(url, method="get", params=params)
+        return res.get("data")
+
+    def send_bot_message(self, receive_id_type: str, receive_id: str, msg_type: str, content: str) -> dict:
+        """
+        API url: https://open.larkoffice.com/document/server-docs/im-v1/message/create
+        """
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/message/send_bot_message"
+        params = {
+            "receive_id_type": receive_id_type,
+        }
+        payload = {
+            "receive_id": receive_id,
+            "msg_type": msg_type,
+            "content": content,
+        }
+        res = self._send_request(url, params=params, payload=payload)
+        return res.get("data")
+
+    def send_webhook_message(self, webhook: str, msg_type: str, content: str) -> dict:
+        url = "https://lark-plugin-api.solutionsuite.cn/lark-plugin/message/send_webhook_message"
+        payload = {
+            "webhook": webhook,
+            "msg_type": msg_type,
+            "content": content,
+        }
+        res = self._send_request(url, require_token=False, payload=payload)
+        return res