← 实验日志
API
Token

从你自己的代码运行实验日志

计量的通道可以通过 HTTP 调用,返回一个 JSON 对象。分工是:页面按 skill 的规则生成实验 ID 和样品批次,按示例日志的字段写好 YAML frontmatter,从笔记里提取数值和温度程序,把模糊、缺失、矛盾和缺单位的值列成待确认行,算好派生数,再把这些作为事实交给运行。运行只写日志正文和问题;页面再用自己的事实核对它(labreply.js)。frontmatter 始终是页面的。

基础 URL 和响应的形状

所有接口都在 https://api.skillsafe.ai/v1/app-api 下面,每个响应都是同一个形状:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

token 是为这个 app 生成的(访客调用带 {"slug":"lab-log"}),之后不需要 slug 头。用 Authorization: Bearer YOUR_TOKEN 发送。运行的请求体就是输入对象,裸着发。

状态代码含义
400validation_error请求体不是 JSON 对象,或者缺了必填的键(task、entry、notes、facts)。页面总是把四个都发过去。
401unauthorized没有 token,或者 token 过期了。生成访客 token 或重新登录。
402insufficient_credits余额低于 min_credits。先用 /estimate 估价。
403forbidden访客 token 想运行:这个通道是计量的,需要个人登录。
404not_found未知的任务 id。
429rate_limited等一会儿再试。
5xxserver_error临时故障。用同一个 Idempotency-Key 重试,重试不会重复收费。

先看 task 字段

一个通道,由 task 选择:

缺了或是未知的 task 按 log 回答。facts 是页面的结论、待确认行(Q1、Q2 …)、每个字段的值和来源、派生数和检查;以 "must:" 开头的一行是回复必须遵守的规则。

输入

字段必填内容
task是固定为 log。缺失或是别的值时,按 log 做,并写 "task": "log"。
entry是页面整理好的这条记录:标题、体系和体系代码、实验类型、字段模板、设备、日期、实验 ID、批次、是否异常、日志路径、原始材料归档和附件,以及页面写好的 frontmatter。
notes是用户的原始笔记(文字或语音转录),原样照录。
facts是页面的结论;待确认行(Q1、Q2 …,每行有字段名、来源和页面要问的问题);每个字段的值和来源;派生数和它的公式;检查结果。以 "must:" 开头的一行是你的回复必须遵守的规则。
retry_note否可选;出现时说明你上一次的回复没法用,照它说的改。

模型遵循的规则

来自源 skill @yuan1z0825/nature-experiment-log - 运行唯一遵循的标准:

其余规则

回复

log

把笔记整理成日志正文。

  1. summary - 一句话:这次做了什么,以及页面的结论(可以写日志,或者要先回答待确认问题)。
  2. sections - 一个对象,六个键照写:实验目的(一段话:这次要验证或测定什么,只写笔记里有的)、实验步骤(数组:按做的顺序,一条一步,不要自己加编号,页面会编号)、观察(数组:笔记里记下的现象,一条一个)、结果(数组:笔记里的测量结果和页面的派生数;待确认的写「待确认」)、异常(一段话:没有异常时只写「无。」;有异常时客观写出笔记里的异常现象)、下一步(数组:笔记里提到的后续安排,也可以按结果提建议,但不带新的数字)。
  3. questions - 数组:先按 facts 里 Q1、Q2 … 的顺序逐条问,每条写出该行的字段名(如 temperature)并以问号结尾;之后最多再问 3 个笔记没交代清楚的问题。每一条都是问题,不是答案。没有待确认行时,只问你自己的(最多 3 个,也可以没有)。
  4. tags - 最多 6 个标签,用笔记或表单里的原词(体系、实验类型、材料、方法)。
  5. caveats - 数组:页面确认不了、需要用户自己核对的地方,一句一条(可以为空)。
{
  "task": "log",
  "summary": "一句话",
  "sections": { "实验目的": "一段话", "实验步骤": ["第一步", "第二步"], "观察": ["一条现象"], "结果": ["一条结果;待确认的写待确认"], "异常": "无。", "下一步": ["一条后续安排"] },
  "questions": ["Q1 的问题,写出字段名?", "一个补充问题?"],
  "tags": ["体系名称", "实验类型"],
  "caveats": ["一句话"]
}

