Toadied commited on
Commit
8b383ad
·
verified ·
1 Parent(s): 500ca51
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. backend/.dockerignore +53 -0
  3. backend/Dockerfile.backend +18 -0
  4. backend/backend_app/__init__.py +0 -0
  5. backend/backend_app/__pycache__/__init__.cpython-311.pyc +0 -0
  6. backend/backend_app/__pycache__/__init__.cpython-312.pyc +0 -0
  7. backend/backend_app/__pycache__/__init__.cpython-313.pyc +0 -0
  8. backend/backend_app/__pycache__/config.cpython-311.pyc +0 -0
  9. backend/backend_app/__pycache__/config.cpython-313.pyc +0 -0
  10. backend/backend_app/__pycache__/constants.cpython-311.pyc +0 -0
  11. backend/backend_app/__pycache__/constants.cpython-313.pyc +0 -0
  12. backend/backend_app/__pycache__/consumer.cpython-311.pyc +0 -0
  13. backend/backend_app/__pycache__/di.cpython-311.pyc +0 -0
  14. backend/backend_app/__pycache__/di.cpython-313.pyc +0 -0
  15. backend/backend_app/__pycache__/main.cpython-311.pyc +0 -0
  16. backend/backend_app/__pycache__/main.cpython-312.pyc +0 -0
  17. backend/backend_app/__pycache__/main.cpython-313.pyc +0 -0
  18. backend/backend_app/__pycache__/redis_config.cpython-311.pyc +0 -0
  19. backend/backend_app/api/__init__.py +0 -0
  20. backend/backend_app/api/__pycache__/__init__.cpython-311.pyc +0 -0
  21. backend/backend_app/api/__pycache__/__init__.cpython-313.pyc +0 -0
  22. backend/backend_app/api/__pycache__/api_router.cpython-311.pyc +0 -0
  23. backend/backend_app/api/__pycache__/api_router.cpython-313.pyc +0 -0
  24. backend/backend_app/api/api_router.py +23 -0
  25. backend/backend_app/api/llm_api/__init__.py +0 -0
  26. backend/backend_app/api/llm_api/__pycache__/__init__.cpython-311.pyc +0 -0
  27. backend/backend_app/api/llm_api/__pycache__/__init__.cpython-313.pyc +0 -0
  28. backend/backend_app/api/llm_api/__pycache__/llm_model.cpython-311.pyc +0 -0
  29. backend/backend_app/api/llm_api/__pycache__/llm_model.cpython-313.pyc +0 -0
  30. backend/backend_app/api/llm_api/ingest/__init__.py +0 -0
  31. backend/backend_app/api/llm_api/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
  32. backend/backend_app/api/llm_api/ingest/__pycache__/__init__.cpython-313.pyc +0 -0
  33. backend/backend_app/api/llm_api/ingest/__pycache__/ingest.cpython-311.pyc +0 -0
  34. backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component.cpython-311.pyc +0 -0
  35. backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component.cpython-313.pyc +0 -0
  36. backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component_kg_rag.cpython-313.pyc +0 -0
  37. backend/backend_app/api/llm_api/ingest/__pycache__/ingest_helper.cpython-311.pyc +0 -0
  38. backend/backend_app/api/llm_api/ingest/__pycache__/ingest_helper.cpython-313.pyc +0 -0
  39. backend/backend_app/api/llm_api/ingest/ingest.py +185 -0
  40. backend/backend_app/api/llm_api/newUpdateFile/__init__.py +0 -0
  41. backend/backend_app/api/llm_api/newUpdateFile/__pycache__/__init__.cpython-311.pyc +0 -0
  42. backend/backend_app/api/llm_api/newUpdateFile/__pycache__/__init__.cpython-313.pyc +0 -0
  43. backend/backend_app/api/llm_api/newUpdateFile/__pycache__/new_updateFile.cpython-311.pyc +0 -0
  44. backend/backend_app/api/llm_api/newUpdateFile/__pycache__/new_updateFile.cpython-313.pyc +0 -0
  45. backend/backend_app/api/llm_api/newUpdateFile/new_updateFile.py +245 -0
  46. backend/backend_app/api/llm_api/universal/__init__.py +1 -0
  47. backend/backend_app/api/llm_api/universal/__pycache__/__init__.cpython-311.pyc +0 -0
  48. backend/backend_app/api/llm_api/universal/__pycache__/universal_router.cpython-311.pyc +0 -0
  49. backend/backend_app/api/llm_api/universal/universal_router.py +128 -0
  50. backend/backend_app/api/video/README.md +428 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ backend/cache/models--BAAI--bge-small-zh/blobs/5930ef2631f95185a3b74db3dcff787376bc93cdf693321ef75b2d4ed3b3545d filter=lfs diff=lfs merge=lfs -text
