chore(config): 初始化项目配置与部署基础

This commit is contained in:
2026-05-30 00:07:37 +08:00
parent b5ed5b6faa
commit 6291940734
17 changed files with 300 additions and 0 deletions

18
.env.example Normal file
View File

@@ -0,0 +1,18 @@
DJANGO_SECRET_KEY=replace-with-a-local-secret-key
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=*
# OpenAI-compatible LLM API
LLM_API_KEY=your_llm_api_key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4.1-mini
# Embedding model for RAG
# Leave EMBEDDING_API_KEY empty to reuse LLM_API_KEY if desired.
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=text-embedding-3-small
SCENARIO_CONFIG_DIR=configs
UPLOAD_ROOT=data/uploads
CHROMA_PATH=data/chroma

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
EXPOSE 8000
CMD ["sh", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]

View File

@@ -146,6 +146,41 @@ docker compose up --build
当前文档目标已统一为完整 V1 闭环:真实 Chroma RAG、OpenAI 兼容 LLM、OpenAI 兼容 Embedding、工具注册和审计日志。开发阶段可以用测试桩验证页面和边界但不作为 V1 验收结果。 当前文档目标已统一为完整 V1 闭环:真实 Chroma RAG、OpenAI 兼容 LLM、OpenAI 兼容 Embedding、工具注册和审计日志。开发阶段可以用测试桩验证页面和边界但不作为 V1 验收结果。
## 环境变量
项目当前通过 `os.environ` 读取配置,核心变量如下:
```env
DJANGO_SECRET_KEY=replace-with-a-local-secret-key
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=*
LLM_API_KEY=your_llm_api_key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4.1-mini
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=text-embedding-3-small
SCENARIO_CONFIG_DIR=configs
UPLOAD_ROOT=data/uploads
CHROMA_PATH=data/chroma
```
说明:
- `EMBEDDING_API_KEY` 为空时,代码会自动复用 `LLM_API_KEY`
- `EMBEDDING_BASE_URL` 为空时,代码会自动复用 `LLM_BASE_URL`
- `.env.example` 只作为模板,不应填写真实密钥并提交到仓库。
- 当前代码会在 Django settings 初始化时自动加载根目录 `.env`,本地 `python manage.py runserver``pytest` 和 Docker Compose 可以复用同一套配置。
- Docker Compose 当前在 `docker-compose.yml` 中通过 `env_file` 读取 `.env`
常见做法:
- 本地开发:复制 `.env.example``.env`,填入真实参数后运行。
- Docker 演示:确认 `.env` 已配置后,再执行 `docker compose up --build`
## 文档入口 ## 文档入口
- [V1 总需求文档](docs/需求分析/1.V1总需求文档.md) - [V1 总需求文档](docs/需求分析/1.V1总需求文档.md)

1
config/__init__.py Normal file
View File

@@ -0,0 +1 @@

8
config/asgi.py Normal file
View File

@@ -0,0 +1,8 @@
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()

108
config/settings.py Normal file
View File

@@ -0,0 +1,108 @@
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
def load_dotenv(dotenv_path: Path) -> None:
if not dotenv_path.exists():
return
for raw_line in dotenv_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
os.environ.setdefault(key, value)
load_dotenv(BASE_DIR / ".env")
def env_bool(name: str, default: bool = False) -> bool:
value = os.environ.get(name)
if value is None:
return default
return value.lower() in {"1", "true", "yes", "on"}
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-secret-key")
DEBUG = env_bool("DJANGO_DEBUG", True)
ALLOWED_HOSTS = [
host.strip()
for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "*").split(",")
if host.strip()
]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"apps.scenarios",
"apps.documents",
"apps.chat",
"apps.audit",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "data" / "db.sqlite3",
}
}
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "media/"
MEDIA_ROOT = Path(os.environ.get("UPLOAD_ROOT", BASE_DIR / "data" / "uploads"))
SCENARIO_CONFIG_DIR = Path(os.environ.get("SCENARIO_CONFIG_DIR", BASE_DIR / "configs"))
CHROMA_PATH = Path(os.environ.get("CHROMA_PATH", BASE_DIR / "data" / "chroma"))
LLM_API_KEY = os.environ.get("LLM_API_KEY", "")
LLM_BASE_URL = os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1")
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4.1-mini")
EMBEDDING_API_KEY = os.environ.get("EMBEDDING_API_KEY", LLM_API_KEY)
EMBEDDING_BASE_URL = os.environ.get("EMBEDDING_BASE_URL", LLM_BASE_URL)
EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "text-embedding-3-small")
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

16
config/urls.py Normal file
View File

