mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
feat: daily sync (#38593)
Co-authored-by: 盐粒 Yanli <mail@yanli.one> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
This commit is contained in:
co-authored by
盐粒 Yanli
yyh
Joel
autofix-ci[bot]
QuantumGhost
yyh
盐粒 Yanli
parent
9252d81826
commit
925f97be20
@@ -27,6 +27,7 @@ from clients.agent_backend import (
|
||||
AgentBackendInternalEventType,
|
||||
AgentBackendRunClient,
|
||||
AgentBackendRunEventAdapter,
|
||||
AgentBackendRunFailedInternalEvent,
|
||||
AgentBackendRunSucceededInternalEvent,
|
||||
AgentBackendStreamInternalEvent,
|
||||
extract_runtime_layer_specs,
|
||||
@@ -57,6 +58,14 @@ from core.workflow.nodes.agent_v2.ask_human_resume import build_deferred_tool_re
|
||||
from extensions.ext_database import db
|
||||
from graphon.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
|
||||
from graphon.model_runtime.entities.message_entities import AssistantPromptMessage, PromptMessage, UserPromptMessage
|
||||
from graphon.model_runtime.errors.invoke import (
|
||||
InvokeAuthorizationError,
|
||||
InvokeBadRequestError,
|
||||
InvokeConnectionError,
|
||||
InvokeError,
|
||||
InvokeRateLimitError,
|
||||
InvokeServerUnavailableError,
|
||||
)
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
from models.enums import CreatorUserRole
|
||||
from models.model import MessageAgentThought
|
||||
@@ -71,6 +80,22 @@ class _DefaultSessionScopeSnapshotId:
|
||||
|
||||
_DEFAULT_SESSION_SCOPE_SNAPSHOT_ID = _DefaultSessionScopeSnapshotId()
|
||||
|
||||
_AGENT_BACKEND_INVOKE_ERROR_BY_REASON: Mapping[str, type[InvokeError]] = {
|
||||
"InvokeAuthorizationError": InvokeAuthorizationError,
|
||||
"InvokeBadRequestError": InvokeBadRequestError,
|
||||
"CredentialsValidateFailedError": InvokeBadRequestError,
|
||||
"InvokeConnectionError": InvokeConnectionError,
|
||||
"InvokeRateLimitError": InvokeRateLimitError,
|
||||
"InvokeServerUnavailableError": InvokeServerUnavailableError,
|
||||
}
|
||||
|
||||
|
||||
def _agent_backend_failure_to_exception(event: AgentBackendRunFailedInternalEvent) -> Exception:
|
||||
err_cls = _AGENT_BACKEND_INVOKE_ERROR_BY_REASON.get(event.reason or "")
|
||||
if err_cls is not None:
|
||||
return err_cls(event.error)
|
||||
return AgentBackendError(event.error or "Agent backend run did not complete successfully.")
|
||||
|
||||
|
||||
def _prompt_messages_from_query(user_query: str | None) -> list[PromptMessage]:
|
||||
if not user_query:
|
||||
@@ -412,12 +437,15 @@ class _AgentProcessRecorder:
|
||||
def _lookup_tool_thought(self, *, index: int, tool_call_id: str | None) -> str | None:
|
||||
if tool_call_id and tool_call_id in self._tool_by_call_id:
|
||||
return self._tool_by_call_id[tool_call_id]
|
||||
if index < 0:
|
||||
return None
|
||||
return self._tool_by_index.get(index)
|
||||
|
||||
def _remember_tool_thought(
|
||||
self, *, index: int, tool_call_id: str | None, tool_name: str | None, thought_id: str
|
||||
) -> None:
|
||||
self._tool_by_index[index] = thought_id
|
||||
if index >= 0:
|
||||
self._tool_by_index[index] = thought_id
|
||||
if tool_call_id:
|
||||
self._tool_by_call_id[tool_call_id] = thought_id
|
||||
if tool_name:
|
||||
@@ -433,6 +461,10 @@ class _AgentProcessRecorder:
|
||||
return None
|
||||
|
||||
def _mark_tool_observed(self, thought_id: str) -> None:
|
||||
self._tool_by_index = {index: value for index, value in self._tool_by_index.items() if value != thought_id}
|
||||
self._tool_by_call_id = {
|
||||
tool_call_id: value for tool_call_id, value in self._tool_by_call_id.items() if value != thought_id
|
||||
}
|
||||
for open_thought_ids in self._open_tool_by_name.values():
|
||||
open_thought_ids.discard(thought_id)
|
||||
|
||||
@@ -530,7 +562,12 @@ def _event_index(data: dict[str, Any]) -> int:
|
||||
|
||||
|
||||
def _string_or_none(value: Any) -> str | None:
|
||||
return value if isinstance(value, str) and value else None
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
normalized = value.strip()
|
||||
if not normalized or normalized.lower() in {"none", "null"}:
|
||||
return None
|
||||
return normalized
|
||||
|
||||
|
||||
def _json_or_text(value: Any) -> str | None:
|
||||
@@ -652,8 +689,9 @@ class AgentAppRunner:
|
||||
return
|
||||
|
||||
if not isinstance(terminal, AgentBackendRunSucceededInternalEvent):
|
||||
error = getattr(terminal, "error", None) or "Agent backend run did not complete successfully."
|
||||
raise AgentBackendError(str(error))
|
||||
if isinstance(terminal, AgentBackendRunFailedInternalEvent):
|
||||
raise _agent_backend_failure_to_exception(terminal)
|
||||
raise AgentBackendError("Agent backend run did not complete successfully.")
|
||||
|
||||
answer = self._terminal_output_to_answer(terminal.output)
|
||||
try:
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic import JsonValue
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.app.entities.task_entities import AppBlockingResponse, AppStreamResponse
|
||||
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||
from graphon.model_runtime.errors.invoke import InvokeError
|
||||
from graphon.model_runtime.errors.invoke import InvokeError, InvokeRateLimitError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -127,6 +127,7 @@ class AppGenerateResponseConverter[TBlockingResponse: AppBlockingResponse](ABC):
|
||||
},
|
||||
ModelCurrentlyNotSupportError: {"code": "model_currently_not_support", "status": 400},
|
||||
InvokeError: {"code": "completion_request_error", "status": 400},
|
||||
InvokeRateLimitError: {"code": "rate_limit_error", "status": 429},
|
||||
}
|
||||
|
||||
# Determine the response based on the type of exception
|
||||
|
||||
@@ -420,6 +420,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline[EasyUIAppGenerat
|
||||
message.total_price = usage.total_price
|
||||
message.currency = usage.currency
|
||||
self._task_state.llm_result.usage.latency = message.provider_response_latency
|
||||
self._task_state.metadata.usage = self._task_state.llm_result.usage
|
||||
message.message_metadata = self._task_state.metadata.model_dump_json()
|
||||
|
||||
if trace_manager:
|
||||
|
||||
@@ -276,6 +276,7 @@ class RequestRequestDownloadFile(BaseModel):
|
||||
"validation",
|
||||
]
|
||||
file: RequestDownloadFileMapping
|
||||
for_external: bool = True
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import hmac
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import urllib.parse
|
||||
from collections.abc import Generator
|
||||
from mimetypes import guess_extension, guess_type
|
||||
from uuid import uuid4
|
||||
@@ -26,7 +27,7 @@ logger = logging.getLogger(__name__)
|
||||
class ToolFileManager:
|
||||
@staticmethod
|
||||
def _build_graph_file_reference(tool_file: ToolFile) -> File:
|
||||
extension = guess_extension(tool_file.mimetype) or ".bin"
|
||||
extension = resolve_extension(filename=tool_file.name, mimetype=tool_file.mimetype)
|
||||
return File(
|
||||
file_type=get_file_type_by_mime_type(tool_file.mimetype),
|
||||
transfer_method=FileTransferMethod.TOOL_FILE,
|
||||
@@ -70,7 +71,7 @@ class ToolFileManager:
|
||||
mimetype: str,
|
||||
filename: str | None = None,
|
||||
) -> ToolFile:
|
||||
extension = guess_extension(mimetype) or ".bin"
|
||||
extension = resolve_extension(filename=filename, mimetype=mimetype)
|
||||
unique_name = uuid4().hex
|
||||
unique_filename = f"{unique_name}{extension}"
|
||||
# default just as before
|
||||
@@ -120,7 +121,8 @@ class ToolFileManager:
|
||||
or response.headers.get("Content-Type", "").split(";")[0].strip()
|
||||
or "application/octet-stream"
|
||||
)
|
||||
extension = guess_extension(mimetype) or ".bin"
|
||||
url_filename = os.path.basename(urllib.parse.urlparse(file_url).path)
|
||||
extension = resolve_extension(filename=url_filename, mimetype=mimetype)
|
||||
unique_name = uuid4().hex
|
||||
filename = f"{unique_name}{extension}"
|
||||
filepath = f"tools/{tenant_id}/{filename}"
|
||||
@@ -220,4 +222,11 @@ def _factory() -> ToolFileManager:
|
||||
return ToolFileManager()
|
||||
|
||||
|
||||
def resolve_extension(*, filename: str | None, mimetype: str) -> str:
|
||||
filename_extension = os.path.splitext(filename or "")[1].lower()
|
||||
if filename_extension:
|
||||
return filename_extension
|
||||
return guess_extension(mimetype) or ".bin"
|
||||
|
||||
|
||||
set_tool_file_manager_factory(_factory)
|
||||
|
||||
@@ -3,7 +3,6 @@ import re
|
||||
from collections.abc import Generator
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from mimetypes import guess_extension
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
@@ -11,7 +10,7 @@ import numpy as np
|
||||
import pytz
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
from core.tools.tool_file_manager import ToolFileManager, resolve_extension
|
||||
from core.workflow.file_reference import parse_file_reference
|
||||
from graphon.file import File, FileTransferMethod, FileType
|
||||
from libs.login import current_user
|
||||
@@ -91,7 +90,8 @@ class ToolFileMessageTransformer:
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
|
||||
url = f"/files/tools/{tool_file.id}{guess_extension(tool_file.mimetype) or '.png'}"
|
||||
extension = resolve_extension(filename=tool_file.name, mimetype=tool_file.mimetype)
|
||||
url = cls.get_tool_file_url(tool_file_id=tool_file.id, extension=extension)
|
||||
meta = cls._with_tool_file_meta(
|
||||
message.meta,
|
||||
tool_file_id=str(tool_file.id),
|
||||
@@ -136,7 +136,8 @@ class ToolFileMessageTransformer:
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
url = cls.get_tool_file_url(tool_file_id=tool_file.id, extension=guess_extension(tool_file.mimetype))
|
||||
extension = resolve_extension(filename=tool_file.name, mimetype=tool_file.mimetype)
|
||||
url = cls.get_tool_file_url(tool_file_id=tool_file.id, extension=extension)
|
||||
meta = cls._with_tool_file_meta(meta, tool_file_id=str(tool_file.id))
|
||||
|
||||
# check if file is image
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any, override
|
||||
from agenton.compositor import CompositorSessionSnapshot
|
||||
|
||||
from clients.agent_backend import (
|
||||
AgentBackendAgentMessageDeltaInternalEvent,
|
||||
AgentBackendDeferredToolCallInternalEvent,
|
||||
AgentBackendError,
|
||||
AgentBackendHTTPError,
|
||||
@@ -481,6 +482,10 @@ class DifyAgentNode(Node[DifyAgentNodeData]):
|
||||
if isinstance(internal_event, AgentBackendStreamInternalEvent):
|
||||
self._record_stream_metadata(metadata, internal_event)
|
||||
continue
|
||||
if internal_event.type == AgentBackendInternalEventType.AGENT_MESSAGE_DELTA:
|
||||
if isinstance(internal_event, AgentBackendAgentMessageDeltaInternalEvent):
|
||||
self._record_agent_message_delta_metadata(metadata, internal_event)
|
||||
continue
|
||||
metadata["agent_backend"] = {
|
||||
**dict(metadata.get("agent_backend") or {}),
|
||||
"stream_event_count": stream_event_count,
|
||||
@@ -734,6 +739,17 @@ class DifyAgentNode(Node[DifyAgentNodeData]):
|
||||
agent_backend["usage"] = dict(usage)
|
||||
metadata["agent_backend"] = agent_backend
|
||||
|
||||
@staticmethod
|
||||
def _record_agent_message_delta_metadata(
|
||||
metadata: dict[str, Any], event: AgentBackendAgentMessageDeltaInternalEvent
|
||||
) -> None:
|
||||
agent_backend = dict(metadata.get("agent_backend") or {})
|
||||
agent_backend["agent_message_delta_count"] = int(agent_backend.get("agent_message_delta_count") or 0) + 1
|
||||
agent_backend["agent_message_delta_length"] = int(agent_backend.get("agent_message_delta_length") or 0) + len(
|
||||
event.delta
|
||||
)
|
||||
metadata["agent_backend"] = agent_backend
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def _extract_variable_selector_to_variable_mapping(
|
||||
|
||||
@@ -10,13 +10,13 @@ trustworthy metadata.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from mimetypes import guess_extension
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import DataError, SQLAlchemyError
|
||||
|
||||
from core.db.session_factory import session_factory
|
||||
from core.tools.tool_file_manager import resolve_extension
|
||||
from core.workflow.file_reference import build_file_reference
|
||||
from graphon.file import File, FileTransferMethod, get_file_type_by_mime_type
|
||||
from models.tools import ToolFile
|
||||
@@ -46,7 +46,7 @@ def reback_tool_file_output(*, tenant_id: str, tool_file_id: str) -> File | None
|
||||
return None
|
||||
|
||||
mime_type = tool_file.mimetype or ""
|
||||
extension = guess_extension(mime_type) or ".bin"
|
||||
extension = resolve_extension(filename=tool_file.name, mimetype=mime_type)
|
||||
return File(
|
||||
type=get_file_type_by_mime_type(mime_type),
|
||||
transfer_method=FileTransferMethod.TOOL_FILE,
|
||||
|
||||
Reference in New Issue
Block a user