Eliot0110 commited on
Commit
8bacbbf
·
1 Parent(s): f30e96e

fix: 修复缓存权限和根路径404问题

Browse files

- 在Dockerfile中创建并设置HuggingFace缓存目录权限
- 设置HF_HOME等环境变量指向可写目录
- 在AI模型中明确指定缓存目录
- 修复根路径404错误
- 解决Permission denied: '/.cache'问题

Files changed (3) hide show
  1. Dockerfile +12 -3
  2. app.py +123 -3
  3. modules/ai_model.py +34 -40
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
- # Dockerfile - 更新版本
2
- FROM python:3.11-slim
3
 
4
  # 设置工作目录
5
  WORKDIR /app
@@ -8,6 +8,9 @@ WORKDIR /app
8
  ENV PYTHONPATH=/app
9
  ENV PYTHONUNBUFFERED=1
10
  ENV PIP_NO_CACHE_DIR=1
 
 
 
11
 
12
  # 安装系统依赖
13
  RUN apt-get update && apt-get install -y \
@@ -16,6 +19,11 @@ RUN apt-get update && apt-get install -y \
16
  build-essential \
17
  && rm -rf /var/lib/apt/lists/*
18
 
 
 
 
 
 
19
  # 升级 pip 到最新版本
20
  RUN pip install --upgrade pip
21
 
@@ -26,9 +34,10 @@ RUN pip install --no-cache-dir -r requirements.txt
26
  # 复制项目文件
27
  COPY . .
28
 
29
- #hf_cli authenticate
30
  ARG Assitant_tocken
31
  RUN if [ -n "$Assitant_tocken" ]; then \
 
32
  python -c "from huggingface_hub import login; login('$Assitant_tocken', add_to_git_credential=False)"; \
33
  fi
34
 
 
1
+ # Dockerfile - 修复缓存权限
2
+ FROM python:3.11-slim
3
 
4
  # 设置工作目录
5
  WORKDIR /app
 
8
  ENV PYTHONPATH=/app
9
  ENV PYTHONUNBUFFERED=1
10
  ENV PIP_NO_CACHE_DIR=1
11
+ ENV HF_HOME=/app/.cache/huggingface # 设置HF缓存目录
12
+ ENV TRANSFORMERS_CACHE=/app/.cache/huggingface
13
+ ENV HF_DATASETS_CACHE=/app/.cache/huggingface
14
 
15
  # 安装系统依赖
16
  RUN apt-get update && apt-get install -y \
 
19
  build-essential \
20
  && rm -rf /var/lib/apt/lists/*
21
 
22
+ # 创建缓存目录并设置权限
23
+ RUN mkdir -p /app/.cache/huggingface && \
24
+ chmod 755 /app/.cache && \
25
+ chmod 755 /app/.cache/huggingface
26
+
27
  # 升级 pip 到最新版本
28
  RUN pip install --upgrade pip
29
 
 
34
  # 复制项目文件
35
  COPY . .
36
 
37
+ # 在构建时进行HF认证(如果token可用)
38
  ARG Assitant_tocken
39
  RUN if [ -n "$Assitant_tocken" ]; then \
40
+ export HF_HOME=/app/.cache/huggingface && \
41
  python -c "from huggingface_hub import login; login('$Assitant_tocken', add_to_git_credential=False)"; \
42
  fi
43
 
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # app.py - 正确的版本
2
  from fastapi import FastAPI, HTTPException
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from fastapi.responses import JSONResponse
@@ -27,7 +26,7 @@ app.add_middleware(
27
  # --- 全局服务实例 ---
28
  try:
29
  log.info("🔄 开始初始化 Travel Assistant 服务...")
30
- assistant = TravelAssistant() # 这里才是正确的模型加载方式
31
  SERVICE_READY = True
32
  log.info("🚀 FastAPI 应用启动成功,服务已就绪。")
33
  except Exception as e:
@@ -36,4 +35,125 @@ except Exception as e:
36
  log.critical(f"💥 FATAL: 服务初始化失败: {e}")
37
  log.critical(f"💥 错误详情: {traceback.format_exc()}")
38
 
39
- # ... 其余的API端点代码
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import JSONResponse
 
26
  # --- 全局服务实例 ---
27
  try:
28
  log.info("🔄 开始初始化 Travel Assistant 服务...")
29
+ assistant = TravelAssistant()
30
  SERVICE_READY = True
31
  log.info("🚀 FastAPI 应用启动成功,服务已就绪。")
32
  except Exception as e:
 
35
  log.critical(f"💥 FATAL: 服务初始化失败: {e}")
36
  log.critical(f"💥 错误详情: {traceback.format_exc()}")
37
 
38
+ # --- API 端点 ---
39
+ @app.get("/")
40
+ async def root():
41
+ """根路径 - 提供基本信息"""
42
+ ai_available = False
43
+ if SERVICE_READY and assistant and hasattr(assistant, 'ai_model'):
44
+ ai_available = assistant.ai_model.is_available()
45
+
46
+ return {
47
+ "message": "Travel Assistant API",
48
+ "version": "2.0.0",
49
+ "status": "ready" if SERVICE_READY else "degraded",
50
+ "ai_model_status": "available" if ai_available else "unavailable",
51
+ "docs": "/docs",
52
+ "health": "/health"
53
+ }
54
+
55
+ @app.get("/health")
56
+ async def health_check():
57
+ """健康检查端点"""
58
+ if SERVICE_READY and assistant:
59
+ ai_available = assistant.ai_model.is_available() if hasattr(assistant.ai_model, 'is_available') else False
60
+ return {
61
+ "status": "healthy" if ai_available else "degraded",
62
+ "service_ready": True,
63
+ "ai_model_available": ai_available,
64
+ "components": {
65
+ "config": True,
66
+ "knowledge_base": len(assistant.kb.knowledge) > 0 if hasattr(assistant, 'kb') else False,
67
+ "ai_model": ai_available,
68
+ "session_manager": True
69
+ }
70
+ }
71
+ else:
72
+ return JSONResponse(
73
+ status_code=503,
74
+ content={
75
+ "status": "unhealthy",
76
+ "service_ready": False,
77
+ "ai_model_available": False
78
+ }
79
+ )
80
+
81
+ @app.post("/api/chat", response_model=ChatResponse)
82
+ async def chat_endpoint(request: ChatRequest):
83
+ """主要的聊天API端点"""
84
+ if not SERVICE_READY or not assistant:
85
+ raise HTTPException(
86
+ status_code=503,
87
+ detail="Service Unavailable: Backend assistant failed to initialize."
88
+ )
89
+
90
+ try:
91
+ log.info(f"收到聊天请求: {request.message[:50]}...")
92
+
93
+ reply, session_id, status_info, history = assistant.chat(
94
+ request.message,
95
+ request.session_id,
96
+ request.history or []
97
+ )
98
+
99
+ log.info(f"聊天响应生成成功,会话ID: {session_id}")
100
+
101
+ return ChatResponse(
102
+ reply=reply,
103
+ session_id=session_id,
104
+ status_info=status_info,
105
+ history=history
106
+ )
107
+
108
+ except Exception as e:
109
+ log.error(f"❌ Chat endpoint error: {e}", exc_info=True)
110
+ raise HTTPException(
111
+ status_code=500,
112
+ detail="Internal Server Error: Failed to process chat request."
113
+ )
114
+
115
+ @app.post("/api/reset")
116
+ async def reset_session(session_id: str):
117
+ """重置会话端点"""
118
+ if not SERVICE_READY or not assistant:
119
+ raise HTTPException(
120
+ status_code=503,
121
+ detail="Service Unavailable"
122
+ )
123
+
124
+ try:
125
+ assistant.session_manager.reset(session_id)
126
+ log.info(f"会话重置成功: {session_id}")
127
+ return {
128
+ "message": "Session reset successfully",
129
+ "session_id": session_id,
130
+ "status": "success"
131
+ }
132
+ except Exception as e:
133
+ log.error(f"❌ Reset session error: {e}")
134
+ raise HTTPException(
135
+ status_code=500,
136
+ detail="Failed to reset session"
137
+ )
138
+
139
+ # --- 全局异常处理 ---
140
+ @app.exception_handler(Exception)
141
+ async def global_exception_handler(request, exc):
142
+ """全局异常处理器"""
143
+ log.error(f"❌ 未处理的异常: {exc}", exc_info=True)
144
+ return JSONResponse(
145
+ status_code=500,
146
+ content={"detail": "Internal server error", "message": "请稍后重试"}
147
+ )
148
+
149
+ # HuggingFace Spaces 和本地运行配置
150
+ if __name__ == "__main__":
151
+ import uvicorn
152
+ log.info("🔧 本地开发模式启动...")
153
+ uvicorn.run(
154
+ app,
155
+ host="0.0.0.0",
156
+ port=7860,
157
+ reload=True,
158
+ log_level="info"
159
+ )
modules/ai_model.py CHANGED
@@ -17,66 +17,60 @@ class AIModel:
17
  self.processor = None
18
  self._initialize_model()
19
 
20
- # modules/ai_model.py - 添加调试版本
21
- def _authenticate_hf(self):
22
- try:
23
- # 检查所有可能的环境变量
24
- assitant_token = os.getenv("Assitant_tocken")
25
- hf_token = os.getenv("HUGGINGFACE_HUB_TOKEN")
26
- hf_token_alt = os.getenv("HF_TOKEN")
27
-
28
- log.info("=== 环境变量调试 ===")
29
- log.info(f"Assitant_tocken: {'存在' if assitant_token else '不存在'}")
30
- log.info(f"HUGGINGFACE_HUB_TOKEN: {'存在' if hf_token else '不存在'}")
31
- log.info(f"HF_TOKEN: {'存在' if hf_token_alt else '不存在'}")
32
 
33
- # 使用找到的token
34
- token_to_use = assitant_token or hf_token or hf_token_alt
 
 
35
 
36
- if token_to_use:
37
- log.info(f"使用token: {token_to_use[:10]}...")
38
- login(token=token_to_use, add_to_git_credential=False)
 
 
 
 
 
 
 
39
  log.info("✅ HuggingFace 认证成功")
40
- return token_to_use
41
  else:
42
- log.error(" 未找到任何有效的 HuggingFace token")
43
- return None
44
-
45
  except Exception as e:
46
  log.error(f"❌ HuggingFace 认证失败: {e}")
47
- return None
48
 
49
  def _initialize_model(self):
 
50
  try:
51
  log.info(f"正在加载模型: {self.model_name}")
52
-
53
- # 先进行认证并获取token
54
- token = self._authenticate_hf()
55
-
56
- if not token:
57
- log.error("❌ 无法获取有效token,模型加载失败")
58
- self.model = None
59
- self.processor = None
60
- return
61
-
62
  self.model = Gemma3nForConditionalGeneration.from_pretrained(
63
  self.model_name,
64
  device_map="auto",
65
  torch_dtype=torch.bfloat16,
66
- trust_remote_code=True,
67
- token=token,
68
- use_auth_token=token # 双重保险
69
  ).eval()
70
-
71
  self.processor = AutoProcessor.from_pretrained(
72
  self.model_name,
73
  trust_remote_code=True,
74
- token=token,
75
- use_auth_token=token
76
  )
77
-
78
  log.info("✅ Gemma AI 模型初始化成功")
79
-
80
  except Exception as e:
81
  log.error(f"❌ Gemma AI 模型初始化失败: {e}", exc_info=True)
82
  self.model = None
 
17
  self.processor = None
18
  self._initialize_model()
19
 
20
+ def _setup_cache_dirs(self):
21
+ """设置缓存目录"""
22
+ cache_dir = "/app/.cache/huggingface"
23
+ os.makedirs(cache_dir, exist_ok=True)
 
 
 
 
 
 
 
 
24
 
25
+ # 设置环境变量
26
+ os.environ["HF_HOME"] = cache_dir
27
+ os.environ["TRANSFORMERS_CACHE"] = cache_dir
28
+ os.environ["HF_DATASETS_CACHE"] = cache_dir
29
 
30
+ log.info(f"设置缓存目录: {cache_dir}")
31
+
32
+ def _authenticate_hf(self):
33
+ try:
34
+ # 从环境变量获取HF token
35
+ hf_token = os.getenv("Assitant_tocken")
36
+
37
+ if hf_token:
38
+ cache_dir = "/app/.cache/huggingface"
39
+ login(token=hf_token, add_to_git_credential=False)
40
  log.info("✅ HuggingFace 认证成功")
 
41
  else:
42
+ log.warning("⚠️ 未找到 HuggingFace token,可能无法访问受限模型")
43
+
 
44
  except Exception as e:
45
  log.error(f"❌ HuggingFace 认证失败: {e}")
46
+
47
 
48
  def _initialize_model(self):
49
+ """初始化Gemma模型 - 基于官方调用方式"""
50
  try:
51
  log.info(f"正在加载模型: {self.model_name}")
52
+
53
+ hf_token = self._authenticate_hf()
54
+
55
+ cache_dir = "/app/.cache/huggingface"
56
+
 
 
 
 
 
57
  self.model = Gemma3nForConditionalGeneration.from_pretrained(
58
  self.model_name,
59
  device_map="auto",
60
  torch_dtype=torch.bfloat16,
61
+ cache_dir=cache_dir,
62
+ trust_remote_code=True
 
63
  ).eval()
64
+
65
  self.processor = AutoProcessor.from_pretrained(
66
  self.model_name,
67
  trust_remote_code=True,
68
+ cache_dir=cache_dir,
69
+ tocken=hf_token
70
  )
71
+
72
  log.info("✅ Gemma AI 模型初始化成功")
73
+
74
  except Exception as e:
75
  log.error(f"❌ Gemma AI 模型初始化失败: {e}", exc_info=True)
76
  self.model = None