From c0bba14ee841d8bbd5f817783cc243afaeaff494 Mon Sep 17 00:00:00 2001 From: wanjia Date: Thu, 20 Mar 2025 13:48:36 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E7=94=A8api=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- role_based_system/settings.py | 2 +- user_management/views.py | 277 ++++++++++++++++++++++++++++++---- 2 files changed, 246 insertions(+), 33 deletions(-) diff --git a/role_based_system/settings.py b/role_based_system/settings.py index 1fe78058..9a12460e 100644 --- a/role_based_system/settings.py +++ b/role_based_system/settings.py @@ -14,7 +14,7 @@ import os from pathlib import Path # API 配置 -API_BASE_URL = 'http://81.69.223.133:48329' +API_BASE_URL = 'http://180.163.88.62:30331' DEPARTMENT_GROUPS = { "技术部": ["开发组", "测试组", "运维组"], diff --git a/user_management/views.py b/user_management/views.py index f2b19adf..8cfcc1dd 100644 --- a/user_management/views.py +++ b/user_management/views.py @@ -40,6 +40,8 @@ from django.utils.decorators import method_decorator import uuid from rest_framework import serializers import traceback +import requests +import json @@ -178,55 +180,145 @@ class ChatHistoryViewSet(viewsets.ModelViewSet): """创建聊天记录""" try: data = request.data - required_fields = ['dataset_id', 'dataset_name', 'question', 'answer'] - - # 检查必填字段 - for field in required_fields: - if field not in data: - return Response({ - 'code': 400, - 'message': f'缺少必填字段: {field}', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) - - # 获取或创建对话ID - conversation_id = data.get('conversation_id', str(uuid.uuid4())) - - # 获取知识库 - 不进行 UUID 转换 - try: - knowledge_base = KnowledgeBase.objects.filter(id=data['dataset_id']).first() - if not knowledge_base: - return Response({ - 'code': 404, - 'message': '知识库不存在', - 'data': None - }, status=status.HTTP_404_NOT_FOUND) - except Exception as e: + + # 检查必填字段 - 支持单知识库或多知识库模式 + if 'question' not in data: return Response({ 'code': 400, - 'message': f'无效的知识库ID: {str(e)}', + 'message': '缺少必填字段: question', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 检查知识库ID:支持dataset_id或dataset_id_list格式 + dataset_ids = [] + if 'dataset_id' in data: + dataset_ids.append(data['dataset_id']) + elif 'dataset_id_list' in data and isinstance(data['dataset_id_list'], list): + dataset_ids = data['dataset_id_list'] + else: + return Response({ + 'code': 400, + 'message': '缺少必填字段: dataset_id 或 dataset_id_list', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + if not dataset_ids: + return Response({ + 'code': 400, + 'message': '至少需要提供一个知识库ID', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + # 验证所有知识库并收集external_ids + external_id_list = [] + user = request.user + primary_knowledge_base = None # 主知识库,用于关联聊天记录 + + for idx, kb_id in enumerate(dataset_ids): + try: + knowledge_base = KnowledgeBase.objects.filter(id=kb_id).first() + if not knowledge_base: + return Response({ + 'code': 404, + 'message': f'知识库不存在: {kb_id}', + 'data': None + }, status=status.HTTP_404_NOT_FOUND) + + # 保存第一个知识库作为主知识库 + if idx == 0: + primary_knowledge_base = knowledge_base + + # 检查知识库权限 + can_read = False + + # 检查权限表 + permission = KBPermissionModel.objects.filter( + knowledge_base=knowledge_base, + user=user, + can_read=True, + status='active' + ).first() + + if permission: + can_read = True + else: + # 使用_can_read方法判断 + can_read = self._can_read( + type=knowledge_base.type, + user=user, + department=knowledge_base.department, + group=knowledge_base.group, + creator_id=knowledge_base.user_id + ) + + if not can_read: + return Response({ + 'code': 403, + 'message': f'无权访问知识库: {knowledge_base.name}', + 'data': None + }, status=status.HTTP_403_FORBIDDEN) + + # 添加知识库的external_id到列表 + if knowledge_base.external_id: + external_id_list.append(knowledge_base.external_id) + else: + logger.warning(f"知识库 {knowledge_base.id} ({knowledge_base.name}) 没有external_id") + + except Exception as e: + return Response({ + 'code': 400, + 'message': f'处理知识库ID出错: {str(e)}', + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + if not external_id_list: + return Response({ + 'code': 400, + 'message': '没有有效的知识库external_id', 'data': None }, status=status.HTTP_400_BAD_REQUEST) - # 创建用户问题记录 + # 获取或创建对话ID + conversation_id = data.get('conversation_id', str(uuid.uuid4())) + + # 调用外部API获取答案 (传递多个knowledge base的external_id) + answer = self._get_answer_from_external_api( + dataset_external_id_list=external_id_list, + question=data['question'] + ) + + if not answer: + return Response({ + 'code': 500, + 'message': '获取AI回答失败', + 'data': None + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + # 创建用户问题记录 (关联到主知识库) question_record = ChatHistory.objects.create( user=request.user, - knowledge_base=knowledge_base, + knowledge_base=primary_knowledge_base, conversation_id=conversation_id, role='user', content=data['question'], - metadata={'model_name': data.get('model_name', 'default')} + metadata={ + 'model_id': data.get('model_id', '58c5deb4-f2e2-11ef-9a1b-0242ac120009'), + 'dataset_id_list': dataset_ids + } ) # 创建AI回答记录 answer_record = ChatHistory.objects.create( user=request.user, - knowledge_base=knowledge_base, + knowledge_base=primary_knowledge_base, conversation_id=conversation_id, parent_id=str(question_record.id), role='assistant', - content=data['answer'], - metadata={'model_name': data.get('model_name', 'default')} + content=answer, + metadata={ + 'model_id': data.get('model_id', '58c5deb4-f2e2-11ef-9a1b-0242ac120009'), + 'dataset_id_list': dataset_ids + } ) return Response({ @@ -235,7 +327,8 @@ class ChatHistoryViewSet(viewsets.ModelViewSet): 'data': { 'id': answer_record.id, 'conversation_id': conversation_id, - 'dataset_id': str(knowledge_base.id), + 'dataset_id': str(primary_knowledge_base.id), + 'dataset_name': primary_knowledge_base.name, 'role': 'assistant', 'content': answer_record.content, 'created_at': answer_record.created_at.strftime('%Y-%m-%d %H:%M:%S') @@ -250,6 +343,126 @@ class ChatHistoryViewSet(viewsets.ModelViewSet): 'message': f'创建聊天记录失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def _get_answer_from_external_api(self, dataset_external_id_list, question): + """调用外部API获取AI回答""" + try: + # 确保所有ID都是字符串 + dataset_external_ids = [str(id) if isinstance(id, uuid.UUID) else id for id in dataset_external_id_list] + + logger.info(f"准备调用外部API,知识库ID列表: {dataset_external_ids}") + + # 第一个API调用创建聊天 + chat_request_data = { + "id": "d333dee2-b3c2-11ef-af2c-a4bb6dafa942", + "model_id": "58c5deb4-f2e2-11ef-9a1b-0242ac120009", + "dataset_id_list": dataset_external_ids, + "multiple_rounds_dialogue": False, + "dataset_setting": { + "top_n": 10, + "similarity": "0.3", + "max_paragraph_char_number": 10000, + "search_mode": "blend", + "no_references_setting": { + "value": "{question}", + "status": "ai_questioning" + } + }, + "model_setting": { + "prompt": "**相关文档内容**:{data} **回答要求**:如果相关文档内容中没有可用信息,请回答\"没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作\"。请根据相关文档内容回答用户问题。不要输出与用户问题无关的内容。请使用中文回答客户问题。**用户问题**:{question}" + }, + "problem_optimization": False + } + + logger.info(f"发送创建聊天请求:{settings.API_BASE_URL}/api/application/chat/open") + + try: + # 测试JSON序列化,提前捕获可能的错误 + json_data = json.dumps(chat_request_data) + logger.debug(f"请求数据序列化成功,长度: {len(json_data)}") + except TypeError as e: + logger.error(f"JSON序列化失败: {str(e)}") + return None + + chat_response = requests.post( + url=f"{settings.API_BASE_URL}/api/application/chat/open", + json=chat_request_data, + headers={"Content-Type": "application/json"}, + timeout=30 + ) + + logger.info(f"API响应状态码: {chat_response.status_code}") + + if chat_response.status_code != 200: + logger.error(f"外部API调用失败: {chat_response.text}") + return None + + chat_data = chat_response.json() + logger.debug(f"API响应数据: {chat_data}") + + if chat_data.get('code') != 200 or not chat_data.get('data'): + logger.error(f"外部API返回错误: {chat_data}") + return None + + chat_id = chat_data['data'] + logger.info(f"聊天创建成功,chat_id: {chat_id}") + + # 第二个API调用发送消息 + message_request_data = { + "message": question, + "re_chat": False, + "stream": True + } + + logger.info(f"发送聊天消息请求: {settings.API_BASE_URL}/api/application/chat_message/{chat_id}") + message_response = requests.post( + url=f"{settings.API_BASE_URL}/api/application/chat_message/{chat_id}", + json=message_request_data, + headers={"Content-Type": "application/json"}, + stream=True, + timeout=60 + ) + + if message_response.status_code != 200: + logger.error(f"外部API聊天消息调用失败: {message_response.status_code}, {message_response.text}") + return None + + # 拼接流式响应 - 修复SSE格式解析 + full_content = "" + try: + for line in message_response.iter_lines(): + if line: + line_text = line.decode('utf-8') + # 处理SSE格式 (data: {...}) + if line_text.startswith('data: '): + json_str = line_text[6:] # 去掉 "data: " 前缀 + logger.debug(f"处理SSE数据: {json_str}") + try: + chunk = json.loads(json_str) + if 'content' in chunk: + content_part = chunk['content'] + full_content += content_part + logger.debug(f"追加内容: '{content_part}'") + if chunk.get('is_end', False): + logger.debug("收到结束标记") + except json.JSONDecodeError as e: + logger.error(f"JSON解析错误: {str(e)}, 原始数据: {json_str}") + else: + logger.debug(f"收到非SSE格式数据: {line_text}") + except Exception as e: + logger.error(f"处理流式响应出错: {str(e)}") + if full_content: + logger.info(f"已接收部分内容: {len(full_content)} 字符") + return full_content.strip() + return None + + logger.info(f"聊天回答拼接完成,总长度: {len(full_content)}") + return full_content.strip() if full_content else "未能获取到有效回答" + + except Exception as e: + logger.error(f"调用外部API获取回答失败: {str(e)}") + logger.error(traceback.format_exc()) + return None def update(self, request, pk=None): """更新聊天记录"""