diff --git a/api/apps/sdk/files.py b/api/apps/sdk/files.py index 14ed3bf706..4d762017e9 100644 --- a/api/apps/sdk/files.py +++ b/api/apps/sdk/files.py @@ -148,6 +148,62 @@ async def upload(tenant_id): return server_error_response(e) +@manager.route("/file/upload_info", methods=["POST"]) # noqa: F821 +@token_required +async def upload_info(tenant_id): + """ + Upload runtime file metadata for SDK chat completions. + --- + tags: + - File + security: + - ApiKeyAuth: [] + parameters: + - in: formData + name: file + type: file + required: false + description: File(s) to upload as runtime attachments. + - in: query + name: url + type: string + required: false + description: Optional URL to fetch and convert into a runtime attachment. + responses: + 200: + description: Runtime attachment descriptor(s) for the `files` field in completions requests. + """ + files = await request.files + file_objs = files.getlist("file") if files and files.get("file") else [] + url = request.args.get("url") + + if file_objs and url: + return get_json_result( + data=False, + message="Provide either multipart file(s) or ?url=..., not both.", + code=RetCode.BAD_REQUEST, + ) + + if not file_objs and not url: + return get_json_result( + data=False, + message="Missing input: provide multipart file(s) or url", + code=RetCode.BAD_REQUEST, + ) + + try: + if url and not file_objs: + return get_json_result(data=FileService.upload_info(tenant_id, None, url)) + + if len(file_objs) == 1: + return get_json_result(data=FileService.upload_info(tenant_id, file_objs[0], None)) + + results = [FileService.upload_info(tenant_id, f) for f in file_objs] + return get_json_result(data=results) + except Exception as e: + return server_error_response(e) + + @manager.route('/file/create', methods=['POST']) # noqa: F821 @token_required async def create(tenant_id): diff --git a/api/db/services/conversation_service.py b/api/db/services/conversation_service.py index 3287ac1578..0a433b6926 100644 --- a/api/db/services/conversation_service.py +++ b/api/db/services/conversation_service.py @@ -158,6 +158,11 @@ async def async_completion(tenant_id, chat_id, question, name="New session", ses "role": "user", "id": str(uuid4()) } + + # Propagate runtime attachments so downstream chat flow can resolve file content. + if isinstance(kwargs.get("files"), list) and kwargs["files"]: + question["files"] = kwargs["files"] + conv.message.append(question) for m in conv.message: if m["role"] == "system": diff --git a/docs/references/http_api_reference.md b/docs/references/http_api_reference.md index 5cc930f57c..65a020ab77 100644 --- a/docs/references/http_api_reference.md +++ b/docs/references/http_api_reference.md @@ -6124,6 +6124,69 @@ Failure: --- +### Upload document + +**POST** `/api/v1/file/upload_info` + +Uploads a file and creates the respective document + +#### Request + +- Method: POST +- URL: `/api/v1/file/upload_info` +- Headers: + - `'Content-Type: multipart/form-data` + - `'Authorization: Bearer '` +- Form: + - `'file=@{FILE_PATH}'` + +##### Request example + +```bash +curl --request POST \ + --url http://{address}/api/v1/file/upload_info \ + --header 'Content-Type: multipart/form-data' \ + --header 'Authorization: Bearer ' \ + --form 'file=@./test1.pdf' +``` + +##### Request parameters + +- `'file'`: (*Form parameter*), `file`, *Required* + The file to upload. + +#### Response + +Success: + +```json +{ + "code": 0, + "data": { + "created_at": 1772451421.7924063, + "created by": "be951084066611f18f5f00155d2f98f4", + "extension": "pdf", + "id": "2143a03d162c11f1b80f00155d334d02", + "mime_type": "application/pdf", + "name": "test1.pdf", + "preview_url": null, + "size": 49705 + }, + "message": "success" +} +``` + +Failure: + +```json +{ + "code": 400, + "message": "Provide either multipart file(s) or ?url=...!" +} +``` + +--- + ### Create file or folder **POST** `/api/v1/file/create`