""" FastAPI 主服务 - AI 客服系统 """ import os import sys import uuid from pathlib import Path from fastapi import FastAPI, Request, UploadFile, File from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pydantic import BaseModel import uvicorn # 确保可以导入 app 模块 sys.path.insert(0, str(Path(__file__).parent)) from config import HOST, PORT, BASE_DIR from chatbot import get_chatbot from knowledge import init_knowledge_base, get_knowledge_base # 创建 FastAPI 应用 app = FastAPI( title="AI 客服系统", description="基于 LangChain + LangGraph 的智能客服", version="1.0.0" ) # 静态文件和模板 app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) # 修复 Jinja2 模板缓存问题 templates.env.cache.clear() if hasattr(templates.env, 'cache') and hasattr(templates.env.cache, 'clear') else None # 数据模型 class ChatRequest(BaseModel): message: str session_id: str = None class ChatResponse(BaseModel): response: str session_id: str # 路由 @app.get("/", response_class=HTMLResponse) async def index(request: Request): """聊天界面""" # 生成新会话ID session_id = str(uuid.uuid4())[:8] # 直接读取 HTML 文件 html_path = BASE_DIR / "templates" / "index.html" with open(html_path, "r", encoding="utf-8") as f: html_content = f.read() # 替换 session_id html_content = html_content.replace('{{ session_id }}', session_id) return HTMLResponse(content=html_content) @app.post("/chat", response_model=ChatResponse) async def chat(request: ChatRequest): """发送消息""" chatbot = get_chatbot() # 如果没有 session_id,生成一个 session_id = request.session_id or str(uuid.uuid4())[:8] # 获取回复 response = await chatbot.chat(request.message, session_id) return ChatResponse(response=response, session_id=session_id) @app.get("/history/{session_id}") async def get_history(session_id: str): """获取对话历史""" chatbot = get_chatbot() history = chatbot.get_history(session_id) return {"session_id": session_id, "history": history} @app.post("/upload") async def upload_file(file: UploadFile = File(...)): """上传知识库文档""" from config import KNOWLEDGE_DIR from knowledge import KnowledgeBase from langchain.schema import Document # 保存文件 file_path = KNOWLEDGE_DIR / file.filename with open(file_path, "wb") as f: content = await file.read() f.write(content) # 加载并添加到知识库 kb = get_knowledge_base() # 简单文本加载 if file.filename.endswith(".txt") or file.filename.endswith(".md"): with open(file_path, "r", encoding="utf-8") as f: text = f.read() doc = Document(page_content=text, metadata={"source": file.filename}) kb.add_documents([doc]) return {"message": f"✅ 已上传并添加到知识库: {file.filename}"} return {"message": f"✅ 已上传: {file.filename},请重启服务加载"} @app.get("/health") async def health_check(): """健康检查""" return {"status": "ok", "service": "ai-chatbot"} @app.on_event("startup") async def startup_event(): """启动时初始化""" print("=" * 60) print("🤖 AI 客服系统启动中...") print("=" * 60) # 检查 API Key from config import LLM_API_KEY if LLM_API_KEY == "your-deepseek-api-key-here": print("⚠️ 警告: 请在 app/config.py 中设置 DEEPSEEK_API_KEY") print(" 或者设置环境变量: export DEEPSEEK_API_KEY=your-key") else: print("✅ API Key 已配置") # 初始化知识库 print("\n📚 初始化知识库...") try: kb = get_knowledge_base() # 尝试加载已有文档 from pathlib import Path from config import KNOWLEDGE_DIR if KNOWLEDGE_DIR.exists(): docs = list(KNOWLEDGE_DIR.glob("*")) if docs: print(f" 发现 {len(docs)} 个文档文件") else: print(" 知识库目录为空,请在 knowledge_base/ 添加文档") except Exception as e: print(f" ⚠️ 知识库初始化失败: {e}") print("\n🚀 服务已启动!") print(f" 访问: http://{HOST}:{PORT}") print("=" * 60) if __name__ == "__main__": # 开发模式运行 uvicorn.run( "main:app", host=HOST, port=PORT, reload=True, log_level="info" )