Spaces:
Running
Running
| # app.py | |
| import os | |
| import threading | |
| from functools import wraps | |
| import google.generativeai as genai | |
| from flask import Flask, request, jsonify, send_from_directory | |
| from flask_cors import CORS | |
| from dotenv import load_dotenv | |
| import chromadb | |
| # 从您的核心逻辑文件中导入类 | |
| from app_chromadb import MarkdownKnowledgeBase | |
| # --- 初始化与配置 --- | |
| load_dotenv() | |
| app = Flask(__name__, static_folder='.', static_url_path='') | |
| CORS(app) | |
| # --- API 密钥认证配置 --- | |
| VALID_API_KEYS_STR = os.environ.get("KNOWLEDGE_BASE_API_KEYS", "") | |
| VALID_API_KEYS = {key.strip() for key in VALID_API_KEYS_STR.split(',') if key.strip()} | |
| if not VALID_API_KEYS: | |
| print("⚠️ 警告: 未配置 KNOWLEDGE_BASE_API_KEYS。API 将对所有人开放!") | |
| # --- ChromaDB, Gemini, SiliconFlow 实例配置 --- | |
| try: | |
| CHROMA_DATA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "chroma_db") | |
| COLLECTION_NAME = "markdown_knowledge_base_m3" | |
| chroma_client = chromadb.PersistentClient(path=CHROMA_DATA_PATH) | |
| collection = chroma_client.get_or_create_collection(name=COLLECTION_NAME) | |
| print(f"✅ ChromaDB 客户端已连接,数据存储在 '{CHROMA_DATA_PATH}'") | |
| except Exception as e: | |
| chroma_client = None | |
| collection = None | |
| print(f"❌ 初始化 ChromaDB 失败: {e}") | |
| try: | |
| GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| gemini_model = genai.GenerativeModel('gemini-1.5-flash') | |
| print("✅ Gemini API 已配置。") | |
| except Exception as e: | |
| gemini_model = None | |
| print(f"❌ Gemini API 配置失败: {e}") | |
| try: | |
| SF_API_TOKEN = os.environ.get("SILICONFLOW_API_TOKEN") | |
| kb_instance = MarkdownKnowledgeBase(api_token=SF_API_TOKEN, chroma_collection=collection) | |
| print("✅ SiliconFlow 与知识库实例已配置。") | |
| except Exception as e: | |
| kb_instance = None | |
| print(f"❌ 知识库实例配置失败: {e}") | |
| kb_status = { "is_building": False } | |
| # --- API 密钥认证装饰器 --- | |
| def require_api_key(f): | |
| def decorated_function(*args, **kwargs): | |
| if not VALID_API_KEYS: return f(*args, **kwargs) | |
| api_key = request.headers.get('X-API-Key') | |
| if api_key and api_key in VALID_API_KEYS: | |
| return f(*args, **kwargs) | |
| else: | |
| return jsonify({"error": "授权失败。请提供有效'X-API-Key'请求头。"}), 403 | |
| return decorated_function | |
| # --- 前端页面路由 --- | |
| def serve_index(): | |
| return send_from_directory('.', 'index.html') | |
| # --- API 端点 --- | |
| def get_status(): | |
| if collection: | |
| kb_status['total_items'] = collection.count() | |
| kb_status['is_built'] = kb_status['total_items'] > 0 | |
| if not kb_status['is_building']: | |
| kb_status['message'] = f"知识库已就绪,共有 {kb_status['total_items']} 个条目。" | |
| else: | |
| kb_status['message'] = "ChromaDB 未连接。" | |
| return jsonify(kb_status) | |
| def build_knowledge_base(): | |
| if kb_status['is_building']: | |
| return jsonify({"error": "知识库已在构建中,请稍后。"}), 409 | |
| if not kb_instance: | |
| return jsonify({"error": "知识库实例未初始化,无法构建。"}), 500 | |
| data = request.get_json() | |
| clear_existing = data.get('clear_existing', False) | |
| build_params = { | |
| 'folder_path': data.get('folder_path'), | |
| 'chunk_size': data.get('chunk_size', 4096), | |
| 'overlap': data.get('overlap', 400), | |
| 'max_files': data.get('max_files', 500), | |
| 'sample_mode': data.get('sample_mode', 'largest') | |
| } | |
| def build_in_background(): | |
| global kb_status, collection | |
| kb_status['is_building'] = True | |
| kb_status['message'] = "构建任务开始..." | |
| try: | |
| if clear_existing and chroma_client: | |
| print(f"正在清空现有集合: {COLLECTION_NAME}") | |
| chroma_client.delete_collection(name=COLLECTION_NAME) | |
| collection = chroma_client.get_or_create_collection(name=COLLECTION_NAME) | |
| kb_instance.collection = collection | |
| print("集合已清空并重建。") | |
| kb_instance.build_knowledge_base(**build_params) | |
| kb_status['message'] = f"构建完成!知识库现有 {collection.count()} 个条目。" | |
| except Exception as e: | |
| kb_status['message'] = f"构建时出错: {e}" | |
| print(f"Error during build: {e}") | |
| finally: | |
| kb_status['is_building'] = False | |
| thread = threading.Thread(target=build_in_background) | |
| thread.start() | |
| return jsonify({"message": "知识库构建任务已在后台启动。"}), 202 | |
| def search_in_kb(): | |
| if not (collection and collection.count() > 0): | |
| return jsonify({"error": "知识库为空,请先构建。"}), 400 | |
| if not kb_instance: | |
| return jsonify({"error": "知识库实例未初始化,无法搜索。"}), 500 | |
| query = request.args.get('query') | |
| top_k = request.args.get('top_k', default=5, type=int) | |
| if not query: | |
| return jsonify({"error": "必须提供 'query' 参数"}), 400 | |
| try: | |
| results = kb_instance.search(query, top_k=top_k) | |
| return jsonify(results) | |
| except Exception as e: | |
| return jsonify({"error": f"搜索时发生错误: {e}"}), 500 | |
| def summarize_results(): | |
| if not gemini_model: | |
| return jsonify({"error": "Gemini API 未配置或初始化失败。"}), 500 | |
| data = request.get_json() | |
| query = data.get('query') | |
| search_results = data.get('results') | |
| if not query or not search_results: | |
| return jsonify({"error": "必须提供查询和搜索结果。"}), 400 | |
| context = "\n\n---\n\n".join([item['content'] for item in search_results]) | |
| prompt = f""" | |
| 根据以下本地知识库中搜索到的内容,请用清晰、简洁的中文直接回答用户的问题。 | |
| 如果内容不足以回答,请说明现有信息无法直接回答。 | |
| 用户问题: "{query}" | |
| 搜索到的内容: | |
| --- | |
| {context} | |
| --- | |
| 你的回答: | |
| """ | |
| try: | |
| print(f"正在向 Gemini 模型 '{gemini_model.model_name}' 发送请求...") | |
| response = gemini_model.generate_content(prompt) | |
| summary = response.text | |
| return jsonify({"summary": summary}) | |
| except Exception as e: | |
| print(f"调用 Gemini API 时出错: {e}") | |
| return jsonify({"error": f"调用 AI 服务时出错: {e}"}), 500 | |
| if __name__ == '__main__': | |
| print("知识库后端服务 (最终版) 启动...") | |
| print("✅ 服务已启动!请在浏览器中打开 http://127.0.0.1:5000") | |
| # 使用生产级服务器时应移除 debug=False | |
| app.run(host='0.0.0.0', port=5000, debug=False) | |