Merge branch 'langgenius:main' into add-message-extra-data

pull/20921/head
GuanMu 12 months ago committed by GitHub
commit ecadfd3387
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -70,7 +70,7 @@ class ModelConfigConverter:
if not model_mode: if not model_mode:
model_mode = LLMMode.CHAT.value model_mode = LLMMode.CHAT.value
if model_schema and model_schema.model_properties.get(ModelPropertyKey.MODE): if model_schema and model_schema.model_properties.get(ModelPropertyKey.MODE):
model_mode = LLMMode.value_of(model_schema.model_properties[ModelPropertyKey.MODE]).value model_mode = LLMMode(model_schema.model_properties[ModelPropertyKey.MODE]).value
if not model_schema: if not model_schema:
raise ValueError(f"Model {model_name} not exist.") raise ValueError(f"Model {model_name} not exist.")

@ -15,6 +15,7 @@ from core.helper.code_executor.python3.python3_transformer import Python3Templat
from core.helper.code_executor.template_transformer import TemplateTransformer from core.helper.code_executor.template_transformer import TemplateTransformer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
code_execution_endpoint_url = URL(str(dify_config.CODE_EXECUTION_ENDPOINT))
class CodeExecutionError(Exception): class CodeExecutionError(Exception):
@ -64,7 +65,7 @@ class CodeExecutor:
:param code: code :param code: code
:return: :return:
""" """
url = URL(str(dify_config.CODE_EXECUTION_ENDPOINT)) / "v1" / "sandbox" / "run" url = code_execution_endpoint_url / "v1" / "sandbox" / "run"
headers = {"X-Api-Key": dify_config.CODE_EXECUTION_API_KEY} headers = {"X-Api-Key": dify_config.CODE_EXECUTION_API_KEY}

