merge main

This commit is contained in:
Joel
2024-10-28 10:51:02 +08:00
858 changed files with 16206 additions and 17932 deletions

View File

@ -32,8 +32,8 @@ class UserToolProvider(BaseModel):
original_credentials: Optional[dict] = None
is_team_authorization: bool = False
allow_delete: bool = True
tools: list[UserTool] = None
labels: list[str] = None
tools: list[UserTool] | None = None
labels: list[str] | None = None
def to_dict(self) -> dict:
# -------------
@ -42,7 +42,7 @@ class UserToolProvider(BaseModel):
for tool in tools:
if tool.get("parameters"):
for parameter in tool.get("parameters"):
if parameter.get("type") == ToolParameter.ToolParameterType.FILE.value:
if parameter.get("type") == ToolParameter.ToolParameterType.SYSTEM_FILES.value:
parameter["type"] = "files"
# -------------

View File

@ -104,14 +104,15 @@ class ToolInvokeMessage(BaseModel):
BLOB = "blob"
JSON = "json"
IMAGE_LINK = "image_link"
FILE_VAR = "file_var"
FILE = "file"
type: MessageType = MessageType.TEXT
"""
plain text, image url or link url
"""
message: str | bytes | dict | None = None
meta: dict[str, Any] | None = None
# TODO: Use a BaseModel for meta
meta: dict[str, Any] = Field(default_factory=dict)
save_as: str = ""
@ -143,6 +144,67 @@ class ToolParameter(BaseModel):
SELECT = "select"
SECRET_INPUT = "secret-input"
FILE = "file"
FILES = "files"
# deprecated, should not use.
SYSTEM_FILES = "systme-files"
def as_normal_type(self):
if self in {
ToolParameter.ToolParameterType.SECRET_INPUT,
ToolParameter.ToolParameterType.SELECT,
}:
return "string"
return self.value
def cast_value(self, value: Any, /):
try:
match self:
case (
ToolParameter.ToolParameterType.STRING
| ToolParameter.ToolParameterType.SECRET_INPUT
| ToolParameter.ToolParameterType.SELECT
):
if value is None:
return ""
else:
return value if isinstance(value, str) else str(value)
case ToolParameter.ToolParameterType.BOOLEAN:
if value is None:
return False
elif isinstance(value, str):
# Allowed YAML boolean value strings: https://yaml.org/type/bool.html
# and also '0' for False and '1' for True
match value.lower():
case "true" | "yes" | "y" | "1":
return True
case "false" | "no" | "n" | "0":
return False
case _:
return bool(value)
else:
return value if isinstance(value, bool) else bool(value)
case ToolParameter.ToolParameterType.NUMBER:
if isinstance(value, int | float):
return value
elif isinstance(value, str) and value:
if "." in value:
return float(value)
else:
return int(value)
case (
ToolParameter.ToolParameterType.SYSTEM_FILES
| ToolParameter.ToolParameterType.FILE
| ToolParameter.ToolParameterType.FILES
):
return value
case _:
return str(value)
except Exception:
raise ValueError(f"The tool parameter value {value} is not in correct type.")
class ToolParameterForm(Enum):
SCHEMA = "schema" # should be set while adding tool

View File

@ -61,6 +61,7 @@
- vectorizer
- qrcode
- tianditu
- aliyuque
- google_translate
- hap
- json_process

View File

@ -1,10 +1,3 @@
"""
语雀客户端
"""
__author__ = "佐井"
__created__ = "2024-06-01 09:45:20"
from typing import Any
import requests
@ -17,8 +10,10 @@ class AliYuqueTool:
@staticmethod
def auth(token):
session = requests.Session()
session.headers.update({"Accept": "application/json", "X-Auth-Token": token})
login = session.request("GET", AliYuqueTool.server_url + "/api/v2/user")
session.headers.update(
{"Accept": "application/json", "X-Auth-Token": token})
login = session.request(
"GET", AliYuqueTool.server_url + "/api/v2/user")
login.raise_for_status()
resp = login.json()
return resp
@ -27,24 +22,27 @@ class AliYuqueTool:
if not token:
raise Exception("token is required")
session = requests.Session()
session.headers.update({"accept": "application/json", "X-Auth-Token": token})
session.headers.update(
{"accept": "application/json", "X-Auth-Token": token})
new_params = {**tool_parameters}
# 找出需要替换的变量
replacements = {k: v for k, v in new_params.items() if f"{{{k}}}" in path}
# 替换 path 中的变量
replacements = {k: v for k, v in new_params.items()
if f"{{{k}}}" in path}
for key, value in replacements.items():
path = path.replace(f"{{{key}}}", str(value))
del new_params[key] # 从 kwargs 中删除已经替换的变量
# 请求接口
del new_params[key]
if method.upper() in {"POST", "PUT"}:
session.headers.update(
{
"Content-Type": "application/json",
}
)
response = session.request(method.upper(), self.server_url + path, json=new_params)
response = session.request(
method.upper(), self.server_url + path, json=new_params)
else:
response = session.request(method, self.server_url + path, params=new_params)
response = session.request(
method, self.server_url + path, params=new_params)
response.raise_for_status()
return response.text

View File

@ -1,10 +1,3 @@
"""
创建文档
"""
__author__ = "佐井"
__created__ = "2024-06-01 10:45:20"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage

View File

@ -13,7 +13,7 @@ description:
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,11 +1,3 @@
#!/usr/bin/env python3
"""
删除文档
"""
__author__ = "佐井"
__created__ = "2024-09-17 22:04"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage
@ -21,5 +13,6 @@ class AliYuqueDeleteDocumentTool(AliYuqueTool, BuiltinTool):
if not token:
raise Exception("token is required")
return self.create_text_message(
self.request("DELETE", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
self.request("DELETE", token, tool_parameters,
"/api/v2/repos/{book_id}/docs/{id}")
)

View File

@ -13,7 +13,7 @@ description:
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,10 +1,3 @@
"""
获取知识库首页
"""
__author__ = "佐井"
__created__ = "2024-06-01 22:57:14"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage
@ -20,5 +13,6 @@ class AliYuqueDescribeBookIndexPageTool(AliYuqueTool, BuiltinTool):
if not token:
raise Exception("token is required")
return self.create_text_message(
self.request("GET", token, tool_parameters, "/api/v2/repos/{group_login}/{book_slug}/index_page")
self.request("GET", token, tool_parameters,
"/api/v2/repos/{group_login}/{book_slug}/index_page")
)

View File

@ -1,11 +1,3 @@
#!/usr/bin/env python3
"""
获取知识库目录
"""
__author__ = "佐井"
__created__ = "2024-09-17 15:17:11"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage

View File

@ -13,7 +13,7 @@ description:
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,10 +1,3 @@
"""
获取文档
"""
__author__ = "佐井"
__created__ = "2024-06-02 07:11:45"
import json
from typing import Any, Union
from urllib.parse import urlparse
@ -37,19 +30,19 @@ class AliYuqueDescribeDocumentContentTool(AliYuqueTool, BuiltinTool):
book_slug = path_parts[-2]
group_id = path_parts[-3]
# 1. 请求首页信息获取book_id
new_params["group_login"] = group_id
new_params["book_slug"] = book_slug
index_page = json.loads(
self.request("GET", token, new_params, "/api/v2/repos/{group_login}/{book_slug}/index_page")
self.request("GET", token, new_params,
"/api/v2/repos/{group_login}/{book_slug}/index_page")
)
book_id = index_page.get("data", {}).get("book", {}).get("id")
if not book_id:
raise Exception(f"can not parse book_id from {index_page}")
# 2. 获取文档内容
new_params["book_id"] = book_id
new_params["id"] = doc_id
data = self.request("GET", token, new_params, "/api/v2/repos/{book_id}/docs/{id}")
data = self.request("GET", token, new_params,
"/api/v2/repos/{book_id}/docs/{id}")
data = json.loads(data)
body_only = tool_parameters.get("body_only") or ""
if body_only.lower() == "true":

View File

@ -1,10 +1,3 @@
"""
获取文档
"""
__author__ = "佐井"
__created__ = "2024-06-01 10:45:20"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage
@ -20,5 +13,6 @@ class AliYuqueDescribeDocumentsTool(AliYuqueTool, BuiltinTool):
if not token:
raise Exception("token is required")
return self.create_text_message(
self.request("GET", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
self.request("GET", token, tool_parameters,
"/api/v2/repos/{book_id}/docs/{id}")
)

View File

@ -14,7 +14,7 @@ description:
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,11 +1,3 @@
#!/usr/bin/env python3
"""
获取知识库目录
"""
__author__ = "佐井"
__created__ = "2024-09-17 15:17:11"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage

View File

@ -13,7 +13,7 @@ description:
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,10 +1,3 @@
"""
更新文档
"""
__author__ = "佐井"
__created__ = "2024-06-19 16:50:07"
from typing import Any, Union
from core.tools.entities.tool_entities import ToolInvokeMessage
@ -20,5 +13,6 @@ class AliYuqueUpdateDocumentTool(AliYuqueTool, BuiltinTool):
if not token:
raise Exception("token is required")
return self.create_text_message(
self.request("PUT", token, tool_parameters, "/api/v2/repos/{book_id}/docs/{id}")
self.request("PUT", token, tool_parameters,
"/api/v2/repos/{book_id}/docs/{id}")
)

View File

@ -12,7 +12,7 @@ description:
llm: Update doc in a knowledge base via ID/path.
parameters:
- name: book_id
type: number
type: string
required: true
form: llm
label:

View File

@ -1,77 +1,36 @@
import matplotlib.pyplot as plt
from fontTools.ttLib import TTFont
from matplotlib.font_manager import findSystemFonts
from matplotlib.font_manager import FontProperties
from core.tools.errors import ToolProviderCredentialValidationError
from core.tools.provider.builtin.chart.tools.line import LinearChartTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
def set_chinese_font():
font_list = [
"PingFang SC",
"SimHei",
"Microsoft YaHei",
"STSong",
"SimSun",
"Arial Unicode MS",
"Noto Sans CJK SC",
"Noto Sans CJK JP",
]
for font in font_list:
chinese_font = FontProperties(font)
if chinese_font.get_name() == font:
return chinese_font
return FontProperties()
# use a business theme
plt.style.use("seaborn-v0_8-darkgrid")
plt.rcParams["axes.unicode_minus"] = False
def init_fonts():
fonts = findSystemFonts()
popular_unicode_fonts = [
"Arial Unicode MS",
"DejaVu Sans",
"DejaVu Sans Mono",
"DejaVu Serif",
"FreeMono",
"FreeSans",
"FreeSerif",
"Liberation Mono",
"Liberation Sans",
"Liberation Serif",
"Noto Mono",
"Noto Sans",
"Noto Serif",
"Open Sans",
"Roboto",
"Source Code Pro",
"Source Sans Pro",
"Source Serif Pro",
"Ubuntu",
"Ubuntu Mono",
]
supported_fonts = []
for font_path in fonts:
try:
font = TTFont(font_path)
# get family name
family_name = font["name"].getName(1, 3, 1).toUnicode()
if family_name in popular_unicode_fonts:
supported_fonts.append(family_name)
except:
pass
plt.rcParams["font.family"] = "sans-serif"
# sort by order of popular_unicode_fonts
for font in popular_unicode_fonts:
if font in supported_fonts:
plt.rcParams["font.sans-serif"] = font
break
init_fonts()
font_properties = set_chinese_font()
plt.rcParams["font.family"] = font_properties.get_name()
class ChartProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict) -> None:
try:
LinearChartTool().fork_tool_runtime(
runtime={
"credentials": credentials,
}
).invoke(
user_id="",
tool_parameters={
"data": "1,3,5,7,9,2,4,6,8,10",
},
)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
pass

View File

@ -1,3 +1,5 @@
import base64
import io
import json
import random
import uuid
@ -6,45 +8,48 @@ import httpx
from websocket import WebSocket
from yarl import URL
from core.file.file_manager import _get_encoded_string
from core.file.models import File
class ComfyUiClient:
def __init__(self, base_url: str):
self.base_url = URL(base_url)
def get_history(self, prompt_id: str):
def get_history(self, prompt_id: str) -> dict:
res = httpx.get(str(self.base_url / "history"), params={"prompt_id": prompt_id})
history = res.json()[prompt_id]
return history
def get_image(self, filename: str, subfolder: str, folder_type: str):
def get_image(self, filename: str, subfolder: str, folder_type: str) -> bytes:
response = httpx.get(
str(self.base_url / "view"),
params={"filename": filename, "subfolder": subfolder, "type": folder_type},
)
return response.content
def upload_image(self, input_path: str, name: str, image_type: str = "input", overwrite: bool = False):
# plan to support img2img in dify 0.10.0
with open(input_path, "rb") as file:
files = {"image": (name, file, "image/png")}
data = {"type": image_type, "overwrite": str(overwrite).lower()}
def upload_image(self, image_file: File) -> dict:
image_content = base64.b64decode(_get_encoded_string(image_file))
file = io.BytesIO(image_content)
files = {"image": (image_file.filename, file, image_file.mime_type), "overwrite": "true"}
res = httpx.post(str(self.base_url / "upload/image"), files=files)
return res.json()
res = httpx.post(str(self.base_url / "upload/image"), data=data, files=files)
return res
def queue_prompt(self, client_id: str, prompt: dict):
def queue_prompt(self, client_id: str, prompt: dict) -> str:
res = httpx.post(str(self.base_url / "prompt"), json={"client_id": client_id, "prompt": prompt})
prompt_id = res.json()["prompt_id"]
return prompt_id
def open_websocket_connection(self):
def open_websocket_connection(self) -> tuple[WebSocket, str]:
client_id = str(uuid.uuid4())
ws = WebSocket()
ws_address = f"ws://{self.base_url.authority}/ws?clientId={client_id}"
ws.connect(ws_address)
return ws, client_id
def set_prompt(self, origin_prompt: dict, positive_prompt: str, negative_prompt: str = ""):
def set_prompt(
self, origin_prompt: dict, positive_prompt: str, negative_prompt: str = "", image_name: str = ""
) -> dict:
"""
find the first KSampler, then can find the prompt node through it.
"""
@ -58,6 +63,10 @@ class ComfyUiClient:
if negative_prompt != "":
negative_input_id = prompt.get(k_sampler)["inputs"]["negative"][0]
prompt.get(negative_input_id)["inputs"]["text"] = negative_prompt
if image_name != "":
image_loader = [key for key, value in id_to_class_type.items() if value == "LoadImage"][0]
prompt.get(image_loader)["inputs"]["image"] = image_name
return prompt
def track_progress(self, prompt: dict, ws: WebSocket, prompt_id: str):
@ -89,7 +98,7 @@ class ComfyUiClient:
else:
continue
def generate_image_by_prompt(self, prompt: dict):
def generate_image_by_prompt(self, prompt: dict) -> list[bytes]:
try:
ws, client_id = self.open_websocket_connection()
prompt_id = self.queue_prompt(client_id, prompt)

View File

@ -2,10 +2,9 @@ import json
from typing import Any
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.provider.builtin.comfyui.tools.comfyui_client import ComfyUiClient
from core.tools.tool.builtin_tool import BuiltinTool
from .comfyui_client import ComfyUiClient
class ComfyUIWorkflowTool(BuiltinTool):
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]:
@ -14,13 +13,16 @@ class ComfyUIWorkflowTool(BuiltinTool):
positive_prompt = tool_parameters.get("positive_prompt")
negative_prompt = tool_parameters.get("negative_prompt")
workflow = tool_parameters.get("workflow_json")
image_name = ""
if image := tool_parameters.get("image"):
image_name = comfyui.upload_image(image).get("name")
try:
origin_prompt = json.loads(workflow)
except:
return self.create_text_message("the Workflow JSON is not correct")
prompt = comfyui.set_prompt(origin_prompt, positive_prompt, negative_prompt)
prompt = comfyui.set_prompt(origin_prompt, positive_prompt, negative_prompt, image_name)
images = comfyui.generate_image_by_prompt(prompt)
result = []
for img in images:

