一个基于 FastAPI 构建的轻量级事件驱动后端框架,用于实现结构化的大模型(LLM)事件流程系统。
本项目提供一个最小可运行(MVP)的后端基座,支持以“事件”为核心的系统架构设计,例如:
INIT → ACTION → RESOLUTION
适用于 AI 叙事系统、游戏流程引擎、Agent 调度系统等场景。
- 基于 FastAPI 构建的后端服务
- 统一事件入口(/invoke)
- 事件路由分发骨架
- 统一返回结构(success / error)
- 全局异常处理
- 基础结构化日志
.env环境配置管理(pydantic-settings)- 健康检查接口
- pytest 测试基座
- 使用
uv管理依赖 - 开发使用
uvicorn - 内部测试/稳定运行使用
gunicorn
客户端
↓
/invoke(事件入口)
↓
事件路由分发
↓
具体事件处理(如 INIT)
↓
LLM 适配层(DeepSeek)
↓
统一响应结构返回
系统分层说明:
- 传输层:FastAPI 接口
- 路由层:事件分发
- 业务层:具体事件逻辑
- 模型层:LLM 调用适配
- 基础设施层:配置、日志、响应封装
- Python 3.10+
- FastAPI
- Pydantic v2
- pydantic-settings
- uv(依赖管理)
- Uvicorn(开发运行)
- Gunicorn(内部测试 / 生产形态运行)
- DeepSeek SDK
使用 uv:
uv sync如需手动添加:
uv add fastapi uvicorn gunicorn pydantic-settings python-dotenv在项目根目录创建 .env 文件:
DEEPSEEK_API_BASE=https://api.deepseek.com
DEEPSEEK_API_KEY=your_key_here
配置读取采用 pydantic-settings 管理。
uv run uvicorn app.main:app --reloaduv run gunicorn app.main:app \
-k uvicorn.workers.UvicornWorker \
-w 4 \
-b 0.0.0.0:8000GET /health
返回:
{
"status": "ok"
}POST /invoke
所有事件通过统一入口分发。
示例请求:
{
"event": {
"type": "INIT"
},
"context": {}
}运行测试:
uv run pytestapp/
├── main.py # FastAPI 入口(app = FastAPI())
├── api/ # HTTP 路由层(只放接口)
│ ├── __init__.py
│ ├── health.py # /health
│ └── invoke.py # /invoke(事件总入口,仅分发骨架)
├── core/ # 基础设施层(项目“地基”)
│ ├── __init__.py
│ ├── config.py # pydantic-settings:统一读取 .env
│ ├── logging.py # logging 基座(格式/请求id等)
│ ├── response.py # success()/error() 统一返回
│ └── exceptions.py # 全局异常处理(注册到 FastAPI)
├── schemas/ # 入参/出参模型(你说的 schema 控制)
│ ├── __init__.py
│ ├── base.py # 公共模型(BaseResponse 等)
│ └── invoke.py # InvokeRequest / InvokeResponse(事件入口模型)
├── events/ # 事件体系(先骨架,不实现具体事件)
│ ├── __init__.py
│ ├── dispatcher.py # 根据 event.type 分发
│ └── types.py # 事件类型常量/枚举(INIT 等)
├── db/ # DuckDB 相关(轻量持久化)
│ ├── __init__.py
│ ├── client.py # duckdb 连接/初始化(最小封装)
│ └── repo.py # 简单读写封装(MVP 可非常薄)
├── utils/ # 工具函数(通用,不依赖业务)
│ ├── __init__.py
│ └── time.py # 例如时间格式化/now等
├── scripts/ # 你现在这些“验证脚本/实验脚本”归档到这里
│ ├── __init__.py
│ ├── check_deepseek_sdk.py
│ └── test_deepseek.py
└── playground/ # 可选:保留你当前 DnD 相关实验,不污染主代码
├── __init__.py
├── dnd_agent.py
└── dnd_game.py
本项目遵循以下原则:
- 先构建稳定的最小基座
- 统一入口,清晰分层
- 可扩展,但不过度设计
- 支持后续平滑演进至完整事件体系
without pytest
project/app/ run tests/check_[tag].py
uv run python -m tests.check_config
logging
nohup uv run gunicorn main:app > server.log 2>&1 &
Health Check
系统提供一个基础健康检查接口,用于确认服务是否正常运行。
启动服务
在 app 目录下执行:
uv run uvicorn main:app --reload
启动成功后会看到类似输出:
Uvicorn running on http://127.0.0.1:8000
然后请求他,方法为GET,例如 “curl http://127.0.0.1:8000/health”,你应该能收到如下响应:
{
"code": 0,
"message": "success",
"data": {
"status": "ok"
}
}
项目包含两类测试内容:
test/:基于 pytest 的自动化测试tests/:开发阶段保留的手动验证脚本,用于快速检查底座模块行为,uv run python -m tests.check_{{tag}}。
MIT License
测试时间:
2026-03-20
uv run pytest tests/test_init_invoke.py tests/test_init_prompt.py
uv run pytest tests/test_init_invoke.py -k state_saved
Pytest 运行结果如下:
=== test session starts ===
platform darwin -- Python 3.10.18, pytest-9.0.2, pluggy-1.6.0 rootdir: /Users/kaoiki/anaconda3/envs/deepseek-dnd/app configfile: pyproject.toml plugins: anyio-4.12.1 collected 7 items
test/test_init_invoke.py ..... [ 85%] test/test_init_prompt.py . [100%]
=== 7 passed in 1.22s ===
=== test session starts ===
platform darwin -- Python 3.10.18, pytest-9.0.2, pluggy-1.6.0 rootdir: /Users/kaoiki/anaconda3/envs/deepseek-dnd/app configfile: pyproject.toml plugins: anyio-4.12.1 collected 6 items / 5 deselected / 1 selected
test/test_init_invoke.py . [100%]
=== 1 passed, 5 deselected in 0.97s ====
测试时间:
2026-03-18
uv run pytest test/test_invoke.py -v
Pytest 运行结果如下:
==== test session starts ===
platform darwin -- Python 3.10.18, pytest-9.0.2, pluggy-1.6.0 -- /Users/kaoiki/anaconda3/envs/deepseek-dnd/app/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /Users/kaoiki/anaconda3/envs/deepseek-dnd/app
configfile: pyproject.toml
plugins: anyio-4.12.1
collected 3 items
test/test_invoke.py::test_invoke_init_success PASSED [ 33%]
test/test_invoke.py::test_invoke_invalid_event_type PASSED [ 66%]
test/test_invoke.py::test_invoke_unregistered_event_type PASSED [100%]
=== 5 passed in 0.97s ===
说明:
- 所有基础模块测试均通过
- 当前 MVP 基础底座运行正常
测试时间:
2026-03-06
uv run pytest
Pytest 运行结果如下:
==== test session starts ===
platform darwin -- Python 3.10.18, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/kaoiki/anaconda3/envs/deepseek-dnd/app
configfile: pyproject.toml
plugins: anyio-4.12.1
collected 5 items
test/test_config.py . [ 20%]
test/test_exceptions.py . [ 40%]
test/test_health.py . [ 60%]
test/test_response.py .. [100%]
=== 5 passed in 0.97s ===
说明:
- 所有基础模块测试均通过
- 当前 MVP 基础底座运行正常
提交
{
"event": {
"type": "init"
},
"session": {
"session_id": "sess_test_001",
"player_count": 1,
"difficulty": "NORMAL"
},
"time": {
"hard_limit_seconds": 300,
"elapsed_active_seconds": 0,
"remaining_seconds": 300
},
"seed": {
"run_seed": "run_test_001"
},
"constraints": {
"language": "zh",
"content_rating": "PG",
"max_chars_scene": 220,
"max_chars_option": 14,
"forbidden_terms": ["骰子", "扑克", "点数", "规则"]
},
"slots": {
"tone_bias": "恐怖刺激",
"theme_bias": "鬼屋",
"npc_bias": "恶鬼"
},
"client_context": {
"platform": "web",
"locale": "en-US"
},
"payload": {},
"context": {}
}
答复
{
"code": 0,
"message": "success",
"data": {
"event": {
"type": "init"
},
"payload": {
"ai_state": {
"world_seed": "run_test_001_haunted_mansion",
"title": "凶宅回响",
"tone": "恐怖刺激",
"memory_summary": "玩家独自进入一座传闻中的凶宅,恶鬼的低语在墙壁间回荡。",
"arc_progress": 0
},
"mainline": {
"premise": "你为了寻找失踪的朋友,深夜踏入这座被诅咒的宅邸。",
"player_role": "一位寻找失踪挚友的普通人",
"primary_goal": "在宅邸中找到朋友并活着离开",
"stakes": "若失败,你将永远成为这座凶宅的一部分"
},
"opening": {
"scene": "腐朽的木门在你身后吱呀关上。月光透过破窗,照亮空气中漂浮的尘埃。走廊深处传来指甲刮过木板的刺耳声响。",
"npc_line": "(一个扭曲的声音从阴影中传来)又一个送上门来的……留下来陪我吧……"
},
"start_hint": {
"how_to_play_next": "选择一个选项来行动,你的选择将决定接下来的遭遇。"
},
"options": [
{
"id": 1,
"text": "朝声音来源前进"
},
{
"id": 2,
"text": "检查旁边的房间"
},
{
"id": 3,
"text": "悄悄后退"
}
],
"routing": {
"next_event_type": "DECISION",
"should_end": false
},
"meta": {
"trace_id": "init_run_test_001_20250418_001"
}
}
}
}