mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 17:08:03 +08:00
merge main
This commit is contained in:
@ -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"
|
||||
# -------------
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -61,6 +61,7 @@
|
||||
- vectorizer
|
||||
- qrcode
|
||||
- tianditu
|
||||
- aliyuque
|
||||
- google_translate
|
||||
- hap
|
||||
- json_process
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,7 +13,7 @@ description:
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
|
||||
@ -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}")
|
||||
)
|
||||
|
||||
@ -13,7 +13,7 @@ description:
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
|
||||
@ -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")
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,7 +13,7 @@ description:
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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}")
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ description:
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,7 +13,7 @@ description:
|
||||
|
||||
parameters:
|
||||
- name: book_id
|
||||
type: number
|
||||
type: string
|
||||
required: true
|
||||
form: llm
|
||||
label:
|
||||
|
||||
@ -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}")
|
||||
)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: 选择模型
|
||||
|
||||
@ -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" or ''})"
|
||||
json_result.append(self.create_json_message(res))
|
||||
return [self.create_text_message(markdown_result)] + json_result
|
||||
|
||||
@ -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 |
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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))
|
||||
@ -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
|
||||
@ -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}")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
"""
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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:
|
||||
"""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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"}'
|
||||
|
||||
@ -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}.")
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user