View File

@ -24,6 +24,13 @@ parameters:
zh_Hans: 负面提示词
llm_description: Negative prompt, you should describe the image you don't want to generate as a list of words as possible as detailed, the prompt must be written in English.
form: llm
- name: image
type: file
label:
en_US: Input Image
zh_Hans: 输入的图片
llm_description: The input image, used to transfer to the comfyui workflow to generate another image.
form: llm
- name: workflow_json
type: string
required: true

View File

@ -66,7 +66,7 @@ class DallE3Tool(BuiltinTool):
for image in response.data:
mime_type, blob_image = DallE3Tool._decode_image(image.b64_json)
blob_message = self.create_blob_message(
blob=blob_image, meta={"mime_type": mime_type}, save_as=self.VariableKey.IMAGE.value
blob=blob_image, meta={"mime_type": mime_type}, save_as=self.VariableKey.IMAGE
)
result.append(blob_message)
return result

View File

@ -21,7 +21,6 @@ class DiscordWebhookTool(BuiltinTool):
return self.create_text_message("Invalid parameter content")
webhook_url = tool_parameters.get("webhook_url", "")
if not webhook_url.startswith("https://discord.com/api/webhooks/"):
return self.create_text_message(
f"Invalid parameter webhook_url ${webhook_url}, \
@ -31,13 +30,14 @@ class DiscordWebhookTool(BuiltinTool):
headers = {
"Content-Type": "application/json",
}
params = {}
payload = {
"username": tool_parameters.get("username") or user_id,
"content": content,
"avatar_url": tool_parameters.get("avatar_url") or None,
}
try:
res = httpx.post(webhook_url, headers=headers, params=params, json=payload)
res = httpx.post(webhook_url, headers=headers, json=payload)
if res.is_success:
return self.create_text_message("Text message was sent successfully")
else:

View File

@ -38,3 +38,28 @@ parameters:
pt_BR: Content to sent to the channel or person.
llm_description: Content of the message
form: llm
- name: username
type: string
required: false
label:
en_US: Discord Webhook Username
zh_Hans: Discord Webhook用户名
pt_BR: Discord Webhook Username
human_description:
en_US: Discord Webhook Username
zh_Hans: Discord Webhook用户名
pt_BR: Discord Webhook Username
llm_description: Discord Webhook Username
form: llm
- name: avatar_url
type: string
required: false
label:
en_US: Discord Webhook Avatar
zh_Hans: Discord Webhook头像
pt_BR: Discord Webhook Avatar
human_description:
en_US: Discord Webhook Avatar URL
zh_Hans: Discord Webhook头像地址
pt_BR: Discord Webhook Avatar URL
form: form

View File

@ -37,7 +37,7 @@ parameters:
- value: mixtral-8x7b
label:
en_US: Mixtral
default: gpt-3.5
default: gpt-4o-mini
label:
en_US: Choose Model
zh_Hans: 选择模型

View File

@ -2,7 +2,6 @@ from typing import Any
from duckduckgo_search import DDGS
from core.file.file_obj import FileTransferMethod
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
@ -20,11 +19,9 @@ class DuckDuckGoImageSearchTool(BuiltinTool):
"max_results": tool_parameters.get("max_results"),
}
response = DDGS().images(**query_dict)
result = []
markdown_result = "\n\n"
json_result = []
for res in response:
res["transfer_method"] = FileTransferMethod.REMOTE_URL
msg = ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.IMAGE_LINK, message=res.get("image"), save_as="", meta=res
)
result.append(msg)
return result
markdown_result += f"![{res.get('title') or ''}]({res.get('image') or ''})"
json_result.append(self.create_json_message(res))
return [self.create_text_message(markdown_result)] + json_result

View File