@ -7,29 +7,28 @@ from configs import dify_config
from core.helper.download import download_with_size_limit from core.helper.download import download_with_size_limit
from core.plugin.entities.marketplace import MarketplacePluginDeclaration from core.plugin.entities.marketplace import MarketplacePluginDeclaration
marketplace_api_url = URL(str(dify_config.MARKETPLACE_API_URL))
def get_plugin_pkg_url(plugin_unique_identifier: str):
return (URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/plugins/download").with_query( def get_plugin_pkg_url(plugin_unique_identifier: str) -> str:
unique_identifier=plugin_unique_identifier return str((marketplace_api_url / "api/v1/plugins/download").with_query(unique_identifier=plugin_unique_identifier))
)
def download_plugin_pkg(plugin_unique_identifier: str): def download_plugin_pkg(plugin_unique_identifier: str):
url = str(get_plugin_pkg_url(plugin_unique_identifier)) return download_with_size_limit(get_plugin_pkg_url(plugin_unique_identifier), dify_config.PLUGIN_MAX_PACKAGE_SIZE)
return download_with_size_limit(url, dify_config.PLUGIN_MAX_PACKAGE_SIZE)
def batch_fetch_plugin_manifests(plugin_ids: list[str]) -> Sequence[MarketplacePluginDeclaration]: def batch_fetch_plugin_manifests(plugin_ids: list[str]) -> Sequence[MarketplacePluginDeclaration]:
if len(plugin_ids) == 0: if len(plugin_ids) == 0:
return [] return []
url = str(URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/plugins/batch") url = str(marketplace_api_url / "api/v1/plugins/batch")
response = requests.post(url, json={"plugin_ids": plugin_ids}) response = requests.post(url, json={"plugin_ids": plugin_ids})
response.raise_for_status() response.raise_for_status()
return [MarketplacePluginDeclaration(**plugin) for plugin in response.json()["data"]["plugins"]] return [MarketplacePluginDeclaration(**plugin) for plugin in response.json()["data"]["plugins"]]
def record_install_plugin_event(plugin_unique_identifier: str): def record_install_plugin_event(plugin_unique_identifier: str):
url = str(URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/stats/plugins/install_count") url = str(marketplace_api_url / "api/v1/stats/plugins/install_count")
response = requests.post(url, json={"unique_identifier": plugin_unique_identifier}) response = requests.post(url, json={"unique_identifier": plugin_unique_identifier})
response.raise_for_status() response.raise_for_status()

@ -17,19 +17,6 @@ class LLMMode(StrEnum):
COMPLETION = "completion" COMPLETION = "completion"
CHAT = "chat" CHAT = "chat"
@classmethod
def value_of(cls, value: str) -> "LLMMode":
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for mode in cls:
if mode.value == value:
return mode
raise ValueError(f"invalid mode value {value}")
class LLMUsage(ModelUsage): class LLMUsage(ModelUsage):
""" """

@ -31,8 +31,7 @@ from core.plugin.impl.exc import (
PluginUniqueIdentifierError, PluginUniqueIdentifierError,
) )
plugin_daemon_inner_api_baseurl = dify_config.PLUGIN_DAEMON_URL plugin_daemon_inner_api_baseurl = URL(str(dify_config.PLUGIN_DAEMON_URL))
plugin_daemon_inner_api_key = dify_config.PLUGIN_DAEMON_KEY
T = TypeVar("T", bound=(BaseModel | dict | list | bool | str)) T = TypeVar("T", bound=(BaseModel | dict | list | bool | str))
@ -53,9 +52,9 @@ class BasePluginClient:
""" """
Make a request to the plugin daemon inner API. Make a request to the plugin daemon inner API.
""" """
url = URL(str(plugin_daemon_inner_api_baseurl)) / path url = plugin_daemon_inner_api_baseurl / path
headers = headers or {} headers = headers or {}
headers["X-Api-Key"] = plugin_daemon_inner_api_key headers["X-Api-Key"] = dify_config.PLUGIN_DAEMON_KEY
headers["Accept-Encoding"] = "gzip, deflate, br" headers["Accept-Encoding"] = "gzip, deflate, br"
if headers.get("Content-Type") == "application/json" and isinstance(data, dict): if headers.get("Content-Type") == "application/json" and isinstance(data, dict):

@ -53,7 +53,6 @@ from core.workflow.nodes.end.end_stream_processor import EndStreamProcessor
from core.workflow.nodes.enums import ErrorStrategy, FailBranchSourceHandle from core.workflow.nodes.enums import ErrorStrategy, FailBranchSourceHandle
from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowType from models.workflow import WorkflowType
@ -607,8 +606,6 @@ class GraphEngine:
error=str(e), error=str(e),
) )
) )
finally:
db.session.remove()
def _run_node( def _run_node(
self, self,
@ -646,7 +643,6 @@ class GraphEngine:
agent_strategy=agent_strategy, agent_strategy=agent_strategy,
) )
db.session.close()
max_retries = node_instance.node_data.retry_config.max_retries max_retries = node_instance.node_data.retry_config.max_retries
retry_interval = node_instance.node_data.retry_config.retry_interval_seconds retry_interval = node_instance.node_data.retry_config.retry_interval_seconds
retries = 0 retries = 0
@ -863,8 +859,6 @@ class GraphEngine:
except Exception as e: except Exception as e:
logger.exception(f"Node {node_instance.node_data.title} run failed") logger.exception(f"Node {node_instance.node_data.title} run failed")
raise e raise e
finally:
db.session.close()
def _append_variables_recursively(self, node_id: str, variable_key_list: list[str], variable_value: VariableValue): def _append_variables_recursively(self, node_id: str, variable_key_list: list[str], variable_value: VariableValue):
""" """

@ -2,6 +2,9 @@ import json
from collections.abc import Generator, Mapping, Sequence from collections.abc import Generator, Mapping, Sequence
from typing import Any, Optional, cast from typing import Any, Optional, cast
from sqlalchemy import select
from sqlalchemy.orm import Session
from core.agent.entities import AgentToolEntity from core.agent.entities import AgentToolEntity
from core.agent.plugin_entities import AgentStrategyParameter from core.agent.plugin_entities import AgentStrategyParameter
from core.memory.token_buffer_memory import TokenBufferMemory from core.memory.token_buffer_memory import TokenBufferMemory
@ -320,12 +323,9 @@ class AgentNode(ToolNode):
return None return None
conversation_id = conversation_id_variable.value conversation_id = conversation_id_variable.value
# get conversation with Session(db.engine, expire_on_commit=False) as session:
conversation = ( stmt = select(Conversation).where(Conversation.app_id == self.app_id, Conversation.id == conversation_id)
db.session.query(Conversation) conversation = session.scalar(stmt)
.filter(Conversation.app_id == self.app_id, Conversation.id == conversation_id)
.first()
)
if not conversation: if not conversation:
return None return None

