mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 06:11:12 +08:00
Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
120 lines
5.0 KiB
Python
120 lines
5.0 KiB
Python
"""File and execution-content infrastructure for detached message projections."""
|
|
|
|
from collections.abc import Sequence
|
|
from typing import cast
|
|
|
|
from pydantic import JsonValue
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from core.app.file_access import DatabaseFileAccessController
|
|
from factories import file_factory
|
|
from graphon.file import FILE_MODEL_IDENTITY, FileTransferMethod
|
|
from models.model import Message, MessageFileInfo
|
|
from models.utils.file_input_compat import build_file_from_input_mapping
|
|
from repositories.execution_extra_content_repository import ExecutionExtraContentRepository
|
|
from services.entities.message_entities import (
|
|
MessageFileProjection,
|
|
MessageFileRecord,
|
|
MessageFileReference,
|
|
MessageInputValue,
|
|
)
|
|
from services.message_extra_content_service import load_message_extra_contents
|
|
|
|
|
|
class MessageFileResolver:
|
|
def __init__(self) -> None:
|
|
self._access_controller: DatabaseFileAccessController = DatabaseFileAccessController()
|
|
|
|
def resolve(
|
|
self,
|
|
*,
|
|
tenant_id: str,
|
|
inputs: dict[str, MessageInputValue],
|
|
answer: str,
|
|
files: Sequence[MessageFileReference],
|
|
) -> MessageFileProjection:
|
|
restored_inputs = dict(inputs)
|
|
|
|
def owner_tenant_id() -> str:
|
|
return tenant_id
|
|
|
|
for key, value in inputs.items():
|
|
if isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY:
|
|
restored_inputs[key] = build_file_from_input_mapping(
|
|
file_mapping=value, tenant_resolver=owner_tenant_id
|
|
)
|
|
elif isinstance(value, list) and all(
|
|
isinstance(item, dict) and item.get("dify_model_identity") == FILE_MODEL_IDENTITY for item in value
|
|
):
|
|
restored_inputs[key] = [
|
|
build_file_from_input_mapping(file_mapping=item, tenant_resolver=owner_tenant_id)
|
|
for item in value
|
|
if isinstance(item, dict)
|
|
]
|
|
|
|
restored_files = [self._file(file, tenant_id=tenant_id) for file in files]
|
|
# Keep the model's legacy URL grammar and signing behavior in one place.
|
|
# This transient bridge has no ORM session and cannot commit a message read.
|
|
signed_answer = Message(answer=answer).re_sign_file_url_answer
|
|
return MessageFileProjection(inputs=restored_inputs, answer=signed_answer, files=restored_files)
|
|
|
|
def _file(self, source: MessageFileReference, *, tenant_id: str) -> MessageFileRecord:
|
|
upload_file_id = source.upload_file_id
|
|
mapping: dict[str, object] = {
|
|
"id": source.id,
|
|
"type": source.type,
|
|
"transfer_method": source.transfer_method,
|
|
"upload_file_id": upload_file_id,
|
|
}
|
|
match source.transfer_method:
|
|
case FileTransferMethod.LOCAL_FILE:
|
|
if upload_file_id is None:
|
|
raise ValueError(f"MessageFile {source.id} is a local file but has no upload_file_id")
|
|
case FileTransferMethod.REMOTE_URL:
|
|
if source.url is None:
|
|
raise ValueError(f"MessageFile {source.id} is a remote url but has no url")
|
|
mapping["url"] = source.url
|
|
case FileTransferMethod.TOOL_FILE:
|
|
if upload_file_id is None:
|
|
assert source.url is not None
|
|
# Historical tool URLs are recovered only in the response.
|
|
upload_file_id = source.url.split("/")[-1].split(".")[0]
|
|
mapping["tool_file_id"] = upload_file_id
|
|
case _:
|
|
raise ValueError(f"MessageFile {source.id} has an invalid transfer_method {source.transfer_method}")
|
|
|
|
file = cast(
|
|
MessageFileInfo,
|
|
file_factory.build_from_mapping(
|
|
mapping=mapping, tenant_id=tenant_id, access_controller=self._access_controller
|
|
).to_dict(),
|
|
)
|
|
return MessageFileRecord(
|
|
id=file["id"],
|
|
filename=file["filename"],
|
|
type=file["type"],
|
|
url=file.get("url"),
|
|
mime_type=file.get("mime_type"),
|
|
size=file.get("size"),
|
|
transfer_method=file["transfer_method"],
|
|
belongs_to=source.belongs_to,
|
|
upload_file_id=upload_file_id,
|
|
)
|
|
|
|
|
|
class ExecutionExtraContentReader:
|
|
def __init__(self, *, repository: ExecutionExtraContentRepository, session_factory: sessionmaker[Session]) -> None:
|
|
self._repository: ExecutionExtraContentRepository = repository
|
|
self._session_factory: sessionmaker[Session] = session_factory
|
|
|
|
def get_by_message_ids(self, message_ids: Sequence[str]) -> list[list[dict[str, JsonValue]]]:
|
|
contents = load_message_extra_contents(
|
|
repository=self._repository,
|
|
message_ids=message_ids,
|
|
session_factory=self._session_factory,
|
|
)
|
|
return [
|
|
[cast(dict[str, JsonValue], item.model_dump(mode="json", exclude_none=True)) for item in items]
|
|
for items in contents
|
|
]
|