@ -1,47 +0,0 @@
<?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="240px" height="240px" viewBox="0 0 240 240" enable-background="new 0 0 240 240" xml:space="preserve"> <image id="image0" width="240" height="240" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAMAAAAJixmgAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAINUExURQAAAJ9g6qNg6qRd6KNe6aVg6J9g6p9g359a6qRd
66Ne66Re66Rd66Rd66Re7Z9g559g76Ne7aRe6qJd76Fe66Ne66Re66Vd7KNc6aRe66Nd66Re7KRd
66Nc6p9g36Re7KRf7KRe7KNe7KNc76Va6qVd66Ne66Re66Nd6qRb6KRe66Zd66Ve7KJd6aNe66Fe
7KRd6p9Y56Re66Je6qRb6qNe66Re7KNc56Jf6qNe6qRe6qFe6KNe6qRf66Rf66Ne7KNe6qRe6qRd
66Rd7KFe6aNc6qNe6qJd7Z9b6KNd659Z7KNd66Ne6aRe66Re7KNc66Nd66Ne66Jd6qNe6qNc6aJd
6qNd6qRe66Jd7KFe6qJe66Re6qNe66Jd66Jd6aRd6qRd66Va759V6qVd6qFe56Ne6KJd6KRe66Zj
67eB79Gu9eLM+OjX+uXS+dSz9bJ37s6p9LV87q9y7de49vn1/f///9q99qlo7NGu9Kxt7d/H+Pz5
/vz6/qdj68CQ8e7h+/Pq/Maa8rqG8NzC9/Pr/Myk8+DH+OXR+cyk9Pn1/ujW+u3g+7iB7+vc+r2L
8N3C9//+//bw/cmf87uG8OPM+ebR+axt7Muk8/Dm/Mif8/n0/c+p9MOV8fHm++7g+/n0/vbv/bqG
78CQ8Kxu7cCR8ePM+LqH8MCP8bV87+LM+bqF8Muk9OLL+NSy9bqF75VHsr4AAABndFJOUwAYSHCA
WDAQMJfn57+PVyAQb98/gO/mllDPr1/ebwiHn+6eLzCvf/63OL4/xkfOT58g9odfZ65Aj3fHT2+X
j3f3z7efgEjPRzi3KKd/7sZAv+6fri+P3rY3X77G5j9oxn4fGGCAbzcMFjqxAAAAAWJLR0R1qGqY
+wAAAAd0SU1FB+gGDQkfBmABjhYAAAXYSURBVHja7d35WxtFGAfwbUGDtRWKilAwFg+2VHtwqa31
qlfV1qta7/vYkDQhJFnTliQWhFYoDaVY8ECw3vffaHgefZ5CZvbZIbMz83a/35/ngfkwx+5m3iyW
hSAIgiAIgiAIgiAIgiAIQjAbNtbVa0vdNRuvjSjlNly3ydGd6zdvuaFRDbdpa7Nu7f9p3nJjU/De
m27W7bwyLbcEbW5t021cm7atQc7thm26fYw01wdHbjdmAa9Kx61NwXijt+mm8dLWHgi5cbtuGDed
t98RAPjOu3S7PBLEIBsNdjq7pIvNBjv2ju5wgR17593hAjvOPbtCBnZ27wkZWK6YAljqOqYAlrpX
kwA79t6ecIGd3r6mcIGdjv6QgaUtYypgaZOaDFjWpKYDlrRT0wE7LQMhA9v3yvgEhBBYziqmBJay
iimBpQwxKbDdFw0X2Nl8X8jAEuY0LbB9f80H58TAtT9C0AJLmNPEwL37QgaufRFTA29rCBfY2V/r
OQQ18O4HQgau+aGYGrjzQMjAdhfAVzm41idEcuBa7zwANjwAAwwwwKQCcIDg2GA8cXx1kqnBGK/5
UHpta16SqWETwZlszq1O7tMhZuv8CVZrXnKpk8aBh05xOptgDY+Y13VHsj7FysB8QSFerG5e+kzI
67qnRw0DDye4fT0+Vv3nyQp6XffzjFng0XH+Apyoaj10Rhh8dtAs8BeT3K4W0lWtx6aEwedKlMHT
Z4XB58tmgUvnuF0dr95vMjPC4IS/a7EysAeB1dV0QdB7IZs3C8zftWYvMloLz2nGVq8ZXJyYZfZ0
Ms4cmjmxbcvvZVjpvfTcl9UbV+FSmXMzPTwz6ZtbuDTntxNqn5bmp+dWZ3reo3VmzmemMzHfXcDj
IcAAA0wqAAMMMMCkAjDAAANMKgADDDDApAIwwBLB+XI6vjrp0kmB1rx4/RSN4PwC67BlfCHPbB0r
nRc4ePjq67zPXqgD82oeCuzqjIuzAl7X/eaET7E68ASvxiP3LaPGQ/BoiXyNRzEl6DXvuNSjxoNx
IB66Go91lDyErsbDNPDidyGb0h5jxqjxKMbp13hw5/RkmnGcLXxZWvI3o1XeeMSXmT0dSbGGpjjx
vZB3mV0pohPs5EeXLlTPxNOcW8viIqM1N7wbVK3gCvnywtrb/h/4RR4CDw+L/gpLlYNNCMAAAwww
qQAMMMAAkwrAAAMMMKkADDDAAJMKwIGCfxwcW5OMUGte/H9KqxIcKyerD4xyyZ+K7ObTP/s/Xiok
fzEPnE+zqzZy7OqMskhNC+dLyHrB3BKAcVZ1hkeFBDvGfWHaQ8D6SvyE2ItaXHckXvTVD0MPxNfx
0gPTDsQ96q6uztdaeNR4uPGq1ut5ccmvZoE9ypYYO+xvvwuDp/4wC+xRw8CoVlhHYZppLx/il6kU
WDUPpSVB8PKf/vqh7jrM3XjZLxDjVITwwq4U0Qp2MlmWYWSG84o4IfFyat5nL5Q+PAynT02tTiJ1
Oea/NS9//f2P7xd54PEQYIABJhWAAQYYYFIBWDSN23UTFIM3PKiboBgceUg3QTHYevgR3QbF4IFN
ug2Kwa2P6jYoBncf1G1QDO55TLdBMTj6OKldS8I/5G1v1o1QDKa1iCWAad16SADTmtMywKTmtAww
qX1aBth6Yr9uhmIwpW1LCtja86Ruh2IwoSGWA7aeIrOKJYGjXb26JWrB1qGndUsUg63+Dt0UxeBI
H41JLQ1sPfOsbotiMJGdWiKYxjKWCY4eaNHNUQu2IoeP6PaoBVMQywUTEEsGW5F9hq9j2eDK1ek5
WzdKLdg6tMNkcQBgsxey3ReVDras51+wdcO44K4AvCuD/KKh5I7+QMCW1fCSkWT76MsBgQ0lHzks
f8+6gvzKMcPI9quvBeitJPL6GyYNs/3mrmC9K+kZOPqWbQba3vl28N6VRBve2ftuRW1rhFd+97H3
3lfj/S8fdLd+WFevKx99/EkQdxwIgiAIgiAIgiAIgiAIgiBB519+T+5Fl+ldNwAAACV0RVh0ZGF0
ZTpjcmVhdGUAMjAyNC0wNi0xM1QwOTozMTowNiswMDowMPHqs70AAAAldEVYdGRhdGU6bW9kaWZ5
ADIwMjQtMDYtMTNUMDk6MzE6MDYrMDA6MDCAtwsBAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI0
LTA2LTEzVDA5OjMxOjA2KzAwOjAw16Iq3gAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,8 +1,7 @@
from core.tools.provider.builtin.feishu_base.tools.get_tenant_access_token import GetTenantAccessTokenTool
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
from core.tools.utils.feishu_api_utils import auth
class FeishuBaseProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict) -> None:
GetTenantAccessTokenTool()
pass
auth(credentials)

View File

@ -5,10 +5,32 @@ identity:
en_US: Feishu Base
zh_Hans: 飞书多维表格
description:
en_US: Feishu Base
zh_Hans: 飞书多维表格
icon: icon.svg
en_US: |
Feishu base, requires the following permissions: bitable:app.
zh_Hans: |
飞书多维表格,需要开通以下权限: bitable:app。
icon: icon.png
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.larkoffice.com/app
app_secret:
type: secret-input
required: true
label:
en_US: APP Secret
placeholder:
en_US: Please input your app secret
zh_Hans: 请输入你的飞书 app secret

View File

@ -1,56 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class AddBaseRecordTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_id = tool_parameters.get("table_id", "")
if not table_id:
return self.create_text_message("Invalid parameter table_id")
fields = tool_parameters.get("fields", "")
if not fields:
return self.create_text_message("Invalid parameter fields")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"fields": json.loads(fields)}
try:
res = httpx.post(
url.format(app_token=app_token, table_id=table_id),
headers=headers,
params=params,
json=payload,
timeout=30,
)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to add base record, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to add base record. {}".format(e))

View File

@ -1,66 +0,0 @@
identity:
name: add_base_record
author: Doug Lea
label:
en_US: Add Base Record
zh_Hans: 在多维表格数据表中新增一条记录
description:
human:
en_US: Add Base Record
zh_Hans: |
在多维表格数据表中新增一条记录详细请参考https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table-record/create
llm: Add a new record in the multidimensional table data table.
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_id
type: string
required: true
label:
en_US: table_id
zh_Hans: 多维表格的数据表
human_description:
en_US: bitable table id
zh_Hans: 多维表格数据表的唯一标识符 table_id
llm_description: bitable table id
form: llm
- name: fields
type: string
required: true
label:
en_US: fields
zh_Hans: 数据表的列字段内容
human_description:
en_US: The fields of the Base data table are the columns of the data table.
zh_Hans: |
要增加一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"}
当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。
不同类型字段的数据结构请参考数据结构概述https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure
llm_description: |
要增加一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"}
当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。
不同类型字段的数据结构请参考数据结构概述https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure
form: llm

View File