@@ -0,0 +1,16 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("apps.scenarios.urls")),
path("chat/", include("apps.chat.urls")),
path("documents/", include("apps.documents.urls")),
path("audit/", include("apps.audit.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

8
config/wsgi.py Normal file
View File

@@ -0,0 +1,8 @@
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()

10
docker-compose.yml Normal file
View File

@@ -0,0 +1,10 @@
services:
web:
build: .
env_file:
- .env
ports:
- "8000:8000"
volumes:
- ./data:/app/data
- ./configs:/app/configs

View File

@@ -18,6 +18,8 @@ python manage.py runserver
本地运行使用 SQLite、`data/uploads``data/chroma` 本地运行使用 SQLite、`data/uploads``data/chroma`
当前本地方式会在启动时自动读取根目录 `.env`,因此 `runserver``pytest` 和日常脚本可以共享同一套配置。
## 3. Docker 运行方式 ## 3. Docker 运行方式
建议命令: 建议命令:
@@ -47,6 +49,12 @@ V1 Docker Compose 只需要一个 Django Web 服务。Chroma 使用本地持久
`.env.example` 应提供这些变量的样例,不写真实密钥。 `.env.example` 应提供这些变量的样例,不写真实密钥。
当前实现说明:
- 本地 Python 方式启动时,会先加载根目录 `.env`,再读取进程环境中的覆盖值。
- Docker Compose 方式可通过 `env_file` 向容器注入环境变量;当前仓库默认读取 `.env`
- 因此本地运行和容器运行可以默认共用一份 `.env`,但演示前仍应确认密钥和模型参数是否正确。
## 5. 目录挂载设计 ## 5. 目录挂载设计
Docker 需要持久化以下目录: Docker 需要持久化以下目录:

View File

@@ -97,6 +97,12 @@ V1 可使用标准库 `os.environ.get()`,不强制引入复杂配置库。
`DJANGO_ALLOWED_HOSTS` 使用逗号分隔,空值时默认 `["*"]` `DJANGO_ALLOWED_HOSTS` 使用逗号分隔,空值时默认 `["*"]`
当前实现约束:
- 本地直接运行 Django 命令时,会先尝试解析根目录 `.env` 文件,再读取进程环境中的覆盖值。
- Docker Compose 方式可以通过 `env_file` 传入同一批变量;当前仓库默认读取 `.env`
- `.env.example` 只保留占位符示例,不保存真实 API Key。
## 8. 验收标准 ## 8. 验收标准
- `python manage.py check` 通过。 - `python manage.py check` 通过。

View File

@@ -362,12 +362,28 @@ V1 模型适配器需要支持:
环境变量示例: 环境变量示例:
```env ```env
DJANGO_SECRET_KEY=replace-with-a-local-secret-key
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=*
LLM_API_KEY=your_api_key LLM_API_KEY=your_api_key
LLM_BASE_URL=https://api.openai.com/v1 LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4.1-mini LLM_MODEL=gpt-4.1-mini
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=text-embedding-3-small EMBEDDING_MODEL=text-embedding-3-small
SCENARIO_CONFIG_DIR=configs
UPLOAD_ROOT=data/uploads
CHROMA_PATH=data/chroma
``` ```
补充说明:
- `EMBEDDING_API_KEY` 为空时可复用 `LLM_API_KEY`
- `EMBEDDING_BASE_URL` 为空时可复用 `LLM_BASE_URL`
- `.env.example` 仅作为模板,不允许放真实密钥。
- 当前 V1 代码会在 settings 初始化时自动读取根目录 `.env`,本地运行与 `pytest` 可复用同一套配置;当前 Docker Compose 配置也通过 `env_file` 读取 `.env`
## 10. Dify 集成策略 ## 10. Dify 集成策略
V1 不把 Dify 作为核心依赖。 V1 不把 Dify 作为核心依赖。

View File

@@ -55,6 +55,12 @@ Config 模块是 Django 项目的基础配置模块,负责系统启动、路
| `UPLOAD_ROOT` | `data/uploads` | 上传文件目录 | | `UPLOAD_ROOT` | `data/uploads` | 上传文件目录 |
| `SCENARIO_CONFIG_DIR` | `configs` | 场景配置目录 | | `SCENARIO_CONFIG_DIR` | `configs` | 场景配置目录 |
补充要求:
- `.env.example` 仅作为模板文件,不得写入真实密钥。
- 本地直接执行 `python manage.py runserver` 时,应自动读取根目录 `.env`
- Docker 运行时可通过 `env_file` 或容器环境变量注入同一组配置;当前仓库默认由 Compose 读取 `.env`
## 5. 目录需求 ## 5. 目录需求
系统启动时需要保证以下目录存在: 系统启动时需要保证以下目录存在:
@@ -91,6 +97,8 @@ python manage.py migrate
python manage.py runserver python manage.py runserver
``` ```
说明:上述命令执行前,应先准备好根目录 `.env`;当前 V1 代码会在启动时自动加载该文件。
Docker 启动: Docker 启动:
```bash ```bash

14
manage.py Normal file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()

3
pytest.ini Normal file
View File

@@ -0,0 +1,3 @@
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = tests.py test_*.py *_tests.py

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Django>=5.1,<6.0
PyYAML>=6.0,<7.0
chromadb>=0.5,<1.0
pytest>=8.0,<9.0
pytest-django>=4.9,<5.0

View File

@@ -0,0 +1,21 @@
import os
from django.conf import settings
from django.urls import reverse
def test_core_settings_expose_documented_paths():
assert settings.SCENARIO_CONFIG_DIR.name == "configs"
assert settings.CHROMA_PATH.name == "chroma"
assert settings.MEDIA_ROOT.name == "uploads"
assert settings.EMBEDDING_MODEL == os.environ.get(
"EMBEDDING_MODEL",
"text-embedding-3-small",
)
assert settings.EMBEDDING_BASE_URL == settings.LLM_BASE_URL
assert settings.EMBEDDING_API_KEY == settings.LLM_API_KEY
def test_home_url_is_registered(client):
response = client.get(reverse("scenarios:index"))
assert response.status_code == 200