From 82488ca9005e208023a4035e75cffe7e7bfe7290 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Mon, 24 Nov 2025 15:30:49 +0800 Subject: [PATCH] feat(api): enhance file upload method to support mime type and file name --- .../agent/runners/dify/dify_agent_runner.py | 5 +- .../agent/runners/dify/dify_api_client.py | 57 +++++++++++-------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/astrbot/core/agent/runners/dify/dify_agent_runner.py b/astrbot/core/agent/runners/dify/dify_agent_runner.py index 1b7642266..d9a8b7cd6 100644 --- a/astrbot/core/agent/runners/dify/dify_agent_runner.py +++ b/astrbot/core/agent/runners/dify/dify_agent_runner.py @@ -128,7 +128,10 @@ class DifyAgentRunner(BaseAgentRunner[TContext]): try: image_data = base64.b64decode(image_url) file_response = await self.api_client.file_upload( - file_data=image_data, user=session_id + file_data=image_data, + user=session_id, + mime_type="image/png", + file_name="image.png", ) logger.debug(f"Dify 上传图片响应:{file_response}") if "id" not in file_response: diff --git a/astrbot/core/agent/runners/dify/dify_api_client.py b/astrbot/core/agent/runners/dify/dify_api_client.py index 4b8ca546f..d9c6556cf 100644 --- a/astrbot/core/agent/runners/dify/dify_api_client.py +++ b/astrbot/core/agent/runners/dify/dify_api_client.py @@ -1,10 +1,9 @@ import codecs -import io import json from collections.abc import AsyncGenerator from typing import Any -from aiohttp import ClientResponse, ClientSession +from aiohttp import ClientResponse, ClientSession, FormData from astrbot.core import logger @@ -105,6 +104,8 @@ class DifyAPIClient: user: str, file_path: str | None = None, file_data: bytes | None = None, + file_name: str | None = None, + mime_type: str | None = None, ) -> dict[str, Any]: """Upload a file to Dify. Must provide either file_path or file_data. @@ -112,38 +113,48 @@ class DifyAPIClient: user: The user ID. file_path: The path to the file to upload. file_data: The file data in bytes. + file_name: Optional file name when using file_data. Returns: A dictionary containing the uploaded file information. """ url = f"{self.api_base}/files/upload" + form = FormData() + form.add_field("user", user) + if file_data is not None: - io_data = io.BytesIO(file_data) - payload = { - "user": user, - "file": io_data, - } - async with self.session.post( - url, - data=payload, - headers=self.headers, - ) as resp: - return await resp.json() # {"id": "xxx", ...} + # 使用 bytes 数据 + form.add_field( + "file", + file_data, + filename=file_name or "uploaded_file", + content_type=mime_type or "application/octet-stream", + ) elif file_path is not None: + # 使用文件路径 + import os + with open(file_path, "rb") as f: - payload = { - "user": user, - "file": f, - } - async with self.session.post( - url, - data=payload, - headers=self.headers, - ) as resp: - return await resp.json() # {"id": "xxx", ...} + file_content = f.read() + form.add_field( + "file", + file_content, + filename=os.path.basename(file_path), + content_type=mime_type or "application/octet-stream", + ) else: raise ValueError("file_path 和 file_data 不能同时为 None") + async with self.session.post( + url, + data=form, + headers=self.headers, # 不包含 Content-Type,让 aiohttp 自动设置 + ) as resp: + if resp.status != 200 and resp.status != 201: + text = await resp.text() + raise Exception(f"Dify 文件上传失败:{resp.status}. {text}") + return await resp.json() # {"id": "xxx", ...} + async def close(self): await self.session.close()