面向 OJ 场景的图谱增强检索服务。项目使用 FastAPI + Neo4j + Redis + MySQL 组织题目、提交记录和知识点,提供题目讲解上下文接口,以及基于 webhook 的增量同步能力。
当前代码重点在于“构建和返回讲解上下文”,而不是直接生成自然语言答案。/explain/problem 会返回题面、知识点、推荐题目和经典 AC 代码,方便上层服务继续做回答生成或页面展示。
- 题目图谱:将 OJ 题目写入 Neo4j,保存题面向量、难度、提交统计等属性。
- 提交图谱:将 AC 提交写入 Neo4j,建立
User -> Submission -> Problem关系,并为代码生成向量。 - 知识点抽取:批量 ETL 时调用 DeepSeek,从题面中提取知识点并建立
Problem -> KnowledgePoint关系。 - 增量同步:通过
/sync/webhook接收事件,写入 Redis 队列,由 worker 异步消费并同步入图。 - 讲解上下文:通过
/explain/problem返回题面、知识点、推荐题和“经典 AC 代码”。
MySQL(problem/submission/submission_misc)
|
| 批量 ETL / webhook 增量事件
v
SentenceTransformers ----> Neo4j <---- DeepSeek(知识点抽取)
^ |
| |
worker FastAPI
| |
Redis queue /explain/problem
/sync/webhook
/health
app/
main.py FastAPI 入口
config.py 环境变量配置
deps.py Neo4j 连接生命周期
models.py 请求/响应模型
worker.py Redis 队列消费者
routers/
explain.py 讲解上下文接口
sync.py webhook 接口
services/
embedding.py 文本/代码向量化
graph.py Neo4j 写入与查询
etl/
batch.py 批量导入 MySQL -> Neo4j
docker-compose.yml Neo4j 本地开发环境
pyproject.toml Python 依赖定义
项目运行依赖以下组件:
- Python 3.11+
- Neo4j 5.x
- Redis 5+
- MySQL / MariaDB
- DeepSeek API Key(用于批量知识点抽取;未配置时 ETL 会跳过该能力)
注意:
- 仓库内的
docker-compose.yml只启动Neo4j,不会启动Redis和MySQL。 - 向量模型默认配置为
BAAI/bge-m3与BAAI/bge-code-v1。如果本地没有缓存,sentence-transformers首次加载时可能会下载模型;也可以把环境变量改为本地模型目录。
复制示例配置:
cp .env.example .env.env 关键配置如下:
# Neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password123
# MySQL / MariaDB
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_mysql_password
MYSQL_DB=levoj
# DeepSeek
DEEPSEEK_API_KEY=your_deepseek_api_key
DEEPSEEK_BASE_URL=https://api.deepseek.com
# Embedding models
EMBEDDING_MODEL_NAME=BAAI/bge-m3
CODE_EMBEDDING_MODEL_NAME=BAAI/bge-code-v1
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_QUEUE_KEY=graphrag:sync_queuepython -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e ".[dev]"docker compose up -d启动后可以打开 Neo4j Browser:
- 地址:
http://localhost:7474 - 用户名:
neo4j - 密码:
password123
确保以下资源已可用:
- Redis:
REDIS_HOST:REDIS_PORT - MySQL:包含
problem、submission、submission_misc表
当前代码默认依赖的字段如下:
problem:id,title,content,difficulty,submits,accepts,timeLimit,memoryLimit,status,closedsubmission:id,userId,problemId,language,status,time,memorysubmission_misc:submissionId,code
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload可用地址:
- OpenAPI 文档:
http://localhost:8000/docs - 健康检查:
http://localhost:8000/health
python -m app.etl.batch批量 ETL 会做这些事:
- 从 MySQL 读取题目和 AC 提交
- 用本地向量模型生成题面向量和代码向量
- 将题目、提交、用户关系写入 Neo4j
- 调用 DeepSeek 提取知识点并建立知识点关系
- 创建 Neo4j 向量索引
python -m app.workerworker 会阻塞消费 Redis 队列中的 webhook 事件,并把新题目或新 AC 提交同步进 Neo4j。
适用于首次构图或周期性补数。
- 从 MySQL 查询题目和 AC 提交。
- 对题面和代码做 embedding。
- 将
Problem、Submission、User节点及关系写入 Neo4j。 - 对题目调用 DeepSeek 抽取知识点,建立
REQUIRES关系。
适用于 OJ 主系统在题目变更、提交通过后即时推送事件。
- 外部系统向
/sync/webhook发事件。 - API 将事件写入 Redis 列表队列。
app.worker消费队列,并调用图谱写入逻辑。
健康检查。
响应示例:
{"status":"ok"}接收外部事件并推入 Redis 队列。
请求体:
{
"event_type": "problem.created",
"payload": {
"id": 1001,
"title": "Two Sum",
"content": "给定一个数组...",
"difficulty": 1,
"submits": 10,
"accepts": 5,
"timeLimit": 1000,
"memoryLimit": 134217728
}
}支持的事件类型:
problem.createdproblem.updatedsubmission.ac
返回示例:
{
"accepted": true,
"message": ""
}submission.ac 事件的 payload 至少应包含:
{
"id": 2001,
"userId": 7,
"problemId": 1001,
"language": "cpp",
"time": 12,
"memory": 65536
}返回题目的讲解上下文,不直接生成答案。
请求体:
{
"problem_id": 5614,
"user_id": 42
}返回字段说明:
title: 题目标题content: 题目内容knowledge_points: 关联知识点recommended_problems: 基于用户已做题知识点匹配出的推荐题classic_code: 该题“最经典”的 AC 代码,可能为空
调用示例:
curl --noproxy localhost -X POST http://localhost:8000/explain/problem \
-H "Content-Type: application/json" \
-d '{"problem_id": 5614, "user_id": 42}'响应示例:
{
"title": "题目标题",
"content": "题面内容",
"knowledge_points": ["动态规划", "背包"],
"recommended_problems": [
{
"problem_id": 123,
"title": "相似题目",
"difficulty": 2,
"score": 1.0
}
],
"classic_code": "#include <bits/stdc++.h>\n..."
}节点:
ProblemSubmissionUserKnowledgePoint
关系:
(User)-[:SUBMITTED]->(Submission)(Submission)-[:FOR_PROBLEM]->(Problem)(Problem)-[:REQUIRES]->(KnowledgePoint)
向量字段:
Problem.content_embeddingSubmission.code_embedding
默认向量索引:
problem_content_idxsubmission_code_idx
查看题目节点:
MATCH (p:Problem) RETURN p LIMIT 25查看提交与题目的关系:
MATCH (s:Submission)-[:FOR_PROBLEM]->(p:Problem)
RETURN s, p
LIMIT 20查看题目和知识点:
MATCH (p:Problem)-[:REQUIRES]->(k:KnowledgePoint)
RETURN p, k
LIMIT 20- FastAPI 启动时会初始化 Neo4j 连接。
- Neo4j 向量索引是在批量 ETL 中创建的,因此首次使用前建议先执行一次
python -m app.etl.batch。 classic_code不存放在 Neo4j,而是在查询时回表 MySQLsubmission_misc获取。- 推荐题逻辑当前基于“用户已掌握知识点重合 + 最近一次 AC 题目向量”筛出候选集,排序逻辑仍然比较基础。
docker-compose.yml当前不包含 Redis / MySQL,需要自行提供。- DeepSeek 主要用于知识点抽取,不参与
/explain/problem的直接回答生成。 - 如果 embedding 模型没有本地缓存,首次运行可能较慢。
- 当前仓库没有自动化测试和一键初始化脚本,首次部署建议按本文步骤逐项验证。
仓库中暂未声明许可证;如需开源发布,请补充明确的 LICENSE 文件。