Compare commits

...

25 Commits

Author SHA1 Message Date
Yi
2474dbdff0 fix the tooltip for the knowledge base's firecrawl max depth attribute 2024-08-28 17:09:30 +08:00
crazywoola
3a071b8db9 fix: datasets permission is missing (#7751) 2024-08-28 15:36:11 +08:00
snickerjp
9342b4b951 Update package "libldap-2.5-0" for docker build. (#7726) 2024-08-28 14:44:05 +08:00
Vimpas
4682e0ac7c fix(storage): 🐛 HeadBucket Operation Permission (#7733)
Co-authored-by: 莫岳恒 <moyueheng@datagrand.com>
2024-08-28 13:57:45 +08:00
sino
7cfebffbb8 chore: update default endpoint for ark provider (#7741) 2024-08-28 13:56:50 +08:00
KVOJJJin
693fe912f2 Fix annotation reply settings (#7696) 2024-08-28 09:42:54 +08:00
kurokobo
bc3a8e0ca2 feat: store created_by and updated_by for apps, modelconfigs, and sites (#7613) 2024-08-28 08:47:30 +08:00
Jiakun Xu
e38334cfd2 fix: doc_language return null when document segment settings (#7719) 2024-08-28 08:45:51 +08:00
走在修行的大街上
92cab33b73 feat(Tools): add feishu document and message plugins (#6435)
Co-authored-by: 黎斌 <libin.23@bytedance.com>
2024-08-27 20:21:42 +08:00
Bowen Liang
3f467613fc feat: support configs for code execution request (#7704) 2024-08-27 19:38:33 +08:00
Bryan
205d33a813 Fix: read properties of undefined issue (#7708)
Co-authored-by: libing <libing@healink.cn>
2024-08-27 19:23:56 +08:00
crazywoola
da326baa5e fix: tongyi Error: 'NoneType' object is not subscriptable (#7705) 2024-08-27 16:56:06 +08:00
crazywoola
d9198b5646 feat: remove unused code (#7702) 2024-08-27 16:47:34 +08:00
Jyong
60001a62c4 fixed chunk_overlap is None (#7703) 2024-08-27 16:38:06 +08:00
sino
ee7d5e7206 feat: support Moonshot and GLM models tool call for volc ark provider (#7666) 2024-08-27 14:43:37 +08:00
呆萌闷油瓶
2726fb3d5d feat:dailymessages (#7603) 2024-08-27 12:53:27 +08:00
kurokobo
d7aa4076c9 feat: display account name on the logs page for the apps (#7668)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
2024-08-27 12:40:44 +08:00
Kenn
122ce41020 feat: rewrite Elasticsearch index and search code to achieve Elasticsearch vector and full-text search (#7641)
Co-authored-by: haokai <haokai@shuwen.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: Bowen Liang <bowenliang@apache.org>
Co-authored-by: wellCh4n <wellCh4n@foxmail.com>
2024-08-27 11:43:44 +08:00
Charlie.Wei
e7afee1176 Langfuse view button (#7684) 2024-08-27 11:25:56 +08:00
zxhlyh
88730906ec fix: empty knowledge add file (#7690) 2024-08-27 11:25:27 +08:00
Bowen Liang
a15080a1d7 bug: (#7586 followup) fix config of CODE_MAX_STRING_LENGTH (#7683) 2024-08-27 10:38:24 +08:00
Jyong
35431bce0d fix dataset_id and index_node_id idx missed in document_segments tabl… (#7681) 2024-08-27 10:25:24 +08:00
Hélio Lúcio
7b7576ad55 Add Azure AI Studio as provider (#7549)
Co-authored-by: Hélio Lúcio <canais.hlucio@voegol.com.br>
2024-08-27 09:52:59 +08:00
Qin Liu
162faee4f2 fix: set score_threshold to zero if it is None for MyScale vectordb (#7640)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
2024-08-27 09:47:16 +08:00
Zhi
b7ff98d7ff fix: Remove useless debug information. (#7647) 2024-08-26 20:40:26 +08:00
106 changed files with 2367 additions and 264 deletions

View File

@@ -55,7 +55,7 @@ RUN apt-get update \
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
&& apt-get update \
# For Security
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-1 libldap-2.5-0=2.5.18+dfsg-2 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.2-1 libldap-2.5-0=2.5.18+dfsg-3 perl=5.38.2-5 libsqlite3-0=3.46.0-1 \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,6 +1,6 @@
from typing import Optional
from pydantic import AliasChoices, Field, NegativeInt, NonNegativeInt, PositiveInt, computed_field
from pydantic import AliasChoices, Field, HttpUrl, NegativeInt, NonNegativeInt, PositiveInt, computed_field
from pydantic_settings import BaseSettings
from configs.feature.hosted_service import HostedServiceConfig
@@ -45,7 +45,7 @@ class CodeExecutionSandboxConfig(BaseSettings):
Code Execution Sandbox configs
"""
CODE_EXECUTION_ENDPOINT: str = Field(
CODE_EXECUTION_ENDPOINT: HttpUrl = Field(
description="endpoint URL of code execution servcie",
default="http://sandbox:8194",
)
@@ -55,6 +55,21 @@ class CodeExecutionSandboxConfig(BaseSettings):
default="dify-sandbox",
)
CODE_EXECUTION_CONNECT_TIMEOUT: Optional[float] = Field(
description="connect timeout in seconds for code execution request",
default=10.0,
)
CODE_EXECUTION_READ_TIMEOUT: Optional[float] = Field(
description="read timeout in seconds for code execution request",
default=60.0,
)
CODE_EXECUTION_WRITE_TIMEOUT: Optional[float] = Field(
description="write timeout in seconds for code execution request",
default=10.0,
)
CODE_MAX_NUMBER: PositiveInt = Field(
description="max depth for code execution",
default=9223372036854775807,

View File

@@ -13,6 +13,7 @@ from configs.middleware.storage.oci_storage_config import OCIStorageConfig
from configs.middleware.storage.tencent_cos_storage_config import TencentCloudCOSStorageConfig
from configs.middleware.vdb.analyticdb_config import AnalyticdbConfig
from configs.middleware.vdb.chroma_config import ChromaConfig
from configs.middleware.vdb.elasticsearch_config import ElasticsearchConfig
from configs.middleware.vdb.milvus_config import MilvusConfig
from configs.middleware.vdb.myscale_config import MyScaleConfig
from configs.middleware.vdb.opensearch_config import OpenSearchConfig
@@ -200,5 +201,6 @@ class MiddlewareConfig(
TencentVectorDBConfig,
TiDBVectorConfig,
WeaviateConfig,
ElasticsearchConfig,
):
pass

View File

@@ -0,0 +1,30 @@
from typing import Optional
from pydantic import Field, PositiveInt
from pydantic_settings import BaseSettings
class ElasticsearchConfig(BaseSettings):
"""
Elasticsearch configs
"""
ELASTICSEARCH_HOST: Optional[str] = Field(
description="Elasticsearch host",
default="127.0.0.1",
)
ELASTICSEARCH_PORT: PositiveInt = Field(
description="Elasticsearch port",
default=9200,
)
ELASTICSEARCH_USERNAME: Optional[str] = Field(
description="Elasticsearch username",
default="elastic",
)
ELASTICSEARCH_PASSWORD: Optional[str] = Field(
description="Elasticsearch password",
default="elastic",
)

View File

@@ -32,6 +32,8 @@ class ModelConfigResource(Resource):
new_app_model_config = AppModelConfig(
app_id=app_model.id,
created_by=current_user.id,
updated_by=current_user.id,
)
new_app_model_config = new_app_model_config.from_model_config_dict(model_configuration)

View File

@@ -1,3 +1,5 @@
from datetime import datetime, timezone
from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse
from werkzeug.exceptions import Forbidden, NotFound
@@ -71,6 +73,8 @@ class AppSite(Resource):
if value is not None:
setattr(site, attr_name, value)
site.updated_by = current_user.id
site.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
return site
@@ -93,6 +97,8 @@ class AppSiteAccessTokenReset(Resource):
raise NotFound
site.code = Site.generate_code(16)
site.updated_by = current_user.id
site.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
return site

View File

@@ -16,6 +16,60 @@ from libs.login import login_required
from models.model import AppMode
class DailyMessageStatistic(Resource):
@setup_required
@login_required
@account_initialization_required
@get_app_model
def get(self, app_model):
account = current_user
parser = reqparse.RequestParser()
parser.add_argument("start", type=datetime_string("%Y-%m-%d %H:%M"), location="args")
parser.add_argument("end", type=datetime_string("%Y-%m-%d %H:%M"), location="args")
args = parser.parse_args()
sql_query = """
SELECT date(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, count(*) AS message_count
FROM messages where app_id = :app_id
"""
arg_dict = {"tz": account.timezone, "app_id": app_model.id}
timezone = pytz.timezone(account.timezone)
utc_timezone = pytz.utc
if args["start"]:
start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
start_datetime = start_datetime.replace(second=0)
start_datetime_timezone = timezone.localize(start_datetime)
start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
sql_query += " and created_at >= :start"
arg_dict["start"] = start_datetime_utc
if args["end"]:
end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
end_datetime = end_datetime.replace(second=0)
end_datetime_timezone = timezone.localize(end_datetime)
end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
sql_query += " and created_at < :end"
arg_dict["end"] = end_datetime_utc
sql_query += " GROUP BY date order by date"
response_data = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(sql_query), arg_dict)
for i in rs:
response_data.append({"date": str(i.date), "message_count": i.message_count})
return jsonify({"data": response_data})
class DailyConversationStatistic(Resource):
@setup_required
@login_required
@@ -419,6 +473,7 @@ WHERE app_id = :app_id"""
return jsonify({"data": response_data})
api.add_resource(DailyMessageStatistic, "/apps/<uuid:app_id>/statistics/daily-messages")
api.add_resource(DailyConversationStatistic, "/apps/<uuid:app_id>/statistics/daily-conversations")
api.add_resource(DailyTerminalsStatistic, "/apps/<uuid:app_id>/statistics/daily-end-users")
api.add_resource(DailyTokenCostStatistic, "/apps/<uuid:app_id>/statistics/token-costs")

View File

@@ -599,6 +599,7 @@ class DocumentDetailApi(DocumentResource):
"hit_count": document.hit_count,
"display_status": document.display_status,
"doc_form": document.doc_form,
"doc_language": document.doc_language,
}
else:
process_rules = DatasetService.get_process_rules(dataset_id)
@@ -631,6 +632,7 @@ class DocumentDetailApi(DocumentResource):
"hit_count": document.hit_count,
"display_status": document.display_status,
"doc_form": document.doc_form,
"doc_language": document.doc_language,
}
return response, 200

View File

@@ -15,12 +15,6 @@ from core.helper.code_executor.template_transformer import TemplateTransformer
logger = logging.getLogger(__name__)
# Code Executor
CODE_EXECUTION_ENDPOINT = dify_config.CODE_EXECUTION_ENDPOINT
CODE_EXECUTION_API_KEY = dify_config.CODE_EXECUTION_API_KEY
CODE_EXECUTION_TIMEOUT = Timeout(connect=10, write=10, read=60, pool=None)
class CodeExecutionException(Exception):
pass
@@ -71,10 +65,10 @@ class CodeExecutor:
:param code: code
:return:
"""
url = URL(CODE_EXECUTION_ENDPOINT) / 'v1' / 'sandbox' / 'run'
url = URL(str(dify_config.CODE_EXECUTION_ENDPOINT)) / 'v1' / 'sandbox' / 'run'
headers = {
'X-Api-Key': CODE_EXECUTION_API_KEY
'X-Api-Key': dify_config.CODE_EXECUTION_API_KEY
}
data = {
@@ -85,7 +79,12 @@ class CodeExecutor:
}
try:
response = post(str(url), json=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT)
response = post(str(url), json=data, headers=headers,
timeout=Timeout(
connect=dify_config.CODE_EXECUTION_CONNECT_TIMEOUT,
read=dify_config.CODE_EXECUTION_READ_TIMEOUT,
write=dify_config.CODE_EXECUTION_WRITE_TIMEOUT,
pool=None))
if response.status_code == 503:
raise CodeExecutionException('Code execution service is unavailable')
elif response.status_code != 200:
@@ -96,7 +95,7 @@ class CodeExecutor:
raise CodeExecutionException('Failed to execute code, which is likely a network issue,'
' please check if the sandbox service is running.'
f' ( Error: {str(e)} )')
try:
response = response.json()
except:
@@ -104,12 +103,12 @@ class CodeExecutor:
if (code := response.get('code')) != 0:
raise CodeExecutionException(f"Got error code: {code}. Got error msg: {response.get('message')}")
response = CodeExecutionResponse(**response)
if response.data.error:
raise CodeExecutionException(response.data.error)
return response.data.stdout or ''
@classmethod
@@ -133,4 +132,3 @@ class CodeExecutor:
raise e
return template_transformer.transform_response(response)

View File

@@ -720,6 +720,7 @@ class IndexingRunner:
document_ids = [document.metadata['doc_id'] for document in documents]
db.session.query(DocumentSegment).filter(
DocumentSegment.document_id == document_id,
DocumentSegment.dataset_id == dataset_id,
DocumentSegment.index_node_id.in_(document_ids),
DocumentSegment.status == "indexing"
).update({
@@ -751,6 +752,7 @@ class IndexingRunner:
document_ids = [document.metadata['doc_id'] for document in chunk_documents]
db.session.query(DocumentSegment).filter(
DocumentSegment.document_id == dataset_document.id,
DocumentSegment.dataset_id == dataset.id,
DocumentSegment.index_node_id.in_(document_ids),
DocumentSegment.status == "indexing"
).update({

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,17 @@
import logging
from core.model_runtime.model_providers.__base.model_provider import ModelProvider
logger = logging.getLogger(__name__)
class AzureAIStudioProvider(ModelProvider):
def validate_provider_credentials(self, credentials: dict) -> None:
"""
Validate provider credentials
if validate failed, raise exception
:param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
"""
pass

View File

@@ -0,0 +1,65 @@
provider: azure_ai_studio
label:
zh_Hans: Azure AI Studio
en_US: Azure AI Studio
icon_small:
en_US: icon_s_en.png
icon_large:
en_US: icon_l_en.png
description:
en_US: Azure AI Studio
zh_Hans: Azure AI Studio
background: "#93c5fd"
help:
title:
en_US: How to deploy customized model on Azure AI Studio
zh_Hans: 如何在Azure AI Studio上的私有化部署的模型
url:
en_US: https://learn.microsoft.com/en-us/azure/ai-studio/how-to/deploy-models
zh_Hans: https://learn.microsoft.com/zh-cn/azure/ai-studio/how-to/deploy-models
supported_model_types:
- llm
- rerank
configurate_methods:
- customizable-model
model_credential_schema:
model:
label:
en_US: Model Name
zh_Hans: 模型名称
placeholder:
en_US: Enter your model name
zh_Hans: 输入模型名称
credential_form_schemas:
- variable: endpoint
label:
en_US: Azure AI Studio Endpoint
type: text-input
required: true
placeholder:
zh_Hans: 请输入你的Azure AI Studio推理端点
en_US: 'Enter your API Endpoint, eg: https://example.com'
- variable: api_key
required: true
label:
en_US: API Key
zh_Hans: API Key
type: secret-input
placeholder:
en_US: Enter your Azure AI Studio API Key
zh_Hans: 在此输入您的 Azure AI Studio API Key
show_on:
- variable: __model_type
value: llm
- variable: jwt_token
required: true
label:
en_US: JWT Token
zh_Hans: JWT令牌
type: secret-input
placeholder:
en_US: Enter your Azure AI Studio JWT Token
zh_Hans: 在此输入您的 Azure AI Studio 推理 API Key
show_on:
- variable: __model_type
value: rerank

View File

@@ -0,0 +1,334 @@
import logging
from collections.abc import Generator
from typing import Any, Optional, Union
from azure.ai.inference import ChatCompletionsClient
from azure.ai.inference.models import StreamingChatCompletionsUpdate
from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import (
ClientAuthenticationError,
DecodeError,
DeserializationError,
HttpResponseError,
ResourceExistsError,
ResourceModifiedError,
ResourceNotFoundError,
ResourceNotModifiedError,
SerializationError,
ServiceRequestError,
ServiceResponseError,
)
from core.model_runtime.callbacks.base_callback import Callback
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
PromptMessage,
PromptMessageTool,
)
from core.model_runtime.entities.model_entities import (
AIModelEntity,
FetchFrom,
I18nObject,
ModelType,
ParameterRule,
ParameterType,
)
from core.model_runtime.errors.invoke import (
InvokeAuthorizationError,
InvokeBadRequestError,
InvokeConnectionError,
InvokeError,
InvokeServerUnavailableError,
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
logger = logging.getLogger(__name__)
class AzureAIStudioLargeLanguageModel(LargeLanguageModel):
"""
Model class for Azure AI Studio large language model.
"""
client: Any = None
from azure.ai.inference.models import StreamingChatCompletionsUpdate
def _invoke(
self,
model: str,
credentials: dict,
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stream: bool = True,
user: Optional[str] = None,
) -> Union[LLMResult, Generator]:
"""
Invoke large language model
:param model: model name
:param credentials: model credentials
:param prompt_messages: prompt messages
:param model_parameters: model parameters
:param tools: tools for tool calling
:param stop: stop words
:param stream: is stream response
:param user: unique user id
:return: full response or stream response chunk generator result
"""
if not self.client:
endpoint = credentials.get("endpoint")
api_key = credentials.get("api_key")
self.client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(api_key))
messages = [{"role": msg.role.value, "content": msg.content} for msg in prompt_messages]
payload = {
"messages": messages,
"max_tokens": model_parameters.get("max_tokens", 4096),
"temperature": model_parameters.get("temperature", 0),
"top_p": model_parameters.get("top_p", 1),
"stream": stream,
}
if stop:
payload["stop"] = stop
if tools:
payload["tools"] = [tool.model_dump() for tool in tools]
try:
response = self.client.complete(**payload)
if stream:
return self._handle_stream_response(response, model, prompt_messages)
else:
return self._handle_non_stream_response(response, model, prompt_messages, credentials)
except Exception as e:
raise self._transform_invoke_error(e)
def _handle_stream_response(self, response, model: str, prompt_messages: list[PromptMessage]) -> Generator:
for chunk in response:
if isinstance(chunk, StreamingChatCompletionsUpdate):
if chunk.choices:
delta = chunk.choices[0].delta
if delta.content:
yield LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=0,
message=AssistantPromptMessage(content=delta.content, tool_calls=[]),
),
)
def _handle_non_stream_response(
self, response, model: str, prompt_messages: list[PromptMessage], credentials: dict
) -> LLMResult:
assistant_text = response.choices[0].message.content
assistant_prompt_message = AssistantPromptMessage(content=assistant_text)
usage = self._calc_response_usage(
model, credentials, response.usage.prompt_tokens, response.usage.completion_tokens
)
result = LLMResult(model=model, prompt_messages=prompt_messages, message=assistant_prompt_message, usage=usage)
if hasattr(response, "system_fingerprint"):
result.system_fingerprint = response.system_fingerprint
return result
def _invoke_result_generator(
self,
model: str,
result: Generator,
credentials: dict,
prompt_messages: list[PromptMessage],
model_parameters: dict,
tools: Optional[list[PromptMessageTool]] = None,
stop: Optional[list[str]] = None,
stream: bool = True,
user: Optional[str] = None,
callbacks: Optional[list[Callback]] = None,
) -> Generator:
"""
Invoke result generator
:param result: result generator
:return: result generator
"""
callbacks = callbacks or []
prompt_message = AssistantPromptMessage(content="")
usage = None
system_fingerprint = None
real_model = model
try:
for chunk in result:
if isinstance(chunk, dict):
content = chunk["choices"][0]["message"]["content"]
usage = chunk["usage"]
chunk = LLMResultChunk(
model=model,
prompt_messages=prompt_messages,
delta=LLMResultChunkDelta(
index=0,
message=AssistantPromptMessage(content=content, tool_calls=[]),
),
system_fingerprint=chunk.get("system_fingerprint"),
)
yield chunk
self._trigger_new_chunk_callbacks(
chunk=chunk,
model=model,
credentials=credentials,
prompt_messages=prompt_messages,
model_parameters=model_parameters,
tools=tools,
stop=stop,
stream=stream,
user=user,
callbacks=callbacks,
)
prompt_message.content += chunk.delta.message.content
real_model = chunk.model
if hasattr(chunk.delta, "usage"):
usage = chunk.delta.usage
if chunk.system_fingerprint:
system_fingerprint = chunk.system_fingerprint
except Exception as e:
raise self._transform_invoke_error(e)
self._trigger_after_invoke_callbacks(
model=model,
result=LLMResult(
model=real_model,
prompt_messages=prompt_messages,
message=prompt_message,
usage=usage if usage else LLMUsage.empty_usage(),
system_fingerprint=system_fingerprint,
),
credentials=credentials,
prompt_messages=prompt_messages,
model_parameters=model_parameters,
tools=tools,
stop=stop,
stream=stream,
user=user,
callbacks=callbacks,
)
def get_num_tokens(
self,
model: str,
credentials: dict,
prompt_messages: list[PromptMessage],
tools: Optional[list[PromptMessageTool]] = None,
) -> int:
"""
Get number of tokens for given prompt messages
:param model: model name
:param credentials: model credentials
:param prompt_messages: prompt messages
:param tools: tools for tool calling
:return:
"""
# Implement token counting logic here
# Might need to use a tokenizer specific to the Azure AI Studio model
return 0
def validate_credentials(self, model: str, credentials: dict) -> None:
"""
Validate model credentials
:param model: model name
:param credentials: model credentials
:return:
"""
try:
endpoint = credentials.get("endpoint")
api_key = credentials.get("api_key")
client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(api_key))
client.get_model_info()
except Exception as ex:
raise CredentialsValidateFailedError(str(ex))
@property
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
"""
Map model invoke error to unified error
The key is the error type thrown to the caller
The value is the error type thrown by the model,
which needs to be converted into a unified error type for the caller.
:return: Invoke error mapping
"""
return {
InvokeConnectionError: [
ServiceRequestError,
],
InvokeServerUnavailableError: [
ServiceResponseError,
],
InvokeAuthorizationError: [
ClientAuthenticationError,
],
InvokeBadRequestError: [
HttpResponseError,
DecodeError,
ResourceExistsError,
ResourceNotFoundError,
ResourceModifiedError,
ResourceNotModifiedError,
SerializationError,
DeserializationError,
],
}
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
"""
Used to define customizable model schema
"""
rules = [
ParameterRule(
name="temperature",
type=ParameterType.FLOAT,
use_template="temperature",
label=I18nObject(zh_Hans="温度", en_US="Temperature"),
),
ParameterRule(
name="top_p",
type=ParameterType.FLOAT,
use_template="top_p",
label=I18nObject(zh_Hans="Top P", en_US="Top P"),
),
ParameterRule(
name="max_tokens",
type=ParameterType.INT,
use_template="max_tokens",
min=1,
default=512,
label=I18nObject(zh_Hans="最大生成长度", en_US="Max Tokens"),
),
]
entity = AIModelEntity(
model=model,
label=I18nObject(en_US=model),
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
model_type=ModelType.LLM,
features=[],
model_properties={},
parameter_rules=rules,
)
return entity

View File

@@ -0,0 +1,164 @@
import json
import logging
import os
import ssl
import urllib.request
from typing import Optional
from core.model_runtime.entities.common_entities import I18nObject
from core.model_runtime.entities.model_entities import AIModelEntity, FetchFrom, ModelType
from core.model_runtime.entities.rerank_entities import RerankDocument, RerankResult
from core.model_runtime.errors.invoke import (
InvokeAuthorizationError,
InvokeBadRequestError,
InvokeConnectionError,
InvokeError,
InvokeRateLimitError,
InvokeServerUnavailableError,
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.__base.rerank_model import RerankModel
logger = logging.getLogger(__name__)
class AzureRerankModel(RerankModel):
"""
Model class for Azure AI Studio rerank model.
"""
def _allow_self_signed_https(self, allowed):
# bypass the server certificate verification on client side
if allowed and not os.environ.get("PYTHONHTTPSVERIFY", "") and getattr(ssl, "_create_unverified_context", None):
ssl._create_default_https_context = ssl._create_unverified_context
def _azure_rerank(self, query_input: str, docs: list[str], endpoint: str, api_key: str):
# self._allow_self_signed_https(True) # Enable if using self-signed certificate
data = {"inputs": query_input, "docs": docs}
body = json.dumps(data).encode("utf-8")
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
req = urllib.request.Request(endpoint, body, headers)
try:
with urllib.request.urlopen(req) as response:
result = response.read()
return json.loads(result)
except urllib.error.HTTPError as error:
logger.error(f"The request failed with status code: {error.code}")
logger.error(error.info())
logger.error(error.read().decode("utf8", "ignore"))
raise
def _invoke(
self,
model: str,
credentials: dict,
query: str,
docs: list[str],
score_threshold: Optional[float] = None,
top_n: Optional[int] = None,
user: Optional[str] = None,
) -> RerankResult:
"""
Invoke rerank model
:param model: model name
:param credentials: model credentials
:param query: search query
:param docs: docs for reranking
:param score_threshold: score threshold
:param top_n: top n
:param user: unique user id
:return: rerank result
"""
try:
if len(docs) == 0:
return RerankResult(model=model, docs=[])
endpoint = credentials.get("endpoint")
api_key = credentials.get("jwt_token")
if not endpoint or not api_key:
raise ValueError("Azure endpoint and API key must be provided in credentials")
result = self._azure_rerank(query, docs, endpoint, api_key)
logger.info(f"Azure rerank result: {result}")
rerank_documents = []
for idx, (doc, score_dict) in enumerate(zip(docs, result)):
score = score_dict["score"]
rerank_document = RerankDocument(index=idx, text=doc, score=score)
if score_threshold is None or score >= score_threshold:
rerank_documents.append(rerank_document)
rerank_documents.sort(key=lambda x: x.score, reverse=True)
if top_n:
rerank_documents = rerank_documents[:top_n]
return RerankResult(model=model, docs=rerank_documents)
except Exception as e:
logger.exception(f"Exception in Azure rerank: {e}")
raise
def validate_credentials(self, model: str, credentials: dict) -> None:
"""
Validate model credentials
:param model: model name
:param credentials: model credentials
:return:
"""
try:
self._invoke(
model=model,
credentials=credentials,
query="What is the capital of the United States?",
docs=[
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
"Census, Carson City had a population of 55,274.",
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
"are a political division controlled by the United States. Its capital is Saipan.",
],
score_threshold=0.8,
)
except Exception as ex:
raise CredentialsValidateFailedError(str(ex))
@property
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
"""
Map model invoke error to unified error
The key is the error type thrown to the caller
The value is the error type thrown by the model,
which needs to be converted into a unified error type for the caller.
:return: Invoke error mapping
"""
return {
InvokeConnectionError: [urllib.error.URLError],
InvokeServerUnavailableError: [urllib.error.HTTPError],
InvokeRateLimitError: [InvokeRateLimitError],
InvokeAuthorizationError: [InvokeAuthorizationError],
InvokeBadRequestError: [InvokeBadRequestError, KeyError, ValueError, json.JSONDecodeError],
}
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
"""
used to define customizable model schema
"""
entity = AIModelEntity(
model=model,
label=I18nObject(en_US=model),
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
model_type=ModelType.RERANK,
model_properties={},
parameter_rules=[],
)
return entity

View File

@@ -137,9 +137,19 @@ class TongyiTextEmbeddingModel(_CommonTongyi, TextEmbeddingModel):
input=text,
text_type="document",
)
data = response.output["embeddings"][0]
embeddings.append(data["embedding"])
embedding_used_tokens += response.usage["total_tokens"]
if response.output and "embeddings" in response.output and response.output["embeddings"]:
data = response.output["embeddings"][0]
if "embedding" in data:
embeddings.append(data["embedding"])
else:
raise ValueError("Embedding data is missing in the response.")
else:
raise ValueError("Response output is missing or does not contain embeddings.")
if response.usage and "total_tokens" in response.usage:
embedding_used_tokens += response.usage["total_tokens"]
else:
raise ValueError("Response usage is missing or does not contain total tokens.")
return [list(map(float, e)) for e in embeddings], embedding_used_tokens

View File

@@ -32,6 +32,9 @@ from core.model_runtime.entities.message_entities import (
UserPromptMessage,
)
DEFAULT_V2_ENDPOINT = "maas-api.ml-platform-cn-beijing.volces.com"
DEFAULT_V3_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3"
class ArkClientV3:
endpoint_id: Optional[str] = None
@@ -43,16 +46,24 @@ class ArkClientV3:
@staticmethod
def is_legacy(credentials: dict) -> bool:
# match default v2 endpoint
if ArkClientV3.is_compatible_with_legacy(credentials):
return False
sdk_version = credentials.get("sdk_version", "v2")
return sdk_version != "v3"
# match default v3 endpoint
if credentials.get("api_endpoint_host") == DEFAULT_V3_ENDPOINT:
return False
# only v3 support api_key
if credentials.get("auth_method") == "api_key":
return False
# these cases are considered as sdk v2
# - modified default v2 endpoint
# - modified default v3 endpoint and auth without api_key
return True
@staticmethod
def is_compatible_with_legacy(credentials: dict) -> bool:
sdk_version = credentials.get("sdk_version")
endpoint = credentials.get("api_endpoint_host")
return sdk_version is None and endpoint == "maas-api.ml-platform-cn-beijing.volces.com"
return endpoint == DEFAULT_V2_ENDPOINT
@classmethod
def from_credentials(cls, credentials):
@@ -64,7 +75,7 @@ class ArkClientV3:
"sk": credentials['volc_secret_access_key'],
}
if cls.is_compatible_with_legacy(credentials):
args["base_url"] = "https://ark.cn-beijing.volces.com/api/v3"
args["base_url"] = DEFAULT_V3_ENDPOINT
client = ArkClientV3(
**args

View File

@@ -38,7 +38,7 @@ configs: dict[str, ModelConfig] = {
),
'Doubao-lite-128k': ModelConfig(
properties=ModelProperties(context_size=131072, max_tokens=4096, mode=LLMMode.CHAT),
features=[ModelFeature.TOOL_CALL]
features=[]
),
'Skylark2-pro-4k': ModelConfig(
properties=ModelProperties(context_size=4096, max_tokens=4096, mode=LLMMode.CHAT),
@@ -54,23 +54,23 @@ configs: dict[str, ModelConfig] = {
),
'Moonshot-v1-8k': ModelConfig(
properties=ModelProperties(context_size=8192, max_tokens=4096, mode=LLMMode.CHAT),
features=[]
features=[ModelFeature.TOOL_CALL]
),
'Moonshot-v1-32k': ModelConfig(
properties=ModelProperties(context_size=32768, max_tokens=16384, mode=LLMMode.CHAT),
features=[]
features=[ModelFeature.TOOL_CALL]
),
'Moonshot-v1-128k': ModelConfig(
properties=ModelProperties(context_size=131072, max_tokens=65536, mode=LLMMode.CHAT),
features=[]
features=[ModelFeature.TOOL_CALL]
),
'GLM3-130B': ModelConfig(
properties=ModelProperties(context_size=8192, max_tokens=4096, mode=LLMMode.CHAT),
features=[]
features=[ModelFeature.TOOL_CALL]
),
'GLM3-130B-Fin': ModelConfig(
properties=ModelProperties(context_size=8192, max_tokens=4096, mode=LLMMode.CHAT),
features=[]
features=[ModelFeature.TOOL_CALL]
),
'Mistral-7B': ModelConfig(
properties=ModelProperties(context_size=8192, max_tokens=2048, mode=LLMMode.CHAT),

View File

@@ -64,7 +64,7 @@ model_credential_schema:
en_US: API Endpoint Host
zh_Hans: API Endpoint Host
type: text-input
default: maas-api.ml-platform-cn-beijing.volces.com
default: https://ark.cn-beijing.volces.com/api/v3
placeholder:
en_US: Enter your API Endpoint Host
zh_Hans: 输入 API Endpoint Host

View File

@@ -21,7 +21,6 @@ class LangfuseConfig(BaseTracingConfig):
"""
public_key: str
secret_key: str
project_key: str
host: str = 'https://api.langfuse.com'
@field_validator("host")

View File

@@ -1,5 +1,7 @@
import json
from typing import Any
import logging
from typing import Any, Optional
from urllib.parse import urlparse
import requests
from elasticsearch import Elasticsearch
@@ -7,16 +9,20 @@ from flask import current_app
from pydantic import BaseModel, model_validator
from core.rag.datasource.entity.embedding import Embeddings
from core.rag.datasource.vdb.field import Field
from core.rag.datasource.vdb.vector_base import BaseVector
from core.rag.datasource.vdb.vector_factory import AbstractVectorFactory
from core.rag.datasource.vdb.vector_type import VectorType
from core.rag.models.document import Document
from extensions.ext_redis import redis_client
from models.dataset import Dataset
logger = logging.getLogger(__name__)
class ElasticSearchConfig(BaseModel):
host: str
port: str
port: int
username: str
password: str
@@ -37,12 +43,19 @@ class ElasticSearchVector(BaseVector):
def __init__(self, index_name: str, config: ElasticSearchConfig, attributes: list):
super().__init__(index_name.lower())
self._client = self._init_client(config)
self._version = self._get_version()
self._check_version()
self._attributes = attributes
def _init_client(self, config: ElasticSearchConfig) -> Elasticsearch:
try:
parsed_url = urlparse(config.host)
if parsed_url.scheme in ['http', 'https']:
hosts = f'{config.host}:{config.port}'
else:
hosts = f'http://{config.host}:{config.port}'
client = Elasticsearch(
hosts=f'{config.host}:{config.port}',
hosts=hosts,
basic_auth=(config.username, config.password),
request_timeout=100000,
retry_on_timeout=True,
@@ -53,42 +66,27 @@ class ElasticSearchVector(BaseVector):
return client
def _get_version(self) -> str:
info = self._client.info()
return info['version']['number']
def _check_version(self):
if self._version < '8.0.0':
raise ValueError("Elasticsearch vector database version must be greater than 8.0.0")
def get_type(self) -> str:
return 'elasticsearch'
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
uuids = self._get_uuids(documents)
texts = [d.page_content for d in documents]
metadatas = [d.metadata for d in documents]
if not self._client.indices.exists(index=self._collection_name):
dim = len(embeddings[0])
mapping = {
"properties": {
"text": {
"type": "text"
},
"vector": {
"type": "dense_vector",
"index": True,
"dims": dim,
"similarity": "l2_norm"
},
}
}
self._client.indices.create(index=self._collection_name, mappings=mapping)
added_ids = []
for i, text in enumerate(texts):
for i in range(len(documents)):
self._client.index(index=self._collection_name,
id=uuids[i],
document={
"text": text,
"vector": embeddings[i] if embeddings[i] else None,
"metadata": metadatas[i] if metadatas[i] else {},
Field.CONTENT_KEY.value: documents[i].page_content,
Field.VECTOR.value: embeddings[i] if embeddings[i] else None,
Field.METADATA_KEY.value: documents[i].metadata if documents[i].metadata else {}
})
added_ids.append(uuids[i])
self._client.indices.refresh(index=self._collection_name)
return uuids
@@ -116,28 +114,21 @@ class ElasticSearchVector(BaseVector):
self._client.indices.delete(index=self._collection_name)
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
query_str = {
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'vector') + 1.0",
"params": {
"query_vector": query_vector
}
}
}
}
top_k = kwargs.get("top_k", 10)
knn = {
"field": Field.VECTOR.value,
"query_vector": query_vector,
"k": top_k
}
results = self._client.search(index=self._collection_name, body=query_str)
results = self._client.search(index=self._collection_name, knn=knn, size=top_k)
docs_and_scores = []
for hit in results['hits']['hits']:
docs_and_scores.append(
(Document(page_content=hit['_source']['text'], metadata=hit['_source']['metadata']), hit['_score']))
(Document(page_content=hit['_source'][Field.CONTENT_KEY.value],
vector=hit['_source'][Field.VECTOR.value],
metadata=hit['_source'][Field.METADATA_KEY.value]), hit['_score']))
docs = []
for doc, score in docs_and_scores:
@@ -146,25 +137,61 @@ class ElasticSearchVector(BaseVector):
doc.metadata['score'] = score
docs.append(doc)
# Sort the documents by score in descending order
docs = sorted(docs, key=lambda x: x.metadata['score'], reverse=True)
return docs
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
query_str = {
"match": {
"text": query
Field.CONTENT_KEY.value: query
}
}
results = self._client.search(index=self._collection_name, query=query_str)
docs = []
for hit in results['hits']['hits']:
docs.append(Document(page_content=hit['_source']['text'], metadata=hit['_source']['metadata']))
docs.append(Document(
page_content=hit['_source'][Field.CONTENT_KEY.value],
vector=hit['_source'][Field.VECTOR.value],
metadata=hit['_source'][Field.METADATA_KEY.value],
))
return docs
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs):
return self.add_texts(texts, embeddings, **kwargs)
metadatas = [d.metadata for d in texts]
self.create_collection(embeddings, metadatas)
self.add_texts(texts, embeddings, **kwargs)
def create_collection(
self, embeddings: list, metadatas: Optional[list[dict]] = None, index_params: Optional[dict] = None
):
lock_name = f'vector_indexing_lock_{self._collection_name}'
with redis_client.lock(lock_name, timeout=20):
collection_exist_cache_key = f'vector_indexing_{self._collection_name}'
if redis_client.get(collection_exist_cache_key):
logger.info(f"Collection {self._collection_name} already exists.")
return
if not self._client.indices.exists(index=self._collection_name):
dim = len(embeddings[0])
mappings = {
"properties": {
Field.CONTENT_KEY.value: {"type": "text"},
Field.VECTOR.value: { # Make sure the dimension is correct here
"type": "dense_vector",
"dims": dim,
"similarity": "cosine"
},
Field.METADATA_KEY.value: {
"type": "object",
"properties": {
"doc_id": {"type": "keyword"} # Map doc_id to keyword type
}
}
}
}
self._client.indices.create(index=self._collection_name, mappings=mappings)
redis_client.set(collection_exist_cache_key, 1, ex=3600)
class ElasticSearchVectorFactory(AbstractVectorFactory):

View File

@@ -122,7 +122,7 @@ class MyScaleVector(BaseVector):
def _search(self, dist: str, order: SortOrder, **kwargs: Any) -> list[Document]:
top_k = kwargs.get("top_k", 5)
score_threshold = kwargs.get("score_threshold", 0.0)
score_threshold = kwargs.get('score_threshold') or 0.0
where_str = f"WHERE dist < {1 - score_threshold}" if \
self._metric.upper() == "COSINE" and order == SortOrder.ASC and score_threshold > 0.0 else ""
sql = f"""

View File

@@ -57,7 +57,7 @@ class BaseIndexProcessor(ABC):
character_splitter = FixedRecursiveCharacterTextSplitter.from_encoder(
chunk_size=segmentation["max_tokens"],
chunk_overlap=segmentation.get('chunk_overlap', 0),
chunk_overlap=segmentation.get('chunk_overlap', 0) or 0,
fixed_separator=separator,
separators=["\n\n", "", ". ", " ", ""],
embedding_model_instance=embedding_model_instance

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ def load_yaml_file(file_path: str, ignore_error: bool = True, default_value: Any
raise YAMLError(f'Failed to load YAML file {file_path}: {e}')
except Exception as e:
if ignore_error:
logger.debug(f'Failed to load YAML file {file_path}: {e}')
return default_value
else:
raise e

View File

@@ -88,9 +88,9 @@ class CodeNode(BaseNode):
else:
raise ValueError(f"Output variable `{variable}` must be a string")
if len(value) > dify_config.CODE_MAX_STRING_ARRAY_LENGTH:
if len(value) > dify_config.CODE_MAX_STRING_LENGTH:
raise ValueError(f'The length of output variable `{variable}` must be'
f' less than {dify_config.CODE_MAX_STRING_ARRAY_LENGTH} characters')
f' less than {dify_config.CODE_MAX_STRING_LENGTH} characters')
return value.replace('\x00', '')

View File

@@ -17,6 +17,8 @@ def handle(sender, **kwargs):
default_language=account.interface_language,
customize_token_strategy="not_allow",
code=Site.generate_code(16),
created_by=app.created_by,
updated_by=app.updated_by,
)
db.session.add(site)

View File

@@ -35,6 +35,9 @@ class S3Storage(BaseStorage):
# if bucket not exists, create it
if e.response["Error"]["Code"] == "404":
self.client.create_bucket(Bucket=self.bucket_name)
# if bucket is not accessible, pass, maybe the bucket is existing but not accessible
elif e.response["Error"]["Code"] == "403":
pass
else:
# other error, raise exception
raise

View File

@@ -1,5 +1,6 @@
from flask_restful import fields
from fields.workflow_fields import workflow_partial_fields
from libs.helper import AppIconUrlField, TimestampField
app_detail_kernel_fields = {
@@ -39,7 +40,10 @@ model_config_fields = {
"completion_prompt_config": fields.Raw(attribute="completion_prompt_config_dict"),
"dataset_configs": fields.Raw(attribute="dataset_configs_dict"),
"file_upload": fields.Raw(attribute="file_upload_dict"),
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
}
app_detail_fields = {
@@ -52,8 +56,12 @@ app_detail_fields = {
"enable_site": fields.Boolean,
"enable_api": fields.Boolean,
"model_config": fields.Nested(model_config_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"tracing": fields.Raw,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
}
prompt_config_fields = {
@@ -63,6 +71,10 @@ prompt_config_fields = {
model_config_partial_fields = {
"model": fields.Raw(attribute="model_dict"),
"pre_prompt": fields.String,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
}
tag_fields = {"id": fields.String, "name": fields.String, "type": fields.String}
@@ -78,7 +90,11 @@ app_partial_fields = {
"icon_background": fields.String,
"icon_url": AppIconUrlField,
"model_config": fields.Nested(model_config_partial_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
"tags": fields.List(fields.Nested(tag_fields)),
}
@@ -124,6 +140,10 @@ site_fields = {
"prompt_public": fields.Boolean,
"app_base_url": fields.String,
"show_workflow_steps": fields.Boolean,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
}
app_detail_fields_with_site = {
@@ -138,9 +158,13 @@ app_detail_fields_with_site = {
"enable_site": fields.Boolean,
"enable_api": fields.Boolean,
"model_config": fields.Nested(model_config_fields, attribute="app_model_config", allow_null=True),
"workflow": fields.Nested(workflow_partial_fields, allow_null=True),
"site": fields.Nested(site_fields),
"api_base_url": fields.String,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
"deleted_tools": fields.List(fields.String),
}

View File

@@ -111,6 +111,7 @@ conversation_fields = {
"from_end_user_id": fields.String,
"from_end_user_session_id": fields.String(),
"from_account_id": fields.String,
"from_account_name": fields.String,
"read_at": TimestampField,
"created_at": TimestampField,
"annotation": fields.Nested(annotation_fields, allow_null=True),
@@ -146,6 +147,7 @@ conversation_with_summary_fields = {
"from_end_user_id": fields.String,
"from_end_user_session_id": fields.String,
"from_account_id": fields.String,
"from_account_name": fields.String,
"name": fields.String,
"summary": fields.String(attribute="summary_or_query"),
"read_at": TimestampField,

View File

@@ -53,3 +53,11 @@ workflow_fields = {
"environment_variables": fields.List(EnvironmentVariableField()),
"conversation_variables": fields.List(fields.Nested(conversation_variable_fields)),
}
workflow_partial_fields = {
"id": fields.String,
"created_by": fields.String,
"created_at": TimestampField,
"updated_by": fields.String,
"updated_at": TimestampField,
}

View File

@@ -0,0 +1,52 @@
"""add created_by and updated_by to app, modelconfig, and site
Revision ID: d0187d6a88dd
Revises: 2dbe42621d96
Create Date: 2024-08-25 04:41:18.157397
"""
import sqlalchemy as sa
from alembic import op
import models as models
# revision identifiers, used by Alembic.
revision = "d0187d6a88dd"
down_revision = "2dbe42621d96"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("app_model_configs", schema=None) as batch_op:
batch_op.add_column(sa.Column("created_by", models.types.StringUUID(), nullable=True))
batch_op.add_column(sa.Column("updated_by", models.types.StringUUID(), nullable=True))
with op.batch_alter_table("apps", schema=None) as batch_op:
batch_op.add_column(sa.Column("created_by", models.types.StringUUID(), nullable=True))
batch_op.add_column(sa.Column("updated_by", models.types.StringUUID(), nullable=True))
with op.batch_alter_table("sites", schema=None) as batch_op:
batch_op.add_column(sa.Column("created_by", models.types.StringUUID(), nullable=True))
batch_op.add_column(sa.Column("updated_by", models.types.StringUUID(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("sites", schema=None) as batch_op:
batch_op.drop_column("updated_by")
batch_op.drop_column("created_by")
with op.batch_alter_table("apps", schema=None) as batch_op:
batch_op.drop_column("updated_by")
batch_op.drop_column("created_by")
with op.batch_alter_table("app_model_configs", schema=None) as batch_op:
batch_op.drop_column("updated_by")
batch_op.drop_column("created_by")
# ### end Alembic commands ###

View File

@@ -82,7 +82,9 @@ class App(db.Model):
is_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
tracing = db.Column(db.Text, nullable=True)
max_active_requests = db.Column(db.Integer, nullable=True)
created_by = db.Column(StringUUID, nullable=True)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
updated_by = db.Column(StringUUID, nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
@property
@@ -221,7 +223,9 @@ class AppModelConfig(db.Model):
provider = db.Column(db.String(255), nullable=True)
model_id = db.Column(db.String(255), nullable=True)
configs = db.Column(db.JSON, nullable=True)
created_by = db.Column(StringUUID, nullable=True)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
updated_by = db.Column(StringUUID, nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
opening_statement = db.Column(db.Text)
suggested_questions = db.Column(db.Text)
@@ -490,7 +494,6 @@ class InstalledApp(db.Model):
return tenant
class Conversation(db.Model):
__tablename__ = 'conversations'
__table_args__ = (
@@ -623,6 +626,15 @@ class Conversation(db.Model):
return None
@property
def from_account_name(self):
if self.from_account_id:
account = db.session.query(Account).filter(Account.id == self.from_account_id).first()
if account:
return account.name
return None
@property
def in_debug_mode(self):
return self.override_model_configs is not None
@@ -1107,7 +1119,9 @@ class Site(db.Model):
customize_token_strategy = db.Column(db.String(255), nullable=False)
prompt_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false'))
status = db.Column(db.String(255), nullable=False, server_default=db.text("'normal'::character varying"))
created_by = db.Column(StringUUID, nullable=True)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
updated_by = db.Column(StringUUID, nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
code = db.Column(db.String(255))

406
api/poetry.lock generated
View File

@@ -551,6 +551,69 @@ files = [
[package.dependencies]
cryptography = "*"
[[package]]
name = "azure-ai-inference"
version = "1.0.0b3"
description = "Microsoft Azure Ai Inference Client Library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "azure-ai-inference-1.0.0b3.tar.gz", hash = "sha256:1e99dc74c3b335a457500311bbbadb348f54dc4c12252a93cb8ab78d6d217ff0"},
{file = "azure_ai_inference-1.0.0b3-py3-none-any.whl", hash = "sha256:6734ca7334c809a170beb767f1f1455724ab3f006cb60045e42a833c0e764403"},
]
[package.dependencies]
azure-core = ">=1.30.0"
isodate = ">=0.6.1"
typing-extensions = ">=4.6.0"
[[package]]
name = "azure-ai-ml"
version = "1.19.0"
description = "Microsoft Azure Machine Learning Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "azure-ai-ml-1.19.0.tar.gz", hash = "sha256:94bb1afbb0497e539ae75455fc4a51b6942b5b68b3a275727ecce6ceb250eff9"},
{file = "azure_ai_ml-1.19.0-py3-none-any.whl", hash = "sha256:f0385af06efbeae1f83113613e45343508d1288fd2f05857619e7c7d4d4f5302"},
]
[package.dependencies]
azure-common = ">=1.1"
azure-core = ">=1.23.0"
azure-mgmt-core = ">=1.3.0"
azure-storage-blob = ">=12.10.0"
azure-storage-file-datalake = ">=12.2.0"
azure-storage-file-share = "*"
colorama = "*"
isodate = "*"
jsonschema = ">=4.0.0"
marshmallow = ">=3.5"
msrest = ">=0.6.18"
opencensus-ext-azure = "*"
opencensus-ext-logging = "*"
pydash = ">=6.0.0"
pyjwt = "*"
pyyaml = ">=5.1.0"
strictyaml = "*"
tqdm = "*"
typing-extensions = "*"
[package.extras]
designer = ["mldesigner"]
mount = ["azureml-dataprep-rslex (>=2.22.0)"]
[[package]]
name = "azure-common"
version = "1.1.28"
description = "Microsoft Azure Client Library for Python (Common)"
optional = false
python-versions = "*"
files = [
{file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"},
{file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"},
]
[[package]]
name = "azure-core"
version = "1.30.2"
@@ -587,6 +650,20 @@ cryptography = ">=2.5"
msal = ">=1.24.0"
msal-extensions = ">=0.3.0"
[[package]]
name = "azure-mgmt-core"
version = "1.4.0"
description = "Microsoft Azure Management Core Library for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "azure-mgmt-core-1.4.0.zip", hash = "sha256:d195208340094f98e5a6661b781cde6f6a051e79ce317caabd8ff97030a9b3ae"},
{file = "azure_mgmt_core-1.4.0-py3-none-any.whl", hash = "sha256:81071675f186a585555ef01816f2774d49c1c9024cb76e5720c3c0f6b337bb7d"},
]
[package.dependencies]
azure-core = ">=1.26.2,<2.0.0"
[[package]]
name = "azure-storage-blob"
version = "12.13.0"
@@ -603,6 +680,42 @@ azure-core = ">=1.23.1,<2.0.0"
cryptography = ">=2.1.4"
msrest = ">=0.6.21"
[[package]]
name = "azure-storage-file-datalake"
version = "12.8.0"
description = "Microsoft Azure File DataLake Storage Client Library for Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "azure-storage-file-datalake-12.8.0.zip", hash = "sha256:12e6306e5efb5ca28e0ccd9fa79a2c61acd589866d6109fe5601b18509da92f4"},
{file = "azure_storage_file_datalake-12.8.0-py3-none-any.whl", hash = "sha256:b6cf5733fe794bf3c866efbe3ce1941409e35b6b125028ac558b436bf90f2de7"},
]
[package.dependencies]
azure-core = ">=1.23.1,<2.0.0"
azure-storage-blob = ">=12.13.0,<13.0.0"
msrest = ">=0.6.21"
[[package]]
name = "azure-storage-file-share"
version = "12.17.0"
description = "Microsoft Azure Azure File Share Storage Client Library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "azure-storage-file-share-12.17.0.tar.gz", hash = "sha256:f7b2c6cfc1b7cb80097a53b1ed2efa9e545b49a291430d369cdb49fafbc841d6"},
{file = "azure_storage_file_share-12.17.0-py3-none-any.whl", hash = "sha256:c4652759a9d529bf08881bb53275bf38774bb643746b849d27c47118f9cf923d"},
]
[package.dependencies]
azure-core = ">=1.28.0"
cryptography = ">=2.1.4"
isodate = ">=0.6.1"
typing-extensions = ">=4.6.0"
[package.extras]
aio = ["azure-core[aio] (>=1.28.0)"]
[[package]]
name = "backoff"
version = "2.2.1"
@@ -3952,6 +4065,41 @@ files = [
[package.dependencies]
ply = "*"
[[package]]
name = "jsonschema"
version = "4.23.0"
description = "An implementation of JSON Schema validation for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"},
{file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"},
]
[package.dependencies]
attrs = ">=22.2.0"
jsonschema-specifications = ">=2023.03.6"
referencing = ">=0.28.4"
rpds-py = ">=0.7.1"
[package.extras]
format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
[[package]]
name = "jsonschema-specifications"
version = "2023.12.1"
description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
optional = false
python-versions = ">=3.8"
files = [
{file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"},
{file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"},
]
[package.dependencies]
referencing = ">=0.31.0"
[[package]]
name = "kaleido"
version = "0.2.1"
@@ -5277,6 +5425,65 @@ typing-extensions = ">=4.7,<5"
[package.extras]
datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
[[package]]
name = "opencensus"
version = "0.11.4"
description = "A stats collection and distributed tracing framework"
optional = false
python-versions = "*"
files = [
{file = "opencensus-0.11.4-py2.py3-none-any.whl", hash = "sha256:a18487ce68bc19900336e0ff4655c5a116daf10c1b3685ece8d971bddad6a864"},
{file = "opencensus-0.11.4.tar.gz", hash = "sha256:cbef87d8b8773064ab60e5c2a1ced58bbaa38a6d052c41aec224958ce544eff2"},
]
[package.dependencies]
google-api-core = {version = ">=1.0.0,<3.0.0", markers = "python_version >= \"3.6\""}
opencensus-context = ">=0.1.3"
six = ">=1.16,<2.0"
[[package]]
name = "opencensus-context"
version = "0.1.3"
description = "OpenCensus Runtime Context"
optional = false
python-versions = "*"
files = [
{file = "opencensus-context-0.1.3.tar.gz", hash = "sha256:a03108c3c10d8c80bb5ddf5c8a1f033161fa61972a9917f9b9b3a18517f0088c"},
{file = "opencensus_context-0.1.3-py2.py3-none-any.whl", hash = "sha256:073bb0590007af276853009fac7e4bab1d523c3f03baf4cb4511ca38967c6039"},
]
[[package]]
name = "opencensus-ext-azure"
version = "1.1.13"
description = "OpenCensus Azure Monitor Exporter"
optional = false
python-versions = "*"
files = [
{file = "opencensus-ext-azure-1.1.13.tar.gz", hash = "sha256:aec30472177005379ba56a702a097d618c5f57558e1bb6676ec75f948130692a"},
{file = "opencensus_ext_azure-1.1.13-py2.py3-none-any.whl", hash = "sha256:06001fac6f8588ba00726a3a7c6c7f2fc88bc8ad12a65afdca657923085393dd"},
]
[package.dependencies]
azure-core = ">=1.12.0,<2.0.0"
azure-identity = ">=1.5.0,<2.0.0"
opencensus = ">=0.11.4,<1.0.0"
psutil = ">=5.6.3"
requests = ">=2.19.0"
[[package]]
name = "opencensus-ext-logging"
version = "0.1.1"
description = "OpenCensus logging Integration"
optional = false
python-versions = "*"
files = [
{file = "opencensus-ext-logging-0.1.1.tar.gz", hash = "sha256:c203b70f034151dada529f543af330ba17aaffec27d8a5267d03c713eb1de334"},
{file = "opencensus_ext_logging-0.1.1-py2.py3-none-any.whl", hash = "sha256:cfdaf5da5d8b195ff3d1af87a4066a6621a28046173f6be4b0b6caec4a3ca89f"},
]
[package.dependencies]
opencensus = ">=0.8.0,<1.0.0"
[[package]]
name = "openpyxl"
version = "3.1.5"
@@ -6021,6 +6228,35 @@ files = [
{file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"},
]
[[package]]
name = "psutil"
version = "6.0.0"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
{file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
{file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
{file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
{file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
{file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
{file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
{file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
{file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
{file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
{file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
]
[package.extras]
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
[[package]]
name = "psycopg2-binary"
version = "2.9.9"
@@ -6403,6 +6639,23 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0
toml = ["tomli (>=2.0.1)"]
yaml = ["pyyaml (>=6.0.1)"]
[[package]]
name = "pydash"
version = "8.0.3"
description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library."
optional = false
python-versions = ">=3.8"
files = [
{file = "pydash-8.0.3-py3-none-any.whl", hash = "sha256:c16871476822ee6b59b87e206dd27888240eff50a7b4cd72a4b80b43b6b994d7"},
{file = "pydash-8.0.3.tar.gz", hash = "sha256:1b27cd3da05b72f0e5ff786c523afd82af796936462e631ffd1b228d91f8b9aa"},
]
[package.dependencies]
typing-extensions = ">3.10,<4.6.0 || >4.6.0"
[package.extras]
dev = ["build", "coverage", "furo", "invoke", "mypy", "pytest", "pytest-cov", "pytest-mypy-testing", "ruff", "sphinx", "sphinx-autodoc-typehints", "tox", "twine", "wheel"]
[[package]]
name = "pygments"
version = "2.18.0"
@@ -6568,13 +6821,13 @@ files = [
[[package]]
name = "pytest"
version = "8.1.2"
version = "8.3.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.1.2-py3-none-any.whl", hash = "sha256:6c06dc309ff46a05721e6fd48e492a775ed8165d2ecdf57f156a80c7e95bb142"},
{file = "pytest-8.1.2.tar.gz", hash = "sha256:f3c45d1d5eed96b01a2aea70dee6a4a366d51d38f9957768083e4fecfc77f3ef"},
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
]
[package.dependencies]
@@ -6582,11 +6835,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.4,<2.0"
pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-benchmark"
@@ -7170,6 +7423,21 @@ hiredis = {version = ">1.0.0", optional = true, markers = "extra == \"hiredis\""
hiredis = ["hiredis (>1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
name = "referencing"
version = "0.35.1"
description = "JSON Referencing + Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"},
{file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"},
]
[package.dependencies]
attrs = ">=22.2.0"
rpds-py = ">=0.7.0"
[[package]]
name = "regex"
version = "2024.7.24"
@@ -7377,6 +7645,118 @@ pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "rpds-py"
version = "0.20.0"
description = "Python bindings to Rust's persistent data structures (rpds)"
optional = false
python-versions = ">=3.8"
files = [
{file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"},
{file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"},
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"},
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"},
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"},
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"},
{file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"},
{file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"},
{file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"},
{file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"},
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"},
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"},
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"},
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"},
{file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"},
{file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"},
{file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"},
{file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"},
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"},
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"},
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"},
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"},
{file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"},
{file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"},
{file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"},
{file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"},
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"},
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"},
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"},
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"},
{file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"},
{file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"},
{file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"},
{file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"},
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"},
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"},
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"},
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"},
{file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"},
{file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"},
{file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"},
{file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"},
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"},
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"},
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"},
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"},
{file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"},
{file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"},
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"},
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"},
{file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"},
]
[[package]]
name = "rsa"
version = "4.9"
@@ -7987,6 +8367,20 @@ anyio = ">=3.4.0,<5"
[package.extras]
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
[[package]]
name = "strictyaml"
version = "1.7.3"
description = "Strict, typed YAML parser"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7"},
{file = "strictyaml-1.7.3.tar.gz", hash = "sha256:22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407"},
]
[package.dependencies]
python-dateutil = ">=2.6.0"
[[package]]
name = "sympy"
version = "1.13.2"
@@ -9669,4 +10063,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "04f970820de691f40fc9fb30f5ff0618b0f1a04d3315b14467fb88e475fa1243"
content-hash = "e4c00268514d26bd07c6b72925e0e3b4558ec972895d252e60e9571e3ac38895"

View File

@@ -188,6 +188,8 @@ zhipuai = "1.0.7"
# Related transparent dependencies with pinned verion
# required by main implementations
############################################################
azure-ai-ml = "^1.19.0"
azure-ai-inference = "^1.0.0b3"
volcengine-python-sdk = {extras = ["ark"], version = "^1.0.98"}
[tool.poetry.group.indriect.dependencies]
kaleido = "0.2.1"
@@ -241,7 +243,7 @@ optional = true
[tool.poetry.group.dev.dependencies]
coverage = "~7.2.4"
pytest = "~8.1.1"
pytest = "~8.3.2"
pytest-benchmark = "~4.0.0"
pytest-env = "~1.1.3"
pytest-mock = "~3.14.0"

View File

@@ -346,6 +346,8 @@ class AppDslService:
app_model_config = AppModelConfig()
app_model_config = app_model_config.from_model_config_dict(model_config_data)
app_model_config.app_id = app.id
app_model_config.created_by = account.id
app_model_config.updated_by = account.id
db.session.add(app_model_config)
db.session.commit()
@@ -390,6 +392,8 @@ class AppDslService:
icon_background=icon_background,
enable_site=True,
enable_api=True,
created_by=account.id,
updated_by=account.id,
)
db.session.add(app)

View File

@@ -127,6 +127,8 @@ class AppService:
app.tenant_id = tenant_id
app.api_rph = args.get("api_rph", 0)
app.api_rpm = args.get("api_rpm", 0)
app.created_by = account.id
app.updated_by = account.id
db.session.add(app)
db.session.flush()
@@ -134,6 +136,8 @@ class AppService:
if default_model_config:
app_model_config = AppModelConfig(**default_model_config)
app_model_config.app_id = app.id
app_model_config.created_by = account.id
app_model_config.updated_by = account.id
db.session.add(app_model_config)
db.session.flush()
@@ -217,6 +221,7 @@ class AppService:
app.icon_type = args.get("icon_type", "emoji")
app.icon = args.get("icon")
app.icon_background = args.get("icon_background")
app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
@@ -233,6 +238,7 @@ class AppService:
:return: App instance
"""
app.name = name
app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
@@ -248,6 +254,7 @@ class AppService:
"""
app.icon = icon
app.icon_background = icon_background
app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
@@ -264,6 +271,7 @@ class AppService:
return app
app.enable_site = enable_site
app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
@@ -280,6 +288,7 @@ class AppService:
return app
app.enable_api = enable_api
app.updated_by = current_user.id
app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()

View File

@@ -136,7 +136,9 @@ class DatasetService:
return datasets.items, datasets.total
@staticmethod
def create_empty_dataset(tenant_id: str, name: str, indexing_technique: Optional[str], account: Account):
def create_empty_dataset(
tenant_id: str, name: str, indexing_technique: Optional[str], account: Account, permission: Optional[str]
):
# check if dataset name already exists
if Dataset.query.filter_by(name=name, tenant_id=tenant_id).first():
raise DatasetNameDuplicateError(f"Dataset with name {name} already exists.")
@@ -153,6 +155,7 @@ class DatasetService:
dataset.tenant_id = tenant_id
dataset.embedding_model_provider = embedding_model.provider if embedding_model else None
dataset.embedding_model = embedding_model.model if embedding_model else None
dataset.permission = permission if permission else DatasetPermissionEnum.ONLY_ME
db.session.add(dataset)
db.session.commit()
return dataset

View File

@@ -26,16 +26,15 @@ class OpsService:
decrypt_tracing_config = OpsTraceManager.decrypt_tracing_config(
tenant_id, tracing_provider, trace_config_data.tracing_config
)
new_decrypt_tracing_config = OpsTraceManager.obfuscated_decrypt_token(tracing_provider, decrypt_tracing_config)
if tracing_provider == "langfuse" and (
"project_key" not in decrypt_tracing_config or not decrypt_tracing_config.get("project_key")
):
project_key = OpsTraceManager.get_trace_config_project_key(decrypt_tracing_config, tracing_provider)
decrypt_tracing_config["project_key"] = project_key
decrypt_tracing_config = OpsTraceManager.obfuscated_decrypt_token(tracing_provider, decrypt_tracing_config)
trace_config_data.tracing_config = decrypt_tracing_config
new_decrypt_tracing_config.update({"project_key": project_key})
trace_config_data.tracing_config = new_decrypt_tracing_config
return trace_config_data.to_dict()
@classmethod
@@ -79,7 +78,7 @@ class OpsService:
# get tenant id
tenant_id = db.session.query(App).filter(App.id == app_id).first().tenant_id
tracing_config = OpsTraceManager.encrypt_tracing_config(tenant_id, tracing_provider, tracing_config)
if tracing_provider == "langfuse":
if tracing_provider == "langfuse" and project_key:
tracing_config["project_key"] = project_key
trace_config_data = TraceAppConfig(
app_id=app_id,

View File

@@ -74,6 +74,8 @@ class WorkflowConverter:
new_app.api_rph = app_model.api_rph
new_app.is_demo = False
new_app.is_public = app_model.is_public
new_app.created_by = account.id
new_app.updated_by = account.id
db.session.add(new_app)
db.session.flush()
db.session.commit()

View File

@@ -0,0 +1,113 @@
import os
from collections.abc import Generator
import pytest
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta
from core.model_runtime.entities.message_entities import (
AssistantPromptMessage,
ImagePromptMessageContent,
PromptMessageTool,
SystemPromptMessage,
TextPromptMessageContent,
UserPromptMessage,
)
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.azure_ai_studio.llm.llm import AzureAIStudioLargeLanguageModel
from tests.integration_tests.model_runtime.__mock.azure_ai_studio import setup_azure_ai_studio_mock
@pytest.mark.parametrize("setup_azure_ai_studio_mock", [["chat"]], indirect=True)
def test_validate_credentials(setup_azure_ai_studio_mock):
model = AzureAIStudioLargeLanguageModel()
with pytest.raises(CredentialsValidateFailedError):
model.validate_credentials(
model="gpt-35-turbo",
credentials={"api_key": "invalid_key", "api_base": os.getenv("AZURE_AI_STUDIO_API_BASE")},
)
model.validate_credentials(
model="gpt-35-turbo",
credentials={
"api_key": os.getenv("AZURE_AI_STUDIO_API_KEY"),
"api_base": os.getenv("AZURE_AI_STUDIO_API_BASE"),
},
)
@pytest.mark.parametrize("setup_azure_ai_studio_mock", [["chat"]], indirect=True)
def test_invoke_model(setup_azure_ai_studio_mock):
model = AzureAIStudioLargeLanguageModel()
result = model.invoke(
model="gpt-35-turbo",
credentials={
"api_key": os.getenv("AZURE_AI_STUDIO_API_KEY"),
"api_base": os.getenv("AZURE_AI_STUDIO_API_BASE"),
},
prompt_messages=[
SystemPromptMessage(
content="You are a helpful AI assistant.",
),
UserPromptMessage(content="Hello World!"),
],
model_parameters={"temperature": 0.0, "max_tokens": 100},
stream=False,
user="abc-123",
)
assert isinstance(result, LLMResult)
assert len(result.message.content) > 0
@pytest.mark.parametrize("setup_azure_ai_studio_mock", [["chat"]], indirect=True)
def test_invoke_stream_model(setup_azure_ai_studio_mock):
model = AzureAIStudioLargeLanguageModel()
result = model.invoke(
model="gpt-35-turbo",
credentials={
"api_key": os.getenv("AZURE_AI_STUDIO_API_KEY"),
"api_base": os.getenv("AZURE_AI_STUDIO_API_BASE"),
},
prompt_messages=[
SystemPromptMessage(
content="You are a helpful AI assistant.",
),
UserPromptMessage(content="Hello World!"),
],
model_parameters={"temperature": 0.0, "max_tokens": 100},
stream=True,
user="abc-123",
)
assert isinstance(result, Generator)
for chunk in result:
assert isinstance(chunk, LLMResultChunk)
assert isinstance(chunk.delta, LLMResultChunkDelta)
assert isinstance(chunk.delta.message, AssistantPromptMessage)
if chunk.delta.finish_reason is not None:
assert chunk.delta.usage is not None
assert chunk.delta.usage.completion_tokens > 0
def test_get_num_tokens():
model = AzureAIStudioLargeLanguageModel()
num_tokens = model.get_num_tokens(
model="gpt-35-turbo",
credentials={
"api_key": os.getenv("AZURE_AI_STUDIO_API_KEY"),
"api_base": os.getenv("AZURE_AI_STUDIO_API_BASE"),
},
prompt_messages=[
SystemPromptMessage(
content="You are a helpful AI assistant.",
),
UserPromptMessage(content="Hello World!"),
],
)
assert num_tokens == 21

View File

@@ -0,0 +1,17 @@
import os
import pytest
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.azure_ai_studio.azure_ai_studio import AzureAIStudioProvider
def test_validate_provider_credentials():
provider = AzureAIStudioProvider()
with pytest.raises(CredentialsValidateFailedError):
provider.validate_provider_credentials(credentials={})
provider.validate_provider_credentials(
credentials={"api_key": os.getenv("AZURE_AI_STUDIO_API_KEY"), "api_base": os.getenv("AZURE_AI_STUDIO_API_BASE")}
)

View File

@@ -0,0 +1,50 @@
import os
import pytest
from core.model_runtime.entities.rerank_entities import RerankResult
from core.model_runtime.errors.validate import CredentialsValidateFailedError
from core.model_runtime.model_providers.azure_ai_studio.rerank.rerank import AzureAIStudioRerankModel
def test_validate_credentials():
model = AzureAIStudioRerankModel()
with pytest.raises(CredentialsValidateFailedError):
model.validate_credentials(
model="azure-ai-studio-rerank-v1",
credentials={"api_key": "invalid_key", "api_base": os.getenv("AZURE_AI_STUDIO_API_BASE")},
query="What is the capital of the United States?",
docs=[
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
"Census, Carson City had a population of 55,274.",
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
"are a political division controlled by the United States. Its capital is Saipan.",
],
score_threshold=0.8,
)
def test_invoke_model():
model = AzureAIStudioRerankModel()
result = model.invoke(
model="azure-ai-studio-rerank-v1",
credentials={
"api_key": os.getenv("AZURE_AI_STUDIO_JWT_TOKEN"),
"api_base": os.getenv("AZURE_AI_STUDIO_API_BASE"),
},
query="What is the capital of the United States?",
docs=[
"Carson City is the capital city of the American state of Nevada. At the 2010 United States "
"Census, Carson City had a population of 55,274.",
"The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
"are a political division controlled by the United States. Its capital is Saipan.",
],
score_threshold=0.8,
)
assert isinstance(result, RerankResult)
assert len(result.docs) == 1
assert result.docs[0].index == 1
assert result.docs[0].score >= 0.8

View File

@@ -3,6 +3,7 @@ from textwrap import dedent
import pytest
from flask import Flask
from yarl import URL
from configs.app_config import DifyConfig
@@ -84,3 +85,6 @@ def test_flask_configs(example_env_file):
assert config["CONSOLE_WEB_URL"] == "https://example.com"
assert config["CONSOLE_CORS_ALLOW_ORIGINS"] == ["https://example.com"]
assert config["WEB_API_CORS_ALLOW_ORIGINS"] == ["*"]
assert str(config["CODE_EXECUTION_ENDPOINT"]) == "http://sandbox:8194/"
assert str(URL(str(config["CODE_EXECUTION_ENDPOINT"])) / "v1") == "http://sandbox:8194/v1"

View File

@@ -4,7 +4,7 @@ import dayjs from 'dayjs'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import { useTranslation } from 'react-i18next'
import type { PeriodParams } from '@/app/components/app/overview/appChart'
import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/appChart'
import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, MessagesChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/appChart'
import type { Item } from '@/app/components/base/select'
import { SimpleSelect } from '@/app/components/base/select'
import { TIME_PERIOD_LIST } from '@/app/components/app/log/filter'
@@ -79,6 +79,11 @@ export default function ChartView({ appId }: IChartViewProps) {
<CostChart period={period} id={appId} />
</div>
)}
{!isWorkflow && isChatApp && (
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<MessagesChart period={period} id={appId} />
</div>
)}
{isWorkflow && (
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
<WorkflowMessagesChart period={period} id={appId} />

View File

@@ -280,7 +280,7 @@ const Annotation: FC<Props> = ({
onSave={async (embeddingModel, score) => {
if (
embeddingModel.embedding_model_name !== annotationConfig?.embedding_model?.embedding_model_name
&& embeddingModel.embedding_provider_name !== annotationConfig?.embedding_model?.embedding_provider_name
|| embeddingModel.embedding_provider_name !== annotationConfig?.embedding_model?.embedding_provider_name
) {
const { job_id: jobId }: any = await updateAnnotationStatus(appDetail.id, AnnotationEnableStatus.enable, embeddingModel, score)
await ensureJobCompleted(jobId, AnnotationEnableStatus.enable)

View File

@@ -98,7 +98,7 @@ const AnnotationReplyConfig: FC<Props> = ({
let isEmbeddingModelChanged = false
if (
embeddingModel.embedding_model_name !== annotationConfig.embedding_model.embedding_model_name
&& embeddingModel.embedding_provider_name !== annotationConfig.embedding_model.embedding_provider_name
|| embeddingModel.embedding_provider_name !== annotationConfig.embedding_model.embedding_provider_name
) {
await onEmbeddingChange(embeddingModel)
isEmbeddingModelChanged = true

View File

@@ -32,7 +32,7 @@ const Toolbox: FC<ToolboxProps> = ({
)
}
{
(showAnnotation || true) && (
showAnnotation && (
<Annotation
onEmbeddingChange={onEmbeddingChange}
onScoreChange={onScoreChange}

View File

@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useRouter } from 'next/navigation'
import cn from '@/utils/classnames'
@@ -23,10 +23,14 @@ const LogAnnotation: FC<Props> = ({
const router = useRouter()
const appDetail = useAppStore(state => state.appDetail)
const options = [
{ value: PageType.log, text: t('appLog.title') },
{ value: PageType.annotation, text: t('appAnnotation.title') },
]
const options = useMemo(() => {
if (appDetail?.mode === 'completion')
return [{ value: PageType.log, text: t('appLog.title') }]
return [
{ value: PageType.log, text: t('appLog.title') },
{ value: PageType.annotation, text: t('appAnnotation.title') },
]
}, [appDetail])
if (!appDetail) {
return (

View File

@@ -678,7 +678,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
</thead>
<tbody className="text-gray-500">
{logs.data.map((log: any) => {
const endUser = log.from_end_user_session_id
const endUser = log.from_end_user_session_id || log.from_account_name
const leftValue = get(log, isChatMode ? 'name' : 'message.inputs.query') || (!isChatMode ? (get(log, 'message.query') || get(log, 'message.inputs.default_input')) : '') || ''
const rightValue = get(log, isChatMode ? 'message_count' : 'message.answer')
return <tr

View File

@@ -10,8 +10,8 @@ import { useTranslation } from 'react-i18next'
import { formatNumber } from '@/utils/format'
import Basic from '@/app/components/app-sidebar/basic'
import Loading from '@/app/components/base/loading'
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppTokenCostsResponse } from '@/models/app'
import { getAppDailyConversations, getAppDailyEndUsers, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps'
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppTokenCostsResponse } from '@/models/app'
import { getAppDailyConversations, getAppDailyEndUsers, getAppDailyMessages, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps'
const valueFormatter = (v: string | number) => v
const COLOR_TYPE_MAP = {
@@ -36,12 +36,15 @@ const COMMON_COLOR_MAP = {
}
type IColorType = 'green' | 'orange' | 'blue'
type IChartType = 'conversations' | 'endUsers' | 'costs' | 'workflowCosts'
type IChartType = 'messages' | 'conversations' | 'endUsers' | 'costs' | 'workflowCosts'
type IChartConfigType = { colorType: IColorType; showTokens?: boolean }
const commonDateFormat = 'MMM D, YYYY'
const CHART_TYPE_CONFIG: Record<string, IChartConfigType> = {
messages: {
colorType: 'green',
},
conversations: {
colorType: 'green',
},
@@ -89,7 +92,7 @@ export type IChartProps = {
unit?: string
yMax?: number
chartType: IChartType
chartData: AppDailyConversationsResponse | AppDailyEndUsersResponse | AppTokenCostsResponse | { data: Array<{ date: string; count: number }> }
chartData: AppDailyMessagesResponse | AppDailyConversationsResponse | AppDailyEndUsersResponse | AppTokenCostsResponse | { data: Array<{ date: string; count: number }> }
}
const Chart: React.FC<IChartProps> = ({
@@ -258,6 +261,20 @@ const getDefaultChartData = ({ start, end, key = 'count' }: { start: string; end
})
}
export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
const { t } = useTranslation()
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-messages`, params: period.query }, getAppDailyMessages)
if (!response)
return <Loading />
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartType='messages'
{...(noDataFlag && { yMax: 500 })}
/>
}
export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
const { t } = useTranslation()
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-conversations`, params: period.query }, getAppDailyConversations)
@@ -265,7 +282,7 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
return <Loading />
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
basicInfo={{ title: t('appOverview.analysis.totalConversations.title'), explanation: t('appOverview.analysis.totalConversations.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartType='conversations'
{...(noDataFlag && { yMax: 500 })}

View File

@@ -91,7 +91,7 @@ const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
</thead>
<tbody className="text-gray-700 text-[13px]">
{logs.data.map((log: WorkflowAppLogDetail) => {
const endUser = log.created_by_end_user ? log.created_by_end_user.session_id : defaultValue
const endUser = log.created_by_end_user ? log.created_by_end_user.session_id : log.created_by_account ? log.created_by_account.name : defaultValue
return <tr
key={log.id}
className={`border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer ${currentLog?.id !== log.id ? '' : 'bg-gray-50'}`}

View File

@@ -1,62 +0,0 @@
import { useTranslation } from 'react-i18next'
import s from './style.module.css'
import Button from '@/app/components/base/button'
import { Grid01 } from '@/app/components/base/icons/src/vender/solid/layout'
import { Container, Database01 } from '@/app/components/base/icons/src/vender/line/development'
import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images'
import { useProviderContext } from '@/context/provider-context'
import { Plan } from '@/app/components/billing/type'
const CustomAppHeaderBrand = () => {
const { t } = useTranslation()
const { plan } = useProviderContext()
return (
<div className='py-3'>
<div className='mb-2 text-sm font-medium text-gray-900'>{t('custom.app.title')}</div>
<div className='relative mb-4 rounded-xl bg-gray-100 border-[0.5px] border-black/8 shadow-xs'>
<div className={`${s.mask} absolute inset-0 rounded-xl`}></div>
<div className='flex items-center pl-5 h-14 rounded-t-xl'>
<div className='relative flex items-center mr-[199px] w-[120px] h-10 bg-[rgba(217,45,32,0.12)]'>
<div className='ml-[1px] mr-[3px] w-[34px] h-[34px] border-8 border-black/[0.16] rounded-full'></div>
<div className='text-[13px] font-bold text-black/[0.24]'>YOUR LOGO</div>
<div className='absolute top-0 bottom-0 left-0.5 w-[0.5px] bg-[#F97066] opacity-50'></div>
<div className='absolute top-0 bottom-0 right-0.5 w-[0.5px] bg-[#F97066] opacity-50'></div>
<div className='absolute left-0 right-0 top-0.5 h-[0.5px] bg-[#F97066] opacity-50'></div>
<div className='absolute left-0 right-0 bottom-0.5 h-[0.5px] bg-[#F97066] opacity-50'></div>
</div>
<div className='flex items-center mr-3 px-3 h-7 rounded-xl bg-white shadow-xs'>
<Grid01 className='shrink-0 mr-2 w-4 h-4 text-[#155eef]' />
<div className='w-12 h-1.5 rounded-[5px] bg-[#155eef] opacity-80'></div>
</div>
<div className='flex items-center mr-3 px-3 h-7'>
<Container className='shrink-0 mr-2 w-4 h-4 text-gray-500' />
<div className='w-[50px] h-1.5 rounded-[5px] bg-gray-300'></div>
</div>
<div className='flex items-center px-3 h-7'>
<Database01 className='shrink-0 mr-2 w-4 h-4 text-gray-500' />
<div className='w-14 h-1.5 rounded-[5px] bg-gray-300 opacity-80'></div>
</div>
</div>
<div className='h-8 border-t border-t-gray-200 rounded-b-xl'></div>
</div>
<div className='flex items-center mb-2'>
<Button
disabled={plan.type === Plan.sandbox}
>
<ImagePlus className='mr-2 w-4 h-4' />
{t('custom.upload')}
</Button>
<div className='mx-2 h-5 w-[1px] bg-black/5'></div>
<Button
disabled={plan.type === Plan.sandbox}
>
{t('custom.restore')}
</Button>
</div>
<div className='text-xs text-gray-500'>{t('custom.app.changeLogoTip')}</div>
</div>
)
}
export default CustomAppHeaderBrand

View File

@@ -1,3 +0,0 @@
.mask {
background: linear-gradient(95deg, rgba(255, 255, 255, 0.00) 43.9%, rgba(255, 255, 255, 0.80) 95.76%); ;
}

View File

@@ -1,6 +1,5 @@
import { useTranslation } from 'react-i18next'
import CustomWebAppBrand from '../custom-web-app-brand'
import CustomAppHeaderBrand from '../custom-app-header-brand'
import s from '../style.module.css'
import GridMask from '@/app/components/base/grid-mask'
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
@@ -13,7 +12,6 @@ const CustomPage = () => {
const { plan, enableBilling } = useProviderContext()
const showBillingTip = enableBilling && plan.type === Plan.sandbox
const showCustomAppHeaderBrand = enableBilling && plan.type === Plan.sandbox
const showContact = enableBilling && (plan.type === Plan.professional || plan.type === Plan.team)
return (
@@ -32,14 +30,6 @@ const CustomPage = () => {
)
}
<CustomWebAppBrand />
{
showCustomAppHeaderBrand && (
<>
<div className='my-2 h-[0.5px] bg-gray-100'></div>
<CustomAppHeaderBrand />
</>
)
}
{
showContact && (
<div className='absolute bottom-0 h-[50px] leading-[50px] text-xs text-gray-500'>

View File

@@ -10,7 +10,6 @@ import {
} from '@remixicon/react'
import Link from 'next/link'
import { groupBy } from 'lodash-es'
import RetrievalMethodInfo from '../../common/retrieval-method-info'
import PreviewItem, { PreviewType } from './preview-item'
import LanguageSelect from './language-select'
import s from './index.module.css'
@@ -124,7 +123,9 @@ const StepTwo = ({
const [docForm, setDocForm] = useState<DocForm | string>(
(datasetId && documentDetail) ? documentDetail.doc_form : DocForm.TEXT,
)
const [docLanguage, setDocLanguage] = useState<string>(locale !== LanguagesSupported[1] ? 'English' : 'Chinese')
const [docLanguage, setDocLanguage] = useState<string>(
(datasetId && documentDetail) ? documentDetail.doc_language : (locale !== LanguagesSupported[1] ? 'English' : 'Chinese'),
)
const [QATipHide, setQATipHide] = useState(false)
const [previewSwitched, setPreviewSwitched] = useState(false)
const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
@@ -785,34 +786,21 @@ const StepTwo = ({
)}
<div className='max-w-[640px]'>
{!datasetId
? (<>
{getIndexing_technique() === IndexingType.QUALIFIED
? (
<RetrievalMethodConfig
value={retrievalConfig}
onChange={setRetrievalConfig}
/>
)
: (
<EconomicalRetrievalMethodConfig
value={retrievalConfig}
onChange={setRetrievalConfig}
/>
)}
</>)
: (
<div>
<RetrievalMethodInfo
{
getIndexing_technique() === IndexingType.QUALIFIED
? (
<RetrievalMethodConfig
value={retrievalConfig}
onChange={setRetrievalConfig}
/>
<div className='mt-2 text-xs text-gray-500 font-medium'>
{t('datasetCreation.stepTwo.retrivalSettedTip')}
<Link className='text-[#155EEF]' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link>
</div>
</div>
)}
)
: (
<EconomicalRetrievalMethodConfig
value={retrievalConfig}
onChange={setRetrievalConfig}
/>
)
}
</div>
</div>

View File

@@ -38,7 +38,7 @@ const Field: FC<Props> = ({
popupContent={
<div className='w-[200px]'>{tooltip}</div>
}
popupClassName='relative top-[3px] w-3 h-3 ml-1'
triggerClassName='ml-0.5 w-4 h-4'
/>
)}
</div>

View File

@@ -405,7 +405,7 @@ const DocumentList: FC<IDocumentListProps> = ({ embeddingAvailable, documents =
<tbody className="text-gray-700">
{localDocs.map((doc) => {
const isFile = doc.data_source_type === DataSourceType.FILE
const fileType = isFile ? doc.data_source_detail_dict?.upload_file.extension : ''
const fileType = isFile ? doc.data_source_detail_dict?.upload_file?.extension : ''
return <tr
key={doc.id}
className={'border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer'}

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Aktualisierungszeit',
time: 'Erstellungszeit',
endUser: 'Endbenutzer',
endUser: 'Endbenutzer oder Konto',
input: 'Eingabe',
output: 'Ausgabe',
summary: 'Titel',

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Updated time',
time: 'Created time',
endUser: 'End User',
endUser: 'End User or Account',
input: 'Input',
output: 'Output',
summary: 'Title',
@@ -17,7 +17,7 @@ const translation = {
status: 'STATUS',
runtime: 'RUN TIME',
tokens: 'TOKENS',
user: 'END-USER',
user: 'End User or Account',
version: 'VERSION',
},
pagination: {

View File

@@ -127,7 +127,11 @@ const translation = {
tokenPS: 'Token/s',
totalMessages: {
title: 'Total Messages',
explanation: 'Daily AI interactions count; prompt engineering/debugging excluded.',
explanation: 'Daily AI interactions count.',
},
totalConversations: {
title: 'Total Conversations',
explanation: 'Daily AI conversations count; prompt engineering/debugging excluded.',
},
activeUsers: {
title: 'Active Users',

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Hora actualizada',
time: 'Hora creada',
endUser: 'Usuario Final',
endUser: 'Usuario Final o Cuenta',
input: 'Entrada',
output: 'Salida',
summary: 'Título',
@@ -17,7 +17,7 @@ const translation = {
status: 'ESTADO',
runtime: 'TIEMPO DE EJECUCIÓN',
tokens: 'TOKENS',
user: 'USUARIO FINAL',
user: 'USUARIO FINAL O CUENTA',
version: 'VERSIÓN',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'زمان به‌روزرسانی',
time: 'زمان ایجاد',
endUser: 'کاربر نهایی',
endUser: 'کاربر نهایی یا حساب',
input: 'ورودی',
output: 'خروجی',
summary: 'عنوان',
@@ -17,7 +17,7 @@ const translation = {
status: 'وضعیت',
runtime: 'زمان اجرا',
tokens: 'توکن‌ها',
user: 'کاربر نهایی',
user: 'کاربر نهایی یا حساب',
version: 'نسخه',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Heure de mise à jour',
time: 'Heure de création',
endUser: 'Utilisateur final',
endUser: 'Utilisateur final ou compte',
input: 'Entrée',
output: 'Sortie',
summary: 'Titre',
@@ -17,7 +17,7 @@ const translation = {
status: 'STATUT',
runtime: 'TEMPS D\'EXÉCUTION',
tokens: 'JETONS',
user: 'UTILISATEUR FINAL',
user: 'UTILISATEUR FINAL OU COMPTE',
version: 'VERSION',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'अपडेट का समय',
time: 'बनाने का समय',
endUser: 'अंतिम उपयोगकर्ता',
endUser: 'अंतिम उपयोगकर्ता या खाता',
input: 'इनपुट',
output: 'आउटपुट',
summary: 'शीर्षक',
@@ -17,7 +17,7 @@ const translation = {
status: 'स्थिति',
runtime: 'रन टाइम',
tokens: 'टोकन',
user: 'अंतिम उपयोगकर्ता',
user: 'अंतिम उपयोगकर्ता या खाता',
version: 'संस्करण',
},
pagination: {

View File

@@ -7,7 +7,7 @@ const translation = {
header: {
updatedTime: 'Ora di aggiornamento',
time: 'Ora di creazione',
endUser: 'Utente Finale',
endUser: 'Utente Finale o Account',
input: 'Input',
output: 'Output',
summary: 'Titolo',
@@ -18,7 +18,7 @@ const translation = {
status: 'STATO',
runtime: 'TEMPO DI ESECUZIONE',
tokens: 'TOKEN',
user: 'UTENTE FINALE',
user: 'UTENTE FINALE O ACCOUNT',
version: 'VERSIONE',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: '更新時間',
time: '作成時間',
endUser: 'エンドユーザー',
endUser: 'エンドユーザーまたはアカウント',
input: '入力',
output: '出力',
summary: 'タイトル',
@@ -17,7 +17,7 @@ const translation = {
status: 'ステータス',
runtime: 'ランタイム',
tokens: 'トークン',
user: 'エンドユーザー',
user: 'エンドユーザーまたはアカウント',
version: 'バージョン',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: '업데이트 시간',
time: '생성 시간',
endUser: '엔드 유저',
endUser: '엔드 유저 또는 계정',
input: '입력',
output: '출력',
summary: '요약',
@@ -17,7 +17,7 @@ const translation = {
status: '상태',
runtime: '실행 시간',
tokens: '토큰',
user: '엔드 유저',
user: '엔드 유저 또는 계정',
version: '버전',
},
pagination: {

View File

@@ -7,7 +7,7 @@ const translation = {
header: {
updatedTime: 'Czas aktualizacji',
time: 'Czas utworzenia',
endUser: 'Użytkownik końcowy',
endUser: 'Użytkownik końcowy lub konto',
input: 'Wejście',
output: 'Wyjście',
summary: 'Tytuł',
@@ -18,7 +18,7 @@ const translation = {
status: 'STATUS',
runtime: 'CZAS DZIAŁANIA',
tokens: 'TOKENY',
user: 'UŻYTKOWNIK KOŃCOWY',
user: 'UŻYTKOWNIK KOŃCOWY LUB KONTO',
version: 'WERSJA',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Hora de atualização',
time: 'Hora de criação',
endUser: 'Usuário Final',
endUser: 'Usuário final ou conta',
input: 'Entrada',
output: 'Saída',
summary: 'Título',
@@ -17,7 +17,7 @@ const translation = {
status: 'STATUS',
runtime: 'TEMPO DE EXECUÇÃO',
tokens: 'TOKENS',
user: 'USUÁRIO FINAL',
user: 'USUÁRIO FINAL OU CONTA',
version: 'VERSÃO',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Timp actualizare',
time: 'Timp creare',
endUser: 'Utilizator final',
endUser: 'Utilizator final sau cont',
input: 'Intrare',
output: 'Ieșire',
summary: 'Titlu',
@@ -17,7 +17,7 @@ const translation = {
status: 'STARE',
runtime: 'TIMP DE RULARE',
tokens: 'JETOANE',
user: 'UTILIZATOR FINAL',
user: 'UTILIZATOR FINAL SAU CONT',
version: 'VERSIUNE',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Güncellenme zamanı',
time: 'Oluşturulma zamanı',
endUser: 'Son Kullanıcı',
endUser: 'Son Kullanıcı veya Hesap',
input: 'Girdi',
output: ıktı',
summary: 'Başlık',
@@ -17,7 +17,7 @@ const translation = {
status: 'DURUM',
runtime: 'ÇALIŞMA SÜRESİ',
tokens: 'TOKENLAR',
user: 'SON KULLANICI',
user: 'SON KULLANICI VEYA HESAP',
version: 'VERSİYON',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Час оновлення',
time: 'Час створення',
endUser: 'Кінцевий Користувач',
endUser: 'Кінцевий Користувач або Обліковий Запис',
input: 'Введення',
output: 'Виведення',
summary: 'Заголовок',
@@ -17,7 +17,7 @@ const translation = {
status: 'СТАТУС',
runtime: 'ЧАС ВИКОНАННЯ',
tokens: 'ТОКЕНИ',
user: 'КІНЦЕВИЙ КОРИСТУВАЧ',
user: 'КІНЦЕВИЙ КОРИСТУВАЧ АБО ОБЛІКОВИЙ ЗАПИС',
version: 'ВЕРСІЯ',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: 'Thời gian cập nhật',
time: 'Thời gian tạo',
endUser: 'Người dùng cuối',
endUser: 'Người dùng cuối hoặc tài khoản',
input: 'Đầu vào',
output: 'Đầu ra',
summary: 'Tóm tắt',
@@ -17,7 +17,7 @@ const translation = {
status: 'TRẠNG THÁI',
runtime: 'THỜI GIAN CHẠY',
tokens: 'TOKEN',
user: 'NGƯỜI DÙNG CUỐI',
user: 'NGƯỜI DÙNG CUỐI HOẶC TÀI KHOẢN',
version: 'PHIÊN BẢN',
},
pagination: {

View File

@@ -6,7 +6,7 @@ const translation = {
header: {
updatedTime: '更新时间',
time: '创建时间',
endUser: '用户',
endUser: '用户或账户',
input: '输入',
output: '输出',
summary: '标题',
@@ -17,7 +17,7 @@ const translation = {
status: '状态',
runtime: '运行时间',
tokens: 'TOKENS',
user: '用户',
user: '用户或账户',
version: '版本',
},
pagination: {

View File

@@ -127,7 +127,11 @@ const translation = {
tokenPS: 'Token/秒',
totalMessages: {
title: '全部消息数',
explanation: '反映 AI 每天的互动总次数,每回答用户一个问题算一条 Message。提示词编排和调试的消息不计入。',
explanation: '反映 AI 每天的互动总次数,每回答用户一个问题算一条 Message。',
},
totalConversations: {
title: '全部会话数',
explanation: '反映 AI 每天的会话总次数,提示词编排和调试的消息不计入。',
},
activeUsers: {
title: '活跃用户数',

Some files were not shown because too many files have changed in this diff Show More