2026-05-18 11:41:20 +08:00
|
|
|
|
# 车辆法规智能检索系统 - 后端 API
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
基于 FastAPI + LangGraph + Milvus + 千问大模型 的法规检索与合规分析后端服务。
|
|
|
|
|
|
|
|
|
|
|
|
## 目录
|
|
|
|
|
|
|
|
|
|
|
|
- [技术栈](#技术栈)
|
|
|
|
|
|
- [服务依赖](#服务依赖)
|
|
|
|
|
|
- [快速开始](#快速开始)
|
|
|
|
|
|
- [环境配置](#环境配置)
|
|
|
|
|
|
- [API 接口文档](#api-接口文档)
|
|
|
|
|
|
- [项目结构](#项目结构)
|
|
|
|
|
|
- [核心模块详解](#核心模块详解)
|
|
|
|
|
|
- [工作流设计](#工作流设计)
|
|
|
|
|
|
- [数据模型](#数据模型)
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
## 技术栈
|
|
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
| 组件 | 技术选型 | 说明 |
|
|
|
|
|
|
|------|----------|------|
|
|
|
|
|
|
| Web框架 | FastAPI | 高性能异步 API 框架 |
|
|
|
|
|
|
| AI框架 | LangGraph | 状态图工作流编排 |
|
|
|
|
|
|
| LLM | 千问 Qwen-Max | 阿里云 DashScope API |
|
|
|
|
|
|
| Embedding | text-embedding-v3 | DashScope 文本向量服务 |
|
|
|
|
|
|
| 向量数据库 | Milvus | 开源高性能向量检索引擎 |
|
|
|
|
|
|
| 关系数据库 | PostgreSQL | 数据持久化存储 |
|
|
|
|
|
|
| 缓存 | Redis | 会话缓存与任务队列 |
|
|
|
|
|
|
| 图数据库 | Neo4j | 法规关系图谱存储 |
|
|
|
|
|
|
| 消息队列 | RabbitMQ | 异步任务处理 |
|
|
|
|
|
|
|
|
|
|
|
|
## 服务依赖
|
|
|
|
|
|
|
|
|
|
|
|
需要启动以下基础服务:
|
|
|
|
|
|
|
|
|
|
|
|
| 服务 | 端口 | 用户/密码 |
|
|
|
|
|
|
|------|------|-----------|
|
|
|
|
|
|
| PostgreSQL | 5432 | postgresql/postgresql123456 |
|
|
|
|
|
|
| Redis | 6379 | redis@123 |
|
|
|
|
|
|
| Milvus | 19530, 9091 | - |
|
|
|
|
|
|
| MinIO | 9000, 9001 | minioadmin/minioadmin |
|
|
|
|
|
|
| Neo4j | 7474, 7687 | neo4j/neo4j123 |
|
|
|
|
|
|
| RabbitMQ | 5672, 15672 | admin/admin@123 |
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
### 1. 安装依赖
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-18 11:41:20 +08:00
|
|
|
|
cd backend
|
|
|
|
|
|
uv pip install -r requirements.txt
|
2026-05-11 11:19:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 配置环境变量
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cp .env.example .env
|
2026-05-18 11:41:20 +08:00
|
|
|
|
# 编辑 .env 文件,配置各项服务参数
|
2026-05-11 11:19:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
### 3. 启动服务
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-18 11:41:20 +08:00
|
|
|
|
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
2026-05-11 11:19:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
### 4. 访问 API 文档
|
|
|
|
|
|
|
|
|
|
|
|
- Swagger UI: http://localhost:8000/docs
|
|
|
|
|
|
- ReDoc: http://localhost:8000/redoc
|
|
|
|
|
|
|
|
|
|
|
|
## 环境配置
|
|
|
|
|
|
|
|
|
|
|
|
`.env` 文件配置项:
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-18 11:41:20 +08:00
|
|
|
|
# DashScope API(LLM 与 Embedding)
|
|
|
|
|
|
DASHSCOPE_API_KEY=your_api_key_here
|
|
|
|
|
|
LLM_MODEL=qwen-max
|
|
|
|
|
|
EMBEDDING_MODEL=text-embedding-v3
|
|
|
|
|
|
EMBEDDING_DIM=1536
|
|
|
|
|
|
|
|
|
|
|
|
# PostgreSQL
|
|
|
|
|
|
POSTGRES_HOST=localhost
|
|
|
|
|
|
POSTGRES_PORT=5432
|
|
|
|
|
|
POSTGRES_USER=postgresql
|
|
|
|
|
|
POSTGRES_PASSWORD=postgresql123456
|
|
|
|
|
|
POSTGRES_DB=mydb
|
|
|
|
|
|
|
|
|
|
|
|
# Redis
|
|
|
|
|
|
REDIS_HOST=localhost
|
|
|
|
|
|
REDIS_PORT=6379
|
|
|
|
|
|
REDIS_PASSWORD=redis@123
|
|
|
|
|
|
|
|
|
|
|
|
# Milvus
|
|
|
|
|
|
MILVUS_HOST=localhost
|
|
|
|
|
|
MILVUS_PORT=19530
|
|
|
|
|
|
|
|
|
|
|
|
# MinIO
|
|
|
|
|
|
MINIO_ENDPOINT=localhost:9000
|
|
|
|
|
|
MINIO_ACCESS_KEY=minioadmin
|
|
|
|
|
|
MINIO_SECRET_KEY=minioadmin
|
|
|
|
|
|
|
|
|
|
|
|
# Neo4j
|
|
|
|
|
|
NEO4J_URI=bolt://localhost:7687
|
|
|
|
|
|
NEO4J_USER=neo4j
|
|
|
|
|
|
NEO4J_PASSWORD=neo4j123
|
|
|
|
|
|
|
|
|
|
|
|
# RabbitMQ
|
|
|
|
|
|
RABBITMQ_HOST=localhost
|
|
|
|
|
|
RABBITMQ_PORT=5672
|
|
|
|
|
|
RABBITMQ_USER=admin
|
|
|
|
|
|
RABBITMQ_PASSWORD=admin@123
|
|
|
|
|
|
|
|
|
|
|
|
# 检索配置
|
|
|
|
|
|
VECTOR_TOP_K=10
|
|
|
|
|
|
BM25_TOP_K=10
|
|
|
|
|
|
FINAL_TOP_K=5
|
|
|
|
|
|
|
|
|
|
|
|
# 分块配置
|
|
|
|
|
|
CHUNK_SIZE=800
|
|
|
|
|
|
CHUNK_OVERLAP=50
|
|
|
|
|
|
|
|
|
|
|
|
# 服务配置
|
|
|
|
|
|
API_HOST=0.0.0.0
|
|
|
|
|
|
API_PORT=8000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## API 接口文档
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 文档管理 `/api/docs`
|
|
|
|
|
|
|
|
|
|
|
|
| 接口 | 方法 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `/upload` | POST | 上传法规文档 (PDF/DOCX/TXT) |
|
|
|
|
|
|
| `/list` | GET | 获取已索引文档列表 |
|
|
|
|
|
|
| `/parse/{doc_id}` | POST | 解析文档并分块 |
|
|
|
|
|
|
| `/embed/{doc_id}` | POST | 向量化并存入 Milvus |
|
|
|
|
|
|
| `/delete/{doc_id}` | DELETE | 删除文档 |
|
|
|
|
|
|
|
|
|
|
|
|
**上传文档响应示例:**
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"doc_id": "doc-001",
|
|
|
|
|
|
"filename": "道路交通安全法.pdf",
|
|
|
|
|
|
"size": 102400,
|
|
|
|
|
|
"status": "uploaded"
|
|
|
|
|
|
}
|
2026-05-11 11:19:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
### 2. RAG 问答 `/api/rag`
|
|
|
|
|
|
|
|
|
|
|
|
| 接口 | 方法 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `/chat` | POST | SSE 流式问答 |
|
|
|
|
|
|
| `/quick-questions` | GET | 获取预设快捷问题 |
|
|
|
|
|
|
|
|
|
|
|
|
**请求参数:**
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"query": "电动自行车需要上牌照吗?",
|
|
|
|
|
|
"top_k": 5
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
**SSE 事件流格式:**
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"type": "retrieving"}
|
|
|
|
|
|
{"type": "retrieved", "docs": [...]}
|
|
|
|
|
|
{"type": "generating", "text": "正在生成答案..."}
|
|
|
|
|
|
{"type": "chunk", "text": "答案片段..."}
|
|
|
|
|
|
{"type": "done"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 合规分析 `/api/compliance`
|
|
|
|
|
|
|
|
|
|
|
|
| 接口 | 方法 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `/analyze` | POST | 上传设计方案进行分析 |
|
|
|
|
|
|
| `/result/{task_id}` | GET | 获取分析结果 |
|
|
|
|
|
|
| `/chat/{segment_id}` | POST | 针对特定段落进行合规对话 |
|
|
|
|
|
|
|
|
|
|
|
|
**分析结果响应示例:**
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"task_id": "task-xxx",
|
|
|
|
|
|
"dashboard": {
|
|
|
|
|
|
"score": 78,
|
|
|
|
|
|
"high_risk_count": 2,
|
|
|
|
|
|
"medium_risk_count": 1,
|
|
|
|
|
|
"low_risk_count": 0,
|
|
|
|
|
|
"need_fix_segments": 3,
|
|
|
|
|
|
"status": "warning",
|
|
|
|
|
|
"status_label": "需优化"
|
|
|
|
|
|
},
|
|
|
|
|
|
"segments": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 1,
|
|
|
|
|
|
"intent": "车身结构设计",
|
|
|
|
|
|
"content": "...",
|
|
|
|
|
|
"risk_level": "high",
|
|
|
|
|
|
"regulations": [...]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"priority_actions": [...]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
### 4. 系统状态 `/api/status`
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
| 接口 | 方法 | 说明 |
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|------|------|------|
|
2026-05-18 11:41:20 +08:00
|
|
|
|
| `/stats` | GET | 系统统计数据 |
|
|
|
|
|
|
| `/config` | GET | 当前配置信息 |
|
|
|
|
|
|
| `/milvus/health` | GET | Milvus 健康检查 |
|
|
|
|
|
|
|
|
|
|
|
|
**统计数据响应:**
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"docs": 5,
|
|
|
|
|
|
"chunks": 510,
|
|
|
|
|
|
"vectors": 510,
|
|
|
|
|
|
"segments": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-05-11 11:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-05-18 11:41:20 +08:00
|
|
|
|
backend/
|
|
|
|
|
|
├── app/
|
|
|
|
|
|
│ ├── main.py # FastAPI 应用入口
|
|
|
|
|
|
│ ├── core/
|
|
|
|
|
|
│ │ └── config.py # Pydantic Settings 配置管理
|
|
|
|
|
|
│ ├── api/
|
|
|
|
|
|
│ │ ├── __init__.py # API 路由聚合
|
|
|
|
|
|
│ │ └── routes/
|
|
|
|
|
|
│ │ ├── docs.py # 文档管理接口
|
|
|
|
|
|
│ │ ├── rag.py # RAG 问答接口
|
|
|
|
|
|
│ │ ├── compliance.py # 合规分析接口
|
|
|
|
|
|
│ │ └── status.py # 系统状态接口
|
|
|
|
|
|
│ ├── schemas/
|
|
|
|
|
|
│ │ ├── doc.py # 文档相关数据模型
|
|
|
|
|
|
│ │ ├── rag.py # RAG 问答数据模型
|
|
|
|
|
|
│ │ └── compliance.py # 合规分析数据模型
|
|
|
|
|
|
│ ├── services/
|
|
|
|
|
|
│ │ ├── llm.py # LLM 服务封装
|
|
|
|
|
|
│ │ ├── embedding.py # Embedding 服务封装
|
|
|
|
|
|
│ │ ├── milvus.py # Milvus 向量库服务
|
|
|
|
|
|
│ │ ├── document.py # 文档解析服务
|
|
|
|
|
|
│ │ └── mock_data.py # Mock 数据(开发测试)
|
|
|
|
|
|
│ ├── workflows/
|
|
|
|
|
|
│ │ ├── rag_workflow.py # RAG 工作流
|
|
|
|
|
|
│ │ └── compliance_workflow.py # 合规分析工作流
|
|
|
|
|
|
│ └── utils/
|
|
|
|
|
|
│ ├── chunking.py # 文本分块工具
|
|
|
|
|
|
│ └── logger.py # 日志工具
|
|
|
|
|
|
├── data/
|
|
|
|
|
|
│ ├── raw/ # 原始上传文件
|
|
|
|
|
|
│ └── parsed/ # 解析后文件
|
|
|
|
|
|
├── tests/ # 测试目录
|
|
|
|
|
|
├── .env # 环境变量配置
|
|
|
|
|
|
├── .env.example # 环境变量示例
|
|
|
|
|
|
├── requirements.txt # Python 依赖
|
|
|
|
|
|
├── pyproject.toml # 项目配置
|
|
|
|
|
|
└── main.py # 入口脚本
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 核心模块详解
|
|
|
|
|
|
|
|
|
|
|
|
### 配置管理 (`app/core/config.py`)
|
|
|
|
|
|
|
|
|
|
|
|
使用 Pydantic Settings 管理配置,自动从 `.env` 文件加载:
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
|
|
|
|
dashscope_api_key: str = ""
|
|
|
|
|
|
milvus_host: str = "localhost"
|
|
|
|
|
|
milvus_port: int = 19530
|
|
|
|
|
|
llm_model: str = "qwen-max"
|
|
|
|
|
|
embedding_model: str = "text-embedding-v3"
|
|
|
|
|
|
embedding_dim: int = 1536
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
env_file = ".env"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 服务层 (`app/services/`)
|
|
|
|
|
|
|
|
|
|
|
|
#### LLM 服务 (`llm.py`)
|
|
|
|
|
|
- 封装 DashScope API 调用
|
|
|
|
|
|
- 支持流式输出
|
|
|
|
|
|
- 提供对话补全功能
|
|
|
|
|
|
|
|
|
|
|
|
#### Embedding 服务 (`embedding.py`)
|
|
|
|
|
|
- 文本向量化
|
|
|
|
|
|
- 批量嵌入支持
|
|
|
|
|
|
- 维度配置 (1536)
|
|
|
|
|
|
|
|
|
|
|
|
#### Milvus 服务 (`milvus.py`)
|
|
|
|
|
|
- Collection 创建与管理
|
|
|
|
|
|
- 向量插入与检索
|
|
|
|
|
|
- 混合检索 (向量 + BM25)
|
|
|
|
|
|
|
|
|
|
|
|
#### Mock 数据服务 (`mock_data.py`)
|
|
|
|
|
|
- 预设法规文档数据
|
|
|
|
|
|
- 预设问答数据
|
|
|
|
|
|
- 预设合规分析结果
|
|
|
|
|
|
- 用于开发测试阶段
|
|
|
|
|
|
|
|
|
|
|
|
## 工作流设计
|
|
|
|
|
|
|
|
|
|
|
|
### RAG 工作流 (`rag_workflow.py`)
|
|
|
|
|
|
|
|
|
|
|
|
基于 LangGraph 构建的状态图工作流:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[用户查询] -> [检索向量库] -> [BM25补充] -> [结果融合] -> [LLM生成] -> [输出答案]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 合规分析工作流 (`compliance_workflow.py`)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[上传文档] -> [解析文档] -> [AI语义分段] -> [法规匹配] -> [风险评分] -> [生成建议]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
状态节点:
|
|
|
|
|
|
- `parse`: 解析文档提取文本
|
|
|
|
|
|
- `segment`: AI 识别语义段落
|
|
|
|
|
|
- `match`: 向量检索匹配法规
|
|
|
|
|
|
- `score`: 计算风险等级
|
|
|
|
|
|
- `suggest`: 生成优先修改建议
|
|
|
|
|
|
|
|
|
|
|
|
## 数据模型
|
|
|
|
|
|
|
|
|
|
|
|
### 文档模型 (`schemas/doc.py`)
|
|
|
|
|
|
|
|
|
|
|
|
| 模型 | 字段 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| DocumentUploadResponse | doc_id, filename, size, status | 上传响应 |
|
|
|
|
|
|
| DocumentInfo | id, name, chunks, status, created_at | 文档信息 |
|
|
|
|
|
|
| DocumentListResponse | docs | 文档列表 |
|
|
|
|
|
|
| ParseResponse | doc_id, chunks, status | 解析响应 |
|
|
|
|
|
|
| EmbedResponse | doc_id, vectors, status | 嵌入响应 |
|
|
|
|
|
|
|
|
|
|
|
|
### RAG 模型 (`schemas/rag.py`)
|
|
|
|
|
|
|
|
|
|
|
|
| 模型 | 字段 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| RagChatRequest | query, top_k | 问答请求 |
|
|
|
|
|
|
| RetrievedDoc | id, doc_name, clause_id, score, content, preview | 检索文档 |
|
|
|
|
|
|
| QuickQuestion | id, question, category | 快捷问题 |
|
|
|
|
|
|
| QuickQuestionsResponse | questions | 快捷问题列表 |
|
|
|
|
|
|
|
|
|
|
|
|
### 合规模型 (`schemas/compliance.py`)
|
|
|
|
|
|
|
|
|
|
|
|
| 模型 | 字段 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| Regulation | id, name, clause, score, match_keyword, category, full_content | 法规条目 |
|
|
|
|
|
|
| ComplianceSegment | id, index, intent, content, risk_level, regulations | 语义段落 |
|
|
|
|
|
|
| RiskDashboard | score, high_risk_count, medium_risk_count, low_risk_count, status | 风险仪表盘 |
|
|
|
|
|
|
| PriorityAction | regulation, issue, suggestion, severity | 优先建议 |
|
|
|
|
|
|
| ComplianceResult | task_id, dashboard, segments, priority_actions | 分析结果 |
|
|
|
|
|
|
|
|
|
|
|
|
### 风险等级枚举
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class RiskLevel(str, Enum):
|
|
|
|
|
|
high = "high" # 高风险:需立即修改
|
|
|
|
|
|
medium = "medium" # 中风险:建议优化
|
|
|
|
|
|
low = "low" # 低风险:基本合规
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 合规状态枚举
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
class ComplianceStatus(str, Enum):
|
|
|
|
|
|
pass_status = "pass" # 合规通过
|
|
|
|
|
|
warning = "warning" # 需要优化
|
|
|
|
|
|
fail = "fail" # 不合规
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 开发说明
|
|
|
|
|
|
|
|
|
|
|
|
### Mock 模式
|
|
|
|
|
|
|
|
|
|
|
|
当依赖服务未安装或 API Key 未配置时,系统自动使用 Mock 数据模式,返回预设的测试数据,便于前端开发调试。
|
|
|
|
|
|
|
|
|
|
|
|
### SSE 流式输出
|
|
|
|
|
|
|
|
|
|
|
|
使用 `sse-starlette` 库实现 Server-Sent Events 流式响应,适用于:
|
|
|
|
|
|
- RAG 问答实时输出
|
|
|
|
|
|
- 合规对话实时响应
|
|
|
|
|
|
|
|
|
|
|
|
### CORS 配置
|
|
|
|
|
|
|
|
|
|
|
|
已配置允许所有来源的跨域请求,生产环境需根据实际需求调整。
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
|
allow_origins=["*"],
|
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 测试
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pytest tests/
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 许可证
|
|
|
|
|
|
|
|
|
|
|
|
MIT
|