@ -1,41 +1,18 @@
import json
from typing import Any, Union
import httpx
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 CreateBaseTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps"
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)
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
name = tool_parameters.get("name")
folder_token = tool_parameters.get("folder_token")
name = tool_parameters.get("name", "")
folder_token = tool_parameters.get("folder_token", "")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"name": name, "folder_token": folder_token}
try:
res = httpx.post(url, headers=headers, params=params, json=payload, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to create base, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to create base. {}".format(e))
res = client.create_base(name, folder_token)
return self.create_json_message(res)

View File

@ -6,32 +6,21 @@ identity:
zh_Hans: 创建多维表格
description:
human:
en_US: Create base
en_US: Create Multidimensional Table in Specified Directory
zh_Hans: 在指定目录下创建多维表格
llm: A tool for create a multidimensional table in the specified directory.
llm: A tool for creating a multidimensional table in a specified directory. (在指定目录下创建多维表格)
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: name
type: string
required: false
label:
en_US: name
zh_Hans: name
zh_Hans: 多维表格 App 名字
human_description:
en_US: Base App Name
zh_Hans: 多维表格App名字
llm_description: Base App Name
en_US: |
Name of the multidimensional table App. Example value: "A new multidimensional table".
zh_Hans: 多维表格 App 名字,示例值:"一篇新的多维表格"。
llm_description: 多维表格 App 名字,示例值:"一篇新的多维表格"。
form: llm
- name: folder_token
@ -39,9 +28,15 @@ parameters:
required: false
label:
en_US: folder_token
zh_Hans: 多维表格App归属文件夹
zh_Hans: 多维表格 App 归属文件夹
human_description:
en_US: Base App home folder. The default is empty, indicating that Base will be created in the cloud space root directory.
zh_Hans: 多维表格App归属文件夹。默认为空表示多维表格将被创建在云空间根目录。
llm_description: Base App home folder. The default is empty, indicating that Base will be created in the cloud space root directory.
en_US: |
Folder where the multidimensional table App belongs. Default is empty, meaning the table will be created in the root directory of the cloud space. Example values: Fa3sfoAgDlMZCcdcJy1cDFg8nJc or https://svi136aogf123.feishu.cn/drive/folder/Fa3sfoAgDlMZCcdcJy1cDFg8nJc.
The folder_token must be an existing folder and supports inputting folder token or folder URL.
zh_Hans: |
多维表格 App 归属文件夹。默认为空,表示多维表格将被创建在云空间根目录。示例值: Fa3sfoAgDlMZCcdcJy1cDFg8nJc 或者 https://svi136aogf123.feishu.cn/drive/folder/Fa3sfoAgDlMZCcdcJy1cDFg8nJc。
folder_token 必须是已存在的文件夹,支持输入文件夹 token 或者文件夹 URL。
llm_description: |
多维表格 App 归属文件夹。默认为空,表示多维表格将被创建在云空间根目录。示例值: Fa3sfoAgDlMZCcdcJy1cDFg8nJc 或者 https://svi136aogf123.feishu.cn/drive/folder/Fa3sfoAgDlMZCcdcJy1cDFg8nJc。
folder_token 必须是已存在的文件夹,支持输入文件夹 token 或者文件夹 URL。
form: llm

View File

@ -1,48 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class CreateBaseTableTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
name = tool_parameters.get("name", "")
fields = tool_parameters.get("fields", "")
if not fields:
return self.create_text_message("Invalid parameter fields")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"table": {"name": name, "fields": json.loads(fields)}}
try:
res = httpx.post(url.format(app_token=app_token), headers=headers, params=params, json=payload, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to create base table, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to create base table. {}".format(e))

View File

@ -1,106 +0,0 @@
identity:
name: create_base_table
author: Doug Lea
label:
en_US: Create Base Table
zh_Hans: 多维表格新增一个数据表
description:
human:
en_US: Create base table
zh_Hans: |
多维表格新增一个数据表详细请参考https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table/create
llm: A tool for add a new data table to the multidimensional table.
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: name
type: string
required: false
label:
en_US: name
zh_Hans: name
human_description:
en_US: Multidimensional table data table name
zh_Hans: 多维表格数据表名称
llm_description: Multidimensional table data table name
form: llm
- name: fields
type: string
required: true
label:
en_US: fields
zh_Hans: fields
human_description:
en_US: Initial fields of the data table
zh_Hans: |
数据表的初始字段,格式为:[{"field_name":"多行文本","type":1},{"field_name":"数字","type":2},{"field_name":"单选","type":3},{"field_name":"多选","type":4},{"field_name":"日期","type":5}]。
field_name字段名
type: 字段类型;可选值有
1多行文本
2数字
3单选
4多选
5日期
7复选框
11人员
13电话号码
15超链接
17附件
18单向关联
20公式
21双向关联
22地理位置
23群组
1001创建时间
1002最后更新时间
1003创建人
1004修改人
1005自动编号
llm_description: |
数据表的初始字段,格式为:[{"field_name":"多行文本","type":1},{"field_name":"数字","type":2},{"field_name":"单选","type":3},{"field_name":"多选","type":4},{"field_name":"日期","type":5}]。
field_name字段名
type: 字段类型;可选值有
1多行文本
2数字
3单选
4多选
5日期
7复选框
11人员
13电话号码
15超链接
17附件
18单向关联
20公式
21双向关联
22地理位置
23群组
1001创建时间
1002最后更新时间
1003创建人
1004修改人
1005自动编号
form: llm

View File

@ -1,56 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class DeleteBaseRecordsTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_id = tool_parameters.get("table_id", "")
if not table_id:
return self.create_text_message("Invalid parameter table_id")
record_ids = tool_parameters.get("record_ids", "")
if not record_ids:
return self.create_text_message("Invalid parameter record_ids")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"records": json.loads(record_ids)}
try:
res = httpx.post(
url.format(app_token=app_token, table_id=table_id),
headers=headers,
params=params,
json=payload,
timeout=30,
)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to delete base records, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to delete base records. {}".format(e))

View File

@ -1,60 +0,0 @@
identity:
name: delete_base_records
author: Doug Lea
label:
en_US: Delete Base Records
zh_Hans: 在多维表格数据表中删除多条记录
description:
human:
en_US: Delete base records
zh_Hans: |
该接口用于删除多维表格数据表中的多条记录,单次调用中最多删除 500 条记录。
llm: A tool for delete multiple records in a multidimensional table data table, up to 500 records can be deleted in a single call.
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_id
type: string
required: true
label:
en_US: table_id
zh_Hans: 多维表格的数据表
human_description:
en_US: bitable table id
zh_Hans: 多维表格数据表的唯一标识符 table_id
llm_description: bitable table id
form: llm
- name: record_ids
type: string
required: true
label:
en_US: record_ids
zh_Hans: record_ids
human_description:
en_US: A list of multiple record IDs to be deleted, for example ["recwNXzPQv","recpCsf4ME"]
zh_Hans: 待删除的多条记录id列表示例为 ["recwNXzPQv","recpCsf4ME"]
llm_description: A list of multiple record IDs to be deleted, for example ["recwNXzPQv","recpCsf4ME"]
form: llm

View File

