@dsh-bio/dsh-bio-gem 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tools.js ADDED
@@ -0,0 +1,545 @@
1
+ // dsh-bio-gem — 工具层(defineTool 注册,20 语义化工具,2026-08-30 阶段C-C4 起)
2
+ // 全部执行走 python/gem_ops.py(JSON stdin 协议)或 build.py CLI(gem_build 长任务)。
3
+ // op 与工具对照:19 op(含 fluxscan/sensitivity/ledger/benchmark/secretion/double_knockout/enrichment/targets)+ build CLI;详见 docs/ARCHITECTURE.md §3。
4
+ import { defineTool } from '@deepseek-ai/dsh-tools'
5
+ import { join } from 'node:path'
6
+ import { dirname, isAbsolute } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { spawn } from 'node:child_process'
9
+ import { callGem, pythonExe, PYTHON_DIR } from './python.js'
10
+ import { startBuild, jobStatus } from './jobs.js'
11
+
12
+ const PY = pythonExe()
13
+
14
+ /** 校验输入存在(绝对路径或用户给定路径)。 */
15
+ function requirePath(v, label) {
16
+ if (!v) throw new Error(`${label} required`)
17
+ if (!isAbsolute(v)) throw new Error(`${label} 必须是绝对路径: ${v}`)
18
+ return v
19
+ }
20
+
21
+ /** gem_ops 通用工具工厂(同步 op:report/validate/gapfind/gapfill)。 */
22
+ function gemTool(opts) {
23
+ return defineTool({
24
+ name: opts.name,
25
+ description: opts.description,
26
+ parameters: opts.parameters,
27
+ timeoutMs: opts.timeoutMs ?? 300_000,
28
+ output: {
29
+ schema: { type: 'object', additionalProperties: true },
30
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
31
+ },
32
+ async execute(args) {
33
+ return callGem(opts.op, args, { timeoutMs: opts.timeoutMs ?? 300_000 })
34
+ },
35
+ })
36
+ }
37
+
38
+ /** gem_build:spawn build.py(可 60-120s),await 完成返回 {model, card, ...}。 */
39
+ function buildTool() {
40
+ return defineTool({
41
+ name: 'gem_build',
42
+ description:
43
+ '从细菌全基因组构建基因组尺度代谢模型(GEM)。' +
44
+ 'engine=carveme(默认,纯 Windows 快):输入蛋白 FASTA(*.faa),CarveMe -g M9 gapfill → M9 介质验证 → 目标介质 L1/L2 补洞,' +
45
+ '约 1-2 分钟(C58 实测 70s)。' +
46
+ 'engine=gapseq(质量档,需本机 WSL2 gapseq 环境):输入核苷酸 FASTA(*.fna),WSL 桥 gapseq doall → 模型拷回 → 目标介质验证,' +
47
+ '约 30-60 分钟(后台进度日志旁观,不要误判超时)。' +
48
+ '输出标准 SBML(fbc v2)+ 模型卡(sidecar JSON:引擎版本/验证结果/补洞记录)。' +
49
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
50
+ '触发词:构建代谢模型、基因组转模型、建GSMM、carveme 建模、gapseq 建模。',
51
+ parameters: {
52
+ input: {
53
+ type: 'string', required: true,
54
+ description: '输入绝对路径:engine=carveme 用蛋白 FASTA(*.faa);engine=gapseq 用核苷酸 FASTA(*.fna)。',
55
+ },
56
+ name: { type: 'string', description: '模型命名(如 C58),缺省用文件名' },
57
+ engine: { type: 'string', enum: ['carveme', 'gapseq'], description: '构建引擎:carveme(默认,纯 Windows 快出稿)或 gapseq(WSL2,质量档 30-60min)' },
58
+ out_dir: { type: 'string', description: '输出目录,缺省 ~/.dsh/dsh-bio-gem/models' },
59
+ target_medium: {
60
+ type: 'object', additionalProperties: true,
61
+ description: '目标培养基:可传 {"medium_name": "AB"/"M9"}(内置完整成分)或自然名成分字典 {"D-Glucose": -5, "NH3": -10, ...}。跨引擎自动解析。',
62
+ },
63
+ },
64
+ timeoutMs: 3_600_000,
65
+ output: {
66
+ schema: { type: 'object', additionalProperties: true },
67
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
68
+ },
69
+ async execute(args) {
70
+ requirePath(args.input, 'input')
71
+ const job = startBuild({
72
+ input: args.input,
73
+ name: args.name,
74
+ engine: args.engine,
75
+ medium: args.target_medium,
76
+ outDir: args.out_dir,
77
+ })
78
+ // 轮询进度直到 done(build.py 内部已写 result.json)
79
+ const deadline = Date.now() + 3_540_000
80
+ while (Date.now() < deadline) {
81
+ await new Promise((r) => setTimeout(r, 2000))
82
+ const st = jobStatus(job.jobId)
83
+ if (st.done) {
84
+ const d = st.detail ? ` | detail=${JSON.stringify(st.detail)}` : ''
85
+ if (st.error) throw new Error(`gem_build failed: ${st.error}${d}`)
86
+ if (!st.result || st.result.ok === false || st.result.result == null) {
87
+ throw new Error(`gem_build failed: ${st.result?.error_hint ?? st.error ?? 'result missing'}${d}`)
88
+ }
89
+ return st.result.result
90
+ }
91
+ }
92
+ throw new Error('gem_build timeout (840s)')
93
+ },
94
+ })
95
+ }
96
+
97
+ export function registerTools(ctx) {
98
+ const disposers = []
99
+ disposers.push(ctx.tools.register(gemTool({
100
+ name: 'gem_report',
101
+ description:
102
+ '读取 SBML 代谢模型文件,输出模型摘要:基因/反应/代谢物/区室/复制子分布(多质粒/多染色体分离统计)、交换数。' +
103
+ '用于快速检查模型文件是否可加载、规模、是否为多复制子。模型文件不存在或非有效 SBML 会明确报错。' +
104
+ '输出含 ledger_summary(预测账本基率摘要:total/by_status/by_type/by_model;一个模型一个账本——' +
105
+ '传本工具 model 时读该模型自己的账本,own_model_entries=本模型条目数=账本总数,不再混其他模型预测;' +
106
+ '可选 ledger_path 指向自定义账本)与基率披露语境。' +
107
+ '触发词:看模型信息、模型摘要、有多少基因。',
108
+ parameters: { model: { type: 'string', required: true, description: 'SBML 文件绝对路径' }, ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 ~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl)' } },
109
+ op: 'model_info',
110
+ })))
111
+
112
+ disposers.push(ctx.tools.register(gemTool({
113
+ name: 'gem_validate',
114
+ description:
115
+ '对 SBML 代谢模型执行五道验证关卡:G1 加载统计(+多复制子 ID 检查 + GPR 覆盖)、' +
116
+ 'G2 内部反应元素平衡(C/N/P/S 必须为 0,H/O 单独报告)、G3 生长真实性(声明培养基上有碳源>0、无碳=0、全关=0)、' +
117
+ 'G4 底物表型对照(需 phenotype_table 路径,条件执行)、G5 必需基因抽检(需 essential_test 基因列表,条件执行)。' +
118
+ 'medium 用自然名成分(如 D-Glucose/NH3/O2),跨引擎自动解析。G2 的已知生物质方程簿记偏差(如 bio1)报 WARN 不阻塞。' +
119
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
120
+ '触发词:验证模型、质量检查、五道关卡。',
121
+ parameters: {
122
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
123
+ medium: { type: 'object', additionalProperties: true, description: '培养基:可传 {"medium_name": "AB"}(推荐,内置完整 AB 成分含金属)或自然名成分字典 {"D-Glucose": -5, "NH3": -10, ...}' },
124
+ reference_growth: { type: 'number', description: '回归锚:已知野生型生长值(用于 G3 ratio 判定)' },
125
+ phenotype_table: { type: 'string', description: '可选:底物表型 TSV(substrate<TAB>published 0/1)' },
126
+ essential_test: { type: 'array', items: { type: 'string' }, description: '可选:抽检基因 ID 列表' },
127
+ carbon_mode: { type: 'string', enum: ['supplement', 'sole'], description: 'G4 语义(默认 supplement=基准+底物)' },
128
+ },
129
+ op: 'validate',
130
+ timeoutMs: 300_000,
131
+ })))
132
+
133
+ disposers.push(ctx.tools.register(gemTool({
134
+ name: 'gem_gapfind',
135
+ description:
136
+ '代谢模型缺口分级诊断:L1 缺胞外交换(培养基成分无对应 EX)、L2 缺转运(e0 代谢物无入胞出口)、' +
137
+ 'L3 内部路径(有交换+转运但 FBA 不生长)。输出分级缺口清单 + 每条是否规则可修(fixable)。' +
138
+ '已知规律:多数「不能利用某碳源」缺口是 L1/L2 而非 L3。medium 支持自然名(跨引擎解析)。' +
139
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
140
+ '触发词:诊断缺口、为什么不能用这个碳源、gapfind。',
141
+ parameters: {
142
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
143
+ medium: { type: 'object', additionalProperties: true, description: '培养基:可传 {"medium_name": "AB"}(推荐,内置完整 AB 成分含金属)或自然名成分字典 {"D-Glucose": -5, "NH3": -10, ...}' },
144
+ substrates: { type: 'array', items: { type: 'string' }, description: '可选:待检底物名列表(如 Sucrose)' },
145
+ },
146
+ op: 'gapfind',
147
+ })))
148
+
149
+ disposers.push(ctx.tools.register(gemTool({
150
+ name: 'gem_gapfill',
151
+ description:
152
+ '按 gapfind 分级结果自动补洞:L1 补胞外交换(EX_ 反应 + 胞外代谢物)、L2 补转运(e0→c0,GPR 留空标注)。' +
153
+ '每条新增反应打 provenance 标记(source=gem-gapfill + 原因);max_add 封顶防过补;out 缺省生成 <model>_gf.xml(原文件不覆盖,' +
154
+ '就地覆盖才备份 .bak)。L3 内部路径不自动补(需文献反应)。补洞后建议重跑 gem_validate 确认生长恢复。' +
155
+ '触发词:补洞、修复缺口、gapfill、加交换。',
156
+ parameters: {
157
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
158
+ medium: { type: 'object', additionalProperties: true, description: '培养基:可传 {"medium_name": "AB"/"M9"}(内置完整成分)或自然名成分字典' },
159
+ substrates: { type: 'array', items: { type: 'string' }, description: '可选:要支持的底物名列表' },
160
+ max_add: { type: 'integer', description: '单次最多新增反应数(默认 20)' },
161
+ out: { type: 'string', description: '输出模型路径(缺省 <model>_gf.xml)' },
162
+ },
163
+ op: 'gapfill',
164
+ })))
165
+
166
+ disposers.push(ctx.tools.register(buildTool()))
167
+
168
+ // gem_gapseq:gapseq 引擎原子步骤(agent 编排 setup->launch->status*->fetch)
169
+ disposers.push(ctx.tools.register(gemTool({
170
+ name: 'gem_gapseq',
171
+ description:
172
+ 'gapseq 引擎(WSL2)原子步骤工具——长任务由 agent 按编排推进:\n' +
173
+ 'action=setup:能力探测(wsl/发行版/gapseq 版本/序列库注册)→ capability OK 才能继续。\n' +
174
+ 'action=launch:输入核苷酸 FASTA(*.fna 绝对路径)→ 后台启动 gapseq doall(30-60min),立即返回工作目录;不要等它完成。\n' +
175
+ 'action=status:查 doall 状态 → {state: running|done|failed, log_tail};running 时 2-5 分钟后再查,可多轮。\n' +
176
+ 'action=fetch:done 后把产物(XML/faa.gz/tbl/日志)拷回 Windows 输出目录 → {model}。\n' +
177
+ '编排模式:setup → launch →(循环 status 直到 done)→ fetch → 对 model 跑 gem_validate/缺口补洞。' +
178
+ '失败时(failed/COPY_FAIL)根据 hint 自纠后重试 launch。触发词:gapseq 建模、doall、质量重建。',
179
+ parameters: {
180
+ action: { type: 'string', enum: ['setup', 'launch', 'status', 'fetch'], required: true, description: '原子步骤' },
181
+ input: { type: 'string', description: 'launch 用:核苷酸 .fna 绝对路径' },
182
+ name: { type: 'string', description: '模型名(产物 basename),缺省 model' },
183
+ out_dir: { type: 'string', description: '产物输出目录,缺省 ~/.dsh/dsh-bio-gem/models' },
184
+ },
185
+ op: 'gapseq',
186
+ timeoutMs: 180_000,
187
+ })))
188
+
189
+ // gem_phenotype:表型回填迭代(G4 驱动,路线 A3)
190
+ disposers.push(ctx.tools.register(gemTool({
191
+ name: 'gem_phenotype',
192
+ description:
193
+ '表型回填迭代:对模型跑 G4 表型对照(phenotype_table:substrate<TAB>published 0/1,如 Biolog/文献表),' +
194
+ '对「应生长但模型不长」的底物逐个 gapfind 分级 → L1/L2 交换/转运规则自动补洞(累积修复)→ L3 内部路径列候选清单 → 重跑 G4 对比匹配率。' +
195
+ 'medium 推荐 {"medium_name": "AB"}。输出 before/after 匹配率 + 修复清单 + L3 待处理项。' +
196
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
197
+ '触发词:表型回填、提高表型匹配、Biolog 校准、为什么这个底物不长。',
198
+ parameters: {
199
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径(将基于副本修复,原文件不动)' },
200
+ phenotype_table: { type: 'string', required: true, description: '表型表 TSV 绝对路径:substrate<TAB>published(0/1)' },
201
+ medium: { type: 'object', additionalProperties: true, description: '培养基:{"medium_name": "AB"} 或自然名成分字典' },
202
+ max_add: { type: 'integer', description: '每底物最多新增反应数(默认 20)' },
203
+ out: { type: 'string', description: '修复后模型输出路径(缺省 <model>_pf.xml)' },
204
+ },
205
+ op: 'phenotype_fix',
206
+ timeoutMs: 300_000,
207
+ })))
208
+
209
+ // gem_essentiality:G5 全量必需基因扫描(FVA 预筛 + 手工敲除)
210
+ disposers.push(ctx.tools.register(gemTool({
211
+ name: 'gem_essentiality',
212
+ description:
213
+ '对代谢模型做全量必需基因扫描:FVA(全范围)预筛出可通量反应关联基因(死基因免敲,通常省 30-50% 计算),' +
214
+ '再对候选逐一手工敲除(with m: 循环)判定是否必需(敲除后生长<1e-6)。' +
215
+ 'medium 推荐 {"medium_name": "AB"}。输出必需基因列表 + 数量 + wt 生长 + 耗时统计。' +
216
+ '若提供 gene_table(gem_annotate 返回的 <base>.gene_table.tsv),输出额外含 essential_gene_details(' +
217
+ '每必需基因带 locus_tag/product 功能注释——坐标型基因 ID 无此表时是不可解读的)。' +
218
+ '结果可用于模型卡"必需基因"章节(对照文献/实验必需基因集即召回率)。' +
219
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
220
+ '触发词:必需基因扫描、全量必要基因、essentiality scan、敲除全扫。',
221
+ parameters: {
222
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
223
+ medium: { type: 'object', additionalProperties: true, description: '培养基:{"medium_name": "AB"} 或自然名成分字典' },
224
+ gene_subset: { type: 'array', items: { type: 'string' }, description: '可选:只扫描指定基因(限制范围)' },
225
+ gene_table: { type: 'string', description: '可选:gem_annotate 返回的基因注释表 TSV(<base>.gene_table.tsv),提供则 essential_gene_details 带 locus_tag/product' },
226
+ },
227
+ op: 'essential_scan',
228
+ timeoutMs: 600_000,
229
+ })))
230
+
231
+ // gem_annotate:基因组注释(纯 Windows;官方优先 + pyrodigal 兜底)
232
+ disposers.push(ctx.tools.register(gemTool({
233
+ name: 'gem_annotate',
234
+ description:
235
+ '把细菌基因组核苷酸 FASTA(.fna)转成蛋白 FASTA(.faa),供 gem_build(CarveMe) 使用。' +
236
+ '优先级:同目录 *_protein.faa(官方蛋白)→ 同目录 cds_from_genomic.fna 直译 → 同目录 *.gff 解析翻译 → pyrodigal 预测(兜底)。' +
237
+ '返回 {faa, source, stats};GFF 路径额外产出 gene_table(<base>.gene_table.tsv:坐标ID→locus_tag/product 基因注释表),' +
238
+ '传给 gem_essentiality 的 gene_table 参数即可让必需基因结果带功能注释。' +
239
+ '触发词:注释、基因组转蛋白、建蛋白序列、pyrodigal。',
240
+ parameters: {
241
+ fna: { type: 'string', required: true, description: '基因组核苷酸 fasta 绝对路径(.fna)' },
242
+ out: { type: 'string', description: '输出蛋白 fasta 路径(缺省同目录 <base>.gem_annot.faa)' },
243
+ },
244
+ op: 'annotate',
245
+ timeoutMs: 300_000,
246
+ })))
247
+
248
+ // gem_media_resolve:跨引擎介质解析 RPC(genie 消费侧统一入口;防介质语义漂移三次假象重演)
249
+ disposers.push(ctx.tools.register(gemTool({
250
+ name: 'gem_media_resolve',
251
+ description:
252
+ '把自然名培养基(如 {"medium_name": "AB"} 或 {"D-Glucose": -5, "NH3": -10, ...})解析到指定模型的实际交换反应(EX ID 列表)。' +
253
+ '这是全插件统一的介质解析入口——任何消费方(包括 dsh-bio-genie 的 FBA 等)应通过本工具/同款解析层获得交换 ID,' +
254
+ '不要自行实现介质名匹配(已因三次"模型不生长假象"加固)。返回 {resolved_exchanges, unresolved, medium_preset}。' +
255
+ '触发词:介质解析、培养基转交换、media resolve。',
256
+ parameters: {
257
+ model: { type: 'string', required: true, description: '目标模型 SBML 绝对路径' },
258
+ medium: { type: 'object', additionalProperties: true, description: '自然名培养基(medium_name 或成分字典)' },
259
+ },
260
+ op: 'media_resolve',
261
+ timeoutMs: 120_000,
262
+ })))
263
+
264
+ // gem_l3_fix:L3 内部路径补洞(B' 后半:L3a 模型内连通性 + L3b 白名单/BiGG 反应式 + 证据分级)
265
+ disposers.push(ctx.tools.register(gemTool({
266
+ name: 'gem_l3_fix',
267
+ description:
268
+ 'L3 内部路径补洞(两级,白名单驱动):对 gapfind 判为 L3 的底物(有交换+转运但不生长)做修复。\n' +
269
+ 'L3a 模型内连通性:先全内部反应放开方向做 LP 预检(快速判"是否纯连通性问题"),可行才用 MILP 取最小放宽集(改 bounds,不复制反应)。\n' +
270
+ 'L3b 白名单+反应式:diamond 白名单命中集(EC/名字桥接 iML1515 反应式移植到本模型命名空间;无匹配不强补);' +
271
+ 'allow_math=true 时允许纯数学连接(MILP 决策,证据最弱)。PTS 型反应一律排除(PTS-less 机体守则)。\n' +
272
+ '证据分级:EVIDENCE_sequence(白名单直接对应)> EVIDENCE_math(数学连接,附 sequence_hint 表示有序列线索的间接桥);' +
273
+ '防过补第五闸门:历史累计新增 ≤ max(5, 5%·总反应),超限返回 confirm_required 需显式 confirm_budget=true。\n' +
274
+ '补后自动 validate G1-G6 全跑,G6(ATP 泄漏哨兵)非 PASS 自动回滚本批改动。' +
275
+ '返回每底物 before/after sole 生长 + verdict(fixed/not_fixable + 不可补证据链)。\n' +
276
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
277
+ '触发词:补内部路径、L3 补洞、白名单补反应、为什么补了交换还是不长。',
278
+ parameters: {
279
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径(原文件不动,修复写到 out)' },
280
+ medium: { type: 'object', additionalProperties: true, description: '培养基:可传 {"medium_name": "AB"}(推荐,内置完整成分含金属)或自然名成分字典' },
281
+ substrates: { type: 'array', items: { type: 'string' }, required: true, description: 'L3 底物名列表(gapfind 报 L3 的底物,如 Arabinose/Mannitol)' },
282
+ allow_math: { type: 'boolean', description: '是否允许纯数学连接(EVIDENCE_math;默认 false=只用白名单序列证据池)' },
283
+ confirm_budget: { type: 'boolean', description: '第五闸门超限时显式确认继续(默认 false)' },
284
+ whitelist: { type: 'string', description: '白名单命中集 JSON 路径(缺省用 ~/.dsh/dsh-bio-gem/whitelist/ 缓存或现场跑 diamond)' },
285
+ faa: { type: 'string', description: '目标物种蛋白 fasta(白名单未缓存时现场构建用)' },
286
+ out: { type: 'string', description: '修复后模型输出路径(缺省 <model>_l3.xml)' },
287
+ },
288
+ op: 'l3_fix',
289
+ timeoutMs: 900_000,
290
+ })))
291
+
292
+ // gem_biomass:biomass 精修(Q2:inspect 只读 / apply 显式覆盖表 + 三联对照)
293
+ disposers.push(ctx.tools.register(defineTool({
294
+ name: 'gem_biomass',
295
+ description:
296
+ 'biomass(FBA 目标函数)精修工具,action 两步:\n' +
297
+ 'action=inspect(只读,无副作用):解析 biomass 反应 → 组分表(met_id/name/coeff/compartment/类别)+ 摘要' +
298
+ '(组分个数、原子总量、类别分布:氨基酸/核酸/脂质/辅因子/金属/其他);reference 可选 iML1515/both 对照' +
299
+ '(含 iNX1344_v4 按代谢物名同义尽力翻译,翻不了明示 unmapped N 个,不强行全翻)。\n' +
300
+ 'action=apply(显式修改):必须给 biomass_profile 覆盖表 [{"met_id","coeff","op":"set|add|remove"}],' +
301
+ '基于副本替换 biomass → 强制 G1-G6 重验 + 三联对照(生长率/表型匹配率/必需基因 delta,单位 mmol/gDW/h)' +
302
+ '→ 输出 before/after 对照 + 新模型(原文件不动=天然可回滚)+ 模型卡 lineage 追加(有 card 时)。' +
303
+ '生长变差 WARN 不阻塞;默认不应用任何 profile。' +
304
+ '生长/通量数值为单点 FBA 值(非硬结论);条件间对比用 gem_fluxscan(区间制)。' +
305
+ '触发词:biomass 精修、看生物质组成、改 biomass 系数、目标函数调整。',
306
+ parameters: {
307
+ action: { type: 'string', enum: ['inspect', 'apply'], required: true, description: 'inspect=只读诊断;apply=显式应用覆盖表' },
308
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
309
+ biomass_profile: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'apply 用:覆盖表 [{"met_id","coeff","op":"set|add|remove"}](set 沿用原符号;必须显式提供,缺省报错)' },
310
+ reference: { type: 'string', enum: ['iML1515', 'iNX1344_v4', 'both'], description: 'inspect 用:参考模板对照' },
311
+ medium: { type: 'object', additionalProperties: true, description: '对照验证用培养基:{"medium_name": "AB"/"M9"} 或自然名字典' },
312
+ phenotype_table: { type: 'string', description: 'apply 三联对照用:底物表型 TSV(substrate<TAB>published 0/1)' },
313
+ out: { type: 'string', description: 'apply 输出模型路径(缺省 <model>_bm.xml)' },
314
+ essential_sample: { type: 'integer', description: 'apply 必需基因对照抽样数(默认 40,确定性步进抽样)' },
315
+ note: { type: 'string', description: 'apply 备注(写入模型卡 lineage detail)' },
316
+ },
317
+ timeoutMs: 900_000,
318
+ output: {
319
+ schema: { type: 'object', additionalProperties: true },
320
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
321
+ },
322
+ async execute(args) {
323
+ const op = args.action === 'apply' ? 'biomass_apply' : 'biomass_inspect'
324
+ return callGem(op, args, { timeoutMs: 900_000 })
325
+ },
326
+ })))
327
+
328
+ // gem_fluxscan:通量区间制(阶段A-M1:FVA 区间 + pFBA 点值 + 条件对区间分离判定)
329
+ disposers.push(ctx.tools.register(gemTool({
330
+ name: 'gem_fluxscan',
331
+ description:
332
+ '通量区间制(fluxscan):对同一模型在多个培养条件下做全模型 FVA(区间)+ pFBA(点值),' +
333
+ '每反应输出 fva_min/fva_max/pfba;条件对比消费区间分离判定——' +
334
+ '两条件区间分离 = 解空间无关硬结论(a_higher/b_higher),区间重叠(overlap)= 点值差异是求解器伪影,禁止引用。' +
335
+ '背景:单点 FBA 通量取决于求解器顶点(解空间退化),跨条件点值 diff 是伪影,条件比较必须用本工具的区间制。' +
336
+ '输入 conditions 数组(name 唯一;medium 支持 {"medium_name": "AB"};substrates+carbon_mode=sole 对齐 G4 表型语义);' +
337
+ '可选 reactions 关注子集(FVA/pFBA 仍全模型算,仅收窄输出口径)、pairs 显式比较对(缺省全唯一对)、' +
338
+ 'only_diff 只看硬结论、export_csv 全量落盘。' +
339
+ '触发词:通量扫描、区间制、FVA 对比、为什么这个反应通量变了、哪些反应在两个条件下真变了。',
340
+ parameters: {
341
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
342
+ conditions: {
343
+ type: 'array', items: { type: 'object', additionalProperties: true }, required: true,
344
+ description: '条件数组,每项 {name(唯一), medium: {"medium_name": "AB"} 或自然名字典, substrates?: ["L-Arabinose"], carbon_mode?: "supplement"(默认)|"sole"}',
345
+ },
346
+ reactions: { type: 'array', items: { type: 'string' }, description: '可选:关注反应子集(FVA/pFBA 仍全模型计算;comparisons/summary 只含子集)' },
347
+ pairs: { type: 'array', items: { type: 'array', items: { type: 'string' } }, description: '可选:显式比较对 [[\"AB\",\"AB+Ara\"]];缺省=conditions 全唯一对' },
348
+ fraction_of_optimum: { type: 'number', description: 'FVA 最优约束分数(默认 0.9999,避免 1.0 数值噪声致空/窄区间)' },
349
+ tolerance: { type: 'number', description: '区间分离容差 mmol/gDW/h(默认 1e-6;分离要求区间间隙 > 容差)' },
350
+ only_diff: { type: 'boolean', description: 'true=只输出 hard_conclusion 反应(默认 false)' },
351
+ export_csv: { type: 'string', description: '全量结果 CSV 落盘路径(所有反应×条件对×区间/点值/判定),返回文件路径' },
352
+ },
353
+ op: 'fluxscan',
354
+ timeoutMs: 900_000,
355
+ })))
356
+
357
+ // gem_sensitivity:结构性灵敏度(阶段A-M2:GAM×biomass 网格 22 组合 + 稳定性三分类 + 单组分漂移)
358
+ disposers.push(ctx.tools.register(gemTool({
359
+ name: 'gem_sensitivity',
360
+ description:
361
+ '结构性灵敏度(sensitivity):把"模型不确定"量化——biomass 组分系数 ×{0.75,1.0,1.25} × GAM {1,5,10,20,30,40,50} ' +
362
+ '= 21 扫点 + 1 基准组合(不扰动)= 22 组合全量(不抽样),每组合 wt 生长 + 必需性重扫(复用 essential_scan 的 ' +
363
+ 'FVA 预筛+手工敲除逻辑);输出稳定性三分类(always/conditionally/never essential)+ 单组分 ±25% 灵敏度 top10 ' +
364
+ '+ top10 必需性漂移(哪些基因从必需变非必需/新增必需)+ 模型卡"鲁棒性"章节(schema v3,无 card 不造卡)。' +
365
+ 'GAM 载体自动定位(biomass 方程内 ATP stub 或独立 ATPM),biomass 扰动与 GAM 网格正交化。' +
366
+ '生长/必需性数值为单点 FBA 口径(mmol/gDW/h);条件间通量对比用 gem_fluxscan(区间制)。' +
367
+ 'action=probe 秒级只读探测(GAM 载体/组分计数);缺省 full 全量约 35-45 分钟(后台长任务,耐心等待勿误判超时)。' +
368
+ '触发词:灵敏度分析、鲁棒性、GAM 扫描、biomass 不确定性、必需基因稳定性。',
369
+ parameters: {
370
+ action: { type: 'string', enum: ['probe', 'full'], description: 'probe=秒级只读(GAM 载体定位);full=22 组合全量(默认缺省即 full,长任务)' },
371
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
372
+ medium: { type: 'object', additionalProperties: true, description: '培养基:{"medium_name": "AB"}(默认)或自然名成分字典' },
373
+ biomass_scales: { type: 'array', items: { type: 'number' }, description: 'biomass 组分缩放轴(默认 [0.75, 1.0, 1.25])' },
374
+ gam_grid: { type: 'array', items: { type: 'number' }, description: 'GAM 网格(默认 [1,5,10,20,30,40,50] mmol ATP/gDW)' },
375
+ run_component_sensitivity: { type: 'boolean', description: '二级单组分 ±25% 灵敏度(默认 true,~114 次 FBA)' },
376
+ run_drift: { type: 'boolean', description: 'top 敏感组分必需性重扫(默认 true,20 次×~55s)' },
377
+ top_n: { type: 'integer', description: '敏感组分 top N(默认 10)' },
378
+ export_csv: { type: 'string', description: '全量结果 CSV 落盘路径' },
379
+ baseline_check_path: { type: 'string', description: '可选:essential_scan 基线必需集 JSON 文件路径(基准组合做精确复现断言)' },
380
+ },
381
+ op: 'sensitivity',
382
+ timeoutMs: 3_600_000,
383
+ })))
384
+
385
+ // gem_ledger:prediction ledger 预测账本(阶段A-M3:gem_essentiality/gem_phenotype 自动登记的预测可查询/更新/追踪)
386
+ disposers.push(ctx.tools.register(gemTool({
387
+ name: 'gem_ledger',
388
+ description:
389
+ '预测账本(prediction ledger):一个模型一个账本——账本文件按模型名分(ledger/<模型名>.jsonl),' +
390
+ 'gem_essentiality(每必需基因一条)与 gem_phenotype(每底物一条 G4 结果)等自动登记到对应模型的账本(追加式 JSONL,幂等去重,' +
391
+ '同 model+condition+type+content 不重复入账)。' +
392
+ 'action=list 分页列出(limit/offset,返回 total);action=query 条件过滤(type/status/condition/model ' +
393
+ '前缀匹配,可组合;传 model 则定位到该模型自己的账本,不传则聚合所有模型账本=全局视图);action=update 按 prediction_id 改 status(unverified/literature_supported/' +
394
+ 'literature_contradicted/experimentally_verified)/source_refs/comparison_refs(维护 updated_at)。' +
395
+ '只读/追加/更新,不删行;损坏行跳过并报 corrupt_rows 不阻塞。' +
396
+ '账本预测默认 status=unverified——实验或文献兑现前不应当作事实引用(基率披露见 gem_report 的 ledger_summary)。' +
397
+ '生长/通量数值为单点 FBA 口径(mmol/gDW/h);条件间通量对比用 gem_fluxscan(区间制)。' +
398
+ '触发词:预测账本、查询预测、更新预测状态、预测追踪、ledger。',
399
+ parameters: {
400
+ action: { type: 'string', enum: ['list', 'query', 'update'], required: true, description: 'list=分页列出;query=条件过滤;update=改状态/来源' },
401
+ limit: { type: 'integer', description: 'list/query 分页大小' },
402
+ offset: { type: 'integer', description: 'list/query 分页偏移(默认 0)' },
403
+ type: { type: 'string', description: 'query 用:类型前缀过滤(essentiality/phenotype/synthetic_lethal/secretion/other)' },
404
+ status: { type: 'string', description: 'query 用:状态前缀过滤(unverified/literature_supported/literature_contradicted/experimentally_verified)' },
405
+ condition: { type: 'string', description: 'query 用:条件前缀过滤(如 AB)' },
406
+ model: { type: 'string', description: 'query 用:模型路径前缀过滤' },
407
+ prediction_id: { type: 'string', description: 'update 用:预测 ID(如 P0001)' },
408
+ source_refs: { type: 'array', items: { type: 'string' }, description: 'update 用:文献/实验来源引用列表' },
409
+ comparison_refs: { type: 'array', items: { type: 'string' }, description: 'update 用:对照记录引用' },
410
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 ~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl)' },
411
+ },
412
+ op: 'ledger',
413
+ timeoutMs: 60_000,
414
+ })))
415
+
416
+ // gem_benchmark:通用基准对比(阶段B-B1:任何两个 GEM 的规范对比表,六关并列+生长+biomass 探针+必需性+表型+账本回填)
417
+ disposers.push(ctx.tools.register(gemTool({
418
+ name: 'gem_benchmark',
419
+ description:
420
+ '通用基准对比(benchmark):任何两个代谢模型跑规范对比表,产出论文级对比。model_a/model_b 支持本地 SBML 绝对路径' +
421
+ '或 "bigg:<model_id>" URI(如 bigg:iML1515,BiGG 静态库下载到 ~/.dsh/dsh-bio-gem/models/,直连失败自动走本机代理,下载后缓存)。' +
422
+ '输出:ID 体系探测 / 六道关卡 G1-G6 逐项并列 / 声明介质生长(含介质层两级策略——无 EX_ 层的模型自动回退 ' +
423
+ 'boundary 单代谢物反应解析,boundary_style 标注)/ biomass 可行性探针(逐组分净产测试,结构性断供清单)/ ' +
424
+ '必需性对比(复用 essential_scan;任一侧 wt<=EPS 判退化只报结构信息不做垃圾对比,基因映射尽力而为如实报覆盖率,' +
425
+ 'reference_essential 文献值仅标注不冒充模型输出)/ 表型对比(G4 sole)/ 可复现性评估 / 账本 comparison_refs ' +
426
+ '回填(update 语义幂等可重入)。export_md 落盘论文级 Markdown。' +
427
+ '生长/通量数值为单点 FBA 口径(mmol/gDW/h);条件间通量对比用 gem_fluxscan(区间制)。' +
428
+ '触发词:模型对比、两个模型比较、基准、benchmark、跨模型校准。',
429
+ parameters: {
430
+ model_a: { type: 'string', required: true, description: '模型 A SBML 绝对路径' },
431
+ model_b: { type: 'string', required: true, description: '模型 B SBML 绝对路径' },
432
+ medium: { type: 'object', additionalProperties: true, description: '对比介质(缺省 {"medium_name": "AB"})' },
433
+ phenotype_table: { type: 'string', description: '可选:底物表型 TSV(substrate<TAB>published 0/1),提供则跑表型对比' },
434
+ reference_essential: { type: 'object', additionalProperties: true, description: '可选:文献必需基因集(如 {"b": [...]}),仅报告标注不参与计算' },
435
+ essential_full: { type: 'boolean', description: 'true=全量必需性对比(各 ~50s);false=抽检 40(G5 口径,默认)' },
436
+ ledger_refs: { type: 'boolean', description: '对比后回填账本 comparison_refs(默认 true;幂等可重入)' },
437
+ export_md: { type: 'string', description: '论文级 Markdown 落盘路径' },
438
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 ~/.dsh/dsh-bio-gem/ledger/<模型名>.jsonl)' },
439
+ },
440
+ op: 'benchmark',
441
+ timeoutMs: 1_200_000,
442
+ })))
443
+
444
+ // gem_secretion:可分泌代谢物谱(阶段C-C1:production envelope 扫描,纯拓扑边界声明内置)
445
+ disposers.push(ctx.tools.register(gemTool({
446
+ name: 'gem_secretion',
447
+ description:
448
+ '可分泌代谢物谱(secretion):对模型在指定介质下做 production envelope 扫描——候选=介质层两级策略导出的' +
449
+ '交换反应(EX_ 型与 boundary 型模型都适用),固定生长分数 {0.25,0.5,0.75,0.9,0.99,1.0} 下最大化产物交换,' +
450
+ '任一分数下产物交换 >1e-6 判可分泌。**mode 默认 summary**:只返回 top20(按 max_prod)可分泌物 + 统计,' +
451
+ '不内联全量(防大输出被平台省略截断——不要在 summary 返回里找截断区数字);' +
452
+ '完整 {met_id, name, max_prod, growth_at_max, feasible, envelope[...]} 请传 export_csv 落盘 CSV(返回里 full_data_file 指向该文件),' +
453
+ '或显式 mode=full(注意可能很大)。export_csv 全量落盘。**边界声明:未考虑毒性/渗透压/调控,纯拓扑/线性规划结果**——可分泌≠实际会分泌。' +
454
+ '被测模型 wt<=EPS(介质下不生长,如 AB 预设对非根瘤菌模型)→ degenerate=true 不扫描不登记,提示介质适配。' +
455
+ '每个可分泌代谢物自动登记账本 type=secretion(幂等)。' +
456
+ '生长/通量数值为单点 FBA 口径(mmol/gDW/h);条件间通量对比用 gem_fluxscan(区间制)。' +
457
+ '触发词:可分泌谱、分泌能力、secretion、能产什么、代谢物分泌。',
458
+ parameters: {
459
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
460
+ medium: { type: 'object', additionalProperties: true, description: '分泌条件(缺省 {"medium_name": "AB"})' },
461
+ fractions: { type: 'array', items: { type: 'number' }, description: '生长分数网格(默认 [0.25,0.5,0.75,0.9,0.99,1.0])' },
462
+ mode: { type: 'string', enum: ['summary', 'full'], description: 'summary=默认,只返回 top20 可分泌物(防大输出省略);full=全量含 envelope(可能被平台省略截断);完整数据推荐 export_csv 落盘 CSV' },
463
+ export_csv: { type: 'string', description: '全量 envelope CSV 落盘路径(无论 mode 均写全量;返回 full_data_file 指向)' },
464
+ ledger_refs: { type: 'boolean', description: '可分泌代谢物登记账本(默认 true;幂等)' },
465
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 <模型名>.jsonl)' },
466
+ },
467
+ op: 'secretion',
468
+ timeoutMs: 900_000,
469
+ })))
470
+
471
+ // gem_double_knockout:双敲 v1 合成致死(阶段C-C2:GPR 穷尽先验 + FVA 预筛全扫,假设声明内置)
472
+ disposers.push(ctx.tools.register(gemTool({
473
+ name: 'gem_double_knockout',
474
+ description:
475
+ '双敲 v1(合成致死预测):候选池=①GPR 结构先验(纯 or 型且恰 2 基因的反应=穷尽型同工酶对,必做)+' +
476
+ '②FVA 预筛活性反应关联基因中共享反应的基因对(复用 essential_scan 预筛与敲除;全扫受 max_pairs 预算上限' +
477
+ '默认 5000,超限截断+报告)。判定:单敲双活(>1e-6)且双敲死(<=1e-6)→ 合成致死对。' +
478
+ '输出 {pair, single_a_growth, single_b_growth, double_growth, rationale(GPR先验/全扫), source}。' +
479
+ '**假设声明:细菌双敲验证率无大规模实验数据支撑,本结果=假设生成,供实验设计参考非结论**。' +
480
+ '被测模型 wt<=EPS → degenerate=true 不扫描不登记(提示介质适配)。每对自动登记账本 type=synthetic_lethal(幂等)。' +
481
+ '生长/通量数值为单点 FBA 口径(mmol/gDW/h);条件间通量对比用 gem_fluxscan(区间制)。' +
482
+ '触发词:双敲、合成致死、double knockout、基因对敲除、互补基因。',
483
+ parameters: {
484
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
485
+ medium: { type: 'object', additionalProperties: true, description: '判定条件(缺省 {"medium_name": "AB"})' },
486
+ max_pairs: { type: 'integer', description: '全扫预算上限(默认 5000 对;超限截断并报告)' },
487
+ export_csv: { type: 'string', description: '合成致死对 CSV 落盘路径' },
488
+ ledger_refs: { type: 'boolean', description: '合成致死对登记账本(默认 true;幂等)' },
489
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 <模型名>.jsonl)' },
490
+ },
491
+ op: 'double_knockout',
492
+ timeoutMs: 1_200_000,
493
+ })))
494
+
495
+ // gem_enrichment:必需基因通路富集(阶段C-C3:超几何+BH FDR;通路源=SBML groups[MetaCyc PWY])
496
+ disposers.push(ctx.tools.register(gemTool({
497
+ name: 'gem_enrichment',
498
+ description:
499
+ '必需基因通路富集(enrichment):对基因列表(缺省从账本读该模型 essentiality 预测)做通路超几何单侧富集检验' +
500
+ '+ Benjamini-Hochberg FDR 校正。通路注释源=模型的 SBML groups(gapseq 重建自带 MetaCyc PWY 分组);' +
501
+ '模型无 groups 注释时按契约返回 annotation_unavailable(不伪造通路,附可补途径说明);' +
502
+ '有效映射基因 <10 如实声明统计功效有限。输出 {pathway, genes_hit, background_hit, total_bg, p_value, fdr, ' +
503
+ 'fold_enrichment, ...} 全表 + export_csv。统计描述性结果,不登记账本,不做跨模型对比。' +
504
+ '触发词:通路富集、必需基因富集、enrichment、哪些通路富集。',
505
+ parameters: {
506
+ model: { type: 'string', required: true, description: 'SBML 文件绝对路径' },
507
+ gene_list: { type: 'array', items: { type: 'string' }, description: '可选:基因列表(缺省从账本 essentiality 预测读取)' },
508
+ pathway_source: { type: 'string', description: '通路注释源(缺省 groups=SBML groups)' },
509
+ export_csv: { type: 'string', description: '富集表 CSV 落盘路径' },
510
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 <模型名>.jsonl;gene_list 缺省来源)' },
511
+ },
512
+ op: 'enrichment',
513
+ timeoutMs: 300_000,
514
+ })))
515
+
516
+ // gem_targets:靶点清单规范导出(阶段C-C4:账本三类预测 -> 锁定 schema,供下游引物/编辑工具直接输入)
517
+ disposers.push(ctx.tools.register(gemTool({
518
+ name: 'gem_targets',
519
+ description:
520
+ '靶点清单规范导出(targets):把账本中的 essentiality/synthetic_lethal/secretion 预测汇总为下游' +
521
+ '引物/编辑工具可直接输入的规范 schema——每行锁定 11 字段:target_id(T0001 递增)/type/genes/met_ids/' +
522
+ 'condition/rationale/evidence_tier/status/growth_or_maxprod/source(ledger:P####)/exported_at。' +
523
+ 'essential 默认读账本不重扫;types 可筛选(essential=essentiality/全部);可选 condition 过滤;' +
524
+ 'export_format=csv(默认,utf-8-sig)/json;与账本计数闭合(exported==ledger per type)。' +
525
+ '引物/质粒设计本身不做(方案文件明确)。触发词:靶点清单、导出靶点、targets、基因编辑靶点、引物输入。',
526
+ parameters: {
527
+ model: { type: 'string', description: '可选:模型路径过滤(精确匹配优先,退化为文件名匹配)' },
528
+ types: { type: 'array', items: { type: 'string' }, description: '类型筛选(essential/synthetic_lethal/secretion;缺省全部三类)' },
529
+ condition: { type: 'string', description: '可选:条件过滤(精确匹配,如 AB)' },
530
+ export_format: { type: 'string', enum: ['csv', 'json'], description: '导出格式(默认 csv)' },
531
+ export_path: { type: 'string', description: '落盘路径(缺省 ~/.dsh/dsh-bio-gem/exports/targets_<ts>.<ext>)' },
532
+ ledger_path: { type: 'string', description: '可选:自定义账本 JSONL 路径(缺省=该模型的账本 <模型名>.jsonl)' },
533
+ },
534
+ op: 'targets',
535
+ timeoutMs: 120_000,
536
+ })))
537
+
538
+
539
+ return () => disposers.forEach((d) => d())
540
+ }
541
+
542
+ export const gemToolNames = ['gem_report', 'gem_validate', 'gem_gapfind', 'gem_gapfill', 'gem_build',
543
+ 'gem_gapseq', 'gem_phenotype', 'gem_essentiality', 'gem_annotate', 'gem_media_resolve', 'gem_l3_fix',
544
+ 'gem_biomass', 'gem_fluxscan', 'gem_sensitivity', 'gem_ledger', 'gem_benchmark', 'gem_secretion',
545
+ 'gem_double_knockout', 'gem_enrichment', 'gem_targets']