页面用自己的事实核对它(labreply.js 里的 check();有任何提醒就算 flagged):回答的是发过去的 task;没有链接、没有反引号、不提输入的字段名、不写人名或操作人;不在正文里改写 frontmatter;正文是中文;五个字段都在,summary 是一句话;六节都在,实验目的和实验步骤不为空,步骤的编号(如果有)从 1 起按顺序;有异常时「异常」写清现象,没有时写「无。」;正文里每个数字连同单位都能在笔记、已知字段、页面的字段或派生数里找到(300°C、300 °C、300℃ 视为同一个),带数字的编号也要找得到;待确认的字段在正文里不给值,正文里至少有一处「待确认」;questions 的前几条按顺序对应每一行待确认并写出字段名,之后最多 3 条,每条以问号结尾;tags 最多 6 个,每个都是笔记或表单里的原词。

完整示例

页面三个免费示例的运行(数据都是虚构的):腐蚀浸泡(可以写,腐蚀速率算出来了)、电化学 CV(有 3 行待确认)、热稳定性(可以写,带异常记录)。每个输入都是页面实际发送的;每个回复都是示例重放的保存回复,没有改动 - 但长字段只在这里的显示中被截短,每处截短都列在示例下面。最后一行是页面对完整回复的检查。

log - 腐蚀浸泡:316L 在 Q2 氯盐中 300 h

页面这样读它:可以写日志(模板 腐蚀浸泡),实验 ID CL-M-260603-001,批次 CL-Q2-B001;待确认:没有。

输入(facts 有 1414 个字符,显示前 1200 个):

