Add backward compat APIs (#14427)

### What problem does this PR solve?

Add backward compat APIs:

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
Wang Qi
2026-04-29 15:15:49 +08:00
committed by GitHub
parent c08ced09a7
commit b684c89950
7 changed files with 475 additions and 13 deletions

View File

@@ -301,6 +301,10 @@ client_urls_prefix = [
register_page(path) for directory in pages_dir for path in search_pages_path(directory)
]
# Register backward compatibility routes for deprecated APIs
from api.apps.backward_compat import register_backward_compat_routes
register_backward_compat_routes(app)
@app.errorhandler(404)
async def not_found(error):

384
api/apps/backward_compat.py Normal file
View File

@@ -0,0 +1,384 @@
#
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Backward compatibility layer for deprecated API endpoints.
This module adds support for old API routes that were deprecated during the
RESTful API migration. Each deprecated route forwards to the corresponding
new API implementation.
Deprecated APIs and their replacements:
- POST /api/v1/chats/{chat_id}/completions -> POST /api/v1/chat/completions
- POST /api/v1/chats_openai/{chat_id}/chat/completions -> POST /api/v1/openai/{chat_id}/chat/completions
- PUT /api/v1/chats/{chat_id}/sessions/{session_id} -> PATCH /api/v1/chats/{chat_id}/sessions/{session_id}
- DELETE /api/v1/chats -> DELETE /api/v1/chats/{chat_id} (with body)
- GET /api/v1/file/* -> GET /api/v1/files*
- POST /api/v1/file/* -> POST /api/v1/files*
- POST /api/v1/sessions/related_questions -> POST /api/v1/chat/recommandation
- PUT (chunk update) -> PATCH (chunk update)
"""
import logging
from quart import Blueprint, request
from api.apps import login_required
from api.apps.restful_apis import chat_api, file_api, chunk_api, openai_api, document_api
from api.apps.services import file_api_service
from api.utils.api_utils import get_data_error_result, get_json_result, add_tenant_id_to_kwargs
manager = Blueprint("backward_compat", __name__)
# =============================================================================
# Chat Completion APIs
# =============================================================================
@manager.route("/chats/<chat_id>/completions", methods=["POST"])
@login_required
async def deprecated_chat_completions(chat_id):
"""
Deprecated: Use POST /api/v1/chat/completions instead.
Old path: POST /api/v1/chats/{chat_id}/completions
New path: POST /api/v1/chat/completions
"""
logging.warning(
"API endpoint /api/v1/chats/%s/completions is deprecated. "
"Please use /api/v1/chat/completions instead.",
chat_id,
)
# Forward to the new API implementation
return await chat_api.session_completion(chat_id)
@manager.route("/chats_openai/<chat_id>/chat/completions", methods=["POST"])
@login_required
async def deprecated_openai_chat_completions(chat_id):
"""
Deprecated: Use POST /api/v1/openai/{chat_id}/chat/completions instead.
Old path: POST /api/v1/chats_openai/{chat_id}/chat/completions
New path: POST /api/v1/openai/{chat_id}/chat/completions
"""
logging.warning(
"API endpoint /api/v1/chats_openai/%s/chat/completions is deprecated. "
"Please use /api/v1/openai/%s/chat/completions instead.",
chat_id, chat_id,
)
# Forward to the new API implementation
return await openai_api.openai_chat_completions(chat_id)
# =============================================================================
# Chat Session APIs
# =============================================================================
@manager.route("/chats/<chat_id>/sessions/<session_id>", methods=["PUT"])
@login_required
async def deprecated_update_session(chat_id, session_id):
"""
Deprecated: Use PATCH /api/v1/chats/{chat_id}/sessions/{session_id} instead.
Old path: PUT /api/v1/chats/{chat_id}/sessions/{session_id}
New path: PATCH /api/v1/chats/{chat_id}/sessions/{session_id}
"""
logging.warning(
"API endpoint PUT /api/v1/chats/%s/sessions/%s is deprecated. "
"Please use PATCH /api/v1/chats/%s/sessions/%s instead.",
chat_id, session_id, chat_id, session_id,
)
# Forward to the new API implementation
return await chat_api.patch_session(chat_id, session_id)
# =============================================================================
# File APIs (Old /api/v1/file/* -> New /api/v1/files*)
# =============================================================================
@manager.route("/file/get/<file_id>", methods=["GET"])
@login_required
async def deprecated_file_get(file_id):
"""
Deprecated: Use GET /api/v1/files/{file_id} instead.
Old path: GET /api/v1/file/get/{file_id}
New path: GET /api/v1/files/{file_id}
"""
logging.warning(
"API endpoint /api/v1/file/get/%s is deprecated. "
"Please use /api/v1/files/%s instead.",
file_id, file_id,
)
# Forward to the new API implementation (download)
return await file_api.download(file_id=file_id)
@manager.route("/file/list", methods=["GET"])
@login_required
async def deprecated_file_list():
"""
Deprecated: Use GET /api/v1/files instead.
Old path: GET /api/v1/file/list?...
New path: GET /api/v1/files?...
"""
logging.warning(
"API endpoint /api/v1/file/list is deprecated. "
"Please use /api/v1/files instead."
)
# Forward to the new API implementation
return await file_api.list_files()
@manager.route("/file/all_parent_folder", methods=["GET"])
@login_required
async def deprecated_file_all_parent_folder():
"""
Deprecated: Use GET /api/v1/files/{file_id}/ancestors instead.
Old path: GET /api/v1/file/all_parent_folder?file_id=...
New path: GET /api/v1/files/{file_id}/ancestors
"""
file_id = request.args.get("file_id")
if not file_id:
return get_data_error_result(message="`file_id` query parameter is required")
logging.warning(
"API endpoint /api/v1/file/all_parent_folder is deprecated. "
"Please use /api/v1/files/%s/ancestors instead.",
file_id,
)
# Forward to the new API implementation
return await file_api.ancestors(file_id=file_id)
@manager.route("/file/parent_folder", methods=["GET"])
@login_required
async def deprecated_file_parent_folder():
"""
Deprecated: Use GET /api/v1/files/{file_id}/parent instead.
Old path: GET /api/v1/file/parent_folder?file_id=...
New path: GET /api/v1/files/{file_id}/parent
"""
file_id = request.args.get("file_id")
if not file_id:
return get_data_error_result(message="`file_id` query parameter is required")
logging.warning(
"API endpoint /api/v1/file/parent_folder is deprecated. "
"Please use /api/v1/files/%s/parent instead.",
file_id,
)
# Forward to the new API implementation
return await file_api.parent_folder(file_id=file_id)
@manager.route("/file/root_folder", methods=["GET"])
@login_required
async def deprecated_file_root_folder():
"""
Deprecated: Root folder is now accessible via GET /api/v1/files with parent_id=...
Old path: GET /api/v1/file/root_folder
New path: GET /api/v1/files?parent_id=<root_id>
"""
logging.warning(
"API endpoint /api/v1/file/root_folder is deprecated. "
"Please use /api/v1/files with appropriate parent_id instead."
)
# Forward to the new API implementation with empty parent_id to get root
return await file_api.list_files()
@manager.route("/file/create", methods=["POST"])
@login_required
@add_tenant_id_to_kwargs
async def deprecated_file_create(tenant_id=None):
"""
Deprecated: Use POST /api/v1/files instead.
Old path: POST /api/v1/file/create
New path: POST /api/v1/files
"""
logging.warning(
"API endpoint /api/v1/file/create is deprecated. "
"Please use POST /api/v1/files instead."
)
# Forward to the new API implementation
return await file_api.create_or_upload(tenant_id=tenant_id)
@manager.route("/file/upload", methods=["POST"])
@login_required
@add_tenant_id_to_kwargs
async def deprecated_file_upload(tenant_id=None):
"""
Deprecated: Use POST /api/v1/files (with multipart/form-data) instead.
Old path: POST /api/v1/file/upload
New path: POST /api/v1/files
"""
logging.warning(
"API endpoint /api/v1/file/upload is deprecated. "
"Please use POST /api/v1/files with multipart/form-data instead."
)
# Forward to the new API implementation
return await file_api.create_or_upload(tenant_id=tenant_id)
@manager.route("/file/mv", methods=["POST"])
@login_required
@add_tenant_id_to_kwargs
async def deprecated_file_mv(tenant_id=None):
"""
Deprecated: Use POST /api/v1/files/move instead.
Old path: POST /api/v1/file/mv
New path: POST /api/v1/files/move
"""
logging.warning(
"API endpoint /api/v1/file/mv is deprecated. "
"Please use POST /api/v1/files/move instead."
)
# Forward to the new API implementation
return await file_api.move(tenant_id=tenant_id)
@manager.route("/file/rename", methods=["POST"])
@login_required
@add_tenant_id_to_kwargs
async def deprecated_file_rename(tenant_id=None):
"""
Deprecated: Use POST /api/v1/files/move with new_name instead.
Old path: POST /api/v1/file/rename
New path: POST /api/v1/files/move
"""
logging.warning(
"API endpoint /api/v1/file/rename is deprecated. "
"Please use POST /api/v1/files/move with `new_name` instead."
)
# Transform the old API format to new format
req = await request.get_json()
# Old API used `file_id` and `name`, new API uses `src_file_ids` and `new_name`
src_file_ids = [req.get("file_id")]
new_name = req.get("name")
# Call the underlying service directly with transformed data
try:
success, result = await file_api_service.move_files(
tenant_id, src_file_ids, None, new_name
)
if success:
return get_json_result(data=result)
else:
return get_data_error_result(message=result)
except Exception as e:
logging.exception(e)
return get_data_error_result(message="Internal server error")
@manager.route("/file/rm", methods=["POST"])
@login_required
@add_tenant_id_to_kwargs
async def deprecated_file_rm(tenant_id=None):
"""
Deprecated: Use DELETE /api/v1/files instead.
Old path: POST /api/v1/file/rm
New path: DELETE /api/v1/files
"""
logging.warning(
"API endpoint /api/v1/file/rm is deprecated. "
"Please use DELETE /api/v1/files instead."
)
# Transform POST with body to DELETE behavior
# The new API expects a JSON body with `ids`
return await file_api.delete(tenant_id=tenant_id)
# =============================================================================
# Related Questions API
# =============================================================================
@manager.route("/sessions/related_questions", methods=["POST"])
@login_required
async def deprecated_related_questions():
"""
Deprecated: Use POST /api/v1/chat/recommandation instead.
Old path: POST /api/v1/sessions/related_questions
New path: POST /api/v1/chat/recommandation
"""
logging.warning(
"API endpoint /api/v1/sessions/related_questions is deprecated. "
"Please use /api/v1/chat/recommandation instead."
)
# Forward to the new API implementation
return await chat_api.recommandation()
# =============================================================================
# Chunk Update API (PUT -> PATCH)
# =============================================================================
@manager.route("/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>", methods=["PUT"])
@login_required
async def deprecated_update_chunk(dataset_id, document_id, chunk_id):
"""
Deprecated: Use PATCH /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id} instead.
Old path: PUT /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}
New path: PATCH /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}
"""
logging.warning(
"API endpoint PUT /api/v1/datasets/%s/documents/%s/chunks/%s is deprecated. "
"Please use PATCH instead.",
dataset_id, document_id, chunk_id,
)
# Forward to the new API implementation
return await chunk_api.patch_chunk(dataset_id, document_id, chunk_id)
# =============================================================================
# File Upload Info API
# =============================================================================
@manager.route("/file/upload_info", methods=["POST"])
@login_required
async def deprecated_file_upload_info():
"""
Deprecated: Use POST /api/v1/documents/upload instead.
Old path: POST /api/v1/file/upload_info
New path: POST /api/v1/documents/upload
"""
from api.apps import current_user
logging.warning(
"API endpoint /api/v1/file/upload_info is deprecated. "
"Please use POST /api/v1/documents/upload instead."
)
# Forward to the new API implementation
# Need to pass tenant_id explicitly since we're calling the function directly
tenant_id = current_user.id
return await document_api.upload_info(tenant_id=tenant_id)
def register_backward_compat_routes(app_instance):
"""
Register all backward compatibility routes with the app.
"""
app_instance.register_blueprint(manager, url_prefix="/api/v1")
logging.info("Backward compatibility routes registered successfully.")

View File

@@ -608,6 +608,15 @@ async def bulk_delete_chats():
if not ids:
return get_json_result(data={})
else:
# keep backward compatibility, DELETE with chat_id in request body
chat_id = req.get("chat_id")
if chat_id:
try:
if not DialogService.update_by_id(chat_id, {"status": StatusEnum.INVALID.value}):
return get_data_error_result(message=f"Failed to delete chat {chat_id}")
return get_json_result(data=True)
except Exception as ex:
return server_error_response(ex)
return get_json_result(data={})
errors = []
@@ -1017,7 +1026,7 @@ async def recommendation():
@manager.route("/chat/completions", methods=["POST"]) # noqa: F821
@login_required
@validate_request("messages")
async def session_completion():
async def session_completion(chat_id_in_arg=""):
req = await get_request_json()
msg = []
for m in req["messages"]:
@@ -1028,6 +1037,7 @@ async def session_completion():
msg.append(m)
message_id = msg[-1].get("id") if msg else None
chat_id = req.pop("chat_id", "") or ""
chat_id = chat_id or chat_id_in_arg
session_id = req.pop("session_id", "") or ""
chat_model_id = req.pop("llm_id", "")

View File

@@ -99,7 +99,7 @@ async def create_or_upload(tenant_id: str = None):
@manager.route("/files", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
def list_files(tenant_id: str = None):
async def list_files(tenant_id: str = None):
"""
List files under a folder.
---
@@ -303,7 +303,7 @@ async def download(tenant_id: str = None, file_id: str = None):
@manager.route("/files/<file_id>/parent", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
def parent_folder(tenant_id: str = None, file_id: str = None):
async def parent_folder(tenant_id: str = None, file_id: str = None):
"""
Get parent folder of a file.
---
@@ -334,7 +334,7 @@ def parent_folder(tenant_id: str = None, file_id: str = None):
@manager.route("/files/<file_id>/ancestors", methods=["GET"]) # noqa: F821
@login_required
@add_tenant_id_to_kwargs
def ancestors(tenant_id: str = None, file_id: str = None):
async def ancestors(tenant_id: str = None, file_id: str = None):
"""
Get all ancestor folders of a file.
---