)
基于 EBI OLS4 API 的本体论术语解析与校验实战参数、陷阱与源码级实现scientific-agent-skills 项目【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000 scientists worldwide. 165 ready-to-use validated skills plus 100 scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills导读本文是一份围绕 EBI OLS4Ontology Lookup Service 4API 的完整实战技术指南主体内容源自scientific-agent-skills仓库中 ontology-term-resolution 技能所附的 OLS4 API 参考文档。文中将逐一拆解/search、/terms等核心端点的参数语义与响应结构揭示 OLS4 相对 OLS3 悄然改变且会静默产出错误答案的八大陷阱并结合仓库内ols_client.py、resolve_terms.py、validate_terms.py等脚本源码与测试用例说明如何在真实代码中规避这些坑。读完本文你将能够正确构造 OLS4 查询实现文本到术语 ID 的解析准确判断一个既有 CURIE 是否真实、是否已废弃并独立写出可落地的解析与校验管线。使用前提与基础约定Base URL 与鉴权OLS4 API 的基地址为https://www.ebi.ac.uk/ols4/api与大多数商业 API 不同OLS4不需要 API key也不需要注册即可调用。但官方文档与仓库脚本都强调要有礼貌地使用发送描述性的User-Agent、保持较低的并发、在收到 HTTP 429限流时退避重试。仓库中的 ols_client.py 正是这样实现的OLS_BASE https://www.ebi.ac.uk/ols4/api USER_AGENT scientific-agent-skills-ontology-term-resolution/1.0 TIMEOUT 30 MAX_ATTEMPTS 3 RETRY_STATUS {429, 500, 502, 503, 504}_request()见 ols_client.py对 429 及各类 5xx 瞬时错误最多重试 3 次退避间隔按1.5 * (attempt 1)秒递增404 被视为正常业务结果而非可重试异常直接向上抛出。这个细节很重要OLS4 中 404 往往是一种合法答案例如术语不存在而不是需要崩溃的异常。文档时效说明本参考文档记录的所有行为均于 2026 年 7 月对照线上服务逐条验证过。特别要注意OLS4 相对 OLS3 更改了多项默认行为下面列出的陷阱正是那些不报错、静默返回错误答案的类型——它们比显式报错更具迷惑性也是这套技能坚持交付脚本而非一段配方的根本原因见 SKILL.md。/search把文本变成候选术语/search是文本 → 候选术语的主入口请求形式为GET /search?q{text}...。它返回一个统一的分页响应{ response: { numFound: N, docs: [...] } }其中docs里最有用的字段包括obo_id如UBERON:0002107、label首选标签、synonym同义词、ontology_nameOLS 本体 id、is_defining_ontology是否由定义本体返回、short_form、iri完整 IRI与typeclass 等。参数一览参数作用q查询字符串必填ontology逗号分隔的 OLS本体 id注意是uberon而不是UBERON。它按本体文档过滤而非按 CURIE 前缀过滤——见陷阱 3queryFields指定匹配哪些字段默认匹配所有被索引字段。建议使用label或label,synonymexacttrue时限制为整词匹配——注意不是精确标签匹配见陷阱 1obsoletestrue时包含已废弃术语默认排除allChildrenOfURL 编码的IRI将命中限制为该术语的后代childrenOf同上但仅限制直接子节点rows、start分页控制fieldList指定返回字段见陷阱 2 关于它给不了你什么仓库客户端search()见 ols_client.py把这套参数组装成请求其中默认query_fieldslabel,synonym、exactTrue、rows10、默认排除废弃术语query_fieldsNone时则放宽到所有索引字段——这正是模糊回退fuzzy fallback的拼写方式。值得留意的是SEARCH_FIELDS常量ols_client.py的注释警告synonym是fieldList唯一认账的同义词字段名exact_synonym会被静默丢弃。陷阱 1exacttrue是精确词元而非精确标签这是最容易踩的坑。看两个实测例子qliverontologyuberonexacttrue - numFound 161 qliverontologyuberonexacttruequeryFieldslabel - numFound 1单独使用exacttrue时caudate lobe of liver也会命中——因为其标签里出现了liver这个完整词元。zzzquux依然返回 0说明该参数确实起了作用——只是作用范围不是它的名字所暗示的精确标签。正确做法是把queryFields收窄到label或label,synonym并且在客户端侧再校验一次精确性。这正是resolve_terms.py把每个命中分类为exact_label、exact_synonym或partial的原因。其核心函数match_type()ols_client.py在本地完成这个判断先把查询串与标签分别做规范化normalize_label()只折叠大小写与空白见 ols_client.py相等即为exact_label再去同义词集合里比较相等即为exact_synonym否则一律partial。测试 test_scripts.py 明确验证了OLS 会把 partial 命中与精确命中混排客户端必须自行区分这一行为。陷阱 2/search永远不会报告废弃状态is_obsolete和term_replaced_by不会由/search返回——即使你在fieldList里显式点名这两个字段它们也会被静默丢弃而不报错。只有术语详情端点见下一节才携带这些信息。好在/search默认就排除废弃术语所以搜索结果是安全的但反过来你无法用搜索来判断一个你手里已有的 ID 是否仍然有效——这必须走术语详情端点。仓库的实时冒烟测试test_search_cannot_report_obsolescencetest_scripts.py专门钉死了这一行为用include_obsoleteTrue搜obsolete_parasitic infection返回的docs[0]中确实没有is_obsolete与term_replaced_by。陷阱 3ontology不等于这个前缀本体之间会互相导入import因此按ontology过滤的搜索结果中会混入外来前缀的术语。实测qparasitic infectionontologyefo - 包含 MONDO:0016472, CL:0001069 qhepatocyteontologyuberon - CL:0000182, is_defining_ontologyfalse也就是说ontologyuberon只保证该术语文档出现在 uberon 的索引里不保证它是UBERON:*前缀。如果目标字段严格要求单一本体就必须自己在客户端按 CURIE 前缀二次过滤。这一点在 ontology-registry.md 的Tissue vs cell type一节有呼应搜索hepatocyte并限定uberon仍会返回作为导入副本的 CL 术语。陷阱 4同一个术语会按导入它的本体各出现一次以liver为例UBERON:0002107会在uberon本体下以is_defining_ontology: true出现又会在cl、hra等下以false出现。因此必须按obo_id去重并保留定义本体defining的那一份。仓库用dedupe_candidates()ols_client.py实现以obo_id缺失时回退到iri/short_form为键合并当新副本is_defining_ontology为真而当前最佳副本为假时替换之随后rank_candidates()ols_client.py按match_type分层exact_labelexact_synonympartial层内定义本体优先、再保留服务端相关度顺序。对应测试test_dedupe_keeps_the_defining_ontologys_copytest_scripts.py验证了去重结果只保留uberon副本。/ontologies/{ontology}/terms?obo_id{CURIE}权威术语详情这是每个术语的权威单点查询也是唯一能报告废弃状态的端点。示例GET /ontologies/efo/terms?obo_idEFO:0001067 is_obsolete true term_replaced_by http://purl.obolibrary.org/obo/MONDO_0005135注意term_replaced_by返回的是完整 IRI 而不是 CURIE。转换方式为按最后一个下划线切分仓库中的iri_to_curie()ols_client.py正是这样实现并且能正确处理多下划线前缀如http://purl.obolibrary.org/obo/APOLLO_SV_00000001→APOLLO_SV:00000001。测试 test_scripts.py 覆盖了 OBO PURL、EFO 命名空间、Orphanet 命名空间与多下划线四种 IRI 形状。术语不存在时该端点返回 HTTP404且带 JSON body。所以 404 是一个需要检查的正常答案而不是需要崩溃的异常——这也解释了为什么_request()把 404 与可重试状态区分对待。陷阱 5obo_id索引存在空洞这是一个隐蔽而严重的坑MONDO:0000001由 MONDO 定义、并被另外十一个本体导入但?obo_idMONDO:0000001却返回零结果——OLS 从未给它建过obo_id索引。如果把这个结果当成该 ID 不存在就是对真实存在的活术语的假失败。正确的回退路径是改查/terms?iri{编码后的 IRI}它返回每个本体对该 IRI 的一个副本应优先选取ontology_name与家本体home ontology一致且is_defining_ontology: true的那份。仓库的term_detail()ols_client.py自动完成这两次查询的整套逻辑先把 CURIE 前缀小写映射为 OLS 本体 idcurie_to_ontology_id()例外见ONTOLOGY_ID_OVERRIDES如orphanet→ordo见 ols_client.py尝试ontologies/{ontology}/terms?obo_id{curie}命中则标记_resolved_via: obo_id返回未命中404 或空列表时用candidate_iris()ols_client.py按 IRI 模板生成候选 IRI 逐个查/terms?iri在全部副本中按家本体且定义→家本体→定义本体→任意副本的优先级选取并标记_resolved_via: iri。这两个内部标记_resolved_via、_home_ontology让调用方能够区分两次解析路径。实时测试test_iri_fallback_resolves_a_term_missing_from_the_obo_id_indextest_scripts.py确认MONDO:0000001真实存在且_resolved_via iri。/terms?iri{编码后的 IRI}跨本体副本该端点返回同一 IRI 在每个本体中的副本。它的两大用途正是陷阱 5 的 IRI 回退以及查看哪些本体导入了某个术语。例如UBERON:0002107在服务中共有42 个副本。仓库侧实现为_terms_by_iri()ols_client.py请求带size100404 时返回空列表。层级Hierarchy祖先链与不可靠的 roots术语详情响应中的_links.hierarchicalAncestors.href给出传递闭包式的祖先列表分页返回追加?size500然后跟随_links.next翻页。它的用途是校验一个术语是否位于元数据字段所要求的分支之下。仓库的ancestor_curies()ols_client.py实现了完整的分页遍历把每个祖先的obo_id收进一个集合返回。两个务必注意的行为CARO 使得cell成为anatomical structure的后代因此CL:0000182hepatocyte确实位于UBERON:0000061anatomical structure之下。这意味着仅做分支检查并不能把细胞类型挡在组织列之外——必须同时约束 CURIE 前缀。/ontologies/{id}/terms/roots对合并型本体merged ontologies不可靠MONDO 的 roots 列表返回的是裸数字 id 以及无关的 BFO/CHEBI/FOODON 条目。不要在其上构建任何业务逻辑。validate_terms.py的--branch与--expect-ontology正是双重约束的落地check_term()validate_terms.py先用ancestor_curies()校验branch是否为祖先自身也算见测试test_a_term_is_in_its_own_branch再用expect_ontologies校验定义本体任一不满足即返回wrong_branch/wrong_ontology失败状态。IRI 模式能解析就不要模板化当你能够解析 IRI 时永远不要靠字符串拼接去造 IRI。OBO PURL 模式并非放之四海而皆准前缀IRI大多数 OBO 前缀http://purl.obolibrary.org/obo/{PREFIX}_{local}EFOhttp://www.ebi.ac.uk/efo/EFO_{local}Orphanethttp://www.orpha.net/ORDO/Orphanet_{local}仓库的处理哲学非常克制IRI_TEMPLATESols_client.py中的模板只用于术语详情回退时试一下绝不用来铸造一个随后被信任的 IRI——注释明确写道a wrong guess simply resolves to nothing猜错了无非解析不到。而正向的iri_for()ols_client.py则始终通过term_detail()问 API 要 IRI。测试test_candidate_iris_use_the_right_template与test_orphanet_namespace_to_curie分别钉住了这三种模板行为。相关服务ZOOMA 可用OxO 已退役ZOOMA自由文本 → 术语的另一种信号ZOOMAhttps://www.ebi.ac.uk/spot/zooma/v2/api/services/annotate利用策展curation历史把自由文本映射到术语。不加过滤时它基本不可用——例如propertyValueliver会返回https://w3id.org/gold.vocab/Liver这种与预期无关的结果。务必始终传入过滤器?propertyValueliverpropertyTypeorganismpartfilterrequired:[none],ontologies:[uberon]上述请求返回UBERON:0002107及相关术语携带confidence: HIGH|GOOD与evidence: ZOOMA_INFERRED_FROM_CURATED。当 OLS 搜索在实验室简写lab shorthand上失败时值得一试 ZOOMA——因为它见过策展人此前是如何映射完全相同的字符串的这往往是比词法搜索更好的信号详见 curation-rules.md 的Normalisations worth retrying。OxO已退役别被 HTTP 200 欺骗OxOhttps://www.ebi.ac.uk/spot/oxo/api/...已经退役。它现在返回一段 HTML 升级通知且状态码是 HTTP200——所以天真的curl | jq会以令人困惑的方式失败而不是干净地报错。跨本体映射请改用两条可行路线术语详情上的交叉引用列表annotation.database_cross_reference字段。例如UBERON:0002107携带 MESH、NCIT、FMA、UMLS、EFO 等映射见 curation-rules.md 的 Cross-ontology mapping 中的 Python 示例。已发布的 SSSOM 映射集Monarch 与 OBO 社区发布适用于对映射来源与谓词skos:exactMatchvscloseMatch有要求的场景。注意交叉引用是策展人按不同置信度断言的并不全是exactMatch。当映射驱动分析而非仅用于展示时把单个 xref 当作线索而非证明。在项目中的落地从 API 行为到解析与校验工具理解了 OLS4 的行为边界后这套技能把它封装成了两个 CLISKILL.md方向脚本回答的问题文本 → IDresolve_terms.pyleft ventricle 对应哪个术语ID → 判定validate_terms.pyEFO:0001067是否真实、有效、标签与文件声称一致两者都支持单值或文件输入、TSV/JSON 输出且只依赖 Python 3.11 标准库无第三方包。resolve_terms.py策略阶梯与match_typecd skills/ontology-term-resolution/scripts # 单个字符串限定应定义它的本体 python3 resolve_terms.py liver --ontology uberon输出示例query rank curie label ontology match_type strategy defining_ontology liver 1 UBERON:0002107 liver uberon exact_label exact true其搜索策略按exact标签与同义词的精确匹配→token跨全部索引字段的整词匹配→fulltext无限制相关度搜索逐级升级在第一个有返回的策略处停止并报告命中的策略见 resolve_terms.py 的STRATEGIES定义与resolve_one()实现resolve_terms.py。--exact-only会禁掉整个阶梯只做第一级。--branch UBERON:0000465则把候选限制为某术语的后代通过allChildrenOf传子树 IRI。测试test_ladder_escalates_when_exact_finds_nothingtest_scripts.py验证了精确层无结果时逐级放宽直到 fulltext的升级路径且末级命中的match_type为partial。使用结果前必读match_typeexact_label与exact_synonym是安全的partial表示 OLS 对按原样不存在于本体的字符串返回了它的最佳猜测需要人工决策unresolved则是合法的输出——先看 curation-rules.md 中值得重试的规范化再下结论。validate_terms.py状态机、退出码与 CI 门禁python3 validate_terms.py UBERON:0002107 EFO:0001067 UBERON:9999999输出示例id status actual_label ontology replacement detail UBERON:0002107 ok liver uberon EFO:0001067 obsolete obsolete_parasitic infection efo MONDO:0005135 obsolete; replaced by MONDO:0005135 UBERON:9999999 not_found no such term in the ontology this prefix names退出码约定有任一失败返回 1全部通过返回 0用法或网络问题返回 2——因此可以直接用作元数据文件的 CI 门禁# id label 两列能抓住ID 真实但标签对不上的问题 python3 validate_terms.py --input metadata.tsv --strict # 组织列必须只含 UBERON 解剖实体 python3 validate_terms.py --input tissue_ids.tsv \ --branch UBERON:0000465 --expect-ontology uberoncheck_term()产生的状态机validate_terms.py 定义失败/警告集合状态含义判定ok存在、有效、与所有断言一致通过matched_synonym声称的标签是同义词主标签不同警告imported_only家本体已不再断言该 ID警告not_a_class术语是属性或个体警告not_found不存在该术语失败obsolete已废弃replacement给出后继如有失败label_mismatchID 与声称的标签描述不同事物失败wrong_ontologyID 类型对但对这一列是错误本体失败wrong_branch不是所要求根节点的后代失败malformed_curie不是PREFIX:local形式失败--strict把警告升级为失败。注意check_term()内部的判定顺序先查存在性term_detail返回None即not_found再查废弃把term_replaced_byIRI 经iri_to_curie转成 CURIE 填入replacement再查本体、标签、分支最后是imported_only与not_a_class警告——失败状态优先于警告测试test_failure_beats_warning验证了这一优先级。测试ExitCodeTests还确认了退出码语义test_scripts.py。输入解析的鲁棒性validate_terms.py --input能自动识别 TSV/CSV按首行是否含制表符判断分隔符识别多种表头名id/curie/term_id/ontology_term_id/obo_id与label/term_label/name/ontology_term_label见 validate_terms.py跳过#注释与空行resolve_terms.py --input则按行读取、去重并保持顺序测试 test_scripts.py。两个脚本都支持-表示从 stdin 读取。测试如何钉死这些 API 行为整套 API 行为的正确性依赖被仓库测试显式锁定离线单元测试全部 stub 掉网络调用可在无网环境运行python -m pytest tests/ontology-term-resolution -q实时冒烟测试以OLS_LIVE_TESTS1环境变量门控test_scripts.py它们逐条印证了本文所述的 API 行为exacttrue单独使用不等于精确标签匹配、/search不返回废弃字段、IRI 回退能解析obo_id索引空洞的术语、Orphanet 覆盖规则生效等。选择本体与报告规范解析之前先想清楚哪个本体拥有这个概念MONDO 管疾病、HP 管表型、UBERON 管组织、CL 管细胞类型、EFO 管实验/测定、ChEBI 管化合物、NCBITaxon 管物种、PATO 管性别与normal。前缀到 OLS id 的映射HP由hp服务、Orphanet由ordo服务、各字段的--branch根节点、以及重叠本体的取舍判断全部收录在 ontology-registry.md。最后报告结果时务必同时给出 ID 和标签并说明每个命中是如何匹配的。一张只含裸 ID 的表无法被审查——没有人能凭肉眼区分UBERON:0002107与UBERON:0002108这正是虚构 ID 能蒙混过关的根源。未解析的术语要明确标注而不是用最近似的结果填空。【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000 scientists worldwide. 165 ready-to-use validated skills plus 100 scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考