{
 "task": "log",
 "entry": "标题:Q2腐蚀验证_316L\n体系:氯盐(体系代码 CL);实验类型:腐蚀验证;字段模板:腐蚀浸泡(references/example-log.md)\n设备:M 马弗炉(热处理、浸泡腐蚀);日期:2026-06-03\n实验 ID:CL-M-260603-001;salt_batch:CL-Q2-B001\n异常:否\n日志文件:我的实验记录/实验日志/氯盐/腐蚀验证/CL-M-260603-001.md\n原始材料归档:raw/experiments/2026.06.03_Q2腐蚀验证_316L_CL-M-260603-001/;附件:图片/IMG_6031.jpg - 腐蚀前样品表面;图片/IMG_6032.jpg - 腐蚀后样品表面;图片/IMG_6033.jpg - 盐冷却后坩埚内部\n\n页面写好的 frontmatter(页面的,不由你改写):\n---\nexp_id: CL-M-260603-001\ndate: 2026-06-03\nsalt_system: 氯盐\nsalt_batch: CL-Q2-B001\nsalt_composition: Q2 (*** mol%)\nexp_type: 腐蚀验证\nfurnace: 马弗炉_1号\ncrucible: AL-002\ntotal_salt_mass_g: 50.0\ntemperature_profile: \"RT→300°C(2h,干燥)→500°C(300h)→炉冷\"\natmosphere: Ar\nmaterial: 316L\nmaterial_category: 奥氏体不锈钢\nsample_id: 316L-2026-004\nsample_dimensions_mm: 20×10×2\nsample_surface: SiC 1200目\npre_mass_g: 3.4612\npost_mass_g: 3.4571\nmass_loss_g: 0.0041\ncorrosion_rate_um_year: 28.85\nanomaly: false\nanomaly_ref:\ntags: []\n---",
 "notes": "配制 Q2 盐 50.0 g,手套箱中混合均匀,装入坩埚 AL-002。\n316L 样品 316L-2026-004,尺寸 20×10×2 mm,SiC 1200目打磨,丙酮超声清洗,干燥后称重 3.4612 g。\n马弗炉_1号,Ar 气氛,温度程序 RT→300°C(2h,干燥)→500°C(300h)→炉冷。\n炉冷至室温后取出样品,去离子水超声清洗,干燥后称重 3.4571 g。\n观察:盐冷却后呈浅黄色透明玻璃态,无明显分层;样品表面失去金属光泽,呈均匀暗灰色;坩埚内壁无可见腐蚀痕迹;未观察到气体释放或飞溅。\n之后要做 SEM-EDS 截面分析和 XRD。",
 "facts": "页面结论:没有待确认的字段:可以写日志。待确认 0 行。\n待确认:没有。\n字段(页面确认的值;待确认的留空):\n- exp_id = CL-M-260603-001 [页面]\n- date = 2026-06-03 [页面]\n- salt_system = 氯盐 [页面]\n- salt_batch = CL-Q2-B001 [页面]\n- salt_composition = Q2 (*** mol%) [已知字段]\n- exp_type = 腐蚀验证 [页面]\n- furnace = 马弗炉_1号 [已知字段]\n- crucible = AL-002 [已知字段]\n- total_salt_mass_g = 50.0 [已知字段]\n- temperature_profile = RT→300°C(2h,干燥)→500°C(300h)→炉冷 [笔记]\n- atmosphere = Ar [笔记]\n- material = 316L [已知字段]\n- material_category = 奥氏体不锈钢 [已知字段]\n- sample_id = 316L-2026-004 [已知字段]\n- sample_dimensions_mm = 20×10×2 [笔记]\n- sample_surface = SiC 1200目 [已知字段]\n- pre_mass_g = 3.4612 [已知字段]\n- post_mass_g = 3.4571 [已知字段]\n- mass_loss_g = 0.0041 [派生]\n- corrosion_rate_um_year = 28.85 [派生]\n- anomaly = false [页面]\n- anomaly_ref = (空) [留空]\n- tags = (空) [由你的 tags 填]\n页面派生数(正文可以照写):\n- 总保温时长:2 h + 300 h = 302 h\n- 失重 mass_loss_g:pre_mass_g − post_mass_g = 3.4612 − 3.4571 = 0.0041 g\n- 失重率:0.0041 / 3.4612 × 100 = 0.12%\n- 样品表面积(矩形试样):2(ab+ac+bc) = 2(20×10 + 20×2 + 10×2) = 520 mm² = 5.2 cm²\n- 腐蚀速率 corrosion_rate_um_year:8.76×10^7 × Δm / (A × t × ρ) = 8.76×10^7 × 0.0041 / (5.2 × 300 × 7.98) = 28.85 µm/年(ASTM G1 公式,非 skill 内容)\n检查:id ok;batch ok;fields ok;ambiguity ok;profile ok;units ok;attachmen\n\n[……这里的显示截掉了 214 个字符……]"
}

回复:

