1- # UburNode
1+ # UburPython
22
3- BioNode 体系中的** 中间层服务 ** :以 ** 音频检索 ** 为核心能力,同时承担算法端与底层存储之间的 ** 数据结构转换 ** 。
3+ BioNode 体系中的 ** Somni 音频检索服务 ** :以三维度检索为核心,从 MongoDB 同步 Somni 原料与标签词典至 Elasticsearch,对外提供 HTTP 检索 API 。
44
5- - ** 核心** :三维度音频检索(ES 召回 + 进程内 Embedding + 四步精排流水线)
6- - ** 中间层** :统一对外 HTTP/Pydantic 契约,对内经 gRPC 访问 comm-service;在 HTTP、Mongo(扁平标签)、ES(六维结构 + 向量)之间做形态互转,避免算法端直连 Mongo 或各自维护多套字段约定
5+ - ** 核心** :三维度音频检索(` somni_audio_materials ` ES 召回 + 标签词典向量 + 四步精排流水线)
6+ - ** 数据源** :MongoDB ` Fullive ` 库(` somni_audio_materials ` 、` somni_audio_tag_dictionary ` )
7+ - ** 索引** :Elasticsearch ` somni_audio_materials ` 、` somni_audio_tag_dictionary ` (字段含义见 mapping ` meta.description ` )
8+ - ** 写路径(遗留)** :HTTP CUD 仍经 comm-service gRPC;Somni 索引由同步脚本维护
79
810## 架构
911
1012``` text
1113算法端 / 调用方
1214 │
1315 ▼
14- 对外 HTTP (FastAPI + Pydantic) ← 中间层:契约统一 + 数据结构转换
16+ 对外 HTTP (FastAPI + Pydantic)
1517 │
16- ├──读──► Elasticsearch + Embedding + RetrievalService
18+ ├──读(检索)──► somni_audio_materials (ES)
19+ │ + somni_audio_tag_dictionary (ES 向量)
20+ │ + 进程内 Embedding (bge-small-zh-v1.5)
1721 │
18- └──写──► comm-service (gRPC) ──► MongoDB(真值库,扁平 tags)
19- └── EsSync ──► Elasticsearch(索引副本,六维 tags + vector_id)
22+ ├──写(CUD,遗留)──► comm-service (gRPC) ──► MongoDB(旧 comm 表)
23+ │
24+ └──同步(定时/手动)──► MongoDB Somni 集合 ──► Elasticsearch
2025```
2126
22- ## 中间层数据转换
23-
24- UburNode 不只做检索,还在各存储边界维持** 单一对外契约** 并完成形态映射:
27+ ## 数据流
2528
26- | 边界 | 入站形态 | 出站形态 | 负责模块 |
27- | ------| ---------- | ---------- | ---- ------|
28- | HTTP 写(CUD) | 六维标签对象 ` AudioTagsInput ` | comm/Mongo 扁平 ` string[] ` (带维度前缀) | ` app/schemas/audio.py ` 、 ` app/core/tags .py` |
29- | HTTP 写响应 | comm ` AudioMaterialInfo ` (gRPC) | HTTP ` AudioMaterialData ` | ` app/schemas/audio.py ` |
30- | ES 同步 | 扁平 tags + 业务字段 | ES 六维 ` tags ` + ` tag_vectors ` embedding | ` app/es/sync .py ` |
31- | HTTP 读(检索 ) | ES 六维 ` TagItem ` (含 ` vector_id ` ) | 出参六维 label 字符串 | ` app/schemas/audio.py ` 、 ` app/ services/retrieval .py` |
29+ | 环节 | 来源 | 目标 | 模块 |
30+ | ------| ------| ------| ------|
31+ | Mongo → ES 同步 | ` somni_audio_materials ` 、 ` somni_audio_tag_dictionary ` | 同名 ES 索引 | ` scripts/sync_es_from_comm .py` |
32+ | 标签向量 | 词典 ` name ` / ` name_en ` | ` name_vector ` / ` name_en_vector ` | 同步脚本 + ` app/embedding/ ` |
33+ | HTTP 检索 | ES 原料文档 | ` data.materials[] ` 原样返回 | ` app/services/retrieval .py ` |
34+ | HTTP CUD(遗留 ) | 六维标签入参 | comm 扁平 tags | ` app/services/audio .py ` |
3235
33- 字段命名全链路 ** snake_case** ;对外以 Pydantic + OpenAPI 为唯一 HTTP 契约,对内 comm 调用走同源 ` bionode_comm.proto ` 。
36+ 字段命名全链路 ** snake_case** 。Somni 表结构详见仓库内 ` 音频表结构.md ` 。
3437
3538## 目录结构
3639
3740``` text
38- UburNode /
41+ UburPython /
3942├── app/
40- │ ├── main.py # FastAPI 入口 + lifespan
41- │ ├── core/ # 配置、日志
42- │ ├── api/audio.py # 4 个 HTTP 端点
43- │ ├── schemas/audio.py # Pydantic 模型
44- │ ├── services/ # AudioService、RetrievalService
45- │ ├── es/ # EsSearch、EsSync
46- │ ├── embedding/encoder.py # bge-small-zh-v1.5 向量编码
47- │ ├── bionode_grpc_clients/ # BioNode 外部微服务 gRPC 客户端
48- │ │ └── comm/ # comm-service(client.py + grpc_gen/)
49- ├── proto/ # bionode_comm.proto(唯一真源)
50- ├── scripts/gen_proto.sh # 生成 gRPC stub
43+ │ ├── main.py # FastAPI 入口 + lifespan
44+ │ ├── core/ # 配置、日志、标签转换
45+ │ ├── api/audio.py # 4 个 HTTP 端点
46+ │ ├── schemas/audio.py # Pydantic 模型
47+ │ ├── services/ # AudioService、RetrievalService
48+ │ ├── es/
49+ │ │ ├── search.py # EsSearch 读路径
50+ │ │ ├── sync.py # EsSync 写路径(CUD 跳过 upsert)
51+ │ │ └── index_mappings.py # ES 索引 mapping + 字段注释
52+ │ ├── embedding/encoder.py # bge-small-zh-v1.5 向量编码
53+ │ └── bionode_grpc_clients/ # comm-service gRPC 客户端
54+ ├── scripts/
55+ │ ├── sync_es_from_comm.py # Mongo → ES 差异同步
56+ │ └── gen_proto.sh # 生成 gRPC stub
57+ ├── proto/ # bionode_comm.proto
5158├── tests/
52- ├── .cursor/skills/ # 项目 Skill
5359├── pyproject.toml
5460└── .env.example
5561```
5662
5763## 快速开始
5864
5965``` bash
60- # 1. 创建虚拟环境并安装依赖
61- python3.12 -m venv .venv
62- source .venv/bin/activate
63- pip install -e " .[dev]"
66+ # 1. 安装依赖(推荐 uv)
67+ uv sync --extra dev
6468
65- # 2. 生成 comm gRPC stub
69+ # 2. 生成 comm gRPC stub(CUD 接口需要)
6670chmod +x scripts/gen_proto.sh
6771./scripts/gen_proto.sh
6872
69- # 3. 本地 Elasticsearch(向量索引,需先就绪)
70- # 未安装 Docker 时:brew install --cask docker-desktop,打开 Docker Desktop 等待就绪
73+ # 3. 本地 Elasticsearch
7174docker compose -f docker-compose.es.yml up -d
72- curl -s http://localhost:9200 # 应返回 cluster 信息
73- # .env 默认 ES_NODE=http://localhost:9200;索引由应用启动时 ensure_indices 自动创建
75+ curl -s http://localhost:9200
7476
7577# 4. 配置环境变量
7678cp .env.example .env
77- # 编辑 ES_NODE、COMM_GRPC_HOST 等
79+ # 编辑 ES_NODE、MONGO_URI、EMBEDDING_ONNX_DIR、COMM_GRPC_* 等
80+
81+ # 5. 导出 ONNX 模型(若 models/ 目录尚无模型)
82+ # 见 scripts/export_onnx_model.py
83+
84+ # 6. Mongo → ES 全量同步
85+ uv run python scripts/sync_es_from_comm.py --dry-run
86+ uv run python scripts/sync_es_from_comm.py
7887
79- # 5 . 启动服务
80- uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload
88+ # 7 . 启动服务
89+ uv run uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload
8190```
8291
8392开发模式(` APP_DEBUG=true ` )跳过 Embedding 模型加载,便于本地调试 HTTP 路由。
@@ -86,20 +95,56 @@ uvicorn app.main:app --host 0.0.0.0 --port 8080 --reload
8695
8796| 端点 | 方法 | 说明 |
8897| ------| ------| ------|
89- | ` /api/audio ` | POST | 创建音频并同步 ES |
90- | ` /api/audio/{id} ` | PUT | 更新音频并同步 ES |
91- | ` /api/audio/{id} ` | DELETE | 删除音频并同步 ES |
98+ | ` /api/audio ` | POST | 创建音频(comm gRPC,遗留) |
99+ | ` /api/audio/{id} ` | PUT | 更新音频(comm gRPC,遗留) |
100+ | ` /api/audio/{id} ` | DELETE | 删除音频 |
92101| ` /api/audio/search ` | POST | 三维度检索 |
93102
94103OpenAPI 文档:启动后访问 ` http://localhost:8080/docs ` 。
95104
96- ### comm-service gRPC 连通探测
105+ ### 检索接口
97106
98- ``` bash
99- source .venv/bin/activate # 或: .venv/bin/python scripts/test_grpc_connect.py
100- python scripts/test_grpc_connect.py
101- # 或集成测试(需可达的 COMM_GRPC_HOST)
102- COMM_GRPC_INTEGRATION=1 pytest tests/test_comm_grpc.py -v
107+ ** 请求** ` POST /api/audio/search ` :
108+
109+ ``` json
110+ {
111+ "sleep_stage_tags" : [" 放松" ],
112+ "content_tags" : [" 慢钢琴" ],
113+ "disliked_tags" : [],
114+ "top_k" : 10
115+ }
116+ ```
117+
118+ ** 响应** ` data.materials ` 为命中条目的 ` somni_audio_materials ` 索引文档(含 ` id ` ,字段与 ES/Mongo 一致,暂不做裁剪):
119+
120+ ``` json
121+ {
122+ "code" : 200 ,
123+ "msg" : " 检索成功" ,
124+ "data" : {
125+ "materials" : [
126+ {
127+ "id" : " 6a33a7928030d4cf420efeb6" ,
128+ "audio_name" : " 专属冥想南极 助眠解压舒缓情绪" ,
129+ "description" : " ..." ,
130+ "status" : true ,
131+ "audio_url" : " https://cdn.fulai.tech/comm/audio/xxx.mp3" ,
132+ "operation_type" : 0 ,
133+ "created_by" : " qwen3.5-omni-plus" ,
134+ "updated_by" : " qwen3.5-omni-plus" ,
135+ "sleep_stage_tags" : [{ "tag_id" : " ..." , "code" : " unwind" , "name" : " 放松" }],
136+ "content_form_tags" : [],
137+ "mechanism_tags" : [],
138+ "audio_engineering_tags" : [],
139+ "medical_risk_tags" : [],
140+ "evidence_level_tags" : [{ "tag_id" : " ..." , "code" : " B" , "name" : " 中等证据" }],
141+ "created_at" : " 2026-06-18T00:00:00.000Z" ,
142+ "updated_at" : " 2026-06-18T00:00:00.000Z"
143+ }
144+ ]
145+ },
146+ "timestamp" : " ..."
147+ }
103148```
104149
105150## 检索流水线
@@ -108,17 +153,41 @@ COMM_GRPC_INTEGRATION=1 pytest tests/test_comm_grpc.py -v
108153睡眠阶段精确过滤 → 内容形态准入 → 厌恶剔除 + 粗排 → 精排
109154```
110155
111- ## Proto 变更
156+ | 步骤 | 说明 |
157+ | ------| ------|
158+ | 1 | ` sleep_stage_tags.name ` nested 精确匹配(可配置跳过) |
159+ | 2 | ` content_tags ` 与内容/机制/工程标签精确或向量模糊命中 |
160+ | 3 | ` disliked_tags ` 向量相似则剔除 |
161+ | 4 | 按 ` match_count ` 降序,` top_k ` 截断 |
112162
113- ` comm-service ` 修改 ` proto/bionode_comm.proto ` 后须重新生成 stub:
163+ ## Mongo → ES 同步
114164
115165``` bash
116- ./scripts/gen_proto.sh
166+ uv run python scripts/sync_es_from_comm.py # 正式同步
167+ uv run python scripts/sync_es_from_comm.py --dry-run # 仅比对统计
117168```
118169
119- ## 日志
170+ - 先同步 ` somni_audio_tag_dictionary ` (写入 ` name_vector ` 、` name_en_vector ` )
171+ - 再同步 ` somni_audio_materials ` (1:1 镜像 Mongo 文档)
172+ - 启动时删除旧索引 ` audio_materials ` 、` tag_vectors `
173+
174+ 服务内按 ` SYNC_INTERVAL_DAYS ` 定时执行(需配置 ` MONGO_URI ` )。
175+
176+ ## 环境变量(节选)
120177
121- 每次 HTTP 请求自动写入日志文件(` RequestLogMiddleware ` ),包含 method、path、status、耗时、` request_id ` 。
178+ | 变量 | 默认值 | 说明 |
179+ | ------| --------| ------|
180+ | ` ES_NODE ` | ` http://localhost:9200 ` | Elasticsearch 地址 |
181+ | ` ES_AUDIO_INDEX ` | ` somni_audio_materials ` | 原料索引名 |
182+ | ` ES_TAG_VECTORS_INDEX ` | ` somni_audio_tag_dictionary ` | 标签词典索引名 |
183+ | ` MONGO_URI ` | — | MongoDB 连接串(同步必填) |
184+ | ` MONGO_DB ` | ` Fullive ` | 数据库名 |
185+ | ` SIM_THRESHOLD ` | ` 0.7 ` | 向量模糊命中阈值 |
186+ | ` EMBEDDING_ONNX_DIR ` | ` models/onnx/bge-small-zh-v1.5 ` | ONNX 模型目录 |
187+
188+ 完整列表见 [ ` .env.example ` ] ( .env.example ) 。
189+
190+ ## 日志
122191
123192| 配置项 | 默认值 | 说明 |
124193| --------| --------| ------|
@@ -127,36 +196,18 @@ COMM_GRPC_INTEGRATION=1 pytest tests/test_comm_grpc.py -v
127196| ` LOG_ROTATION ` | ` 10 MB ` | 单文件滚动大小 |
128197| ` LOG_RETENTION ` | ` 7 days ` | 历史日志保留 |
129198
130- 日志同时输出到控制台和 ` logs/uburnode.log ` 。响应头会回传 ` X-Request-Id ` 便于链路追踪。
131-
132- ## Docker 部署(服务器)
199+ 响应头回传 ` X-Request-Id ` 便于链路追踪。
133200
134- 与 ` intelligent_reimbursement ` 相同: ** 服务器 git pull + docker compose build ** ,不依赖 GHCR。
201+ ## Docker 部署
135202
136203``` bash
137- # 1. 本机一键写入 GitHub Secrets(SSH_HOST / SSH_USER / SSH_PRIVATE_KEY)
138- chmod +x scripts/setup_github_secrets.sh
139- ./scripts/setup_github_secrets.sh
140-
141- # 2. 服务器一次性准备
142- # - 将公钥写入 ~/.ssh/authorized_keys
143- # - 已安装 Docker 与 Compose;/etc/docker/daemon.json 镜像加速自行维护
144- # - 克隆仓库并配置 .env:
145- mkdir -p /opt/uburnode
146- git clone -b dev https://github.com/dwqnidq/UburNode.git /opt/uburnode
147- cp /opt/uburnode/.env.example /opt/uburnode/.env # 编辑 COMM_GRPC_* 等
148-
149- # 3. 首次手动启动(build 约 20~40 分钟)
150- cd /opt/uburnode && docker compose up -d --build
151-
152- # 4. 以后:GitHub → Actions → Deploy UburNode → Run workflow
153- # 或 push 到 dev 分支自动部署(git pull + compose up --build)
204+ cd /opt/uburpython && docker compose up -d --build
154205```
155206
156- 生产访问:` http://<服务器IP>:8001/docs ` (nginx 映射宿主机 8001 → 容器 80,避免与宝塔 80 冲突 )。
207+ 生产访问:` http://<服务器IP>:8001/docs ` (nginx 映射宿主机 8001 → 容器 80)。
157208
158209## 测试
159210
160211``` bash
161- pytest
212+ uv run pytest
162213```
0 commit comments