@ -1,46 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class DeleteBaseTablesTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/batch_delete"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_ids = tool_parameters.get("table_ids", "")
if not table_ids:
return self.create_text_message("Invalid parameter table_ids")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"table_ids": json.loads(table_ids)}
try:
res = httpx.post(url.format(app_token=app_token), headers=headers, params=params, json=payload, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to delete base tables, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to delete base tables. {}".format(e))

View File

@ -1,48 +0,0 @@
identity:
name: delete_base_tables
author: Doug Lea
label:
en_US: Delete Base Tables
zh_Hans: 删除多维表格中的数据表
description:
human:
en_US: Delete base tables
zh_Hans: |
删除多维表格中的数据表
llm: A tool for deleting a data table in a multidimensional table
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_ids
type: string
required: true
label:
en_US: table_ids
zh_Hans: table_ids
human_description:
en_US: The ID list of the data tables to be deleted. Currently, a maximum of 50 data tables can be deleted at a time. The example is ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"]
zh_Hans: 待删除数据表的id列表当前一次操作最多支持50个数据表示例为 ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"]
llm_description: The ID list of the data tables to be deleted. Currently, a maximum of 50 data tables can be deleted at a time. The example is ["tbl1TkhyTWDkSoZ3","tblsRc9GRRXKqhvW"]
form: llm

View File

@ -1,39 +1,17 @@
import json
from typing import Any, Union
import httpx
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 GetBaseInfoTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}"
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)
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
try:
res = httpx.get(url.format(app_token=app_token), headers=headers, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to get base info, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to get base info. {}".format(e))
res = client.get_base_info(app_token)
return self.create_json_message(res)

View File

@ -6,49 +6,18 @@ identity:
zh_Hans: 获取多维表格元数据
description:
human:
en_US: Get base info
zh_Hans: |
获取多维表格元数据,响应体如下:
{
"code": 0,
"msg": "success",
"data": {
"app": {
"app_token": "appbcbWCzen6D8dezhoCH2RpMAh",
"name": "mybase",
"revision": 1,
"is_advanced": false,
"time_zone": "Asia/Beijing"
}
}
}
app_token: 多维表格的 app_token;
name: 多维表格的名字;
revision: 多维表格的版本号;
is_advanced: 多维表格是否开启了高级权限。取值包括:(true-表示开启了高级权限false-表示关闭了高级权限);
time_zone: 文档时区;
llm: A tool to get Base Metadata, imported parameter is Unique Device Identifier app_token of Base, app_token is required.
en_US: Get Metadata Information of Specified Multidimensional Table
zh_Hans: 获取指定多维表格的元数据信息
llm: A tool for getting metadata information of a specified multidimensional table. (获取指定多维表格元数据信息)
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
zh_Hans: app_token
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
en_US: Unique identifier for the multidimensional table, supports inputting document URL.
zh_Hans: 多维表格的唯一标识符,支持输入文档 URL。
llm_description: 多维表格的唯一标识符,支持输入文档 URL。
form: llm

View File

@ -1,48 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class GetTenantAccessTokenTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
app_id = tool_parameters.get("app_id", "")
if not app_id:
return self.create_text_message("Invalid parameter app_id")
app_secret = tool_parameters.get("app_secret", "")
if not app_secret:
return self.create_text_message("Invalid parameter app_secret")
headers = {
"Content-Type": "application/json",
}
params = {}
payload = {"app_id": app_id, "app_secret": app_secret}
"""
{
"code": 0,
"msg": "ok",
"tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
"expire": 7200
}
"""
try:
res = httpx.post(url, headers=headers, params=params, json=payload, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to get tenant access token, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to get tenant access token. {}".format(e))

View File

@ -1,39 +0,0 @@
identity:
name: get_tenant_access_token
author: Doug Lea
label:
en_US: Get Tenant Access Token
zh_Hans: 获取飞书自建应用的 tenant_access_token
description:
human:
en_US: Get tenant access token
zh_Hans: |
获取飞书自建应用的 tenant_access_token响应体示例:
{"code":0,"msg":"ok","tenant_access_token":"t-caecc734c2e3328a62489fe0648c4b98779515d3","expire":7200}
tenant_access_token: 租户访问凭证;
expire: tenant_access_token 的过期时间,单位为秒;
llm: A tool for obtaining a tenant access token. The input parameters must include app_id and app_secret.
parameters:
- name: app_id
type: string
required: true
label:
en_US: app_id
zh_Hans: 应用唯一标识
human_description:
en_US: app_id is the unique identifier of the Lark Open Platform application
zh_Hans: app_id 是飞书开放平台应用的唯一标识
llm_description: app_id is the unique identifier of the Lark Open Platform application
form: llm
- name: app_secret
type: secret-input
required: true
label:
en_US: app_secret
zh_Hans: 应用秘钥
human_description:
en_US: app_secret is the secret key of the application
zh_Hans: app_secret 是应用的秘钥
llm_description: app_secret is the secret key of the application
form: llm

View File

@ -1,65 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class ListBaseRecordsTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/search"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_id = tool_parameters.get("table_id", "")
if not table_id:
return self.create_text_message("Invalid parameter table_id")
page_token = tool_parameters.get("page_token", "")
page_size = tool_parameters.get("page_size", "")
sort_condition = tool_parameters.get("sort_condition", "")
filter_condition = tool_parameters.get("filter_condition", "")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {
"page_token": page_token,
"page_size": page_size,
}
payload = {"automatic_fields": True}
if sort_condition:
payload["sort"] = json.loads(sort_condition)
if filter_condition:
payload["filter"] = json.loads(filter_condition)
try:
res = httpx.post(
url.format(app_token=app_token, table_id=table_id),
headers=headers,
params=params,
json=payload,
timeout=30,
)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to list base records, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to list base records. {}".format(e))

View File

@ -1,108 +0,0 @@
identity:
name: list_base_records
author: Doug Lea
label:
en_US: List Base Records
zh_Hans: 查询多维表格数据表中的现有记录
description:
human:
en_US: List base records
zh_Hans: |
查询多维表格数据表中的现有记录,单次最多查询 500 行记录,支持分页获取。
llm: Query existing records in a multidimensional table data table. A maximum of 500 rows of records can be queried at a time, and paging retrieval is supported.
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_id
type: string
required: true
label:
en_US: table_id
zh_Hans: 多维表格的数据表
human_description:
en_US: bitable table id
zh_Hans: 多维表格数据表的唯一标识符 table_id
llm_description: bitable table id
form: llm
- name: page_token
type: string
required: false
label:
en_US: page_token
zh_Hans: 分页标记
human_description:
en_US: Pagination mark. If it is not filled in the first request, it means to traverse from the beginning.
zh_Hans: 分页标记,第一次请求不填,表示从头开始遍历。
llm_description: 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token下次遍历可采用该 page_token 获取查询结果。
form: llm
- name: page_size
type: number
required: false
default: 20
label:
en_US: page_size
zh_Hans: 分页大小
human_description:
en_US: paging size
zh_Hans: 分页大小,默认值为 20最大值为 100。
llm_description: The default value of paging size is 20 and the maximum value is 100.
form: llm
- name: sort_condition
type: string
required: false
label:
en_US: sort_condition
zh_Hans: 排序条件
human_description:
en_US: sort condition
zh_Hans: |
排序条件,格式为:[{"field_name":"多行文本","desc":true}]。
field_name: 字段名称;
desc: 是否倒序排序;
llm_description: |
Sorting conditions, the format is: [{"field_name":"multi-line text","desc":true}].
form: llm
- name: filter_condition
type: string
required: false
label:
en_US: filter_condition
zh_Hans: 筛选条件
human_description:
en_US: filter condition
zh_Hans: |
筛选条件,格式为:{"conjunction":"and","conditions":[{"field_name":"字段1","operator":"is","value":["文本内容"]}]}。
conjunction条件逻辑连接词
conditions筛选条件集合
field_name筛选条件的左值值为字段的名称
operator条件运算符
value目标值
llm_description: |
The format of the filter condition is: {"conjunction":"and","conditions":[{"field_name":"Field 1","operator":"is","value":["text content"]}]}.
form: llm

View File

@ -1,47 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class ListBaseTablesTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
page_token = tool_parameters.get("page_token", "")
page_size = tool_parameters.get("page_size", "")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {
"page_token": page_token,
"page_size": page_size,
}
try:
res = httpx.get(url.format(app_token=app_token), headers=headers, params=params, timeout=30)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to list base tables, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to list base tables. {}".format(e))

View File

@ -1,65 +0,0 @@
identity:
name: list_base_tables
author: Doug Lea
label:
en_US: List Base Tables
zh_Hans: 根据 app_token 获取多维表格下的所有数据表
description:
human:
en_US: List base tables
zh_Hans: |
根据 app_token 获取多维表格下的所有数据表
llm: A tool for getting all data tables under a multidimensional table based on app_token.
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: page_token
type: string
required: false
label:
en_US: page_token
zh_Hans: 分页标记
human_description:
en_US: Pagination mark. If it is not filled in the first request, it means to traverse from the beginning.
zh_Hans: 分页标记,第一次请求不填,表示从头开始遍历。
llm_description: |
Pagination token. If it is not filled in the first request, it means to start traversal from the beginning.
If there are more items in the pagination query result, a new page_token will be returned at the same time.
The page_token can be used to obtain the query result in the next traversal.
分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token下次遍历可采用该 page_token 获取查询结果。
form: llm
- name: page_size
type: number
required: false
default: 20
label:
en_US: page_size
zh_Hans: 分页大小
human_description:
en_US: paging size
zh_Hans: 分页大小,默认值为 20最大值为 100。
llm_description: The default value of paging size is 20 and the maximum value is 100.
form: llm

View File

@ -1,49 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class ReadBaseRecordTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_id = tool_parameters.get("table_id", "")
if not table_id:
return self.create_text_message("Invalid parameter table_id")
record_id = tool_parameters.get("record_id", "")
if not record_id:
return self.create_text_message("Invalid parameter record_id")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
try:
res = httpx.get(
url.format(app_token=app_token, table_id=table_id, record_id=record_id), headers=headers, timeout=30
)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to read base record, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to read base record. {}".format(e))

View File

@ -1,60 +0,0 @@
identity:
name: read_base_record
author: Doug Lea
label:
en_US: Read Base Record
zh_Hans: 根据 record_id 的值检索多维表格数据表的记录
description:
human:
en_US: Read base record
zh_Hans: |
根据 record_id 的值检索多维表格数据表的记录
llm: Retrieve records from a multidimensional table based on the value of record_id
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_id
type: string
required: true
label:
en_US: table_id
zh_Hans: 多维表格的数据表
human_description:
en_US: bitable table id
zh_Hans: 多维表格数据表的唯一标识符 table_id
llm_description: bitable table id
form: llm
- name: record_id
type: string
required: true
label:
en_US: record_id
zh_Hans: 单条记录的 id
human_description:
en_US: The id of a single record
zh_Hans: 单条记录的 id
llm_description: The id of a single record
form: llm

View File

@ -1,60 +0,0 @@
import json
from typing import Any, Union
import httpx
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
class UpdateBaseRecordTool(BuiltinTool):
def _invoke(
self, user_id: str, tool_parameters: dict[str, Any]
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
url = "https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}"
access_token = tool_parameters.get("Authorization", "")
if not access_token:
return self.create_text_message("Invalid parameter access_token")
app_token = tool_parameters.get("app_token", "")
if not app_token:
return self.create_text_message("Invalid parameter app_token")
table_id = tool_parameters.get("table_id", "")
if not table_id:
return self.create_text_message("Invalid parameter table_id")
record_id = tool_parameters.get("record_id", "")
if not record_id:
return self.create_text_message("Invalid parameter record_id")
fields = tool_parameters.get("fields", "")
if not fields:
return self.create_text_message("Invalid parameter fields")
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
params = {}
payload = {"fields": json.loads(fields)}
try:
res = httpx.put(
url.format(app_token=app_token, table_id=table_id, record_id=record_id),
headers=headers,
params=params,
json=payload,
timeout=30,
)
res_json = res.json()
if res.is_success:
return self.create_text_message(text=json.dumps(res_json))
else:
return self.create_text_message(
f"Failed to update base record, status code: {res.status_code}, response: {res.text}"
)
except Exception as e:
return self.create_text_message("Failed to update base record. {}".format(e))

View File

@ -1,78 +0,0 @@
identity:
name: update_base_record
author: Doug Lea
label:
en_US: Update Base Record
zh_Hans: 更新多维表格数据表中的一条记录
description:
human:
en_US: Update base record
zh_Hans: |
更新多维表格数据表中的一条记录详细请参考https://open.larkoffice.com/document/server-docs/docs/bitable-v1/app-table-record/update
llm: Update a record in a multidimensional table data table
parameters:
- name: Authorization
type: string
required: true
label:
en_US: token
zh_Hans: 凭证
human_description:
en_US: API access token parameter, tenant_access_token or user_access_token
zh_Hans: API 的访问凭证参数tenant_access_token 或 user_access_token
llm_description: API access token parameter, tenant_access_token or user_access_token
form: llm
- name: app_token
type: string
required: true
label:
en_US: app_token
zh_Hans: 多维表格
human_description:
en_US: bitable app token
zh_Hans: 多维表格的唯一标识符 app_token
llm_description: bitable app token
form: llm
- name: table_id
type: string
required: true
label:
en_US: table_id
zh_Hans: 多维表格的数据表
human_description:
en_US: bitable table id
zh_Hans: 多维表格数据表的唯一标识符 table_id
llm_description: bitable table id
form: llm
- name: record_id
type: string
required: true
label:
en_US: record_id
zh_Hans: 单条记录的 id
human_description:
en_US: The id of a single record
zh_Hans: 单条记录的 id
llm_description: The id of a single record
form: llm
- name: fields
type: string
required: true
label:
en_US: fields
zh_Hans: 数据表的列字段内容
human_description:
en_US: The fields of a multidimensional table data table, that is, the columns of the data table.
zh_Hans: |
要更新一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"}
当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。
不同类型字段的数据结构请参考数据结构概述https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure
llm_description: |
要更新一行多维表格记录,字段结构拼接如下:{"多行文本":"多行文本内容","单选":"选项1","多选":["选项1","选项2"],"复选框":true,"人员":[{"id":"ou_2910013f1e6456f16a0ce75ede950a0a"}],"群组":[{"id":"oc_cd07f55f14d6f4a4f1b51504e7e97f48"}],"电话号码":"13026162666"}
当前接口支持的字段类型为:多行文本、单选、条码、多选、日期、人员、附件、复选框、超链接、数字、单向关联、双向关联、电话号码、地理位置。
不同类型字段的数据结构请参考数据结构概述https://open.larkoffice.com/document/server-docs/docs/bitable-v1/bitable-structure
form: llm

View File

@ -5,9 +5,12 @@ import requests
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool
SDURL = {
"sd_3": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-3-medium/text-to-image",
"sd_xl": "https://api.siliconflow.cn/v1/stabilityai/stable-diffusion-xl-base-1.0/text-to-image",
SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/image/generations"
SD_MODELS = {
"sd_3": "stabilityai/stable-diffusion-3-medium",
"sd_xl": "stabilityai/stable-diffusion-xl-base-1.0",
"sd_3.5_large": "stabilityai/stable-diffusion-3-5-large",
}
@ -22,9 +25,10 @@ class StableDiffusionTool(BuiltinTool):
}
model = tool_parameters.get("model", "sd_3")
url = SDURL.get(model)
sd_model = SD_MODELS.get(model)
payload = {
"model": sd_model,
"prompt": tool_parameters.get("prompt"),
"negative_prompt": tool_parameters.get("negative_prompt", ""),
"image_size": tool_parameters.get("image_size", "1024x1024"),
@ -34,7 +38,7 @@ class StableDiffusionTool(BuiltinTool):
"num_inference_steps": tool_parameters.get("num_inference_steps", 20),
}
response = requests.post(url, json=payload, headers=headers)
response = requests.post(SILICONFLOW_API_URL, json=payload, headers=headers)
if response.status_code != 200:
return self.create_text_message(f"Got Error Response:{response.text}")

View File

@ -40,6 +40,9 @@ parameters:
- value: sd_xl
label:
en_US: Stable Diffusion XL
- value: sd_3.5_large
label:
en_US: Stable Diffusion 3.5 Large
default: sd_3
label:
en_US: Choose Image Model

View File

@ -13,7 +13,6 @@ from core.tools.errors import (
from core.tools.provider.tool_provider import ToolProviderController
from core.tools.tool.builtin_tool import BuiltinTool
from core.tools.tool.tool import Tool
from core.tools.utils.tool_parameter_converter import ToolParameterConverter
from core.tools.utils.yaml_utils import load_yaml_file
@ -208,9 +207,7 @@ class BuiltinToolProviderController(ToolProviderController):
# the parameter is not set currently, set the default value if needed
if parameter_schema.default is not None:
default_value = ToolParameterConverter.cast_parameter_by_type(
parameter_schema.default, parameter_schema.type
)
default_value = parameter_schema.type.cast_value(parameter_schema.default)
tool_parameters[parameter] = default_value
def validate_credentials(self, credentials: dict[str, Any]) -> None:

View File

@ -11,7 +11,6 @@ from core.tools.entities.tool_entities import (
)
from core.tools.errors import ToolNotFoundError, ToolParameterValidationError, ToolProviderCredentialValidationError
from core.tools.tool.tool import Tool
from core.tools.utils.tool_parameter_converter import ToolParameterConverter
class ToolProviderController(BaseModel, ABC):
@ -127,9 +126,7 @@ class ToolProviderController(BaseModel, ABC):
# the parameter is not set currently, set the default value if needed
if parameter_schema.default is not None:
tool_parameters[parameter] = ToolParameterConverter.cast_parameter_by_type(
parameter_schema.default, parameter_schema.type
)
tool_parameters[parameter] = parameter_schema.type.cast_value(parameter_schema.default)
def validate_credentials_format(self, credentials: dict[str, Any]) -> None:
"""

View File

@ -1,6 +1,6 @@
from typing import Optional
from core.app.app_config.entities import VariableEntity, VariableEntityType
from core.app.app_config.entities import VariableEntityType
from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager
from core.tools.entities.common_entities import I18nObject
from core.tools.entities.tool_entities import (
@ -23,6 +23,8 @@ VARIABLE_TO_PARAMETER_TYPE_MAPPING = {
VariableEntityType.PARAGRAPH: ToolParameter.ToolParameterType.STRING,
VariableEntityType.SELECT: ToolParameter.ToolParameterType.SELECT,
VariableEntityType.NUMBER: ToolParameter.ToolParameterType.NUMBER,
VariableEntityType.FILE: ToolParameter.ToolParameterType.FILE,
VariableEntityType.FILE_LIST: ToolParameter.ToolParameterType.FILES,
}
@ -36,8 +38,8 @@ class WorkflowToolProviderController(ToolProviderController):
if not app:
raise ValueError("app not found")
controller = WorkflowToolProviderController(
**{
controller = WorkflowToolProviderController.model_validate(
{
"identity": {
"author": db_provider.user.name if db_provider.user_id and db_provider.user else "",
"name": db_provider.label,
@ -67,7 +69,7 @@ class WorkflowToolProviderController(ToolProviderController):
:param app: the app
:return: the tool
"""
workflow: Workflow = (
workflow = (
db.session.query(Workflow)
.filter(Workflow.app_id == db_provider.app_id, Workflow.version == db_provider.version)
.first()
@ -76,14 +78,14 @@ class WorkflowToolProviderController(ToolProviderController):
raise ValueError("workflow not found")
# fetch start node
graph: dict = workflow.graph_dict
features_dict: dict = workflow.features_dict
graph = workflow.graph_dict
features_dict = workflow.features_dict
features = WorkflowAppConfigManager.convert_features(config_dict=features_dict, app_mode=AppMode.WORKFLOW)
parameters = db_provider.parameter_configurations
variables = WorkflowToolConfigurationUtils.get_workflow_graph_variables(graph)
def fetch_workflow_variable(variable_name: str) -> VariableEntity:
def fetch_workflow_variable(variable_name: str):
return next(filter(lambda x: x.variable == variable_name, variables), None)
user = db_provider.user
@ -114,7 +116,6 @@ class WorkflowToolProviderController(ToolProviderController):
llm_description=parameter.description,
required=variable.required,
options=options,
default=variable.default,
)
)
elif features.file_upload:
@ -123,7 +124,7 @@ class WorkflowToolProviderController(ToolProviderController):
name=parameter.name,
label=I18nObject(en_US=parameter.name, zh_Hans=parameter.name),
human_description=I18nObject(en_US=parameter.description, zh_Hans=parameter.description),
type=ToolParameter.ToolParameterType.FILE,
type=ToolParameter.ToolParameterType.SYSTEM_FILES,
llm_description=parameter.description,
required=False,
form=parameter.form,

View File

@ -20,10 +20,9 @@ from core.tools.entities.tool_entities import (
ToolRuntimeVariablePool,
)
from core.tools.tool_file_manager import ToolFileManager
from core.tools.utils.tool_parameter_converter import ToolParameterConverter
if TYPE_CHECKING:
from core.file.file_obj import FileVar
from core.file.models import File
class Tool(BaseModel, ABC):
@ -63,8 +62,12 @@ class Tool(BaseModel, ABC):
def __init__(self, **data: Any):
super().__init__(**data)
class VariableKey(Enum):
class VariableKey(str, Enum):
IMAGE = "image"
DOCUMENT = "document"
VIDEO = "video"
AUDIO = "audio"
CUSTOM = "custom"
def fork_tool_runtime(self, runtime: dict[str, Any]) -> "Tool":
"""
@ -221,9 +224,7 @@ class Tool(BaseModel, ABC):
result = deepcopy(tool_parameters)
for parameter in self.parameters or []:
if parameter.name in tool_parameters:
result[parameter.name] = ToolParameterConverter.cast_parameter_by_type(
tool_parameters[parameter.name], parameter.type
)
result[parameter.name] = parameter.type.cast_value(tool_parameters[parameter.name])
return result
@ -295,10 +296,8 @@ class Tool(BaseModel, ABC):
"""
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.IMAGE, message=image, save_as=save_as)
def create_file_var_message(self, file_var: "FileVar") -> ToolInvokeMessage:
return ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.FILE_VAR, message="", meta={"file_var": file_var}, save_as=""
)
def create_file_message(self, file: "File") -> ToolInvokeMessage:
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.FILE, message="", meta={"file": file}, save_as="")
def create_link_message(self, link: str, save_as: str = "") -> ToolInvokeMessage:
"""

View File

@ -3,7 +3,7 @@ import logging
from copy import deepcopy
from typing import Any, Optional, Union
from core.file.file_obj import FileTransferMethod, FileVar
from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter, ToolProviderType
from core.tools.tool.tool import Tool
from extensions.ext_database import db
@ -45,11 +45,13 @@ class WorkflowTool(Tool):
workflow = self._get_workflow(app_id=self.workflow_app_id, version=self.version)
# transform the tool parameters
tool_parameters, files = self._transform_args(tool_parameters)
tool_parameters, files = self._transform_args(tool_parameters=tool_parameters)
from core.app.apps.workflow.app_generator import WorkflowAppGenerator
generator = WorkflowAppGenerator()
assert self.runtime is not None
assert self.runtime.invoke_from is not None
result = generator.generate(
app_model=app,
workflow=workflow,
@ -74,7 +76,7 @@ class WorkflowTool(Tool):
else:
outputs, files = self._extract_files(outputs)
for file in files:
result.append(self.create_file_var_message(file))
result.append(self.create_file_message(file))
result.append(self.create_text_message(json.dumps(outputs, ensure_ascii=False)))
result.append(self.create_json_message(outputs))
@ -154,22 +156,22 @@ class WorkflowTool(Tool):
parameters_result = {}
files = []
for parameter in parameter_rules:
if parameter.type == ToolParameter.ToolParameterType.FILE:
if parameter.type == ToolParameter.ToolParameterType.SYSTEM_FILES:
file = tool_parameters.get(parameter.name)
if file:
try:
file_var_list = [FileVar(**f) for f in file]
for file_var in file_var_list:
file_dict = {
"transfer_method": file_var.transfer_method.value,
"type": file_var.type.value,
file_var_list = [File.model_validate(f) for f in file]
for file in file_var_list:
file_dict: dict[str, str | None] = {
"transfer_method": file.transfer_method.value,
"type": file.type.value,
}
if file_var.transfer_method == FileTransferMethod.TOOL_FILE:
file_dict["tool_file_id"] = file_var.related_id
elif file_var.transfer_method == FileTransferMethod.LOCAL_FILE:
file_dict["upload_file_id"] = file_var.related_id
elif file_var.transfer_method == FileTransferMethod.REMOTE_URL:
file_dict["url"] = file_var.preview_url
if file.transfer_method == FileTransferMethod.TOOL_FILE:
file_dict["tool_file_id"] = file.related_id
elif file.transfer_method == FileTransferMethod.LOCAL_FILE:
file_dict["upload_file_id"] = file.related_id
elif file.transfer_method == FileTransferMethod.REMOTE_URL:
file_dict["url"] = file.generate_url()
files.append(file_dict)
except Exception as e:
@ -179,7 +181,7 @@ class WorkflowTool(Tool):
return parameters_result, files
def _extract_files(self, outputs: dict) -> tuple[dict, list[FileVar]]:
def _extract_files(self, outputs: dict) -> tuple[dict, list[File]]:
"""
extract files from the result
@ -190,17 +192,13 @@ class WorkflowTool(Tool):
result = {}
for key, value in outputs.items():
if isinstance(value, list):
has_file = False
for item in value:
if isinstance(item, dict) and item.get("__variant") == "FileVar":
try:
files.append(FileVar(**item))
has_file = True
except Exception as e:
pass
if has_file:
continue
if isinstance(item, dict) and item.get("dify_model_identity") == FILE_MODEL_IDENTITY:
file = File.model_validate(item)
files.append(file)
elif isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY:
file = File.model_validate(value)
files.append(file)
result[key] = value
return result, files

View File

@ -10,7 +10,8 @@ from yarl import URL
from core.app.entities.app_invoke_entities import InvokeFrom
from core.callback_handler.agent_tool_callback_handler import DifyAgentCallbackHandler
from core.callback_handler.workflow_tool_callback_handler import DifyWorkflowCallbackHandler
from core.file.file_obj import FileTransferMethod
from core.file import FileType
from core.file.models import FileTransferMethod
from core.ops.ops_trace_manager import TraceQueueManager
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolInvokeMessageBinary, ToolInvokeMeta, ToolParameter
from core.tools.errors import (
@ -26,6 +27,7 @@ from core.tools.tool.tool import Tool
from core.tools.tool.workflow_tool import WorkflowTool
from core.tools.utils.message_transformer import ToolFileMessageTransformer
from extensions.ext_database import db
from models.enums import CreatedByRole
from models.model import Message, MessageFile
@ -128,6 +130,7 @@ class ToolEngine:
"""
try:
# hit the callback handler
assert tool.identity is not None
workflow_tool_callback.on_tool_start(tool_name=tool.identity.name, tool_inputs=tool_parameters)
if isinstance(tool, WorkflowTool):
@ -258,7 +261,10 @@ class ToolEngine:
@staticmethod
def _create_message_files(
tool_messages: list[ToolInvokeMessageBinary], agent_message: Message, invoke_from: InvokeFrom, user_id: str
tool_messages: list[ToolInvokeMessageBinary],
agent_message: Message,
invoke_from: InvokeFrom,
user_id: str,
) -> list[tuple[Any, str]]:
"""
Create message file
@ -269,29 +275,31 @@ class ToolEngine:
result = []
for message in tool_messages:
file_type = "bin"
if "image" in message.mimetype:
file_type = "image"
file_type = FileType.IMAGE
elif "video" in message.mimetype:
file_type = "video"
file_type = FileType.VIDEO
elif "audio" in message.mimetype:
file_type = "audio"
elif "text" in message.mimetype:
file_type = "text"
elif "pdf" in message.mimetype:
file_type = "pdf"
elif "zip" in message.mimetype:
file_type = "archive"
# ...
file_type = FileType.AUDIO
elif "text" in message.mimetype or "pdf" in message.mimetype:
file_type = FileType.DOCUMENT
else:
file_type = FileType.CUSTOM
# extract tool file id from url
tool_file_id = message.url.split("/")[-1].split(".")[0]
message_file = MessageFile(
message_id=agent_message.id,
type=file_type,
transfer_method=FileTransferMethod.TOOL_FILE.value,
transfer_method=FileTransferMethod.TOOL_FILE,
belongs_to="assistant",
url=message.url,
upload_file_id=None,
created_by_role=("account" if invoke_from in {InvokeFrom.EXPLORE, InvokeFrom.DEBUGGER} else "end_user"),
upload_file_id=tool_file_id,
created_by_role=(
CreatedByRole.ACCOUNT
if invoke_from in {InvokeFrom.EXPLORE, InvokeFrom.DEBUGGER}
else CreatedByRole.END_USER
),
created_by=user_id,
)

View File

@ -4,7 +4,6 @@ import hmac
import logging
import os
import time
from collections.abc import Generator
from mimetypes import guess_extension, guess_type
from typing import Optional, Union
from uuid import uuid4
@ -57,22 +56,32 @@ class ToolFileManager:
@staticmethod
def create_file_by_raw(
user_id: str, tenant_id: str, conversation_id: Optional[str], file_binary: bytes, mimetype: str
*,
user_id: str,
tenant_id: str,
conversation_id: Optional[str],
file_binary: bytes,
mimetype: str,
) -> ToolFile:
"""
create file
"""
extension = guess_extension(mimetype) or ".bin"
unique_name = uuid4().hex
filename = f"tools/{tenant_id}/{unique_name}{extension}"
storage.save(filename, file_binary)
filename = f"{unique_name}{extension}"
filepath = f"tools/{tenant_id}/{filename}"
storage.save(filepath, file_binary)
tool_file = ToolFile(
user_id=user_id, tenant_id=tenant_id, conversation_id=conversation_id, file_key=filename, mimetype=mimetype
user_id=user_id,
tenant_id=tenant_id,
conversation_id=conversation_id,
file_key=filepath,
mimetype=mimetype,
name=filename,
size=len(file_binary),
)
db.session.add(tool_file)
db.session.commit()
db.session.refresh(tool_file)
return tool_file
@ -80,29 +89,34 @@ class ToolFileManager:
def create_file_by_url(
user_id: str,
tenant_id: str,
conversation_id: str,
conversation_id: str | None,
file_url: str,
) -> ToolFile:
"""
create file
"""
# try to download image
response = get(file_url)
response.raise_for_status()
blob = response.content
try:
response = get(file_url)
response.raise_for_status()
blob = response.content
except Exception as e:
logger.error(f"Failed to download file from {file_url}: {e}")
raise
mimetype = guess_type(file_url)[0] or "octet/stream"
extension = guess_extension(mimetype) or ".bin"
unique_name = uuid4().hex
filename = f"tools/{tenant_id}/{unique_name}{extension}"
storage.save(filename, blob)
filename = f"{unique_name}{extension}"
filepath = f"tools/{tenant_id}/{filename}"
storage.save(filepath, blob)
tool_file = ToolFile(
user_id=user_id,
tenant_id=tenant_id,
conversation_id=conversation_id,
file_key=filename,
file_key=filepath,
mimetype=mimetype,
original_url=file_url,
name=filename,
size=len(blob),
)
db.session.add(tool_file)
@ -110,18 +124,6 @@ class ToolFileManager:
return tool_file
@staticmethod
def create_file_by_key(
user_id: str, tenant_id: str, conversation_id: str, file_key: str, mimetype: str
) -> ToolFile:
"""
create file
"""
tool_file = ToolFile(
user_id=user_id, tenant_id=tenant_id, conversation_id=conversation_id, file_key=file_key, mimetype=mimetype
)
return tool_file
@staticmethod
def get_file_binary(id: str) -> Union[tuple[bytes, str], None]:
"""
@ -131,7 +133,7 @@ class ToolFileManager:
:return: the binary of the file, mime type
"""
tool_file: ToolFile = (
tool_file = (
db.session.query(ToolFile)
.filter(
ToolFile.id == id,
@ -155,7 +157,7 @@ class ToolFileManager:
:return: the binary of the file, mime type
"""
message_file: MessageFile = (
message_file = (
db.session.query(MessageFile)
.filter(
MessageFile.id == id,
@ -166,13 +168,16 @@ class ToolFileManager:
# Check if message_file is not None
if message_file is not None:
# get tool file id
tool_file_id = message_file.url.split("/")[-1]
# trim extension
tool_file_id = tool_file_id.split(".")[0]
if message_file.url is not None:
tool_file_id = message_file.url.split("/")[-1]
# trim extension
tool_file_id = tool_file_id.split(".")[0]
else:
tool_file_id = None
else:
tool_file_id = None
tool_file: ToolFile = (
tool_file = (
db.session.query(ToolFile)
.filter(
ToolFile.id == tool_file_id,
@ -188,7 +193,7 @@ class ToolFileManager:
return blob, tool_file.mimetype
@staticmethod
def get_file_generator_by_tool_file_id(tool_file_id: str) -> Union[tuple[Generator, str], None]:
def get_file_generator_by_tool_file_id(tool_file_id: str):
"""
get file binary
@ -196,7 +201,7 @@ class ToolFileManager:
:return: the binary of the file, mime type
"""
tool_file: ToolFile = (
tool_file = (
db.session.query(ToolFile)
.filter(
ToolFile.id == tool_file_id,
@ -205,11 +210,11 @@ class ToolFileManager:
)
if not tool_file:
return None
return None, None
generator = storage.load_stream(tool_file.file_key)
stream = storage.load_stream(tool_file.file_key)
return generator, tool_file.mimetype
return stream, tool_file
# init tool_file_parser

View File

@ -24,7 +24,6 @@ from core.tools.tool.builtin_tool import BuiltinTool
from core.tools.tool.tool import Tool
from core.tools.tool_label_manager import ToolLabelManager
from core.tools.utils.configuration import ToolConfigurationManager, ToolParameterConfigurationManager
from core.tools.utils.tool_parameter_converter import ToolParameterConverter
from extensions.ext_database import db
from models.tools import ApiToolProvider, BuiltinToolProvider, WorkflowToolProvider
from services.tools.tools_transform_service import ToolTransformService
@ -203,7 +202,7 @@ class ToolManager:
raise ToolProviderNotFoundError(f"provider type {provider_type} not found")
@classmethod
def _init_runtime_parameter(cls, parameter_rule: ToolParameter, parameters: dict) -> Union[str, int, float, bool]:
def _init_runtime_parameter(cls, parameter_rule: ToolParameter, parameters: dict):
"""
init runtime parameter
"""
@ -222,7 +221,7 @@ class ToolManager:
f"tool parameter {parameter_rule.name} value {parameter_value} not in options {options}"
)
return ToolParameterConverter.cast_parameter_by_type(parameter_value, parameter_rule.type)
return parameter_rule.type.cast_value(parameter_value)
@classmethod
def get_agent_tool_runtime(
@ -243,7 +242,11 @@ class ToolManager:
parameters = tool_entity.get_all_runtime_parameters()
for parameter in parameters:
# check file types
if parameter.type == ToolParameter.ToolParameterType.FILE:
if parameter.type in {
ToolParameter.ToolParameterType.SYSTEM_FILES,
ToolParameter.ToolParameterType.FILE,
ToolParameter.ToolParameterType.FILES,
}:
raise ValueError(f"file type parameter {parameter.name} not supported in agent")
if parameter.form == ToolParameter.ToolParameterForm.FORM:

View File

@ -1,3 +1,4 @@
import json
from typing import Optional
import httpx
@ -17,6 +18,41 @@ def auth(credentials):
raise ToolProviderCredentialValidationError(str(e))
def convert_add_records(json_str):
try:
data = json.loads(json_str)
if not isinstance(data, list):
raise ValueError("Parsed data must be a list")
converted_data = [{"fields": json.dumps(item, ensure_ascii=False)} for item in data]
return converted_data
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
except Exception as e:
raise ValueError(f"An error occurred while processing the data: {e}")
def convert_update_records(json_str):
try:
data = json.loads(json_str)
if not isinstance(data, list):
raise ValueError("Parsed data must be a list")
converted_data = [
{"fields": json.dumps(record["fields"], ensure_ascii=False), "record_id": record["record_id"]}
for record in data
if "fields" in record and "record_id" in record
]
if len(converted_data) != len(data):
raise ValueError("Each record must contain 'fields' and 'record_id'")
return converted_data
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
except Exception as e:
raise ValueError(f"An error occurred while processing the data: {e}")
class FeishuRequest:
API_BASE_URL = "https://lark-plugin-api.solutionsuite.cn/lark-plugin"
@ -517,3 +553,270 @@ class FeishuRequest:
}
res = self._send_request(url, method="GET", params=params)
return res.get("data")
def create_base(
self,
name: str,
folder_token: str,
) -> dict:
# 创建多维表格
url = f"{self.API_BASE_URL}/base/create_base"
payload = {
"name": name,
"folder_token": folder_token,
}
res = self._send_request(url, payload=payload)
return res.get("data")
def add_records(
self,
app_token: str,
table_id: str,
table_name: str,
records: str,
user_id_type: str = "open_id",
) -> dict:
# 新增多条记录
url = f"{self.API_BASE_URL}/base/add_records"
params = {
"app_token": app_token,
"table_id": table_id,
"table_name": table_name,
"user_id_type": user_id_type,
}
payload = {
"records": convert_add_records(records),
}
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def update_records(
self,
app_token: str,
table_id: str,
table_name: str,
records: str,
user_id_type: str,
) -> dict:
# 更新多条记录
url = f"{self.API_BASE_URL}/base/update_records"
params = {
"app_token": app_token,
"table_id": table_id,
"table_name": table_name,
"user_id_type": user_id_type,
}
payload = {
"records": convert_update_records(records),
}
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def delete_records(
self,
app_token: str,
table_id: str,
table_name: str,
record_ids: str,
) -> dict:
# 删除多条记录
url = f"{self.API_BASE_URL}/base/delete_records"
params = {
"app_token": app_token,
"table_id": table_id,
"table_name": table_name,
}
if not record_ids:
record_id_list = []
else:
try:
record_id_list = json.loads(record_ids)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
payload = {
"records": record_id_list,
}
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def search_record(
self,
app_token: str,
table_id: str,
table_name: str,
view_id: str,
field_names: str,
sort: str,
filters: str,
page_token: str,
automatic_fields: bool = False,
user_id_type: str = "open_id",
page_size: int = 20,
) -> dict:
# 查询记录,单次最多查询 500 行记录。
url = f"{self.API_BASE_URL}/base/search_record"
params = {
"app_token": app_token,
"table_id": table_id,
"table_name": table_name,
"user_id_type": user_id_type,
"page_token": page_token,
"page_size": page_size,
}
if not field_names:
field_name_list = []
else:
try:
field_name_list = json.loads(field_names)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
if not sort:
sort_list = []
else:
try:
sort_list = json.loads(sort)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
if not filters:
filter_dict = {}
else:
try:
filter_dict = json.loads(filters)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
payload = {}
if view_id:
payload["view_id"] = view_id
if field_names:
payload["field_names"] = field_name_list
if sort:
payload["sort"] = sort_list
if filters:
payload["filter"] = filter_dict
if automatic_fields:
payload["automatic_fields"] = automatic_fields
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def get_base_info(
self,
app_token: str,
) -> dict:
# 获取多维表格元数据
url = f"{self.API_BASE_URL}/base/get_base_info"
params = {
"app_token": app_token,
}
res = self._send_request(url, method="GET", params=params)
return res.get("data")
def create_table(
self,
app_token: str,
table_name: str,
default_view_name: str,
fields: str,
) -> dict:
# 新增一个数据表
url = f"{self.API_BASE_URL}/base/create_table"
params = {
"app_token": app_token,
}
if not fields:
fields_list = []
else:
try:
fields_list = json.loads(fields)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
payload = {
"name": table_name,
"fields": fields_list,
}
if default_view_name:
payload["default_view_name"] = default_view_name
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def delete_tables(
self,
app_token: str,
table_ids: str,
table_names: str,
) -> dict:
# 删除多个数据表
url = f"{self.API_BASE_URL}/base/delete_tables"
params = {
"app_token": app_token,
}
if not table_ids:
table_id_list = []
else:
try:
table_id_list = json.loads(table_ids)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
if not table_names:
table_name_list = []
else:
try:
table_name_list = json.loads(table_names)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
payload = {
"table_ids": table_id_list,
"table_names": table_name_list,
}
res = self._send_request(url, params=params, payload=payload)
return res.get("data")
def list_tables(
self,
app_token: str,
page_token: str,
page_size: int = 20,
) -> dict:
# 列出多维表格下的全部数据表
url = f"{self.API_BASE_URL}/base/list_tables"
params = {
"app_token": app_token,
"page_token": page_token,
"page_size": page_size,
}
res = self._send_request(url, method="GET", params=params)
return res.get("data")
def read_records(
self,
app_token: str,
table_id: str,
table_name: str,
record_ids: str,
user_id_type: str = "open_id",
) -> dict:
url = f"{self.API_BASE_URL}/base/read_records"
params = {
"app_token": app_token,
"table_id": table_id,
"table_name": table_name,
}
if not record_ids:
record_id_list = []
else:
try:
record_id_list = json.loads(record_ids)
except json.JSONDecodeError:
raise ValueError("The input string is not valid JSON")
payload = {
"record_ids": record_id_list,
"user_id_type": user_id_type,
}
res = self._send_request(url, method="GET", params=params, payload=payload)
return res.get("data")

View File

@ -1,7 +1,8 @@
import logging
from mimetypes import guess_extension
from typing import Optional
from core.file.file_obj import FileTransferMethod, FileType
from core.file import File, FileTransferMethod, FileType
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool_file_manager import ToolFileManager
@ -11,7 +12,7 @@ logger = logging.getLogger(__name__)
class ToolFileMessageTransformer:
@classmethod
def transform_tool_invoke_messages(
cls, messages: list[ToolInvokeMessage], user_id: str, tenant_id: str, conversation_id: str
cls, messages: list[ToolInvokeMessage], user_id: str, tenant_id: str, conversation_id: str | None
) -> list[ToolInvokeMessage]:
"""
Transform tool message and handle file download
@ -21,7 +22,7 @@ class ToolFileMessageTransformer:
for message in messages:
if message.type in {ToolInvokeMessage.MessageType.TEXT, ToolInvokeMessage.MessageType.LINK}:
result.append(message)
elif message.type == ToolInvokeMessage.MessageType.IMAGE:
elif message.type == ToolInvokeMessage.MessageType.IMAGE and isinstance(message.message, str):
# try to download image
try:
file = ToolFileManager.create_file_by_url(
@ -50,11 +51,14 @@ class ToolFileMessageTransformer:
)
elif message.type == ToolInvokeMessage.MessageType.BLOB:
# get mime type and save blob to storage
assert message.meta is not None
mimetype = message.meta.get("mime_type", "octet/stream")
# if message is str, encode it to bytes
if isinstance(message.message, str):
message.message = message.message.encode("utf-8")
# FIXME: should do a type check here.
assert isinstance(message.message, bytes)
file = ToolFileManager.create_file_by_raw(
user_id=user_id,
tenant_id=tenant_id,
@ -63,7 +67,7 @@ class ToolFileMessageTransformer:
mimetype=mimetype,
)
url = cls.get_tool_file_url(file.id, guess_extension(file.mimetype))
url = cls.get_tool_file_url(tool_file_id=file.id, extension=guess_extension(file.mimetype))
# check if file is image
if "image" in mimetype:
@ -84,12 +88,14 @@ class ToolFileMessageTransformer:
meta=message.meta.copy() if message.meta is not None else {},
)
)
elif message.type == ToolInvokeMessage.MessageType.FILE_VAR:
file_var = message.meta.get("file_var")
if file_var:
if file_var.transfer_method == FileTransferMethod.TOOL_FILE:
url = cls.get_tool_file_url(file_var.related_id, file_var.extension)
if file_var.type == FileType.IMAGE:
elif message.type == ToolInvokeMessage.MessageType.FILE:
assert message.meta is not None
file = message.meta.get("file")
if isinstance(file, File):
if file.transfer_method == FileTransferMethod.TOOL_FILE:
assert file.related_id is not None
url = cls.get_tool_file_url(tool_file_id=file.related_id, extension=file.extension)
if file.type == FileType.IMAGE:
result.append(
ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.IMAGE_LINK,
@ -107,11 +113,13 @@ class ToolFileMessageTransformer:
meta=message.meta.copy() if message.meta is not None else {},
)
)
else:
result.append(message)
else:
result.append(message)
return result
@classmethod
def get_tool_file_url(cls, tool_file_id: str, extension: str) -> str:
def get_tool_file_url(cls, tool_file_id: str, extension: Optional[str]) -> str:
return f'/files/tools/{tool_file_id}{extension or ".bin"}'

View File

@ -1,71 +0,0 @@
from typing import Any
from core.tools.entities.tool_entities import ToolParameter
class ToolParameterConverter:
@staticmethod
def get_parameter_type(parameter_type: str | ToolParameter.ToolParameterType) -> str:
match parameter_type:
case (
ToolParameter.ToolParameterType.STRING
| ToolParameter.ToolParameterType.SECRET_INPUT
| ToolParameter.ToolParameterType.SELECT
):
return "string"
case ToolParameter.ToolParameterType.BOOLEAN:
return "boolean"
case ToolParameter.ToolParameterType.NUMBER:
return "number"
case _:
raise ValueError(f"Unsupported parameter type {parameter_type}")
@staticmethod
def cast_parameter_by_type(value: Any, parameter_type: str) -> Any:
# convert tool parameter config to correct type
try:
match parameter_type:
case (
ToolParameter.ToolParameterType.STRING
| ToolParameter.ToolParameterType.SECRET_INPUT
| ToolParameter.ToolParameterType.SELECT
):
if value is None:
return ""
else:
return value if isinstance(value, str) else str(value)
case ToolParameter.ToolParameterType.BOOLEAN:
if value is None:
return False
elif isinstance(value, str):
# Allowed YAML boolean value strings: https://yaml.org/type/bool.html
# and also '0' for False and '1' for True
match value.lower():
case "true" | "yes" | "y" | "1":
return True
case "false" | "no" | "n" | "0":
return False
case _:
return bool(value)
else:
return value if isinstance(value, bool) else bool(value)
case ToolParameter.ToolParameterType.NUMBER:
if isinstance(value, int) | isinstance(value, float):
return value
elif isinstance(value, str) and value != "":
if "." in value:
return float(value)
else:
return int(value)
case ToolParameter.ToolParameterType.FILE:
return value
case _:
return str(value)
except Exception:
raise ValueError(f"The tool parameter value {value} is not in correct type of {parameter_type}.")

View File

@ -1,19 +1,18 @@
from collections.abc import Mapping, Sequence
from typing import Any
from core.app.app_config.entities import VariableEntity
from core.tools.entities.tool_entities import WorkflowToolParameterConfiguration
class WorkflowToolConfigurationUtils:
@classmethod
def check_parameter_configurations(cls, configurations: list[dict]):
"""
check parameter configurations
"""
def check_parameter_configurations(cls, configurations: Mapping[str, Any]):
for configuration in configurations:
if not WorkflowToolParameterConfiguration(**configuration):
raise ValueError("invalid parameter configuration")
WorkflowToolParameterConfiguration.model_validate(configuration)
@classmethod
def get_workflow_graph_variables(cls, graph: dict) -> list[VariableEntity]:
def get_workflow_graph_variables(cls, graph: Mapping[str, Any]) -> Sequence[VariableEntity]:
"""
get workflow graph variables
"""
@ -23,7 +22,7 @@ class WorkflowToolConfigurationUtils:
if not start_node:
return []
return [VariableEntity(**variable) for variable in start_node.get("data", {}).get("variables", [])]
return [VariableEntity.model_validate(variable) for variable in start_node.get("data", {}).get("variables", [])]
@classmethod
def check_is_synced(

View File

@ -1,4 +1,5 @@
import logging
from pathlib import Path
from typing import Any
import yaml
@ -17,15 +18,18 @@ def load_yaml_file(file_path: str, ignore_error: bool = True, default_value: Any
:param default_value: the value returned when errors ignored
:return: an object of the YAML content
"""
try:
with open(file_path, encoding="utf-8") as yaml_file:
try:
yaml_content = yaml.safe_load(yaml_file)
return yaml_content or default_value
except Exception as e:
raise YAMLError(f"Failed to load YAML file {file_path}: {e}")
except Exception as e:
if not file_path or not Path(file_path).exists():
if ignore_error:
return default_value
else:
raise e
raise FileNotFoundError(f"File not found: {file_path}")
with open(file_path, encoding="utf-8") as yaml_file:
try:
yaml_content = yaml.safe_load(yaml_file)
return yaml_content or default_value
except Exception as e:
if ignore_error:
return default_value
else:
raise YAMLError(f"Failed to load YAML file {file_path}: {e}") from e