{
 "task": "log",
 "summary": "验证候选配方 Q2 在 500°C、Ar 气氛下对 316L 的腐蚀行为,失重 0.0041 g(0.12%),页面判定可以写日志。",
 "sections": {
  "实验目的": "验证候选配方 Q2 在 500°C 下对 316L 奥氏体不锈钢的腐蚀行为。",
  "实验步骤": [
   "配制 Q2 盐 50.0 g,在手套箱中混合均匀,装入坩埚 AL-002",
   "316L 样品 316L-2026-004(20×10×2 mm)用 SiC 1200目打磨,丙酮超声清洗,干燥后称重 3.4612 g",
   "在马弗炉_1号中、Ar 气氛下运行温度程序 RT→300°C(2h,干燥)→500°C(300h)→炉冷",
   "炉冷至室温后取出样品,去离子水超声清洗",
   "干燥后称重 3.4571 g,计算失重和腐蚀速率"
  ],
  "观察": [
   "盐冷却后呈浅黄色透明玻璃态,无明显分层",
   "样品表面失去金属光泽,呈均匀暗灰色",
   "坩埚内壁无可见腐蚀痕迹",
   "未观察到气体释放或飞溅"
  ],
  "结果": [
   "失重 0.0041 g(3.4612 g − 3.4571 g),失重率 0.12%",
   "样品表面积 5.2 cm²(20×10×2 mm 的矩形试样)",
   "腐蚀速率 28.85 µm/年,按 ASTM G1 公式用确认的密度 7.98 g/cm³ 和暴露时长 300 h 算出",
   "腐蚀产物组成要等 SEM-EDS 和 XRD 确认"
  ],
  "异常": "无。",
  "下一步": [
   "做 SEM-EDS 截面分析,看腐蚀层结构",
   "做 XRD,确认腐蚀产物的相组成"
  ]
 },
 "questions": [
  "密度 7.98 g/cm³ 是这批 316L 的实测值还是手册值?"
 ],
 "tags": [
  "氯盐",
  "腐蚀验证",
  "316L",
  "Q2"
 ],
 "caveats": [
  "腐蚀速率用的是 ASTM G1 公式,不是源 skill 的内容,密度和暴露时长以你的确认为准",
  "笔记没有记录是否有点蚀,需要显微观察确认"
 ]
}

第 5 步为它打印的要点:验证候选配方 Q2 在 500°C、Ar 气氛下对 316L 的腐蚀行为,失重 0.0041 g(0.12%),页面判定可以写日志。 | 1. 密度 7.98 g/cm³ 是这批 316L 的实测值还是手册值?

页面的检查:kept - 没有要提醒的

log - 电化学 CV:语音转录,有待确认

页面这样读它:先回答待确认问题(模板 电化学),实验 ID OX-E-260616-001,批次 待确认;待确认:Q1 salt_batch、Q2 temperature、Q3 potential_window_V。

输入(facts 有 1451 个字符,显示前 1200 个):