@ -8,6 +8,7 @@ from typing import Any, Optional, cast
from sqlalchemy import Float, and_, func, or_, text from sqlalchemy import Float, and_, func, or_, text
from sqlalchemy import cast as sqlalchemy_cast from sqlalchemy import cast as sqlalchemy_cast
from sqlalchemy.orm import Session
from core.app.app_config.entities import DatasetRetrieveConfigEntity from core.app.app_config.entities import DatasetRetrieveConfigEntity
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
@ -85,8 +86,8 @@ class KnowledgeRetrievalNode(LLMNode):
return NodeRunResult( return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error="Query is required." status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error="Query is required."
) )
# TODO(-LAN-): Move this check outside.
# check rate limit # check rate limit
if self.tenant_id:
knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(self.tenant_id) knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(self.tenant_id)
if knowledge_rate_limit.enabled: if knowledge_rate_limit.enabled:
current_time = int(time.time() * 1000) current_time = int(time.time() * 1000)
@ -95,14 +96,15 @@ class KnowledgeRetrievalNode(LLMNode):
redis_client.zremrangebyscore(key, 0, current_time - 60000) redis_client.zremrangebyscore(key, 0, current_time - 60000)
request_count = redis_client.zcard(key) request_count = redis_client.zcard(key)
if request_count > knowledge_rate_limit.limit: if request_count > knowledge_rate_limit.limit:
with Session(db.engine) as session:
# add ratelimit record # add ratelimit record
rate_limit_log = RateLimitLog( rate_limit_log = RateLimitLog(
tenant_id=self.tenant_id, tenant_id=self.tenant_id,
subscription_plan=knowledge_rate_limit.subscription_plan, subscription_plan=knowledge_rate_limit.subscription_plan,
operation="knowledge", operation="knowledge",
) )
db.session.add(rate_limit_log) session.add(rate_limit_log)
db.session.commit() session.commit()
return NodeRunResult( return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED, status=WorkflowNodeExecutionStatus.FAILED,
inputs=variables, inputs=variables,
@ -173,7 +175,9 @@ class KnowledgeRetrievalNode(LLMNode):
dataset_retrieval = DatasetRetrieval() dataset_retrieval = DatasetRetrieval()
if node_data.retrieval_mode == DatasetRetrieveConfigEntity.RetrieveStrategy.SINGLE.value: if node_data.retrieval_mode == DatasetRetrieveConfigEntity.RetrieveStrategy.SINGLE.value:
# fetch model config # fetch model config
model_instance, model_config = self._fetch_model_config(node_data.single_retrieval_config.model) # type: ignore if node_data.single_retrieval_config is None:
raise ValueError("single_retrieval_config is required")
model_instance, model_config = self.get_model_config(node_data.single_retrieval_config.model)
# check model is support tool calling # check model is support tool calling
model_type_instance = model_config.provider_model_bundle.model_type_instance model_type_instance = model_config.provider_model_bundle.model_type_instance
model_type_instance = cast(LargeLanguageModel, model_type_instance) model_type_instance = cast(LargeLanguageModel, model_type_instance)
@ -424,7 +428,7 @@ class KnowledgeRetrievalNode(LLMNode):
raise ValueError("metadata_model_config is required") raise ValueError("metadata_model_config is required")
# get metadata model instance # get metadata model instance
# fetch model config # fetch model config
model_instance, model_config = self._fetch_model_config(node_data.metadata_model_config) # type: ignore model_instance, model_config = self.get_model_config(metadata_model_config)
# fetch prompt messages # fetch prompt messages
prompt_template = self._get_prompt_template( prompt_template = self._get_prompt_template(
node_data=node_data, node_data=node_data,
@ -550,14 +554,7 @@ class KnowledgeRetrievalNode(LLMNode):
variable_mapping[node_id + ".query"] = node_data.query_variable_selector variable_mapping[node_id + ".query"] = node_data.query_variable_selector
return variable_mapping return variable_mapping
def _fetch_model_config(self, model: ModelConfig) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: # type: ignore def get_model_config(self, model: ModelConfig) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]:
"""
Fetch model config
:param model: model
:return:
"""
if model is None:
raise ValueError("model is required")
model_name = model.name model_name = model.name
provider_name = model.provider provider_name = model.provider

@ -7,6 +7,8 @@ from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any, Optional, cast from typing import TYPE_CHECKING, Any, Optional, cast
import json_repair import json_repair
from sqlalchemy import select, update
from sqlalchemy.orm import Session
from configs import dify_config from configs import dify_config
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
@ -303,8 +305,6 @@ class LLMNode(BaseNode[LLMNodeData]):
prompt_messages: Sequence[PromptMessage], prompt_messages: Sequence[PromptMessage],
stop: Optional[Sequence[str]] = None, stop: Optional[Sequence[str]] = None,
) -> Generator[NodeEvent, None, None]: ) -> Generator[NodeEvent, None, None]:
db.session.close()
invoke_result = model_instance.invoke_llm( invoke_result = model_instance.invoke_llm(
prompt_messages=list(prompt_messages), prompt_messages=list(prompt_messages),
model_parameters=node_data_model.completion_params, model_parameters=node_data_model.completion_params,
@ -603,13 +603,9 @@ class LLMNode(BaseNode[LLMNodeData]):
return None return None
conversation_id = conversation_id_variable.value conversation_id = conversation_id_variable.value
# get conversation with Session(db.engine, expire_on_commit=False) as session:
conversation = ( stmt = select(Conversation).where(Conversation.app_id == self.app_id, Conversation.id == conversation_id)
db.session.query(Conversation) conversation = session.scalar(stmt)
.filter(Conversation.app_id == self.app_id, Conversation.id == conversation_id)
.first()
)
if not conversation: if not conversation:
return None return None
@ -847,20 +843,24 @@ class LLMNode(BaseNode[LLMNodeData]):
used_quota = 1 used_quota = 1
if used_quota is not None and system_configuration.current_quota_type is not None: if used_quota is not None and system_configuration.current_quota_type is not None:
db.session.query(Provider).filter( with Session(db.engine) as session:
stmt = (
update(Provider)
.where(
Provider.tenant_id == tenant_id, Provider.tenant_id == tenant_id,
# TODO: Use provider name with prefix after the data migration. # TODO: Use provider name with prefix after the data migration.
Provider.provider_name == ModelProviderID(model_instance.provider).provider_name, Provider.provider_name == ModelProviderID(model_instance.provider).provider_name,
Provider.provider_type == ProviderType.SYSTEM.value, Provider.provider_type == ProviderType.SYSTEM.value,
Provider.quota_type == system_configuration.current_quota_type.value, Provider.quota_type == system_configuration.current_quota_type.value,
Provider.quota_limit > Provider.quota_used, Provider.quota_limit > Provider.quota_used,
).update(
{
"quota_used": Provider.quota_used + used_quota,
"last_used": datetime.now(tz=UTC).replace(tzinfo=None),
}
) )
db.session.commit() .values(
quota_used=Provider.quota_used + used_quota,
last_used=datetime.now(tz=UTC).replace(tzinfo=None),
)
)
session.execute(stmt)
session.commit()
@classmethod @classmethod
def _extract_variable_selector_to_variable_mapping( def _extract_variable_selector_to_variable_mapping(

@ -31,7 +31,6 @@ from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.llm import LLMNode, ModelConfig from core.workflow.nodes.llm import LLMNode, ModelConfig
from core.workflow.utils import variable_template_parser from core.workflow.utils import variable_template_parser
from extensions.ext_database import db
from .entities import ParameterExtractorNodeData from .entities import ParameterExtractorNodeData
from .exc import ( from .exc import (
@ -259,8 +258,6 @@ class ParameterExtractorNode(LLMNode):
tools: list[PromptMessageTool], tools: list[PromptMessageTool],
stop: list[str], stop: list[str],
) -> tuple[str, LLMUsage, Optional[AssistantPromptMessage.ToolCall]]: ) -> tuple[str, LLMUsage, Optional[AssistantPromptMessage.ToolCall]]:
db.session.close()
invoke_result = model_instance.invoke_llm( invoke_result = model_instance.invoke_llm(
prompt_messages=prompt_messages, prompt_messages=prompt_messages,
model_parameters=node_data_model.completion_params, model_parameters=node_data_model.completion_params,

@ -1,7 +1,8 @@
from typing import Literal, Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from core.variables.types import SegmentType
from core.workflow.nodes.base import BaseNodeData from core.workflow.nodes.base import BaseNodeData
@ -17,7 +18,7 @@ class AdvancedSettings(BaseModel):
Group. Group.
""" """
output_type: Literal["string", "number", "object", "array[string]", "array[number]", "array[object]"] output_type: SegmentType
variables: list[list[str]] variables: list[list[str]]
group_name: str group_name: str

@ -28,7 +28,8 @@ class SMTPClient:
else: else:
smtp = smtplib.SMTP(self.server, self.port, timeout=10) smtp = smtplib.SMTP(self.server, self.port, timeout=10)
if self.username and self.password: # Only authenticate if both username and password are non-empty
if self.username and self.password and self.username.strip() and self.password.strip():
smtp.login(self.username, self.password) smtp.login(self.username, self.password)
msg = MIMEMultipart() msg = MIMEMultipart()

@ -14,7 +14,7 @@ dependencies = [
"chardet~=5.1.0", "chardet~=5.1.0",
"flask~=3.1.0", "flask~=3.1.0",
"flask-compress~=1.17", "flask-compress~=1.17",
"flask-cors~=5.0.0", "flask-cors~=6.0.0",
"flask-login~=0.6.3", "flask-login~=0.6.3",
"flask-migrate~=4.0.7", "flask-migrate~=4.0.7",
"flask-restful~=0.3.10", "flask-restful~=0.3.10",
@ -36,7 +36,6 @@ dependencies = [
"mailchimp-transactional~=1.0.50", "mailchimp-transactional~=1.0.50",
"markdown~=3.5.1", "markdown~=3.5.1",
"numpy~=1.26.4", "numpy~=1.26.4",
"oci~=2.135.1",
"openai~=1.61.0", "openai~=1.61.0",
"openpyxl~=3.1.5", "openpyxl~=3.1.5",
"opik~=1.7.25", "opik~=1.7.25",
@ -143,13 +142,16 @@ dev = [
"types-requests~=2.32.0", "types-requests~=2.32.0",
"types-requests-oauthlib~=2.0.0", "types-requests-oauthlib~=2.0.0",
"types-shapely~=2.0.0", "types-shapely~=2.0.0",
"types-simplejson~=3.20.0", "types-simplejson>=3.20.0",
"types-six~=1.17.0", "types-six>=1.17.0",
"types-tensorflow~=2.18.0", "types-tensorflow>=2.18.0",
"types-tqdm~=4.67.0", "types-tqdm>=4.67.0",
"types-ujson~=5.10.0", "types-ujson>=5.10.0",
"boto3-stubs>=1.38.20", "boto3-stubs>=1.38.20",
"types-jmespath>=1.0.2.20240106", "types-jmespath>=1.0.2.20240106",
"types_pyOpenSSL>=24.1.0",
"types_cffi>=1.17.0",
"types_setuptools>=80.9.0",
] ]
############################################################ ############################################################

File diff suppressed because it is too large Load Diff

@ -33,5 +33,6 @@ export const preprocessThinkTag = (content: string) => {
return flow([ return flow([
(str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'), (str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'),
(str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'), (str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'),
(str: string) => str.replace(/(<\/details>)(?![^\S\r\n]*[\r\n])(?![^\S\r\n]*$)/g, '$1\n'),
])(content) ])(content)
} }

Loading…
Cancel
Save