Skip to content

Commit e672e5c

Browse files
add backend for content search
1 parent 4e4c532 commit e672e5c

File tree

28 files changed

+1063
-0
lines changed

28 files changed

+1063
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
ext_components/storage_minio/content_search_minio
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Backend
2+
3+
### System Workflow
4+
The system employs a Producer-Consumer pattern to decouple heavy AI inference from the API response cycle.
5+
6+
```mermaid
7+
graph TD
8+
%% Component Styles
9+
classDef storage fill:#f9f,stroke:#333,stroke-width:2px;
10+
classDef queue fill:#ff9,stroke:#333,stroke-width:2px;
11+
classDef app fill:#bbf,stroke:#333,stroke-width:2px;
12+
13+
User((User / Frontend))
14+
15+
subgraph FastAPI_App [FastAPI Service Layer]
16+
API_Upload[Video Upload API]:::app
17+
API_Summary[Task Summary API]:::app
18+
Processor[Sync Processor Module]:::app
19+
end
20+
21+
subgraph Data_Layer [Infrastructure Layer]
22+
DB[(PostgreSQL)]:::storage
23+
S3[(MinIO Object Storage)]:::storage
24+
Redis{Redis Stream}:::queue
25+
end
26+
27+
subgraph AI_Cluster [AI Execution Layer]
28+
Worker[Async Worker Process]
29+
AI_Service[AI Mock Service]
30+
end
31+
32+
%% Flows
33+
User -->|1. Upload Video| API_Upload
34+
API_Upload -->|2. Store File| S3
35+
API_Upload -->|3. Save Metadata| DB
36+
API_Upload -->|4. Push Task| Redis
37+
38+
User -->|1. Submit JSON| API_Summary
39+
API_Summary -->|2. Log Task| DB
40+
41+
%% Sync vs Async Logic
42+
API_Summary -->|Mode: Sync| Processor
43+
Processor -->|HTTP Req| AI_Service
44+
Processor -->|Update Result| DB
45+
Processor -->|Direct Resp| User
46+
47+
API_Summary -->|Mode: Async| Redis
48+
Redis -->|Immediate Ack| User
49+
50+
%% Worker Logic
51+
Redis -.->|Subscribe/Pull| Worker
52+
Worker -->|Fetch Stream| S3
53+
Worker -->|Inference| AI_Service
54+
Worker -->|Writeback Result| DB
55+
```
56+
### Project Structure
57+
```bash
58+
backend/
59+
├── api/ # API Routes and Business Logic
60+
├── core/ # Database Models (SQLAlchemy)
61+
├── mock_services/ # Independent AI Mock Provider
62+
├── tests/ # Pytest Suite (Unit & Integration)
63+
├── processor.py # Synchronous AI Task Handler
64+
├── database.py # Database Engine & Session Config
65+
├── worker_run.py # Redis Stream Consumer (Worker)
66+
├── main.py # Application Entry Point
67+
└── pytest.ini # Testing Configuration
68+
```
69+
### Prerequisites
70+
#### Hardware & OS
71+
OS: Windows 11 / Linux
72+
73+
Python: 3.12.x
74+
75+
#### Infrastructure
76+
Redis: Task queuing (v5.0+)
77+
78+
PostgreSQL: Metadata storage (v16+)
79+
80+
MinIO: Large file object storage
81+
82+
### Environment Setup
83+
#### Conda Environment (Miniforge)
84+
install conda (miniforge) and setup env
85+
```bash
86+
conda create -n edu-ai python=3.12
87+
conda activate edu-ai
88+
```
89+
#### .condarc
90+
```bash
91+
channels:
92+
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
93+
- https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/
94+
- conda-forge
95+
mirrored_channels:
96+
conda-forge:
97+
- https://conda.anaconda.org/conda-forge
98+
- https://prefix.dev/conda-forge
99+
ssl_verify: false
100+
proxy_servers:
101+
http: http://proxy-dmz.intel.com:911
102+
https: http://proxy-dmz.intel.com:912
103+
```
104+
```
105+
conda install -c conda-forge psycopg2 redis-py
106+
pip install fastapi uvicorn httpx pydantic-settings
107+
```
108+
python (3.12.x) C:\Users\user\miniforge3\python.exe
109+
110+
```powershell
111+
conda activate edu-ai
112+
pip install -r requirements.txt
113+
```
114+
#### backup env
115+
conda env export > environment.yml
116+
#### restore env
117+
conda env create -f environment.yml
118+
119+
### Running the System
120+
#### Prepare 3rdpart modules
121+
copy `education-ai-suite/content-search/content_search_minio` under `backend/ext_components/storage_minio` or just create a softlink,
122+
like
123+
```bash
124+
tree -L 3 ext_components/
125+
ext_components/
126+
├── readme.md
127+
└── storage_minio
128+
└── content_search_minio -> /xx/x/edge-ai-suites/education-ai-suite/content-search/content_search_minio
129+
```
130+
#### Launch the serives
131+
```powershell
132+
# Terminal A
133+
& "C:\Users\user\miniforge3\envs\edu-ai\python.exe" .\main.py
134+
135+
# Terminal B
136+
& "C:\Users\user\miniforge3\envs\edu-ai\python.exe" .\worker_run.py
137+
138+
# Terminal C
139+
& "C:\Users\user\miniforge3\envs\edu-ai\python.exe" .\mock_services\dummy_ai_provider.py
140+
```
141+
142+
### API Usage & Testing
143+
#### Synchronous Summary (Immediate Result)
144+
Method: POST
145+
146+
Endpoint: http://127.0.0.1:8000/api/tasks/video-summary
147+
148+
Body (JSON):
149+
```json
150+
{
151+
"video_url": "C:/videos/classroom_test.mp4",
152+
"sync": true
153+
}
154+
```
155+
156+
#### Asynchronous Summary (Webhook Notification)
157+
Method: POST
158+
159+
Endpoint: http://127.0.0.1:8000/api/tasks/video-summary
160+
161+
Body (JSON):
162+
```json
163+
{
164+
"video_url": "C:/videos/test.mp4",
165+
"sync": false,
166+
"callback_url": "[https://webhook.site/your-unique-id](https://webhook.site/your-unique-id)"
167+
}
168+
```
169+
webhook.site
170+
https://webhook.site/ unique URL: e.g. https://webhook.site/28865adb-376c-4a0a-ac59-5204a60f9fe3
171+
172+
#### Video Upload (MinIO Integration)
173+
Method: POST
174+
175+
Endpoint: http://127.0.0.1:8000/api/tasks/video-upload
176+
177+
Body: form-data | key: video_file | type: File
178+
179+
### Automated Tests
180+
```powershell
181+
pip install -r .\tests\requirements.txt
182+
pytest .\tests\pytest -v
183+
```
184+
185+
### Debug tools
186+
pgadmin 4
187+
tiny RDM
188+
189+
190+
### Others
191+
install Redis
192+
https://github.com/tporadowski/redis/releases
193+
194+
install PostgreSQL
195+
16.11.3 https://www.postgresql.org/download/windows/ passwd: edu-ai port: 5432
196+
197+
install postman
198+
xxx
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Edge AI Libraries Sample Applications API Map
2+
3+
本表用于前端对接与兼容性对照,来源于 `edge-ai-libraries/sample-applications`
4+
5+
| 子项目 | 目录 | 用途 | Base Path / 服务 | API 列表 |
6+
|---|---|---|---|---|
7+
| chat-question-and-answer | `sample-applications/chat-question-and-answer/` | RAG 问答微服务(FastAPI) | `/v1/chatqna` | `GET /`(docs 入口),`GET /health``GET /model``POST /chat`(SSE 流式) |
8+
| chat-question-and-answer-core | `sample-applications/chat-question-and-answer-core/` | 一体化 RAG 服务(FastAPI) | `/v1/chatqna` | `GET /health``GET /model``GET/POST/DELETE /documents``POST /chat`(SSE/非流式),OpenVINO: `GET /devices``GET /devices/{device}`,Ollama: `GET /ollama-models``GET /ollama-model` |
9+
| document-summarization | `sample-applications/document-summarization/` | 文档摘要服务(FastAPI) | `http://localhost:8090/v1/docsum` | `GET /version``POST /summarize/`(multipart + SSE) |
10+
| video-search-and-summarization (pipeline-manager) | `sample-applications/video-search-and-summarization/pipeline-manager/` | 视频检索与摘要编排(NestJS) | 服务根路径 | `GET /app/config``GET /app/features``GET /pipeline/frames``GET /pipeline/evam``GET /audio/models``GET/POST /videos``GET /videos/{videoId}``POST /videos/search-embeddings/{videoId}``GET /tags``DELETE /tags/{tagId}``GET /search``GET /search/watched``GET /search/{queryId}``POST /search``POST /search/{queryId}/refetch``POST /search/query``PATCH /search/{queryId}/watch``DELETE /search/{queryId}``GET /summary``GET /summary/ui``GET /summary/{stateId}``GET /summary/{stateId}/raw``POST /summary``DELETE /summary/{stateId}``GET /health`;内部隐藏:`POST /llm``POST /vlm``GET /states/{stateId}``GET /states/raw/{stateId}` |
11+
| video-search-and-summarization (search-ms) | `sample-applications/video-search-and-summarization/search-ms/` | 向量检索服务(FastAPI) | 服务根路径 | `POST /query``GET /watcher-last-updated``GET /health``GET /initial-upload-status` |
12+
| plcopen-benchmark | `sample-applications/plcopen-benchmark/` | PLCopen 基准测试(C++) | N/A | 未发现 HTTP API |
13+
| plcopen-databus | `sample-applications/plcopen-databus/` | PLCopen 数据总线(C++) | N/A | 未发现 HTTP API |
14+
15+
> 备注:如需导出为 OpenAPI 或进一步拆分字段级请求/响应说明,请告知前端对接格式需求。
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# api/route.py
2+
from fastapi import APIRouter, Depends, HTTPException, File, UploadFile
3+
from sqlalchemy.orm import Session
4+
from database import get_db, SessionLocal
5+
from core.models import AITask
6+
7+
from processor import run_dummy_ai_logic
8+
from services.storage_service import storage_service
9+
10+
import redis
11+
import json
12+
13+
router = APIRouter()
14+
15+
# Initialize sync Redis client (tporadowski/redis default port 6379)
16+
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
17+
18+
@router.post("/tasks/video-summary")
19+
async def submit_video_task(payload: dict, db: Session = Depends(get_db)):
20+
# Whether to enable synchronous wait mode (default False, async)
21+
is_sync = payload.get("sync", False)
22+
callback_url = payload.get("callback_url")
23+
24+
# 1. Persist to PostgreSQL regardless of sync/async
25+
new_task = AITask(
26+
task_type="video_summary",
27+
payload=payload,
28+
status="PENDING",
29+
user_id="admin"
30+
)
31+
db.add(new_task)
32+
db.commit()
33+
db.refresh(new_task)
34+
35+
if is_sync:
36+
# --- Case A: synchronous mode ---
37+
try:
38+
# Update status to PROCESSING
39+
new_task.status = "PROCESSING"
40+
db.commit()
41+
42+
# Simulate worker processing (or call the real AI function)
43+
# Note: dummy_ai_work should be an async function
44+
result = await run_dummy_ai_logic(payload.get("video_url"))
45+
46+
# Write back result
47+
new_task.status = "COMPLETED"
48+
new_task.result = result
49+
db.commit()
50+
51+
return {
52+
"task_id": new_task.id,
53+
"status": "COMPLETED",
54+
"result": result,
55+
"mode": "synchronous"
56+
}
57+
except Exception as e:
58+
new_task.status = "FAILED"
59+
db.commit()
60+
raise HTTPException(status_code=500, detail=f"Sync processing failed: {str(e)}")
61+
62+
else:
63+
# --- Case B: async callback mode (original logic) ---
64+
try:
65+
new_task.status = "QUEUED"
66+
db.commit()
67+
68+
redis_client.xadd(
69+
"stream:video_processing",
70+
{"task_id": str(new_task.id)}
71+
)
72+
return {
73+
"task_id": new_task.id,
74+
"status": "QUEUED",
75+
"mode": "asynchronous",
76+
"callback_notified": "pending" if callback_url else "none"
77+
}
78+
except Exception as e:
79+
raise HTTPException(status_code=500, detail=f"Redis error: {str(e)}")
80+
81+
def get_db():
82+
db = SessionLocal()
83+
try:
84+
yield db
85+
finally:
86+
db.close()
87+
88+
@router.get("/tasks/{task_id}")
89+
async def get_task_status(task_id: str, db: Session = Depends(get_db)):
90+
# Look up task in the database
91+
task = db.query(AITask).filter(AITask.id == task_id).first()
92+
93+
if not task:
94+
raise HTTPException(status_code=404, detail="Task not found")
95+
96+
return {
97+
"task_id": task.id,
98+
"status": task.status,
99+
"payload": task.payload,
100+
"result": task.result, # Null if not completed; otherwise the mock summary
101+
"created_at": task.created_at
102+
}
103+
104+
@router.post("/tasks/video-upload")
105+
async def submit_upload_task(
106+
video_file: UploadFile = File(...),
107+
db: Session = Depends(get_db)
108+
):
109+
"""
110+
File-upload task endpoint:
111+
1. Video stream -> MinIO (via symlink module)
112+
2. Path -> PostgreSQL
113+
3. Task ID -> Redis Stream
114+
"""
115+
try:
116+
# 1. Call service to handle storage
117+
minio_payload = await storage_service.upload_and_prepare_payload(video_file)
118+
119+
# 2. Persist to PostgreSQL
120+
new_task = AITask(
121+
task_type="video_summary",
122+
payload=minio_payload, # Stores MinIO path info
123+
status="QUEUED",
124+
user_id="admin"
125+
)
126+
db.add(new_task)
127+
db.commit()
128+
db.refresh(new_task)
129+
130+
# 3. Push to Redis queue
131+
redis_client.xadd(
132+
"stream:video_processing",
133+
{"task_id": str(new_task.id)}
134+
)
135+
136+
return {
137+
"task_id": new_task.id,
138+
"status": "QUEUED",
139+
"storage": "minio",
140+
"object_key": minio_payload["video_key"]
141+
}
142+
143+
except Exception as e:
144+
db.rollback()
145+
raise HTTPException(status_code=500, detail=f"Upload task failed: {str(e)}")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# config.py
2+
from pydantic_settings import BaseSettings
3+
4+
class Settings(BaseSettings):
5+
# Default points to localhost (default for native Windows install)
6+
DATABASE_URL: str = "postgresql+psycopg2://postgres:password@localhost:5432/edu_ai"
7+
REDIS_URL: str = "redis://localhost:6379/0"
8+
9+
class Config:
10+
env_file = ".env"
11+
12+
settings = Settings()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

education-ai-suite/backend/core/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)