{
 "task": "log",
 "entry": "标题:A2电化学窗口测试\n体系:氧化物体系(体系代码 OX);实验类型:电化学;字段模板:电化学(references/example-electrochemical.md)\n设备:E 电化学工作站(CV/SWV/EIS);日期:2026-06-16\n实验 ID:OX-E-260616-001;salt_batch:待确认\n异常:否\n日志文件:我的实验记录/实验日志/氧化物体系/电化学/OX-E-260616-001.md\n原始材料归档:raw/experiments/2026.06.16_A2电化学窗口测试_OX-E-260616-001/;附件:图片/IMG_6161.jpg - 三电极测试台布置;语音/录音_0616.m4a - 测试时的口述\n\n页面写好的 frontmatter(页面的,不由你改写):\n---\nexp_id: OX-E-260616-001\ndate: 2026-06-16\nsalt_system: 氧化物体系\nsalt_batch:\nsalt_composition: \"*** (*** wt%)\"\nexp_type: 电化学\ntest_method: CV\nworking_electrode: Pt丝\ncounter_electrode: Pt网\nreference_electrode: Ag/Ag+ 准参比\ntemperature:\natmosphere: Ar\npotential_window_V:\nscan_rate_mV_s: 50\nevaluation_target: 测定 A2 的电化学窗口,检查杂质峰\nanomaly: false\nanomaly_ref:\ntags: []\n# 待确认:\n#   - salt_batch: 笔记写「用的是哪一批记不清了」:配盐批次(salt_batch)是哪一批(OX-A2-B 几)?\n#   - temperature: 笔记写「温度大概 450 左右」:温度(temperature)的确切值是多少,单位是 °C 还是 K?\n#   - potential_window_V: 笔记写「电位窗口我记得是从 -1.5 V 扫到 0.5-0.8 V」,电位窗口(potential_window_V)需要一个确定的值:是 0.5 V 还是 0.8 V,或别的值?\n---",
 "notes": "【语音转录】今天做 A2 盐的 CV 窗口测试。\nA2 盐 30 g 是在手套箱里配的,用的是哪一批记不清了,可能是第二批?\n先在马弗炉里 300°C 干燥 2h,去除吸附水。\n转到电化学测试台,Ar 气氛,温度大概 450 左右。\n三电极:Pt 丝工作电极,Pt 网对电极,Ag/Ag+ 准参比。\n电位窗口我记得是从 -1.5 V 扫到 0.5-0.8 V,扫速 50 mV/s,记了 3 圈,取第 3 圈。\n盐熔化后是透明液体,没看到悬浮物;曲线在 -0.8 V 附近有个小峰;电极表面没看到腐蚀。",
 "facts": "页面结论:有待确认的字段:日志可以起草,但要先问清楚。待确认 3 行。\n待确认(按顺序;questions 的前 3 条逐条对应,每条写出该行的字段名(不对应字段的写它的标签和值)并以问号结尾):\n- Q1 | salt_batch | 配盐批次(salt_batch) | 来源:笔记:用的是哪一批记不清了 | 页面的问题:笔记写「用的是哪一批记不清了」:配盐批次(salt_batch)是哪一批(OX-A2-B 几)?\n- Q2 | temperature | 温度(temperature) | 来源:笔记:温度大概 450 左右 | 页面的问题:笔记写「温度大概 450 左右」:温度(temperature)的确切值是多少,单位是 °C 还是 K?\n- Q3 | potential_window_V | 电位窗口(potential_window_V) | 来源:笔记:电位窗口我记得是从 -1.5 V 扫到 0.5-0.8 V | 页面的问题:笔记写「电位窗口我记得是从 -1.5 V 扫到 0.5-0.8 V」,电位窗口(potential_window_V)需要一个确定的值:是 0.5 V 还是 0.8 V,或别的值?\n字段(页面确认的值;待确认的留空):\n- exp_id = OX-E-260616-001 [页面]\n- date = 2026-06-16 [页面]\n- salt_system = 氧化物体系 [页面]\n- salt_batch = (空) [待确认 Q1]\n- salt_composition = *** (*** wt%) [已知字段]\n- exp_type = 电化学 [页面]\n- test_method = CV [已知字段]\n- working_electrode = Pt丝 [已知字段]\n- counter_electrode = Pt网 [已知字段]\n- reference_electrode = Ag/Ag+ 准参比 [已知字段]\n- temperature = (空) [待确认 Q2]\n- atmosphere = Ar [笔记]\n- potential_window_V = (空) [待确认 Q3]\n- scan_rate_mV_s = 50 [已知字段]\n- evaluation_target = 测定 A2 的电化学窗口,检查杂质峰 [已知字段]\n- anomaly = false [页面]\n- anomaly_ref = (空) [留空]\n- tags = (空) [由你的 tags 填]\n页面派生数:没有。\n检查:id ok;batch warn;fields warn;ambiguity warn;units warn;attachments ok;anomaly ok;yaml ok。\nmust: 只写正\n\n[……这里的显示截掉了 251 个字符……]"
}

回复:

