我下载的RMBase BED文件打开第一行是这样的chr1 14414 14415 m6A_site_1 0 - m6A 2 GSE102493 GSM2739535,GSM2991403 29507755 HeLa m6A-seq ENSG00000227232.5 ENST00000488147.1 WASH7P unprocessed_pseudogene exon-11 GGCACACCAATCAATAAAGAACTGAGCAGAAACCAACAGTG 3.37338095238095 na na na na na na na na na有29列数据包含的信息远超出我的预期修饰类型m6A、实验编号GSE102493、细胞系HeLa、测序方法m6A-seq、基因名称WASH7P、转录本ID、区域类型exon-11、甚至具体序列和得分。这意味着需要设计一个能容纳这些丰富注释的MySQL表同时向量化文本时要充分利用这些信息才能让RAG回答出有价值的问题。MySQL表结构的设计最初我只打算存chrom, start, end, name, score, strand多余列打包成JSON。但现在看到有这么多有意义的列——细胞系、实验方法、基因名、功能注释——如果都塞进JSON查询和检索都不方便。最终我决定采用宽表 预留JSON的策略。核心列单独建字段其余不常见的放JSON。以下是建表SQL的核心部分CREATETABLErmbase_sites(idINTAUTO_INCREMENTPRIMARYKEY,chromVARCHAR(10),start_posINT,end_posINT,site_nameVARCHAR(50),scoreFLOAT,strandCHAR(1),mod_typeVARCHAR(20),-- m6A, m5C等experiment_idVARCHAR(20),-- GSE102493gsm_idsTEXT,-- 多个GSM用逗号分隔pubmed_idVARCHAR(20),-- 29507755cell_lineVARCHAR(50),-- HeLaassayVARCHAR(30),-- m6A-seqgene_idVARCHAR(30),-- ENSG00000227232.5transcript_idVARCHAR(30),-- ENST00000488147.1gene_symbolVARCHAR(20),-- WASH7Pgene_typeVARCHAR(40),-- unprocessed_pseudogeneregionVARCHAR(20),-- exon-11sequenceTEXT,-- 修饰位点周围的序列mod_scoreFLOAT,-- 3.37338extra JSON);这样设计的好处可以直接用SQL筛选特定细胞系WHERE cell_line‘HeLa’、特定修饰类型WHERE mod_type‘m6A’不需要解析JSON。写了一个解析脚本按tab切分后前6列直接映射后面按位置对应到各个字段。遇到na的置为NULL。批量插入MySQL。核心处理逻辑读取一行split判断列数实际有28列左右建立字段列表和值列表的映射用cursor.executemany批量写入设计文本片段——将每条数据转为文本为了RAG检索我需要把这一行数据变成一段自然语言。一开始我只用坐标和修饰类型但测试发现问“HeLa细胞中的m6A位点”时检索不到因为文本里没提HeLa。于是设计了一个组合模板根据非空字段动态生成模板示例在 chr1:14414-14415 负链上有一个 m6A 修饰位点名称为 m6A_site_1得分为0。该位点来源于实验GSE102493GSM2739535,GSM2991403对应论文 PMID:29507755在 HeLa 细胞系中使用m6A-seq 方法鉴定。位于基因 WASH7Punprocessed_pseudogene的 exon-11 区域转录本ENST00000488147.1。周围序列GGCACACCAATCAATAAAGAACTGAGCAGAAACCAACAGTG修饰强度得分3.37338。这样生成出来的文本非常丰富几乎涵盖了用户可能问到的所有维度细胞系、基因、区域、实验方法、序列等。核心代码片段拼接逻辑parts[f在{chrom}:{start}-{end}{strand}链上有一个{mod_type}修饰位点]ifsite_nameandsite_name!.:parts.append(f名称为{site_name})ifscoreisnotNone:parts.append(f得分为{score})ifcell_line:parts.append(f在{cell_line}细胞系中)ifgene_symbol:parts.append(f位于基因{gene_symbol}({gene_type}) 的{region}区域)ifsequence:parts.append(f周围序列:{sequence[:50]}...)# ... 更多字段text.join(parts)向量化与Qdrant存储由于文本包含大量生物学专有名词HeLa, m6A-seq, pseudogene等需要模型对这些词有较好的表征。我测试了两个模型all-MiniLM-L6-v2速度快但对专业术语效果一般。microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract-fulltext生物医学领域预训练效果好但维度768且模型较大。最终折中使用all-mpnet-base-v2768维在生物文本上表现尚可速度也能接受。流程从MySQL读取所有记录测试阶段只读1000条。对每条记录调用上面的文本生成函数。将文本列表一次性传入model.encode(batch_size32)得到向量矩阵。构造PointStructid使用MySQL的自增idpayload中存文本和mysql_id。调用qdrant.upsert批量写入。注意点向量维度要和Qdrant集合创建时一致。如果数据量很大几十万条可以分段读取MySQL边读边向量化边存入避免内存爆炸。项目推进中的经验教训数据清洗是最大工作量真实BED文件里有很多na列数不固定有些行基因名为空。解析脚本需要处理各种边缘情况。我写了一个鲁棒的解析函数对每列做类型判断和空值处理。最开始只用坐标和修饰类型检索“HeLa细胞”完全没结果。后来把细胞系、基因名、区域都加进去效果立竿见影。建议把用户可能问到的所有字段都写进文本但要注意控制长度嵌入模型有token限制384维模型约256 token768维约512 token。我的每条文本大约150-200词可以接受。忘记创建集合就upsert会报错。建议在脚本开头先检查集合是否存在不存在则创建。BED文件会更新我的策略是每天增量读取新文件检查坐标和修饰名是否已存在用联合唯一索引不存在则插入MySQL同时生成向量插入Qdrant。删除的情况比较少见暂未处理。七、最终效果和后续计划现在系统已经能回答诸如“HeLa细胞中m6A-seq鉴定到的位点有哪些”“WASH7P基因上exon区域的修饰强度是多少”“与PubMed ID 29507755相关的修饰数据”回答时会引用具体的信息来源如“根据记录id 1234在HeLa细胞中…”并且可以给出序列和得分。下一步计划增加混合检索向量关键词BM25提高精确匹配能力。支持用户上传自己的BED文件动态加入到RAG库中。做一个简单的Streamlit界面方便非技术同事使用。八、总结通过这个项目我从一个具体的BED文件出发逐步构建了一个可工作的RAG问答系统。核心收获是真实数据往往比标准格式复杂设计表结构和文本模板前必须仔细检查数据样例。RAG的效果很大程度上取决于文本片段的质量——要把结构化数据“翻译”成人话且覆盖用户可能的查询角度。向量检索不是万能的和MySQL精确查询互补使用最有效。先拿1000条数据跑通全流程再扩展到全部可以快速验证想法。以上是基于实际数据推进项目的完整记录代码不算复杂但每一步的决策都经过了测试和调整。