37
+ backend/cache/models--BAAI--bge-small-zh/blobs/5cc750eb4f5412313aba1482e53aeccd1ed0b97bc213f2962e0c80e6c8809319 filter=lfs diff=lfs merge=lfs -text
38
+ backend/temp_gorilla/berkeley-function-call-leaderboard/architecture_diagram.png filter=lfs diff=lfs merge=lfs -text
backend/.dockerignore ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Python 编译产物(无需打包进镜像)
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .coverage
8
+ htmlcov/
9
+
10
+ # 2. 虚拟环境(镜像内会重新安装依赖,本地虚拟环境无需打包)
11
+ .venv/
12
+ venv/
13
+ ENV/
14
+ env/
15
+
16
+ # 3. 敏感/配置文件(避免密钥、本地配置打包进镜像)
17
+ .env
18
+ .env.local
19
+ .env.*.local
20
+ *.env
21
+ secrets/
22
+
23
+ # 4. 项目临时/大文件(减少镜像体积)
24
+ temp_docs/ # 文档上传临时目录
25
+ model_cache/ # 本地模型缓存(镜像内可按需下载或挂载)
26
+ ~/.cache/huggingface/ # 本地 HuggingFace 模型缓存
27
+ ~/.cache/torch/
28
+ *.log
29
+ *.tmp
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # 5. 版本控制/构建产物(无需打包)
34
+ .git/
35
+ .gitignore
36
+ .github/
37
+ dist/
38
+ build/
39
+ *.egg-info/
40
+
41
+ # 6. Docker 相关(避免循环打包)
42
+ .dockerignore
43
+ Dockerfile
44
+ docker-compose.yml
45
+ *.dockerfile
46
+ src/*.py
47
+ electron_app/
48
+
49
+ # 7. 其他无关文件
50
+ README.md
51
+ LICENSE
52
+ *.txt
53
+ *.md
backend/Dockerfile.backend ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face 官方标准格式 —— 必须这样写
2
+ FROM python:3.11-slim
3
+
4
+ # 固定配置
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+ WORKDIR /app
9
+
10
+ # 安装依赖
11
+ COPY --chown=user ./requirements.txt requirements.txt
12
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
13
+
14
+ # 复制代码
15
+ COPY --chown=user . /app
16
+
17
+ # 必须端口 7860
18
+ CMD ["uvicorn", "backend.backend_app.main:app", "--host", "0.0.0.0", "--port", "7860"]
backend/backend_app/__init__.py ADDED
File without changes
backend/backend_app/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (162 Bytes). View file
 
backend/backend_app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (167 Bytes). View file
 
backend/backend_app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (150 Bytes). View file
 
backend/backend_app/__pycache__/config.cpython-311.pyc ADDED
Binary file (1.9 kB). View file
 
backend/backend_app/__pycache__/config.cpython-313.pyc ADDED
Binary file (1.68 kB). View file
 
backend/backend_app/__pycache__/constants.cpython-311.pyc ADDED
Binary file (2.64 kB). View file
 
backend/backend_app/__pycache__/constants.cpython-313.pyc ADDED
Binary file (2.39 kB). View file
 
backend/backend_app/__pycache__/consumer.cpython-311.pyc ADDED
Binary file (3.14 kB). View file
 
backend/backend_app/__pycache__/di.cpython-311.pyc ADDED
Binary file (803 Bytes). View file
 
backend/backend_app/__pycache__/di.cpython-313.pyc ADDED
Binary file (694 Bytes). View file
 
backend/backend_app/__pycache__/main.cpython-311.pyc ADDED
Binary file (9.49 kB). View file
 
backend/backend_app/__pycache__/main.cpython-312.pyc ADDED
Binary file (12.8 kB). View file
 
backend/backend_app/__pycache__/main.cpython-313.pyc ADDED
Binary file (3.58 kB). View file
 
backend/backend_app/__pycache__/redis_config.cpython-311.pyc ADDED
Binary file (740 Bytes). View file
 
backend/backend_app/api/__init__.py ADDED
File without changes
backend/backend_app/api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (166 Bytes). View file
 
backend/backend_app/api/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (154 Bytes). View file
 
backend/backend_app/api/__pycache__/api_router.cpython-311.pyc ADDED
Binary file (1.3 kB). View file
 
backend/backend_app/api/__pycache__/api_router.cpython-313.pyc ADDED
Binary file (971 Bytes). View file
 
backend/backend_app/api/api_router.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import APIRouter
3
+
4
+ from backend_app.api.llm_api.newUpdateFile.new_updateFile import new_updateFile_router
5
+ from backend_app.api.llm_api.universal.universal_router import universal_router
6
+ from backend_app.api.llm_api.ingest.ingest import ingest_router
7
+ from backend_app.api.voice.voice_router import voice_router
8
+ from backend_app.api.video.video_router import video_router
9
+
10
+ api_router = APIRouter()
11
+
12
+ api_router.include_router(new_updateFile_router, prefix="/new_update_file", tags=["new_update_file"])
13
+
14
+ api_router.include_router(ingest_router, prefix="/ingest", tags=["ingest"])
15
+
16
+ # universal enterprise assistant
17
+ api_router.include_router(universal_router, prefix="/universal", tags=["universal"])
18
+
19
+ # voice generation service
20
+ api_router.include_router(voice_router, prefix="/voice", tags=["voice"])
21
+
22
+ # video generation service
23
+ api_router.include_router(video_router, prefix="/video", tags=["video"])
backend/backend_app/api/llm_api/__init__.py ADDED
File without changes
backend/backend_app/api/llm_api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (174 Bytes). View file
 
backend/backend_app/api/llm_api/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (162 Bytes). View file
 
backend/backend_app/api/llm_api/__pycache__/llm_model.cpython-311.pyc ADDED
Binary file (6.25 kB). View file
 
backend/backend_app/api/llm_api/__pycache__/llm_model.cpython-313.pyc ADDED
Binary file (5.43 kB). View file
 
backend/backend_app/api/llm_api/ingest/__init__.py ADDED
File without changes
backend/backend_app/api/llm_api/ingest/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (173 Bytes). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (161 Bytes). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest.cpython-311.pyc ADDED
Binary file (8.01 kB). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component.cpython-311.pyc ADDED
Binary file (9.65 kB). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component.cpython-313.pyc ADDED
Binary file (8.59 kB). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest_component_kg_rag.cpython-313.pyc ADDED
Binary file (11.1 kB). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest_helper.cpython-311.pyc ADDED
Binary file (5.35 kB). View file
 
backend/backend_app/api/llm_api/ingest/__pycache__/ingest_helper.cpython-313.pyc ADDED
Binary file (4.94 kB). View file
 
backend/backend_app/api/llm_api/ingest/ingest.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import Dict
3
+ from fastapi import APIRouter, Form, HTTPException
4
+
5
+ from datetime import datetime
6
+ import logging
7
+ import os
8
+ from pydantic import BaseModel
9
+ from helloAgents.tools.registry import global_registry
10
+ from helloAgents.tools.builtin.rag_tool import RAGTool
11
+ # 2. 定义请求体
12
+ class FileListRequest(BaseModel):
13
+ user_id: str
14
+
15
+ logger = logging.getLogger(__name__)
16
+ ingest_router = APIRouter()
17
+
18
+ # ------------------------------
19
+ # ⚙️ 企业级配置(和你原有一致)
20
+ # ------------------------------
21
+ MANIFEST_FILE = ".file_manifest.json"
22
+ CHUNK_SIZE = 8192
23
+ MAX_PARALLEL_FILES = 4
24
+ SAVE_BASE_DIR = "./knowledge_base" # 统一基座目录
25
+
26
+ # ------------------------------
27
+ # 🆕 新增:获取用户上传文档接口
28
+ # ------------------------------
29
+ @ingest_router.post("/list")
30
+ async def get_user_uploaded_files(body: FileListRequest):
31
+ """
32
+ 获取指定命名空间下所有已上传的文件列表
33
+ 返回:所有真实文件、文件路径、哈希、去重状态、文件信息
34
+ """
35
+ user_id = body.user_id
36
+ if not user_id.strip():
37
+ raise HTTPException(status_code=400, detail="用户ID不能为空")
38
+
39
+ # 用户文件目录
40
+ user_dir = os.path.join(SAVE_BASE_DIR, user_id)
41
+ if not os.path.exists(user_dir):
42
+ return {
43
+ "success": True,
44
+ "user_id": user_id,
45
+ "total_files": 0,
46
+ "files": [],
47
+ "msg": "该用户暂无上传文件"
48
+ }
49
+
50
+ # 加载 manifest 清单(你原有去重/版本管理核心)
51
+ manifest = load_manifest(user_dir)
52
+ if not manifest:
53
+ return {
54
+ "success": True,
55
+ "user_id": user_id,
56
+ "total_files": 0,
57
+ "files": [],
58
+ "msg": "该用户暂无上传文件(清单为空)"
59
+ }
60
+
61
+ # 构建文件列表
62
+ file_list = []
63
+ for filename, file_hash in manifest.items():
64
+ file_path = os.path.join(user_dir, filename)
65
+
66
+ # 获取文件基本信息
67
+ file_stat = os.stat(file_path)
68
+ create_time = datetime.fromtimestamp(file_stat.st_ctime).strftime("%Y-%m-%d %H:%M:%S")
69
+ update_time = datetime.fromtimestamp(file_stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
70
+ file_size = file_stat.st_size # 字节
71
+
72
+ # 文件格式
73
+ _, ext = os.path.splitext(filename)
74
+ ext = ext.lower().lstrip(".")
75
+
76
+ file_list.append({
77
+ "filename": filename,
78
+ "original_filename": filename.split("_v")[0] + "." + ext if "_v" in filename else filename,
79
+ "file_path": file_path,
80
+ "file_hash": file_hash,
81
+ "file_type": ext,
82
+ "file_size_bytes": file_size,
83
+ "file_size": f"{round(file_size / 1024 / 1024, 2)} MB" if file_size > 1024*1024 else f"{round(file_size / 1024, 2)} KB",
84
+ "create_time": create_time,
85
+ "update_time": update_time,
86
+ "is_versioned": "_v" in filename # 是否是版本文件
87
+ })
88
+
89
+ # 按上传时间倒序排列(最新在前)
90
+ file_list = sorted(file_list, key=lambda x: x["create_time"], reverse=True)
91
+
92
+ return {
93
+ "success": True,
94
+ "user_id": user_id,
95
+ "total_files": len(file_list),
96
+ "files": file_list,
97
+ "msg": f"成功获取 {len(file_list)} 个文件"
98
+ }
99
+
100
+
101
+
102
+ def load_manifest(ns_path: str) -> Dict[str, str]:
103
+ manifest_path = os.path.join(ns_path, MANIFEST_FILE)
104
+ if os.path.exists(manifest_path):
105
+ try:
106
+ with open(manifest_path, "r", encoding="utf-8") as f:
107
+ return json.load(f)
108
+ except Exception:
109
+ logger.warning(f"读取 manifest 失败: {manifest_path}")
110
+ return {}
111
+ return {}
112
+
113
+
114
+
115
+
116
+ class DeleteFileListRequest(BaseModel):
117
+ user_id: str
118
+ doc_id: str
119
+ # ------------------------------
120
+ # 🗑️ 新增:删除文档接口(通过 file_hash 删除)
121
+ # ------------------------------
122
+ @ingest_router.post("/delete")
123
+ async def delete_file_by_hash(
124
+ body: DeleteFileListRequest
125
+ ):
126
+ # 接收前端传来的 user_id 和 doc_id
127
+ user_id = body.user_id
128
+ doc_id = body.doc_id
129
+ if not user_id.strip() or not doc_id.strip():
130
+ raise HTTPException(status_code=400, detail="user_id 和 doc_id 不能为空")
131
+
132
+ user_dir = os.path.join(SAVE_BASE_DIR, user_id)
133
+ manifest = load_manifest(user_dir)
134
+
135
+ # 1. 通过 doc_id 找到对应的文件名
136
+ target_filename = None
137
+ for filename, f_doc_id in manifest.items():
138
+ if f_doc_id == doc_id:
139
+ target_filename = filename
140
+ break
141
+
142
+ if not target_filename:
143
+ raise HTTPException(status_code=404, detail="文件不存在或已删除")
144
+
145
+ # 2. 删除真实文件
146
+ file_path = os.path.join(user_dir, target_filename)
147
+ if os.path.exists(file_path):
148
+ os.remove(file_path)
149
+
150
+ # 3. 从 manifest 中移除
151
+ del manifest[target_filename]
152
+ save_manifest(user_dir, manifest)
153
+
154
+ # 删除向量库
155
+ rag_tool: RAGTool = global_registry.get_tool("rag")
156
+ result = rag_tool.run({
157
+ "action": "delete_document",
158
+ "user_id": user_id,
159
+ "doc_id": doc_id
160
+ })
161
+
162
+ if result:
163
+ return {
164
+ "success": True,
165
+ "msg": "删除成功",
166
+ "filename": target_filename,
167
+ "doc_id": doc_id
168
+ }
169
+ else:
170
+ return {
171
+ "success": False,
172
+ "msg": "删除失败,请手动检查",
173
+ "filename": target_filename,
174
+ "doc_id": doc_id
175
+ }
176
+
177
+
178
+
179
+
180
+ def save_manifest(ns_path: str, manifest: Dict[str, str]):
181
+ manifest_path = os.path.join(ns_path, MANIFEST_FILE)
182
+ temp_path = manifest_path + ".tmp"
183
+ with open(temp_path, "w", encoding="utf-8") as f:
184
+ json.dump(manifest, f, indent=2, ensure_ascii=False)
185
+ os.replace(temp_path, manifest_path)
backend/backend_app/api/llm_api/newUpdateFile/__init__.py ADDED
File without changes
backend/backend_app/api/llm_api/newUpdateFile/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (188 Bytes). View file
 
backend/backend_app/api/llm_api/newUpdateFile/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (176 Bytes). View file
 
backend/backend_app/api/llm_api/newUpdateFile/__pycache__/new_updateFile.cpython-311.pyc ADDED
Binary file (13.5 kB). View file
 
backend/backend_app/api/llm_api/newUpdateFile/__pycache__/new_updateFile.cpython-313.pyc ADDED
Binary file (4.06 kB). View file
 
backend/backend_app/api/llm_api/newUpdateFile/new_updateFile.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, UploadFile, File, Form, HTTPException
2
+ from typing import List, Dict
3
+ from datetime import datetime
4
+ import logging
5
+ import os
6
+ import asyncio
7
+ import hashlib
8
+ import json
9
+ import tempfile
10
+ import shutil
11
+
12
+ from helloAgents.tools.async_executor import AsyncToolExecutor
13
+ from helloAgents.tools.builtin.rag_tool import RAGTool
14
+ from helloAgents.tools.builtin.memory_tool import MemoryTool
15
+ from helloAgents.tools.registry import global_registry
16
+
17
+ from redis_config import get_redis, QUEUE_RAG_QDRANT, QUEUE_RAG_NEO4J, QUEUE_MEMORY
18
+ redis = get_redis()
19
+
20
+ logger = logging.getLogger(__name__)
21
+ new_updateFile_router = APIRouter()
22
+
23
+ # ------------------------------
24
+ # ⚙️ 企业级配置
25
+ # ------------------------------
26
+ MANIFEST_FILE = ".file_manifest.json"
27
+ CHUNK_SIZE = 8192
28
+ MAX_PARALLEL_FILES = 4
29
+
30
+ # ------------------------------
31
+ # 🛠️ 核心工具函数
32
+ # ------------------------------
33
+
34
+ def load_manifest(ns_path: str) -> Dict[str, str]:
35
+ manifest_path = os.path.join(ns_path, MANIFEST_FILE)
36
+ if os.path.exists(manifest_path):
37
+ try:
38
+ with open(manifest_path, "r", encoding="utf-8") as f:
39
+ return json.load(f)
40
+ except Exception:
41
+ logger.warning(f"读取 manifest 失败: {manifest_path}")
42
+ return {}
43
+ return {}
44
+
45
+ def save_manifest(ns_path: str, manifest: Dict[str, str]):
46
+ manifest_path = os.path.join(ns_path, MANIFEST_FILE)
47
+ temp_path = manifest_path + ".tmp"
48
+ with open(temp_path, "w", encoding="utf-8") as f:
49
+ json.dump(manifest, f, indent=2, ensure_ascii=False)
50
+ os.replace(temp_path, manifest_path)
51
+
52
+ def calculate_file_hash_stream(file_path: str) -> str:
53
+ sha256 = hashlib.sha256()
54
+ with open(file_path, "rb") as f:
55
+ while chunk := f.read(CHUNK_SIZE):
56
+ sha256.update(chunk)
57
+ return sha256.hexdigest()
58
+
59
+ def calculate_upload_hash_stream(file_obj) -> str:
60
+ sha256 = hashlib.sha256()
61
+ file_obj.file.seek(0)
62
+ while chunk := file_obj.file.read(CHUNK_SIZE):
63
+ sha256.update(chunk)
64
+ file_obj.file.seek(0)
65
+ return sha256.hexdigest()
66
+
67
+ async def save_file_with_version_and_deduplicate(
68
+ upload_file: UploadFile,
69
+ user_id: str,
70
+ save_dir: str = "./knowledge_base"
71
+ ) -> dict:
72
+ original_name = upload_file.filename.strip()
73
+ name, ext = os.path.splitext(original_name)
74
+ ns_path = os.path.join(save_dir, user_id)
75
+
76
+ os.makedirs(ns_path, exist_ok=True)
77
+ manifest = load_manifest(ns_path)
78
+
79
+ try:
80
+ new_file_hash = calculate_upload_hash_stream(upload_file)
81
+
82
+ for existing_name, existing_hash in manifest.items():
83
+ if existing_hash == new_file_hash:
84
+ return {
85
+ "success": True,
86
+ "is_duplicate": True,
87
+ "filename": original_name,
88
+ "file_path": os.path.join(ns_path, existing_name),
89
+ "message": f"内容已存在(同名:{existing_name}),自动去重"
90
+ }
91
+
92
+ target_name = original_name
93
+ version = 1
94
+ while target_name in manifest:
95
+ version += 1
96
+ target_name = f"{name}_v{version}{ext}"
97
+
98
+ final_path = os.path.join(ns_path, target_name)
99
+
100
+ with tempfile.NamedTemporaryFile(dir=ns_path, delete=False) as tmp_file:
101
+ shutil.copyfileobj(upload_file.file, tmp_file)
102
+ temp_file_path = tmp_file.name
103
+
104
+ os.replace(temp_file_path, final_path)
105
+
106
+ manifest[target_name] = new_file_hash
107
+ save_manifest(ns_path, manifest)
108
+
109
+ return {
110
+ "success": True,
111
+ "is_duplicate": False,
112
+ "filename": original_name,
113
+ "file_path": final_path,
114
+ "hash": new_file_hash,
115
+ "message": f"已保存(版本 v{version})" if version > 1 else "已保存"
116
+ }
117
+
118
+ except Exception as e:
119
+ logger.error(f"保存失败 {upload_file.filename}: {str(e)}")
120
+ return {
121
+ "success": False,
122
+ "filename": upload_file.filename,
123
+ "error": str(e)
124
+ }
125
+
126
+ # ------------------------------
127
+ # 主处理逻辑(已补全记忆)
128
+ # ------------------------------
129
+ async def process_uploaded_files(files: List[UploadFile], user_id: str) -> dict:
130
+ memory_tool: MemoryTool = global_registry.get_tool("memory")
131
+ rag_tool: RAGTool = global_registry.get_tool("rag")
132
+
133
+ if not files:
134
+ raise HTTPException(status_code=400, detail="请上传至少一个文件")
135
+ if not user_id.strip():
136
+ raise HTTPException(status_code=400, detail="命名空间不能为空")
137
+
138
+ semaphore = asyncio.Semaphore(MAX_PARALLEL_FILES)
139
+
140
+ async def save_with_limit(f):
141
+ async with semaphore:
142
+ return await save_file_with_version_and_deduplicate(f, user_id)
143
+
144
+ save_tasks = [save_with_limit(f) for f in files]
145
+ save_results = await asyncio.gather(*save_tasks)
146
+
147
+ saved_files = []
148
+ save_errors = []
149
+ duplicate_files = []
150
+
151
+ for res in save_results:
152
+ if not res["success"]:
153
+ save_errors.append(res)
154
+ elif res.get("is_duplicate"):
155
+ duplicate_files.append(res)
156
+ else:
157
+ saved_files.append(res)
158
+
159
+ # ============================
160
+ # ✅ 企业级精简版:只存 1 条总结记忆(包含所有文件路径+状态)
161
+ # ============================
162
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
163
+ total = len(files)
164
+ success_cnt = len(saved_files)
165
+ dup_cnt = len(duplicate_files)
166
+ fail_cnt = len(save_errors)
167
+
168
+ # 构建清晰的文件清单
169
+ success_files = "\n".join([f"- {f['filename']} | 路径:{f['file_path']}" for f in saved_files])
170
+ dup_files_list = "\n".join([f"- {d['filename']}" for d in duplicate_files])
171
+ err_files_list = "\n".join([f"- {e['filename']} | 原因:{e['error']}" for e in save_errors])
172
+
173
+ # ============================
174
+ # ✅ 记忆存储结束
175
+ # ============================
176
+ summary_content = f"""【文件上传总结】{now} | 用户:{user_id}
177
+ 总文件数:{total}
178
+ ✅ 上传成功:{success_cnt} 个
179
+ {success_files if success_files else '无'}
180
+
181
+ ⚠️ 重复文件:{dup_cnt} 个
182
+ {dup_files_list if dup_files_list else '无'}
183
+
184
+ ❌ 上传失败:{fail_cnt} 个
185
+ {err_files_list if err_files_list else '无'}
186
+ """
187
+ summary_tasks = {
188
+ "action": "add",
189
+ "user_id": user_id,
190
+ "memory_type": "episodic",
191
+ "content": summary_content.strip(),
192
+ "importance": 0.8,
193
+ "session_id": None
194
+ }
195
+ redis.rpush(QUEUE_MEMORY, json.dumps(summary_tasks))
196
+
197
+ if not saved_files:
198
+ return {
199
+ "success": len(save_errors) == 0,
200
+ "msg": f"去重 {len(duplicate_files)} 个 | 失败 {len(save_errors)} 个",
201
+ "duplicate_count": len(duplicate_files),
202
+ "results": [],
203
+ "errors": save_errors,
204
+ "duplicates": duplicate_files
205
+ }
206
+
207
+ # RAG qdrant 入库
208
+ task = {
209
+ "action": "add_document",
210
+ "file_path": saved_files,
211
+ "user_id": user_id
212
+ }
213
+ redis.rpush(QUEUE_RAG_QDRANT, json.dumps(task))
214
+
215
+ # RAG neo4j 入库
216
+ task_neo4j = {
217
+ "action": "add_neo4j_document",
218
+ "file_path": saved_files,
219
+ "user_id": user_id
220
+ }
221
+ redis.rpush(QUEUE_RAG_NEO4J, json.dumps(task_neo4j))
222
+
223
+
224
+ return {
225
+ "success": True,
226
+ "msg": f"成功 {len(saved_files)} | 去重 {len(duplicate_files)}",
227
+ "data": {
228
+ "total": len(files),
229
+ "saved": len(saved_files),
230
+ "success": saved_files,
231
+ "duplicate_files": duplicate_files,
232
+ "namespace": user_id
233
+ }
234
+ }
235
+
236
+ # ------------------------------
237
+ # 路由
238
+ # ------------------------------
239
+ @new_updateFile_router.post("/new_update_file")
240
+ async def new_update_file(
241
+ request: Request,
242
+ files: List[UploadFile] = File(..., description="支持 txt, md, pdf, docx, doc, json"),
243
+ namespace: str = Form(..., description="命名空间")
244
+ ) -> dict:
245
+ return await process_uploaded_files(files, namespace)
backend/backend_app/api/llm_api/universal/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """全能企业级助手API模块"""
backend/backend_app/api/llm_api/universal/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (231 Bytes). View file
 
backend/backend_app/api/llm_api/universal/__pycache__/universal_router.cpython-311.pyc ADDED
Binary file (7.16 kB). View file
 
backend/backend_app/api/llm_api/universal/universal_router.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import asyncio
3
+ import nest_asyncio
4
+ from datetime import datetime
5
+ from fastapi import APIRouter, Request, HTTPException
6
+ from pydantic import BaseModel, Field, field_validator
7
+ from typing import Optional, Dict, Any
8
+ import logging
9
+
10
+ from helloAgents.agents.universal_enterprise_agent import KnowledgeBaseAssistant
11
+ from helloAgents.core.llm import HelloAgentsLLM
12
+ from helloAgents.tools.registry import global_registry
13
+ from helloAgents.core.exceptions import (
14
+ HelloAgentsException,
15
+ ToolException
16
+ )
17
+ nest_asyncio.apply()
18
+
19
+ # 评估工具
20
+ from helloAgents.tools.builtin.bfcl_evaluation_tool import BFCLEvaluationTool
21
+
22
+ logger = logging.getLogger(__name__)
23
+ universal_router = APIRouter()
24
+
25
+ # ==================== 请求/响应模型 ====================
26
+ class UniversalChatRequest(BaseModel):
27
+ text: str = Field(..., description="用户输入的文本", max_length=5000)
28
+ user_id: str = Field(..., description="用户ID(必需)")
29
+ agent_id: Optional[str] = Field("universal_assistant")
30
+ session_id: Optional[str] = Field(None)
31
+ enable_memory: Optional[bool] = Field(True)
32
+ enable_rag: Optional[bool] = Field(True)
33
+ max_context_length: Optional[int] = Field(2000, ge=100, le=8000)
34
+ tool_choice: Optional[str] = Field("auto")
35
+
36
+ @field_validator("user_id")
37
+ def validate_user_id(cls, v):
38
+ if not re.match(r"^[a-zA-Z0-9_-]{3,50}$", v):
39
+ raise ValueError("用户ID格式错误")
40
+ return v
41
+
42
+ class UniversalChatResponse(BaseModel):
43
+ success: bool
44
+ data: str
45
+ session_id: str
46
+ user_id: str
47
+ agent_id: str
48
+ tool_calls: Optional[int]
49
+ timestamp: str
50
+
51
+ class ToolStatisticsResponse(BaseModel):
52
+ success: bool
53
+ statistics: Dict[str, Any]
54
+ timestamp: str
55
+
56
+ # ==================== 【终极正确】异步Agent → 同步适配(FastAPI安全版)====================
57
+ class SyncAgentWrapper:
58
+ def __init__(self, async_agent):
59
+ self.agent = async_agent
60
+ self.name = async_agent.name
61
+
62
+ def run(self, prompt, **kwargs):
63
+ # ✅ 唯一在 FastAPI 中安全运行异步 Agent 的方式
64
+ return asyncio.run(self.agent.run(prompt,** kwargs))
65
+
66
+ # ==================== 正常聊天接口 ====================
67
+ @universal_router.post("/chat", response_model=UniversalChatResponse)
68
+ async def universal_chat(
69
+ request: Request,
70
+ body: UniversalChatRequest
71
+ ) -> UniversalChatResponse:
72
+ try:
73
+ agent = KnowledgeBaseAssistant(
74
+ name="通用助手",
75
+ llm=HelloAgentsLLM(),
76
+ tool_registry=global_registry
77
+ )
78
+
79
+ response = await agent.run(
80
+ input_text=body.text,
81
+ user_id=body.user_id,
82
+ agent_id=body.agent_id,
83
+ session_id=body.session_id
84
+ )
85
+
86
+ session_id = body.session_id or f"ses_{int(datetime.now().timestamp())}"
87
+
88
+ return UniversalChatResponse(
89
+ success=True,
90
+ data=response,
91
+ session_id=session_id,
92
+ user_id=body.user_id,
93
+ agent_id=body.agent_id,
94
+ tool_calls=0,
95
+ timestamp=datetime.now().isoformat()
96
+ )
97
+
98
+ except HelloAgentsException as e:
99
+ logger.error(f"业务异常: {e}", exc_info=True)
100
+ raise HTTPException(status_code=400, detail=str(e))
101
+ except Exception as e:
102
+ logger.error(f"聊天失败: {e}", exc_info=True)
103
+ raise HTTPException(status_code=500, detail="服务内部错误")
104
+
105
+ # ==================== 【独立】BFCL评估接口(不影响正常业务) ====================
106
+ @universal_router.get("/bfcl-eval")
107
+ def run_bfcl_evaluation(): # 注意:这里必须是 同步函数!
108
+ try:
109
+ # 初始化
110
+ llm = HelloAgentsLLM()
111
+ agent = KnowledgeBaseAssistant(name="BFCL-Eval-Agent", llm=llm)
112
+ sync_agent = SyncAgentWrapper(agent)
113
+
114
+ # 执行评估
115
+ bfcl_tool = BFCLEvaluationTool()
116
+ result = bfcl_tool.run(
117
+ agent=sync_agent,
118
+ category="simple_python",
119
+ max_samples=0,
120
+ run_official_eval=True
121
+ )
122
+
123
+ return {
124
+ "success": True,
125
+ "result": result
126
+ }
127
+ except Exception as e:
128
+ return {"success": False, "error": str(e)}
backend/backend_app/api/video/README.md ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 视频生成API文档
2
+
3
+ ## 概述
4
+ 视频生成API提供企业级文本到视频生成服务,支持多种视频风格、分辨率、时长等参数控制,可生成MP4、AVI、MOV等多种格式的视频文件,支持背景音乐和语音解说。
5
+
6
+ ## API端点
7
+
8
+ ### 1. 生成视频
9
+ **POST** `/api/v1/video/generate`
10
+
11
+ **请求体:**
12
+ ```json
13
+ {
14
+ "prompt": "一只小鸟从天空飞过,落在树枝上,背景是森林",
15
+ "style": "animation",
16
+ "duration": 5,
17
+ "resolution": "720p",
18
+ "aspect_ratio": "16:9",
19
+ "frame_rate": 30,
20
+ "background_music": false,
21
+ "voice_over": true,
22
+ "voice_over_text": "这是一只美丽的小鸟,它在森林中自由飞翔",
23
+ "user_id": "user_12345"
24
+ }
25
+ ```
26
+
27
+ **参数说明:**
28
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
29
+ |------|------|------|--------|------|
30
+ | prompt | string | 是 | - | 视频描述文本,最长2000字符 |
31
+ | style | string | 否 | animation | 视频风格:animation(动画)、realistic(真人实拍)、3d(3D建模)、cartoon(卡通)、cinematic(电影感) |
32
+ | duration | int | 否 | 5 | 视频时长(秒),范围1-60 |
33
+ | resolution | string | 否 | 720p | 分辨率:480p、720p、1080p、4k |
34
+ | aspect_ratio | string | 否 | 16:9 | 宽高比:16:9、4:3、1:1、9:16 |
35
+ | frame_rate | int | 否 | 30 | 帧率,范围1-60 |
36
+ | background_music | bool | 否 | false | 是否添加背景音乐 |
37
+ | voice_over | bool | 否 | false | 是否添加语音解说 |
38
+ | voice_over_text | string | 否 | null | 语音解说文本 |
39
+ | user_id | string | 否 | null | 用户ID,用于统计和配额管理 |
40
+
41
+ **响应:**
42
+ ```json
43
+ {
44
+ "success": true,
45
+ "video_url": "/api/video/files/video_abc123.mp4",
46
+ "video_id": "video_abc123",
47
+ "duration": 5,
48
+ "file_size": 1024000,
49
+ "resolution": "720p",
50
+ "format": "mp4",
51
+ "thumbnail_url": "/api/video/files/thumbnails/thumb_abc123.jpg",
52
+ "timestamp": "2024-01-01T12:00:00",
53
+ "estimated_generation_time": 30
54
+ }
55
+ ```
56
+
57
+ ### 2. 获取视频状态
58
+ **POST** `/api/v1/video/status`
59
+
60
+ **请求体:**
61
+ ```json
62
+ {
63
+ "video_id": "video_abc123"
64
+ }
65
+ ```
66
+
67
+ **响应:**
68
+ ```json
69
+ {
70
+ "success": true,
71
+ "video_id": "video_abc123",
72
+ "status": "processing",
73
+ "progress": 65.5,
74
+ "estimated_completion_time": "2024-01-01T12:01:30",
75
+ "message": "正在生成视频帧...",
76
+ "timestamp": "2024-01-01T12:00:30"
77
+ }
78
+ ```
79
+
80
+ **状态说明:**
81
+ - `pending`: 等待处理
82
+ - `processing`: 处理中
83
+ - `completed`: 已完成
84
+ - `failed`: 失败
85
+
86
+ ### 3. 获取视频文件
87
+ **GET** `/api/v1/video/files/{video_id}.{format}`
88
+
89
+ **参数:**
90
+ | 参数 | 类型 | 必填 | 说明 |
91
+ |------|------|------|------|
92
+ | video_id | string | 是 | 视频文件ID |
93
+ | format | string | 是 | 文件格式:mp4、avi、mov、webm |
94
+
95
+ **响应:**
96
+ 返回视频文件流,Content-Type为对应的视频类型。
97
+
98
+ ### 4. 获取视频缩略图
99
+ **GET** `/api/v1/video/files/thumbnails/{thumbnail_filename}`
100
+
101
+ **参数:**
102
+ | 参数 | 类型 | 必填 | 说明 |
103
+ |------|------|------|------|
104
+ | thumbnail_filename | string | 是 | 缩略图文件名,格式:thumb_{video_id}.jpg |
105
+
106
+ **响应:**
107
+ 返回缩略图文件流,Content-Type为image/jpeg。
108
+
109
+ ### 5. 获取支持的视频风格
110
+ **GET** `/api/v1/video/supported-styles`
111
+
112
+ **响应:**
113
+ ```json
114
+ {
115
+ "success": true,
116
+ "styles": {
117
+ "animation": {"name": "动画", "description": "2D/3D动画风格"},
118
+ "realistic": {"name": "真人实拍", "description": "实拍视频效果"},
119
+ "3d": {"name": "3D建模", "description": "三维建模渲染"},
120
+ "cartoon": {"name": "卡通", "description": "卡通漫画风格"},
121
+ "cinematic": {"name": "电影感", "description": "电影级视觉效果"}
122
+ },
123
+ "resolutions": ["480p", "720p", "1080p", "4k"],
124
+ "formats": ["mp4", "avi", "mov", "webm"],
125
+ "timestamp": "2024-01-01T12:00:00"
126
+ }
127
+ ```
128
+
129
+ ### 6. 列出视频文件
130
+ **GET** `/api/v1/video/list?page=1&page_size=20`
131
+
132
+ **查询参数:**
133
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
134
+ |------|------|------|--------|------|
135
+ | page | int | 否 | 1 | 页码 |
136
+ | page_size | int | 否 | 20 | 每页数量 |
137
+
138
+ **响应:**
139
+ ```json
140
+ {
141
+ "success": true,
142
+ "videos": [
143
+ {
144
+ "video_id": "video_abc123",
145
+ "filename": "video_abc123.mp4",
146
+ "size": 1024000,
147
+ "created_at": "2024-01-01T12:00:00",
148
+ "format": "mp4",
149
+ "has_thumbnail": true
150
+ }
151
+ ],
152
+ "total": 8,
153
+ "page": 1,
154
+ "page_size": 20,
155
+ "timestamp": "2024-01-01T12:00:00"
156
+ }
157
+ ```
158
+
159
+ ### 7. 删除视频文件
160
+ **POST** `/api/v1/video/delete`
161
+
162
+ **请求体:**
163
+ ```json
164
+ {
165
+ "video_id": "video_abc123",
166
+ "confirm": true
167
+ }
168
+ ```
169
+
170
+ **响应:**
171
+ ```json
172
+ {
173
+ "success": true,
174
+ "message": "视频文件已成功删除",
175
+ "timestamp": "2024-01-01T12:00:00"
176
+ }
177
+ ```
178
+
179
+ ### 8. 健康检查
180
+ **GET** `/api/v1/video/health`
181
+
182
+ **响应:**
183
+ ```json
184
+ {
185
+ "status": "healthy",
186
+ "service": "video_generation",
187
+ "output_dir": "./video_outputs",
188
+ "video_files_count": 15,
189
+ "thumbnail_files_count": 15,
190
+ "timestamp": "2024-01-01T12:00:00"
191
+ }
192
+ ```
193
+
194
+ ## 错误处理
195
+
196
+ 所有API端点都遵循统一的错误响应格式:
197
+
198
+ ```json
199
+ {
200
+ "detail": "错误描述信息"
201
+ }
202
+ ```
203
+
204
+ **HTTP状态码:**
205
+ - 200: 请求成功
206
+ - 400: 参数错误
207
+ - 404: 资源不存在
208
+ - 500: 服务器内部错误
209
+
210
+ ## 企业级功能
211
+
212
+ ### 1. 异步处理
213
+ 支持长视频生成的异步处理,可通过状态查询接口实时获取生成进度。
214
+
215
+ ### 2. 多格式支持
216
+ 支持多种视频格式和分辨率,满足不同场景需求。
217
+
218
+ ### 3. 缩略图生成
219
+ 自动为每个视频生成缩略图,便于预览和展示。
220
+
221
+ ### 4. 语音解说集成
222
+ 支持为视频添加语音解说,实现文本到语音再到视频的完整流程。
223
+
224
+ ### 5. 进度跟踪
225
+ 实时进度跟踪和状态更新,提供良好的用户体验。
226
+
227
+ ## 部署配置
228
+
229
+ ### 环境变量
230
+ ```
231
+ # 视频引擎配置
232
+ VIDEO_ENGINE=simulated # simulated, moviepy, svd, runwayml
233
+ VIDEO_API_KEY=your_api_key
234
+ VIDEO_MODEL=svd
235
+
236
+ # 存储配置
237
+ STORAGE_TYPE=local # local, s3, azure_blob, gcs
238
+ STORAGE_BUCKET=video-bucket
239
+ STORAGE_REGION=us-west-1
240
+
241
+ # 异步处理配置
242
+ VIDEO_ASYNC_PROCESSING=true
243
+ MAX_WORKERS=4
244
+ QUEUE_TIMEOUT=300
245
+
246
+ # API配置
247
+ API_RATE_LIMIT=30 # 视频生成较耗资源,限制更低
248
+ API_AUTH_REQUIRED=false
249
+ ```
250
+
251
+ ### 依赖安装
252
+ ```bash
253
+ # 基本依赖
254
+ pip install fastapi uvicorn python-multipart
255
+
256
+ # 视频处理依赖(根据选择的引擎)
257
+ pip install moviepy # 视频编辑和处理
258
+ pip install opencv-python # 计算机视觉库
259
+ pip install pillow # 图像处理
260
+
261
+ # AI模型相关(用于高级视频生成)
262
+ pip install diffusers # 扩散模型(用于视频生成)
263
+ pip install transformers # 已包含
264
+ pip install torch # 已包含
265
+ ```
266
+
267
+ ## 使用示例
268
+
269
+ ### Python客户端
270
+ ```python
271
+ import requests
272
+ import time
273
+
274
+ BASE_URL = "http://localhost:8000/api/v1"
275
+
276
+ def generate_video(prompt, style="animation"):
277
+ # 提交生成请求
278
+ response = requests.post(f"{BASE_URL}/video/generate", json={
279
+ "prompt": prompt,
280
+ "style": style,
281
+ "duration": 5,
282
+ "resolution": "720p"
283
+ })
284
+
285
+ if response.status_code != 200:
286
+ print(f"提交失败: {response.text}")
287
+ return None
288
+
289
+ data = response.json()
290
+ if not data["success"]:
291
+ print(f"生成失败: {data.get('message')}")
292
+ return None
293
+
294
+ video_id = data["video_id"]
295
+ print(f"视频生成已提交,ID: {video_id}")
296
+
297
+ # 轮询状态
298
+ while True:
299
+ status_response = requests.post(f"{BASE_URL}/video/status", json={
300
+ "video_id": video_id
301
+ })
302
+
303
+ if status_response.status_code == 200:
304
+ status_data = status_response.json()
305
+ if status_data["success"]:
306
+ status = status_data["status"]
307
+ progress = status_data["progress"]
308
+
309
+ print(f"进度: {progress}% - 状态: {status}")
310
+
311
+ if status == "completed":
312
+ print(f"视频生成完成: {data['video_url']}")
313
+ return data
314
+ elif status == "failed":
315
+ print("视频生成失败")
316
+ return None
317
+
318
+ time.sleep(2)
319
+ ```
320
+
321
+ ### JavaScript/TypeScript客户端
322
+ ```typescript
323
+ const API_BASE_URL = 'http://localhost:8000/api/v1';
324
+
325
+ class VideoGenerator {
326
+ async generateVideo(prompt: string): Promise<any> {
327
+ // 提交生成请求
328
+ const response = await fetch(`${API_BASE_URL}/video/generate`, {
329
+ method: 'POST',
330
+ headers: {
331
+ 'Content-Type': 'application/json',
332
+ },
333
+ body: JSON.stringify({
334
+ prompt,
335
+ style: 'animation',
336
+ duration: 5,
337
+ resolution: '720p'
338
+ })
339
+ });
340
+
341
+ if (!response.ok) {
342
+ throw new Error(`提交失败: ${response.status}`);
343
+ }
344
+
345
+ const data = await response.json();
346
+ if (!data.success) {
347
+ throw new Error(`生成失败: ${data.message}`);
348
+ }
349
+
350
+ return data;
351
+ }
352
+
353
+ async checkStatus(videoId: string): Promise<any> {
354
+ const response = await fetch(`${API_BASE_URL}/video/status`, {
355
+ method: 'POST',
356
+ headers: {
357
+ 'Content-Type': 'application/json',
358
+ },
359
+ body: JSON.stringify({ video_id: videoId })
360
+ });
361
+
362
+ if (!response.ok) {
363
+ throw new Error(`状态查询失败: ${response.status}`);
364
+ }
365
+
366
+ return await response.json();
367
+ }
368
+
369
+ async downloadVideo(videoId: string, format: string = 'mp4'): Promise<void> {
370
+ const url = `${API_BASE_URL}/video/files/${videoId}.${format}`;
371
+ window.open(url, '_blank');
372
+ }
373
+ }
374
+ ```
375
+
376
+ ## 性能优化建议
377
+
378
+ 1. **异步队列**:使用消息队列处理视频生成任务,避免阻塞主线程
379
+ 2. **GPU加速**:视频生成任务尽量使用GPU加速
380
+ 3. **分级存储**:热数据使用SSD,冷数据迁移到低成本存储
381
+ 4. **CDN分发**:生成的视频文件通过CDN分发,提高访问速度
382
+ 5. **预览优化**:先生成低分辨率预览,再生成全分辨率版本
383
+
384
+ ## 安全注意事���
385
+
386
+ 1. **内容审核**:对用户输入的描述文本进行内容安全审核
387
+ 2. **资源限制**:实施严格的资源使用限制,防止滥用
388
+ 3. **访问控制**:敏感操作需要身份验证和授权
389
+ 4. **数据隔离**:不同用户的数据进行逻辑或物理隔离
390
+ 5. **监控告警**:实时监控系统资源使用情况,设置告警阈值
391
+
392
+ ## 扩展功能
393
+
394
+ ### 1. 批量生成
395
+ 支持批量视频生成,提高处理效率。
396
+
397
+ ### 2. 自定义模板
398
+ 支持用户上传自定义视频模板。
399
+
400
+ ### 3. 高级编辑
401
+ 支持对生成的视频进行进一步编辑(裁剪、滤镜、字幕等)。
402
+
403
+ ### 4. 多语言支持
404
+ 支持更多语言的语音解说。
405
+
406
+ ### 5. 实时预览
407
+ 支持生成过程中的实时预览功能。
408
+
409
+ ## 故障排除
410
+
411
+ ### 常见问题
412
+ 1. **生成速度慢**:检查GPU资源是否充足,考虑升级硬件或优化算法
413
+ 2. **内存不足**:调整视频分辨率或时长,减少内存占用
414
+ 3. **文件太大**:调整视频编码参数,优化文件大小
415
+ 4. **质量不佳**:优化描述文本,调整生成参数
416
+ 5. **服务不可用**:检查依赖服务状态,查看错误日志
417
+
418
+ ### 日志查看
419
+ ```bash
420
+ # 查看应用日志
421
+ tail -f logs/application.log
422
+
423
+ # 查看错误日志
424
+ tail -f logs/error.log
425
+
426
+ # 查看访问日志
427
+ tail -f logs/access.log
428
+ ```