{
 "task": "log",
 "summary": "A2 盐在 Ar 气氛下做了 CV 窗口测试,但配盐批次、测试温度和电位窗口上限待确认,要先回答问题再定稿。",
 "sections": {
  "实验目的": "测定 A2 的电化学窗口,检查杂质峰。",
  "实验步骤": [
   "在手套箱中配制 A2 盐 30 g(批次待确认)",
   "在马弗炉中 300°C 干燥 2h,去除吸附水",
   "转到电化学测试台,在 Ar 气氛下升温(测试温度待确认)",
   "三电极体系:Pt 丝工作电极、Pt 网对电极、Ag/Ag+ 准参比",
   "CV 扫描从 -1.5 V 起,正向上限待确认,扫速 50 mV/s",
   "记录 3 圈,取第 3 圈"
  ],
  "观察": [
   "盐熔化后是透明液体,没看到悬浮物",
   "曲线在 -0.8 V 附近有一个小峰",
   "电极表面没看到腐蚀"
  ],
  "结果": [
   "电化学窗口的下限为 -1.5 V,上限待确认,窗口宽度要等上限确认后才能给出",
   "-0.8 V 附近小峰的来源待确认"
  ],
  "异常": "无。",
  "下一步": [
   "确认配盐批次、测试温度和电位窗口上限后补全日志",
   "查明 -0.8 V 附近小峰的来源"
  ]
 },
 "questions": [
  "配盐批次(salt_batch)用的是 A2 的哪一批,OX-A2-B 几?",
  "测试温度(temperature)的确切值是多少,单位是 °C 还是 K?",
  "电位窗口(potential_window_V)的正向上限是 0.5 V 还是 0.8 V,或者别的值?",
  "曲线上除了 -0.8 V 附近,还有别的峰吗?"
 ],
 "tags": [
  "氧化物体系",
  "电化学",
  "CV",
  "A2"
 ],
 "caveats": [
  "配盐批次、测试温度和电位窗口上限确认之前,这份日志只是草稿",
  "-0.8 V 附近的小峰是否来自杂质,笔记里没有判断依据"
 ]
}

第 5 步为它打印的要点:A2 盐在 Ar 气氛下做了 CV 窗口测试,但配盐批次、测试温度和电位窗口上限待确认,要先回答问题再定稿。 | 1. 配盐批次(salt_batch)用的是 A2 的哪一批,OX-A2-B 几? | 2. 测试温度(temperature)的确切值是多少,单位是 °C 还是 K? | 3. 电位窗口(potential_window_V)的正向上限是 0.5 V 还是 0.8 V,或者别的值? | 4. 曲线上除了 -0.8 V 附近,还有别的峰吗?

页面的检查:kept - 没有要提醒的

log - 热稳定性:坩埚开裂的异常

页面这样读它:可以写日志(模板 热稳定性),实验 ID HY-T-260602-003,批次 HY-B1-B001;待确认:没有;有异常,追加一条异常记录。

输入:

