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) | |