diff --git a/deepinsight/api/app.py b/deepinsight/api/app.py new file mode 100755 index 0000000000000000000000000000000000000000..ba978a979d221d1a052f2f7cba3251d45ac243a7 --- /dev/null +++ b/deepinsight/api/app.py @@ -0,0 +1,163 @@ +# Copyright (c) 2025 Huawei Technologies Co. Ltd. +# deepinsight is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +import os +import uuid +from datetime import datetime +from typing import Dict + +from flask import Flask, request, jsonify, Response, stream_with_context +from flask_cors import CORS + +from deepinsight.service.conversation import ConversationService +from deepinsight.service.deep_research import MessageType, DeepResearchService +from deepinsight.service.schemas.chat import GetChatHistoryStructure, GetChatHistoryRsp +from deepinsight.service.schemas.conversation import ( + ConversationListRsp, ConversationListMsg, ConversationListItem, + AddConversationRsp, AddConversationMsg +) + +# 读取环境变量中的 API 前缀 +API_PREFIX = os.getenv("API_PREFIX", "") + +# 创建 Flask 实例 +app = Flask(__name__) +_conversations: Dict[str, ConversationListItem] = {} + +# 跨域配置 +CORS(app, resources={r"/*": {"origins": "*"}}) + + +@app.route(f"{API_PREFIX}/api/chat", methods=['POST']) +def chat_stream(): + try: + body = request.get_json() + conversation_id = body.get("conversation_id", "") or str(uuid.uuid4()) + conversation_info = ConversationService.get_conversation_info(conversation_id) + if not conversation_info: + return jsonify({"error": "Conversation not found"}), 404 + + messages = body.get("messages", []) + if not isinstance(messages, list): + return jsonify({"error": "messages must be a list"}), 400 + + query = None + for item in reversed(messages): + if item.get('role') == MessageType.USER.value and item.get("content", None): + query = item.get("content") + + def generate(): + uuid_str = str(uuid.uuid4()) + for item in DeepResearchService.research(query=query, conversation_id=conversation_id, user_id=""): + resp = GetChatHistoryRsp( + code=0, + message="", + data=GetChatHistoryStructure( + id=uuid_str, + conversation_id=conversation_id, + user_id=conversation_info.user_id, + created_time=str(datetime.now()), + title=conversation_info.title, + status=conversation_info.status, + messages=item + ) + ).model_dump_json() + yield f"data: {resp}\n\n" + yield 'data: [DONE]\n\n' + + return Response(stream_with_context(generate()), mimetype="text/event-stream") + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route(f"{API_PREFIX}/api/conversations", methods=['GET']) +def get_conversation_list(): + try: + user_id = "test_user" + offset = int(request.args.get('offset', 0)) + limit = int(request.args.get('limit', 100)) + + conversation_list = ConversationService.get_list(user_id=user_id, offset=offset, limit=limit) + response = ConversationListRsp( + code=0, + message="OK", + data=ConversationListMsg(conversations=conversation_list) + ) + return jsonify(response.dict()) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route(f"{API_PREFIX}/api/conversation", methods=['POST']) +def add_conversation(): + try: + body = request.get_json() + new_conversation = ConversationService.add_conversation( + user_id="test_user", + title=body['title'], + conversation_id=body.get('conversation_id') + ) + + response = AddConversationRsp( + code=0, + message="OK", + data=AddConversationMsg( + conversationId=str(new_conversation.conversation_id), + created_time=str(new_conversation.created_time) + ) + ) + return jsonify(response.dict()) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route(f"{API_PREFIX}/api/conversation", methods=['DELETE']) +def delete_conversation(): + try: + data = request.get_json() + for cid in data['conversation_list']: + ConversationService.del_conversation(conversation_id=cid) + return jsonify({"code": 0, "message": "Deleted", "data": {}}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route(f"{API_PREFIX}/api/conversation", methods=['PUT']) +def rename_conversation(): + try: + data = request.get_json() + conversation, is_succeed = ConversationService.rename_conversation( + conversation_id=data['conversation_id'], + new_name=data['new_name'] + ) + if is_succeed: + return jsonify({"code": 0, "message": "Modified", "data": {"new_name": data['new_name']}}) + else: + return jsonify({"code": 100, "message": "Conversation Not Found", "data": {"new_name": data['new_name']}}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route(f"{API_PREFIX}/api/conversations//messages", methods=['GET']) +def get_conversation_messages(conversation_id): + try: + conversation_info = ConversationService.get_conversation_info(conversation_id) + history_present = ConversationService.get_history_messages(conversation_id) + new_data = GetChatHistoryStructure( + conversation_id=conversation_id, + user_id=conversation_info.user_id, + created_time=str(conversation_info.created_time), + title=conversation_info.title, + status=conversation_info.status, + messages=history_present + ) + return jsonify(GetChatHistoryRsp(code=0, message="ok", data=new_data).dict()) + except Exception as e: + return jsonify({"error": str(e)}), 500 \ No newline at end of file diff --git a/deepinsight/service/conversation.py b/deepinsight/service/conversation.py new file mode 100755 index 0000000000000000000000000000000000000000..df223ad79ebcc327cb8213a4deec66ffa5ac6772 --- /dev/null +++ b/deepinsight/service/conversation.py @@ -0,0 +1,94 @@ +# Copyright (c) 2025 Huawei Technologies Co. Ltd. +# deepinsight is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. + +from typing import List + +from deepinsight.service.deep_research import DeepResearchService +from deepinsight.service.schemas.chat import ServiceMessage +from deepinsight.service.schemas.conversation import ConversationListItem +from deepinsight.stores.postgresql.database import get_database_session +from deepinsight.stores.postgresql.repositories.conversation_repository import ConversationRepository +from deepinsight.stores.postgresql.repositories.message_repository import MessageRepository +from deepinsight.stores.postgresql.repositories.report_repository import ReportRepository + + +class ConversationService: + @classmethod + def get_list(cls, user_id, offset: int = 0, limit: int = 100): + db = get_database_session() + conversation_repo = ConversationRepository(db) + conversation_list = conversation_repo.get_by_user_id(user_id=user_id, offset=offset, limit=limit) + conv_item_list = [] + for conv in conversation_list: + conv_item_list.append(ConversationListItem( + conversationId=str(conv.conversation_id), + title=conv.title, + createdTime=str(conv.created_time) + ) + ) + return conv_item_list + + @classmethod + def del_conversation(cls, conversation_id): + db = get_database_session() + conversation_repo = ConversationRepository(db) + message_repo = MessageRepository(db) + report_repo = ReportRepository(db) + message_repo.delete_by_conversation_id(conversation_id=conversation_id) + report_repo.delete_by_conversation_id(conversation_id=conversation_id) + conversation_repo.delete_conversation(conversation_id=conversation_id) + + @classmethod + def rename_conversation(cls, conversation_id, new_name): + db = get_database_session() + conversation_repo = ConversationRepository(db) + return conversation_repo.update_title(conversation_id=conversation_id, new_title=new_name) + + @classmethod + def add_conversation(cls, user_id, title, conversation_id): + db = get_database_session() + conversation_repo = ConversationRepository(db) + saved_conversation = conversation_repo.create_conversation(user_id, title, conversation_id) + return saved_conversation + + @classmethod + def get_conversation_info(cls, conversation_id_str: str): + db = get_database_session + repository = ConversationRepository(db) + return repository.get_by_id(conversation_id_str) + + @classmethod + def get_history_messages(cls, conversation_id_str: str) -> List[ServiceMessage]: + db = get_database_session() + repository = MessageRepository(db) + messages_from_db = repository.get_by_conversation_id(conversation_id_str) + + processed_messages = [] + for msg in messages_from_db: + content_to_use = msg.content + if msg.type == "report": + processed_report = DeepResearchService.get_report_and_thought_by_message_id(msg.message_id) + content_to_use = processed_report.thought.messages + [processed_report.report] + + # @TODO: 还差updated time + + processed_message = ServiceMessage( + id=str(msg.message_id), + content=content_to_use, + role=msg.type, + created_at=msg.created_time.isoformat() if msg.created_time else None + ) + + processed_messages.append(processed_message) + return processed_messages + + +if __name__ == '__main__': + print(ConversationService.add_conversation(user_id="1"))