{
 "task": "log",
 "entry": "标题:B1热稳定性复测\n体系:混合盐体系(体系代码 HY);实验类型:热稳定性;字段模板:热稳定性(references/example-thermal-stability.md)\n设备:T 管式炉(气氛控制、脱水、热稳定性);日期:2026-06-02\n实验 ID:HY-T-260602-003;salt_batch:HY-B1-B001(复用已有批次)\n异常:是(设备故障 / 中 / 需复查)\n日志文件:我的实验记录/实验日志/混合盐体系/热稳定性/HY-T-260602-003.md\n原始材料归档:raw/experiments/2026.06.02_B1热稳定性复测_HY-T-260602-003/;附件:图片/IMG_6021.jpg - 坩埚侧壁裂纹;图片/IMG_6022.jpg - 垫片上渗出的盐\n\n页面写好的 frontmatter(页面的,不由你改写):\n---\nexp_id: HY-T-260602-003\ndate: 2026-06-02\nsalt_system: 混合盐体系\nsalt_batch: HY-B1-B001\nsalt_composition: \"*** (*** wt%)\"\nexp_type: 热稳定性\nfurnace: 管式炉_1号\ncrucible: AL-006\ntotal_salt_mass_g: 40.0\ntemperature_profile: \"RT→200°C(4h,脱水)→500°C(8h)→700°C(2h)→炉冷\"\natmosphere: Ar (100 mL/min)\ncondensate: 收集到 2.1 g 无色液体\nabsorption_solution: 0.1M NaOH 50mL\nanomaly: true\nanomaly_ref: 实验日志/异常记录.md\ntags: []\n---",
 "notes": "B1 盐 40.0 g,沿用 HY-B1-B001 这一批,手套箱中转入坩埚 AL-006。\n管式炉_1号,Ar (100 mL/min),温度程序 RT→200°C(4h,脱水)→500°C(8h)→700°C(2h)→炉冷。\n出气口接冷凝管和 0.1M NaOH 50mL 吸收瓶。\n500°C 段冷凝管收集到 2.1 g 无色液体。\n700°C 段结束、炉冷后发现坩埚左右两侧各有一道裂纹,部分盐渗到坩埚外的垫片上。\n炉冷后称重,坩埚内剩余盐 35.2 g。",
 "facts": "页面结论:没有待确认的字段:可以写日志。待确认 0 行。\n待确认:没有。\n字段(页面确认的值;待确认的留空):\n- exp_id = HY-T-260602-003 [页面]\n- date = 2026-06-02 [页面]\n- salt_system = 混合盐体系 [页面]\n- salt_batch = HY-B1-B001 [页面]\n- salt_composition = *** (*** wt%) [已知字段]\n- exp_type = 热稳定性 [页面]\n- furnace = 管式炉_1号 [已知字段]\n- crucible = AL-006 [已知字段]\n- total_salt_mass_g = 40.0 [已知字段]\n- temperature_profile = RT→200°C(4h,脱水)→500°C(8h)→700°C(2h)→炉冷 [笔记]\n- atmosphere = Ar (100 mL/min) [已知字段]\n- condensate = 收集到 2.1 g 无色液体 [已知字段]\n- absorption_solution = 0.1M NaOH 50mL [已知字段]\n- anomaly = true [页面]\n- anomaly_ref = 实验日志/异常记录.md [页面]\n- tags = (空) [由你的 tags 填]\n页面派生数(正文可以照写):\n- 总保温时长:4 h + 8 h + 2 h = 14 h\n检查:id ok;batch ok;fields ok;ambiguity ok;profile ok;units ok;attachments ok;anomaly ok;yaml ok。\nmust: 只写正文(summary、sections、questions、tags、caveats),frontmatter 是页面的;正文里的每个数字连同单位都要在笔记、已知字段、上面的字段或派生数里出现过;questions 之后最多再问 3 个;tags 最多 6 个,用笔记里的原词;异常:写清笔记里的异常现象;不写人名、不写链接、不用反引号;只用中文。"
}

回复:

{
 "task": "log",
 "summary": "B1 盐沿用 HY-B1-B001 这一批在管式炉_1号中做热稳定性复测,炉冷后发现坩埚开裂,页面判定可以写日志并追加异常记录。",
 "sections": {
  "实验目的": "复测候选配方 B1 在 Ar 保护气氛下的热稳定性。",
  "实验步骤": [
   "沿用 HY-B1-B001 这一批 B1 盐 40.0 g,在手套箱中转入坩埚 AL-006",
   "在管式炉_1号中通 Ar (100 mL/min),运行温度程序 RT→200°C(4h,脱水)→500°C(8h)→700°C(2h)→炉冷",
   "出气口接冷凝管和 0.1M NaOH 50mL 吸收瓶",
   "炉冷后称重坩埚内剩余的盐"
  ],
  "观察": [
   "500°C 段冷凝管收集到 2.1 g 无色液体",
   "炉冷后发现坩埚左右两侧各有一道裂纹,部分盐渗到坩埚外的垫片上"
  ],
  "结果": [
   "炉冷后坩埚内剩余盐 35.2 g",
   "冷凝管收集到 2.1 g 无色液体",
   "坩埚开裂、盐渗出,剩余盐的质量不能直接用来判断热稳定性"
  ],
  "异常": "700°C 段结束、炉冷后发现坩埚 AL-006 左右两侧各有一道裂纹,部分盐渗到坩埚外的垫片上。",
  "下一步": [
   "检查坩埚 AL-006 的裂纹,并在设备与试剂追踪里更新它的状态",
   "换一个坩埚复测 B1 的热稳定性",
   "收集并称量垫片上渗出的盐"
  ]
 },
 "questions": [
  "冷凝管里的无色液体有没有做成分分析?",
  "坩埚 AL-006 开裂前用过几次?"
 ],
 "tags": [
  "热稳定性",
  "管式炉",
  "混合盐体系",
  "B1"
 ],
 "caveats": [
  "笔记只记到炉冷后发现裂纹,坩埚是在哪一段开裂的无法确定",
  "异常记录里的影响和处理两节要你自己补写"
 ]
}

第 5 步为它打印的要点:B1 盐沿用 HY-B1-B001 这一批在管式炉_1号中做热稳定性复测,炉冷后发现坩埚开裂,页面判定可以写日志并追加异常记录。 | 1. 冷凝管里的无色液体有没有做成分分析? | 2. 坩埚 AL-006 开裂前用过几次?

页面的检查:kept - 没有要提醒的

自己构造输入

在 node 里载入引擎(global.window = global; require("./labkit.js"); require("./labreply.js")),然后:

const K = LabLogKit, R = LabLogReply;
const f = K.form(Object.assign(K.blank(), { title: "B1热稳定性", system_name: "混合盐体系", system_code: "HY", exp_type: "热稳定性",
  template: "thermal", device: "T", date: "2026-06-01", notes: "温度大概 500 左右,RT→200°C(4h,脱水)→500°C(8h)→炉冷" }));
// f.values (每个字段的值和来源), f.pending (待确认行和问题), f.yaml, f.derived, f.paths, f.checks, f.verdict
const b = K.buildInput(f, "log");
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "log");
// R.check(reply, b)                                         // {status: kept or flagged, notes}

下面几步用的 token 从 /tokens.html 拿。

第 1 步 — 一个最小的客户端

一个函数:发送 JSON,拆开响应。从 /tokens.html 贴一个个人 token,或者留空,在第 2 步生成访客 token。

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN"   # from https://lab-log.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

第 2 步 — 一个 token

访客 token 够调用 /me 和 /estimate。运行是计量的,需要个人 token:在 /tokens.html 登录,从那里复制。

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"lab-log"}' | jq -r '.data.token')

第 3 步 — GET /me

token 属于谁(subject_type:user 或 guest),以及额度余额。

api GET /me | jq '.data | {subject_type, credits}' 

第 4 步 — POST /estimate

免费。请求体就是运行输入本身,一个裸对象(从不包在 {"input": ...} 里)。hold_credits 是预留额度,不是价格;min_credits 是余额至少要有的;model_alias 是 gpt-terra。

# input.json: the run input object - {"task": "log", "entry": "标题:... | 实验 ID:... | 页面写好的 frontmatter ...", "notes": "原始笔记 ...", "facts": "页面结论:... | 待确认 Q1 ... | must: ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

第 5 步 — POST /run,然后轮询任务

用通道和输入做一个 Idempotency-Key:同一个键的重试从不重复收费。页面用的是 lab-log:<task>:<inputHash>:a<attempt> - 通道是键的一部分,retry_note 不算进哈希 - 它唯一的一次自动格式重试换一个新的尝试号,在页面上明说是多一次运行。log 的回复带 summary、sections、questions、tags 和 caveats。下面的代码打印它的要点:一句话的 summary,再按顺序打印每个问题(页面的待确认行在前)。

KEY="lab-log:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.summary, (.questions | to_entries[] | "\(.key + 1). \(.value)")' reply.json

第 6 步 — POST /run-stream

同一次运行,用 Server-Sent Events,同一个 input.json。浏览器收到的是进度信号而不是文字片段;最后的 done 事件带着回复。

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: lab-log:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json