[{"data":1,"prerenderedAt":3271},["ShallowReactive",2],{"\u002F2026-08-08":3,"\u002F2026-08-08-rel":1124},{"id":4,"title":5,"body":6,"column":1108,"date":1109,"description":12,"extension":1110,"hero_image":1111,"meta":1112,"navigation":266,"path":1113,"seo":1114,"series_id":1111,"severity":1111,"stem":1115,"summary":1116,"tags":1117,"__hash__":1123},"posts\u002F2026-08-08-同一张能力表抄了三份.md","同一张能力表，抄了三份",{"type":7,"value":8,"toc":1100},"minimark",[9,13,16,19,24,27,201,204,211,217,220,295,298,302,305,308,333,336,339,345,360,363,367,370,373,381,384,514,517,527,530,537,755,758,762,773,776,779,843,846,908,911,944,948,951,954,960,967,970,977,984,988,991,1021,1028,1031,1087,1093,1096],[10,11,12],"p",{},"用户反馈：设置里选完模型，分辨率档不对——Seedance-2.5 只有 480P 和 720P；另外 2.5 的上游支持到 29 秒，故事板那边应该跟着放开。",[10,14,15],{},"第一句说得对，2.5 确实只有 480p 和 720p。第二句也对，它确实支持 4~29 秒。",[10,17,18],{},"两条都对，说明我们发出去的档位和上游支持的对不上。查下去发现，同一份能力表被抄了三份。",[20,21,23],"h2",{"id":22},"一三处各自漂移","一、三处各自漂移",[10,25,26],{},"后端有一张权威表，写清了每个模型支持哪些分辨率、哪些时长：",[28,29,34],"pre",{"className":30,"code":31,"language":32,"meta":33,"style":33},"language-ts shiki shiki-themes github-light github-dark","const MODEL_RESOLUTIONS: Record\u003CVideoModel, readonly VideoResolution[]> = {\n  \"seedance-2\": [\"480p\", \"720p\", \"1080p\", \"4k\"],\n  \"seedance-2-fast\": [\"480p\", \"720p\"],\n  \"seedance-2-mini\": [\"480p\", \"720p\"],\n  \"seedance-2.5\": [\"480p\", \"720p\"],\n  \"kling-v3\": [\"720p\", \"1080p\"],\n  \"minimax-h3\": [\"2k\", \"768p\"],\n};\n","ts","",[35,36,37,82,113,129,145,161,177,195],"code",{"__ignoreMap":33},[38,39,42,46,50,53,57,61,64,67,70,73,76,79],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"szBVR","const",[38,47,49],{"class":48},"sj4cs"," MODEL_RESOLUTIONS",[38,51,52],{"class":44},":",[38,54,56],{"class":55},"sScJk"," Record",[38,58,60],{"class":59},"sVt8B","\u003C",[38,62,63],{"class":55},"VideoModel",[38,65,66],{"class":59},", ",[38,68,69],{"class":44},"readonly",[38,71,72],{"class":55}," VideoResolution",[38,74,75],{"class":59},"[]> ",[38,77,78],{"class":44},"=",[38,80,81],{"class":59}," {\n",[38,83,85,89,92,95,97,100,102,105,107,110],{"class":40,"line":84},2,[38,86,88],{"class":87},"sZZnC","  \"seedance-2\"",[38,90,91],{"class":59},": [",[38,93,94],{"class":87},"\"480p\"",[38,96,66],{"class":59},[38,98,99],{"class":87},"\"720p\"",[38,101,66],{"class":59},[38,103,104],{"class":87},"\"1080p\"",[38,106,66],{"class":59},[38,108,109],{"class":87},"\"4k\"",[38,111,112],{"class":59},"],\n",[38,114,116,119,121,123,125,127],{"class":40,"line":115},3,[38,117,118],{"class":87},"  \"seedance-2-fast\"",[38,120,91],{"class":59},[38,122,94],{"class":87},[38,124,66],{"class":59},[38,126,99],{"class":87},[38,128,112],{"class":59},[38,130,132,135,137,139,141,143],{"class":40,"line":131},4,[38,133,134],{"class":87},"  \"seedance-2-mini\"",[38,136,91],{"class":59},[38,138,94],{"class":87},[38,140,66],{"class":59},[38,142,99],{"class":87},[38,144,112],{"class":59},[38,146,148,151,153,155,157,159],{"class":40,"line":147},5,[38,149,150],{"class":87},"  \"seedance-2.5\"",[38,152,91],{"class":59},[38,154,94],{"class":87},[38,156,66],{"class":59},[38,158,99],{"class":87},[38,160,112],{"class":59},[38,162,164,167,169,171,173,175],{"class":40,"line":163},6,[38,165,166],{"class":87},"  \"kling-v3\"",[38,168,91],{"class":59},[38,170,99],{"class":87},[38,172,66],{"class":59},[38,174,104],{"class":87},[38,176,112],{"class":59},[38,178,180,183,185,188,190,193],{"class":40,"line":179},7,[38,181,182],{"class":87},"  \"minimax-h3\"",[38,184,91],{"class":59},[38,186,187],{"class":87},"\"2k\"",[38,189,66],{"class":59},[38,191,192],{"class":87},"\"768p\"",[38,194,112],{"class":59},[38,196,198],{"class":40,"line":197},8,[38,199,200],{"class":59},"};\n",[10,202,203],{},"前端有一份手抄的副本。它早就漂了，而且漂在两个方向。",[10,205,206,210],{},[207,208,209],"strong",{},"多出的档位。"," 设置页的取档逻辑是写死的规则：「H3 两档、其余一律四档」。于是 2.5、fast、mini 这三个只有 480p 和 720p 的模型，界面上给出 1080P 和 4K。用户选了，选中即报未配价，或者直接被上游拒。",[10,212,213,216],{},[207,214,215],{},"少掉的档位。"," Kling 只支持 720p 和 1080p，界面给的是四档。反过来的情况也存在：如果某个模型的档位比默认四档更多，界面也显示不出来。",[10,218,219],{},"时长那一处漂得更彻底。前端和后端各写了一份 4~15 的夹取：",[28,221,223],{"className":30,"code":222,"language":32,"meta":33,"style":33},"\u002F\u002F 前端\nreturn Math.max(4, Math.min(15, value));\n\n\u002F\u002F 后端\nreturn Math.max(4, Math.min(15, value));\n",[35,224,225,231,262,268,273],{"__ignoreMap":33},[38,226,227],{"class":40,"line":41},[38,228,230],{"class":229},"sJ8bj","\u002F\u002F 前端\n",[38,232,233,236,239,242,245,248,251,254,256,259],{"class":40,"line":84},[38,234,235],{"class":44},"return",[38,237,238],{"class":59}," Math.",[38,240,241],{"class":55},"max",[38,243,244],{"class":59},"(",[38,246,247],{"class":48},"4",[38,249,250],{"class":59},", Math.",[38,252,253],{"class":55},"min",[38,255,244],{"class":59},[38,257,258],{"class":48},"15",[38,260,261],{"class":59},", value));\n",[38,263,264],{"class":40,"line":115},[38,265,267],{"emptyLinePlaceholder":266},true,"\n",[38,269,270],{"class":40,"line":131},[38,271,272],{"class":229},"\u002F\u002F 后端\n",[38,274,275,277,279,281,283,285,287,289,291,293],{"class":40,"line":147},[38,276,235],{"class":44},[38,278,238],{"class":59},[38,280,241],{"class":55},[38,282,244],{"class":59},[38,284,247],{"class":48},[38,286,250],{"class":59},[38,288,253],{"class":55},[38,290,244],{"class":59},[38,292,258],{"class":48},[38,294,261],{"class":59},[10,296,297],{},"2.5 传 29 秒，被悄悄砍成 15。用户看不出为什么变短——没有任何提示，界面上显示的就是 15 秒。",[20,299,301],{"id":300},"二被砍掉的秒数后面跟着钱","二、被砍掉的秒数后面跟着钱",[10,303,304],{},"时长那一处还不只是显示问题。",[10,306,307],{},"故事板按固定秒数切板。代码里是一个常量：",[28,309,311],{"className":30,"code":310,"language":32,"meta":33,"style":33},"export const SHOT_SECONDS = 15;\n",[35,312,313],{"__ignoreMap":33},[38,314,315,318,321,324,327,330],{"class":40,"line":41},[38,316,317],{"class":44},"export",[38,319,320],{"class":44}," const",[38,322,323],{"class":48}," SHOT_SECONDS",[38,325,326],{"class":44}," =",[38,328,329],{"class":48}," 15",[38,331,332],{"class":59},";\n",[10,334,335],{},"按 15 秒一切。2.5 一板能放 29 秒，硬按 15 秒切，一集会被切成两倍数量的板。",[10,337,338],{},"板数翻倍就是出图与出片的费用翻倍。这不是理论推算——用户选 2.5 的动机就是长板数少切，结果切得和短时长模型一样多，还多花一倍钱。",[10,340,341,344],{},[35,342,343],{},"comic-subshot.ts"," 里留着上限常量的注释，写明了它只是「模型未知时的保守默认」：",[28,346,348],{"className":30,"code":347,"language":32,"meta":33,"style":33},"\u002F\u002F 上限只是「模型未知时的保守默认」——真正的上限逐模型不同（seedance-2.5 能到 29 秒），\n\u002F\u002F 调用方应把该模型的最大秒数作为 maxBoardSec 传进来。写死 15 的话，选了 2.5 也只切 15 秒一板。\n",[35,349,350,355],{"__ignoreMap":33},[38,351,352],{"class":40,"line":41},[38,353,354],{"class":229},"\u002F\u002F 上限只是「模型未知时的保守默认」——真正的上限逐模型不同（seedance-2.5 能到 29 秒），\n",[38,356,357],{"class":40,"line":84},[38,358,359],{"class":229},"\u002F\u002F 调用方应把该模型的最大秒数作为 maxBoardSec 传进来。写死 15 的话，选了 2.5 也只切 15 秒一板。\n",[10,361,362],{},"调用方没传。",[20,364,366],{"id":365},"三提示词长度四个数一个是实测出来的","三、提示词长度：四个数，一个是实测出来的",[10,368,369],{},"同一批里还有一个更贵的限制：提示词长度上限。",[10,371,372],{},"上游对提示词有硬上限，超过直接拒。各模型不一样，而这些值在很长一段时间里没有集中维护。表现是一条线上的完整失败：",[28,374,379],{"className":375,"code":377,"language":378},[376],"language-text","模型 seedance-2.5 的提示词不能超过 5000 个字符，当前为 18545 个字符\n","text",[35,380,377],{"__ignoreMap":33},[10,382,383],{},"接入本身没问题，是缺了长度约束。补完之后这张表长这样：",[28,385,387],{"className":30,"code":386,"language":32,"meta":33,"style":33},"const MODEL_PROMPT_LIMITS: Partial\u003CRecord\u003CVideoModel, number>> = {\n  \"minimax-h3\": 7000,\n  \"seedance-2\": 2500,\n  \"seedance-2-fast\": 2500,\n  \"seedance-2-mini\": 2500,\n  \"seedance-2.5\": 5000,\n  \u002F\u002F 20260809 实测：发 2553 字被硬拒 `prompt: size must be between 0 and 2500`\n  \"kling-v3\": 2500,\n};\nexport const PROMPT_LIMIT_FALLBACK = 50000;\n",[35,388,389,422,435,446,456,466,477,482,492,497],{"__ignoreMap":33},[38,390,391,393,396,398,401,403,406,408,410,412,415,418,420],{"class":40,"line":41},[38,392,45],{"class":44},[38,394,395],{"class":48}," MODEL_PROMPT_LIMITS",[38,397,52],{"class":44},[38,399,400],{"class":55}," Partial",[38,402,60],{"class":59},[38,404,405],{"class":55},"Record",[38,407,60],{"class":59},[38,409,63],{"class":55},[38,411,66],{"class":59},[38,413,414],{"class":48},"number",[38,416,417],{"class":59},">> ",[38,419,78],{"class":44},[38,421,81],{"class":59},[38,423,424,426,429,432],{"class":40,"line":84},[38,425,182],{"class":87},[38,427,428],{"class":59},": ",[38,430,431],{"class":48},"7000",[38,433,434],{"class":59},",\n",[38,436,437,439,441,444],{"class":40,"line":115},[38,438,88],{"class":87},[38,440,428],{"class":59},[38,442,443],{"class":48},"2500",[38,445,434],{"class":59},[38,447,448,450,452,454],{"class":40,"line":131},[38,449,118],{"class":87},[38,451,428],{"class":59},[38,453,443],{"class":48},[38,455,434],{"class":59},[38,457,458,460,462,464],{"class":40,"line":147},[38,459,134],{"class":87},[38,461,428],{"class":59},[38,463,443],{"class":48},[38,465,434],{"class":59},[38,467,468,470,472,475],{"class":40,"line":163},[38,469,150],{"class":87},[38,471,428],{"class":59},[38,473,474],{"class":48},"5000",[38,476,434],{"class":59},[38,478,479],{"class":40,"line":179},[38,480,481],{"class":229},"  \u002F\u002F 20260809 实测：发 2553 字被硬拒 `prompt: size must be between 0 and 2500`\n",[38,483,484,486,488,490],{"class":40,"line":197},[38,485,166],{"class":87},[38,487,428],{"class":59},[38,489,443],{"class":48},[38,491,434],{"class":59},[38,493,495],{"class":40,"line":494},9,[38,496,200],{"class":59},[38,498,500,502,504,507,509,512],{"class":40,"line":499},10,[38,501,317],{"class":44},[38,503,320],{"class":44},[38,505,506],{"class":48}," PROMPT_LIMIT_FALLBACK",[38,508,326],{"class":44},[38,510,511],{"class":48}," 50000",[38,513,332],{"class":59},[10,515,516],{},"这几个数的来源不同，注释里写清了哪个是实测的：",[518,519,520],"blockquote",{},[10,521,522,523,526],{},"seedance-2.5 的 5000 是",[207,524,525],{},"实测出来的，文档只字未提","：线上一条 18545 字的脚本被拒，报文写「提示词不能超过 5000 个字符」。同批实测另两条渠道收 16500 字照样 200，所以这是 2.5 独有的限制，不能推广到整个系列。加新渠道前先拿超长 prompt 打一次，别等线上炸。",[10,528,529],{},"这段话是这张表里唯一带出处的一条。其余几个数是按上游文档填的，文档没提的只能等线上撞。",[10,531,532,533,536],{},"长度约束补齐之后，还有一个配套的预算计算。原先只有 H3 有预算，其余模型返回 ",[35,534,535],{},"null","，等于完全不限：",[28,538,540],{"className":30,"code":539,"language":32,"meta":33,"style":33},"\u002F** 每秒成片对应的脚本篇幅。15 秒 → 7500 字，是人肉审稿与出片效果都合适的密度。 *\u002F\nexport const CHARS_PER_SECOND = 500;\n\u002F** 留给用户自己追加修改的余量：脚本刚好顶满上限时，用户加一句就被上游拒了。 *\u002F\nconst PROMPT_BUDGET_MARGIN = 500;\n\nexport function scriptCharBudget(targetModel: string | undefined, durationSec?: number): number | null {\n  const hardLimit = modelPromptHardLimit(targetModel);\n  if (!durationSec || !Number.isFinite(durationSec) || durationSec \u003C= 0) return hardLimit;\n  const byDuration = Math.round(durationSec * CHARS_PER_SECOND);\n  return hardLimit ? Math.min(byDuration, hardLimit) : byDuration;\n}\n",[35,541,542,547,563,568,581,585,637,653,701,726,749],{"__ignoreMap":33},[38,543,544],{"class":40,"line":41},[38,545,546],{"class":229},"\u002F** 每秒成片对应的脚本篇幅。15 秒 → 7500 字，是人肉审稿与出片效果都合适的密度。 *\u002F\n",[38,548,549,551,553,556,558,561],{"class":40,"line":84},[38,550,317],{"class":44},[38,552,320],{"class":44},[38,554,555],{"class":48}," CHARS_PER_SECOND",[38,557,326],{"class":44},[38,559,560],{"class":48}," 500",[38,562,332],{"class":59},[38,564,565],{"class":40,"line":115},[38,566,567],{"class":229},"\u002F** 留给用户自己追加修改的余量：脚本刚好顶满上限时，用户加一句就被上游拒了。 *\u002F\n",[38,569,570,572,575,577,579],{"class":40,"line":131},[38,571,45],{"class":44},[38,573,574],{"class":48}," PROMPT_BUDGET_MARGIN",[38,576,326],{"class":44},[38,578,560],{"class":48},[38,580,332],{"class":59},[38,582,583],{"class":40,"line":147},[38,584,267],{"emptyLinePlaceholder":266},[38,586,587,589,592,595,597,601,603,606,609,612,614,617,620,623,626,628,630,632,635],{"class":40,"line":163},[38,588,317],{"class":44},[38,590,591],{"class":44}," function",[38,593,594],{"class":55}," scriptCharBudget",[38,596,244],{"class":59},[38,598,600],{"class":599},"s4XuR","targetModel",[38,602,52],{"class":44},[38,604,605],{"class":48}," string",[38,607,608],{"class":44}," |",[38,610,611],{"class":48}," undefined",[38,613,66],{"class":59},[38,615,616],{"class":599},"durationSec",[38,618,619],{"class":44},"?:",[38,621,622],{"class":48}," number",[38,624,625],{"class":59},")",[38,627,52],{"class":44},[38,629,622],{"class":48},[38,631,608],{"class":44},[38,633,634],{"class":48}," null",[38,636,81],{"class":59},[38,638,639,642,645,647,650],{"class":40,"line":179},[38,640,641],{"class":44},"  const",[38,643,644],{"class":48}," hardLimit",[38,646,326],{"class":44},[38,648,649],{"class":55}," modelPromptHardLimit",[38,651,652],{"class":59},"(targetModel);\n",[38,654,655,658,661,664,667,670,673,676,679,682,684,687,690,693,696,698],{"class":40,"line":197},[38,656,657],{"class":44},"  if",[38,659,660],{"class":59}," (",[38,662,663],{"class":44},"!",[38,665,666],{"class":59},"durationSec ",[38,668,669],{"class":44},"||",[38,671,672],{"class":44}," !",[38,674,675],{"class":59},"Number.",[38,677,678],{"class":55},"isFinite",[38,680,681],{"class":59},"(durationSec) ",[38,683,669],{"class":44},[38,685,686],{"class":59}," durationSec ",[38,688,689],{"class":44},"\u003C=",[38,691,692],{"class":48}," 0",[38,694,695],{"class":59},") ",[38,697,235],{"class":44},[38,699,700],{"class":59}," hardLimit;\n",[38,702,703,705,708,710,712,715,718,721,723],{"class":40,"line":494},[38,704,641],{"class":44},[38,706,707],{"class":48}," byDuration",[38,709,326],{"class":44},[38,711,238],{"class":59},[38,713,714],{"class":55},"round",[38,716,717],{"class":59},"(durationSec ",[38,719,720],{"class":44},"*",[38,722,555],{"class":48},[38,724,725],{"class":59},");\n",[38,727,728,731,734,737,739,741,744,746],{"class":40,"line":499},[38,729,730],{"class":44},"  return",[38,732,733],{"class":59}," hardLimit ",[38,735,736],{"class":44},"?",[38,738,238],{"class":59},[38,740,253],{"class":55},[38,742,743],{"class":59},"(byDuration, hardLimit) ",[38,745,52],{"class":44},[38,747,748],{"class":59}," byDuration;\n",[38,750,752],{"class":40,"line":751},11,[38,753,754],{"class":59},"}\n",[10,756,757],{},"按 15 秒算出来是 7500 字——上限撤销之后，脚本直接写到 1.8 万字，那也是生成耗时 193 秒的原因。",[20,759,761],{"id":760},"四抄一份是必要的那就测试它","四、抄一份是必要的，那就测试它",[10,763,764,765,768,769,772],{},"前端为什么不能直接读后端那张表：",[35,766,767],{},"apps\u002Fweb"," 不能 import ",[35,770,771],{},"apps\u002Fapi","。而节点面板必须知道「这个模型支持哪些分辨率、哪些时长」，才能只给出跑得通的选项。",[10,774,775],{},"抄一份是必要的。那就把「不许走样」变成断言。",[10,777,778],{},"新增的共享包文件头写明了它的性质和守卫：",[28,780,782],{"className":30,"code":781,"language":32,"meta":33,"style":33},"\u002F**\n * 生图 \u002F 视频的**上游真实能力**表。\n *\n * 这是 apps\u002Fapi 里两张表的镜像……\n *\n * **为什么要抄一份**：前端（apps\u002Fweb）不能 import apps\u002Fapi，而节点面板必须知道\n * 「这个模型支持哪些分辨率、哪些时长」才能只给出跑得通的选项。\n * 抄一份就有走样的风险，所以 apps\u002Fapi 里有一条对比测试逐项核对两边——\n * 谁改了上游表而没同步这里，测试立刻红。\n *\n * 硬规矩：这里只允许出现上游真支持的取值。多给一个选项，用户就会选到一个必然失败的组合。\n *\u002F\n",[35,783,784,789,794,799,804,808,813,818,823,828,832,837],{"__ignoreMap":33},[38,785,786],{"class":40,"line":41},[38,787,788],{"class":229},"\u002F**\n",[38,790,791],{"class":40,"line":84},[38,792,793],{"class":229}," * 生图 \u002F 视频的**上游真实能力**表。\n",[38,795,796],{"class":40,"line":115},[38,797,798],{"class":229}," *\n",[38,800,801],{"class":40,"line":131},[38,802,803],{"class":229}," * 这是 apps\u002Fapi 里两张表的镜像……\n",[38,805,806],{"class":40,"line":147},[38,807,798],{"class":229},[38,809,810],{"class":40,"line":163},[38,811,812],{"class":229}," * **为什么要抄一份**：前端（apps\u002Fweb）不能 import apps\u002Fapi，而节点面板必须知道\n",[38,814,815],{"class":40,"line":179},[38,816,817],{"class":229}," * 「这个模型支持哪些分辨率、哪些时长」才能只给出跑得通的选项。\n",[38,819,820],{"class":40,"line":197},[38,821,822],{"class":229}," * 抄一份就有走样的风险，所以 apps\u002Fapi 里有一条对比测试逐项核对两边——\n",[38,824,825],{"class":40,"line":494},[38,826,827],{"class":229}," * 谁改了上游表而没同步这里，测试立刻红。\n",[38,829,830],{"class":40,"line":499},[38,831,798],{"class":229},[38,833,834],{"class":40,"line":751},[38,835,836],{"class":229}," * 硬规矩：这里只允许出现上游真支持的取值。多给一个选项，用户就会选到一个必然失败的组合。\n",[38,838,840],{"class":40,"line":839},12,[38,841,842],{"class":229}," *\u002F\n",[10,844,845],{},"同时把取值这件事收成单点：",[28,847,849],{"className":30,"code":848,"language":32,"meta":33,"style":33},"\u002F**\n * 前端渲染面板、后端校验参数都走这一个函数——两边各写一份判断，\n * 迟早出现「界面给得出、服务端不认」的组合。\n *\u002F\nexport function resolveDynamicOptions(kind: \"videoResolution\" | \"videoDuration\", params): readonly { value, label }[]\n",[35,850,851,855,860,865,869],{"__ignoreMap":33},[38,852,853],{"class":40,"line":41},[38,854,788],{"class":229},[38,856,857],{"class":40,"line":84},[38,858,859],{"class":229}," * 前端渲染面板、后端校验参数都走这一个函数——两边各写一份判断，\n",[38,861,862],{"class":40,"line":115},[38,863,864],{"class":229}," * 迟早出现「界面给得出、服务端不认」的组合。\n",[38,866,867],{"class":40,"line":131},[38,868,842],{"class":229},[38,870,871,873,875,878,880,883,885,888,890,893,895,898,900,902,905],{"class":40,"line":147},[38,872,317],{"class":44},[38,874,591],{"class":44},[38,876,877],{"class":55}," resolveDynamicOptions",[38,879,244],{"class":59},[38,881,882],{"class":599},"kind",[38,884,52],{"class":44},[38,886,887],{"class":87}," \"videoResolution\"",[38,889,608],{"class":44},[38,891,892],{"class":87}," \"videoDuration\"",[38,894,66],{"class":59},[38,896,897],{"class":599},"params",[38,899,625],{"class":59},[38,901,52],{"class":44},[38,903,904],{"class":44}," readonly",[38,906,907],{"class":59}," { value, label }[]\n",[10,909,910],{},"细粒度那一档也保留了两份清单，且是有意不同：",[28,912,914],{"className":30,"code":913,"language":32,"meta":33,"style":33},"\u002F**\n * 注意它与下面的 MODEL_DURATION_OPTIONS 是两回事、且**故意不同**：\n * 后者是「按钮行」的精简清单（seedance 只列 7 档，避免一排按钮太长），\n * 而滑块是细粒度选择，应当覆盖服务端真正接受的全部档位——\n * 否则用户拖不到 7\u002F9\u002F11\u002F13\u002F14 秒，白白削掉能力。\n *\u002F\n",[35,915,916,920,925,930,935,940],{"__ignoreMap":33},[38,917,918],{"class":40,"line":41},[38,919,788],{"class":229},[38,921,922],{"class":40,"line":84},[38,923,924],{"class":229}," * 注意它与下面的 MODEL_DURATION_OPTIONS 是两回事、且**故意不同**：\n",[38,926,927],{"class":40,"line":115},[38,928,929],{"class":229}," * 后者是「按钮行」的精简清单（seedance 只列 7 档，避免一排按钮太长），\n",[38,931,932],{"class":40,"line":131},[38,933,934],{"class":229}," * 而滑块是细粒度选择，应当覆盖服务端真正接受的全部档位——\n",[38,936,937],{"class":40,"line":147},[38,938,939],{"class":229}," * 否则用户拖不到 7\u002F9\u002F11\u002F13\u002F14 秒，白白削掉能力。\n",[38,941,942],{"class":40,"line":163},[38,943,842],{"class":229},[20,945,947],{"id":946},"五假绿的七条用例","五、假绿的七条用例",[10,949,950],{},"修的过程中还撞到一个测试问题，值得单独记。",[10,952,953],{},"改动完成后跑测试，有一组用例报了这个警告：",[28,955,958],{"className":956,"code":957,"language":378},[376],"This might cause false positive tests\n",[35,959,957],{"__ignoreMap":33},[10,961,962,963,966],{},"追下去发现是真的假绿。供应商迁移之后，配置加载在某些条件下会抛错，而那个抛错发生在 ",[35,964,965],{},"try"," 之外，成了未处理的 rejection。用例本身并不感知异常，于是照样通过。",[10,968,969],{},"具体是 7 条。",[10,971,972,973,976],{},"修法是让配置加载把异常收敛成一个明确的错误类型（缺 key 重试没有意义，归为永久失败），并在两个相关测试文件的模块加载阶段注入测试用 key。注入位置也有讲究——放 ",[35,974,975],{},"beforeEach"," 会因为跨文件执行顺序失效。",[10,978,979,980,983],{},"这件事和主题有关：",[207,981,982],{},"同一份能力表抄三份会漂，同一份配置在三个地方加载也会。"," 假绿的七条用例，测的是「配置能加载」，而配置在测试环境里根本没被加载。",[20,985,987],{"id":986},"六收口之后","六、收口之后",[10,989,990],{},"同一批里还顺手修了两处同源问题：",[992,993,994,1006],"ul",{},[995,996,997,998,1001,1002,1005],"li",{},"漫剧设置页那份模型列表是前端手写的副本，早已和后端漂开：列出「Seedance-2.0 Pro」和「可灵 V2」两个根本不存在的 ID，而真实的 ID 是 ",[35,999,1000],{},"seedance-2-fast"," 和 ",[35,1003,1004],{},"kling-v3","。选中即被出片接口的 zod 拒掉。改成读取后端的模型清单接口。",[995,1007,1008,1009,1012,1013,1016,1017,1020],{},"长篇项目的设置页没有把 ",[35,1010,1011],{},"videoModel"," \u002F ",[35,1014,1015],{},"videoResolution"," 传给设置组件，",[35,1018,1019],{},"onSave"," 也没往上收。用户选完模型点保存，没有报错，刷新之后回退到原来的值。",[10,1022,1023,1024,1027],{},"三处问题的形式不同，成因是同一个：",[207,1025,1026],{},"同一份事实存在多个副本，而且没有一处是权威","。",[10,1029,1030],{},"收口之后的结构是三份，每一份都有明确职责：",[1032,1033,1034,1050],"table",{},[1035,1036,1037],"thead",{},[1038,1039,1040,1044,1047],"tr",{},[1041,1042,1043],"th",{},"位置",[1041,1045,1046],{},"职责",[1041,1048,1049],{},"守卫",[1051,1052,1053,1065,1076],"tbody",{},[1038,1054,1055,1059,1062],{},[1056,1057,1058],"td",{},"后端能力表",[1056,1060,1061],{},"权威来源",[1056,1063,1064],{},"被镜像表逐项对比",[1038,1066,1067,1070,1073],{},[1056,1068,1069],{},"共享包镜像表",[1056,1071,1072],{},"跨端取值",[1056,1074,1075],{},"对比测试（改一边不改另一边即红）",[1038,1077,1078,1081,1084],{},[1056,1079,1080],{},"前端 UI 清单",[1056,1082,1083],{},"只影响展示形态",[1056,1085,1086],{},"值必须来自共享包，不许写字面量",[10,1088,1089,1090],{},"硬规矩只有一条，写在镜像表里：",[207,1091,1092],{},"只允许出现上游真支持的取值。多给一个选项，用户就会选到一个必然失败的组合。",[10,1094,1095],{},"反过来说，少给一个选项的代价同样实在——2.5 的 29 秒被砍到 15，用户看到的是「这个模型没比别的强」，看不到的是我们没把它的能力交出去。",[1097,1098,1099],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":33,"searchDepth":84,"depth":84,"links":1101},[1102,1103,1104,1105,1106,1107],{"id":22,"depth":84,"text":23},{"id":300,"depth":84,"text":301},{"id":365,"depth":84,"text":366},{"id":760,"depth":84,"text":761},{"id":946,"depth":84,"text":947},{"id":986,"depth":84,"text":987},"Agent 平台","2026-08-08","md",null,{},"\u002F2026-08-08",{"title":5,"description":12},"2026-08-08-同一张能力表抄了三份","上游模型的分辨率、时长、提示词长度散落在三处各自维护，各自漂移。一次用户反馈把三处一起翻出来：多出的档位、被砍掉的秒数、翻倍的板数。",[1118,1119,1120,1121,1122],"模型能力","配置漂移","契约","前端后端一致","提示词长度","xrC9lTC00ghKpLq5ZuDyYhLFe2fpcs3SgCXkd9QeigE",[1125,1806,2443],{"id":1126,"title":1127,"body":1128,"column":1108,"date":1791,"description":1132,"extension":1110,"hero_image":1111,"meta":1792,"navigation":266,"path":1793,"seo":1794,"series_id":1111,"severity":1111,"stem":1795,"summary":1796,"tags":1797,"__hash__":1805},"posts\u002F2026-08-31-阈值不能推只能量.md","阈值不能推，只能量",{"type":7,"value":1129,"toc":1782},[1130,1133,1136,1140,1143,1148,1151,1154,1160,1163,1166,1171,1174,1183,1208,1214,1217,1221,1232,1235,1241,1244,1247,1250,1255,1258,1261,1314,1317,1320,1373,1376,1381,1385,1388,1394,1397,1400,1404,1407,1413,1416,1422,1425,1428,1435,1441,1444,1447,1453,1456,1498,1501,1505,1508,1511,1517,1520,1523,1527,1533,1595,1598,1692,1695,1698,1746,1749,1753,1756,1766,1769,1776,1779],[10,1131,1132],{},"知识库的检索参数有三组：分块阈值、召回阈值、精排阈值。这一天把三组都动了一遍，每一组都留下了实测数据。",[10,1134,1135],{},"起因是一个看不太出来的现象：知识库好像没被用上。",[20,1137,1139],{"id":1138},"一分块中位数-212-字","一、分块：中位数 212 字",[10,1141,1142],{},"先量现状。生产库里的分块统计：",[518,1144,1145],{},[10,1146,1147],{},"13244 个分块中位数只有 212 字，而目标 2400 字，62% 的块不足 300 字。",[10,1149,1150],{},"目标块大小是 2400 字，实际交付的是 212。差了十倍。",[10,1152,1153],{},"分块器原来的逻辑是「一个标题一个块」。这在正常文档上没问题，但清单型、模板型文档里，几乎每一行列表项都会被标题识别逻辑认成标题：",[28,1155,1158],{"className":1156,"code":1157,"language":378},[376],"1. 原本想达成什么？\n",[35,1159,1157],{"__ignoreMap":33},[10,1161,1162],{},"这一行被当成标题，于是自己成了一个块。一篇文档被切成几十个几十字的碎片，注入给模型的全是碎片。",[10,1164,1165],{},"更严重的情况在连续列表项之间没有正文时。老实现让标题行只活在「面包屑」元数据里，不写进块正文。于是被误判成标题的那一行文字直接消失：",[518,1167,1168],{},[10,1169,1170],{},"标题行只活在面包屑里，整行文字直接丢失（那类文档的四个核心问题在索引里根本不存在）。",[10,1172,1173],{},"两处改动：",[28,1175,1177],{"className":30,"code":1176,"language":32,"meta":33,"style":33},"\u002F\u002F 攒够了才在这里切：标题是「首选切点」，不是「强制切点」。\n",[35,1178,1179],{"__ignoreMap":33},[38,1180,1181],{"class":40,"line":41},[38,1182,1176],{"class":229},[28,1184,1186],{"className":30,"code":1185,"language":32,"meta":33,"style":33},"\u002F\u002F 不变量：每一行输入都要落进某个块的正文，面包屑只是附加元数据。\n\u002F\u002F 老实现让标题行只活在面包屑里，于是被 detectHeading 误判成标题的列表项\n\u002F\u002F （「1. 原本想达成什么？」这类）整行文字就没了——连续几个列表项时只留得住最后一条。\n\u002F\u002F 与面包屑重复一次可以接受，丢字不行。\n",[35,1187,1188,1193,1198,1203],{"__ignoreMap":33},[38,1189,1190],{"class":40,"line":41},[38,1191,1192],{"class":229},"\u002F\u002F 不变量：每一行输入都要落进某个块的正文，面包屑只是附加元数据。\n",[38,1194,1195],{"class":40,"line":84},[38,1196,1197],{"class":229},"\u002F\u002F 老实现让标题行只活在面包屑里，于是被 detectHeading 误判成标题的列表项\n",[38,1199,1200],{"class":40,"line":115},[38,1201,1202],{"class":229},"\u002F\u002F （「1. 原本想达成什么？」这类）整行文字就没了——连续几个列表项时只留得住最后一条。\n",[38,1204,1205],{"class":40,"line":131},[38,1206,1207],{"class":229},"\u002F\u002F 与面包屑重复一次可以接受，丢字不行。\n",[10,1209,1210,1211,1027],{},"标题从「强制切点」降为「首选切点」：缓冲区不足阈值时，标题并入当前块，不切。同时标题行本身写进正文，成为一条不变量——",[207,1212,1213],{},"每一行输入都要落进某个块的正文",[10,1215,1216],{},"实测效果：一篇文档从 3 块 52\u002F67\u002F100 字（四个核心问题全丢）变成 1 块 235 字，内容完整。",[20,1218,1220],{"id":1219},"二按比例推算推出了全场最差点","二、按比例推算，推出了全场最差点",[10,1222,1223,1224,1227,1228,1231],{},"第一版把阈值定成 ",[35,1225,1226],{},"maxChars × 0.6","。生产 ",[35,1229,1230],{},"maxChars"," 是 2400，算出来是 1440。",[10,1233,1234],{},"结果整篇文档并成一个块。上线后实测检索：",[28,1236,1239],{"className":1237,"code":1238,"language":378},[376],"5 篇文档 6 个查询，每篇取最佳命中分再平均\n  阈值 0（纯按小节切） 0.7046\n  阈值 250            0.6750\n  阈值 1440（线上）    0.4978\n",[35,1240,1238],{"__ignoreMap":33},[10,1242,1243],{},"1440 正好是最差的那个。",[10,1245,1246],{},"同一篇文档对「核心四问」这个查询，切成小节时得分 0.77，并成整块时只有 0.33——在全库 113 块里排到第 109 名。内容修好了，却再也检索不到。",[10,1248,1249],{},"根因在生产用的向量模型上：",[518,1251,1252],{},[10,1253,1254],{},"生产 embedding 模型对「主题聚焦的小段」打分远高于「整篇文档」。",[10,1256,1257],{},"原来那个「一个标题一个块」的设计，主题纯度是对的。推翻它是错的判断。真正的缺陷只有一条——标题行被丢弃。",[10,1259,1260],{},"最终取值 250：",[28,1262,1264],{"className":30,"code":1263,"language":32,"meta":33,"style":33},"\u002F**\n * 缺省小节合并阈值。250 是在生产 embedding 模型上实测标定的，不是拍脑袋：\n * 5 篇文档 6 个查询，取每篇的最佳命中分做平均——\n *   阈值 0（纯按小节切）0.7046 \u002F 250 → 0.6750 \u002F 1440（= maxChars×0.6）→ 0.4978\n * 这个模型对「主题聚焦的小段」打分远高于「整篇文档」……\n * 所以阈值必须小，千万别再按 maxChars 的比例去推——那样在生产的 2400 上会算出 1440，\n * 正好是最差点。\n * 取 250 而不是 0：排序只差 4%，但块从几十字变成 250~330 字，\n * 同样召回 6 段能多喂两三倍的正文。\n *\u002F\n",[35,1265,1266,1270,1275,1280,1285,1290,1295,1300,1305,1310],{"__ignoreMap":33},[38,1267,1268],{"class":40,"line":41},[38,1269,788],{"class":229},[38,1271,1272],{"class":40,"line":84},[38,1273,1274],{"class":229}," * 缺省小节合并阈值。250 是在生产 embedding 模型上实测标定的，不是拍脑袋：\n",[38,1276,1277],{"class":40,"line":115},[38,1278,1279],{"class":229}," * 5 篇文档 6 个查询，取每篇的最佳命中分做平均——\n",[38,1281,1282],{"class":40,"line":131},[38,1283,1284],{"class":229}," *   阈值 0（纯按小节切）0.7046 \u002F 250 → 0.6750 \u002F 1440（= maxChars×0.6）→ 0.4978\n",[38,1286,1287],{"class":40,"line":147},[38,1288,1289],{"class":229}," * 这个模型对「主题聚焦的小段」打分远高于「整篇文档」……\n",[38,1291,1292],{"class":40,"line":163},[38,1293,1294],{"class":229}," * 所以阈值必须小，千万别再按 maxChars 的比例去推——那样在生产的 2400 上会算出 1440，\n",[38,1296,1297],{"class":40,"line":179},[38,1298,1299],{"class":229}," * 正好是最差点。\n",[38,1301,1302],{"class":40,"line":197},[38,1303,1304],{"class":229}," * 取 250 而不是 0：排序只差 4%，但块从几十字变成 250~330 字，\n",[38,1306,1307],{"class":40,"line":494},[38,1308,1309],{"class":229}," * 同样召回 6 段能多喂两三倍的正文。\n",[38,1311,1312],{"class":40,"line":499},[38,1313,842],{"class":229},[10,1315,1316],{},"取 250 而不是 0 的理由是这段话里第二重要的部分：排序只差 4%，但每个块从几十字变成两三百字，同样召回 6 段能多喂几倍的正文。",[10,1318,1319],{},"代码里还留了一条兜底：",[28,1321,1323],{"className":30,"code":1322,"language":32,"meta":33,"style":33},"\u002F\u002F 取 min：maxChars 很小的配置（测试里 300）不能让阈值反超块大小本身\nconst minChunkChars =\n  opts.minChunkChars ?? Math.min(DEFAULT_MIN_CHUNK_CHARS, Math.floor(maxChars * 0.6));\n",[35,1324,1325,1330,1340],{"__ignoreMap":33},[38,1326,1327],{"class":40,"line":41},[38,1328,1329],{"class":229},"\u002F\u002F 取 min：maxChars 很小的配置（测试里 300）不能让阈值反超块大小本身\n",[38,1331,1332,1334,1337],{"class":40,"line":84},[38,1333,45],{"class":44},[38,1335,1336],{"class":48}," minChunkChars",[38,1338,1339],{"class":44}," =\n",[38,1341,1342,1345,1348,1350,1352,1354,1357,1359,1362,1365,1367,1370],{"class":40,"line":115},[38,1343,1344],{"class":59},"  opts.minChunkChars ",[38,1346,1347],{"class":44},"??",[38,1349,238],{"class":59},[38,1351,253],{"class":55},[38,1353,244],{"class":59},[38,1355,1356],{"class":48},"DEFAULT_MIN_CHUNK_CHARS",[38,1358,250],{"class":59},[38,1360,1361],{"class":55},"floor",[38,1363,1364],{"class":59},"(maxChars ",[38,1366,720],{"class":44},[38,1368,1369],{"class":48}," 0.6",[38,1371,1372],{"class":59},"));\n",[10,1374,1375],{},"以及一条守卫测试，专门防「有人再推一遍公式」：",[518,1377,1378],{},[10,1379,1380],{},"加一条守卫测试钉死生产口径（阈值退回按比例推算即变红），防止将来有人再推一遍公式又回到 1440。",[20,1382,1384],{"id":1383},"三召回阈值会把整轮清零","三、召回阈值：会把整轮清零",[10,1386,1387],{},"分块改大之后，绝对余弦分整体下移。而召回阈值还是旧值 0.5：",[28,1389,1392],{"className":1390,"code":1391,"language":378},[376],"正确命中落在 0.43~0.63，旧值会把最高分 0.488 的查询整轮清零——\n检索到了正确文档却被门槛全部丢弃，用户看到的就是「知识库没被使用」。\n",[35,1393,1391],{"__ignoreMap":33},[10,1395,1396],{},"这条解释了我一开始看到的那个现象。检索其实命中了，只是分数没过门槛，于是整轮被丢掉，模型什么也没拿到。",[10,1398,1399],{},"改成 0.35。",[20,1401,1403],{"id":1402},"四精排从-912-到-1212","四、精排：从 9\u002F12 到 12\u002F12",[10,1405,1406],{},"同一天启用了 cross-encoder 精排。A\u002FB 实测：",[28,1408,1411],{"className":1409,"code":1410,"language":378},[376],"12 条查询 A\u002FB 实测：\n  纯向量  命中 9\u002F12，整轮零注入 1 次\n  开精排  命中 12\u002F12，整轮零注入 0 次，目标文档 10 条排第 1\n",[35,1412,1410],{"__ignoreMap":33},[10,1414,1415],{},"修好的都是「换了说法」的查询——向量检索的固有短板。举一个例子：",[28,1417,1420],{"className":1418,"code":1419,"language":378},[376],"「我想要复刻爆款视频…使用画布」注入 0 段 → 4 段\n（相关文档被向量埋在后面，精排提到第 2 名）\n",[35,1421,1419],{"__ignoreMap":33},[10,1423,1424],{},"精排启用时把两个阈值也一起改了。这是当天最曲折的一处。",[10,1426,1427],{},"第一版把精排阈值设成 0.15。发版后跑完整测试，出现随机漏召。",[10,1429,1430,1431,1434],{},"原因是精排的",[207,1432,1433],{},"绝对分不稳定","：",[28,1436,1439],{"className":1437,"code":1438,"language":378},[376],"同一查询同一候选集，「金字塔原理怎么做到结论先行」两次实测 0.251 与 0.135——\n排名都稳定第 1，只是分数漂了近一半。0.15 卡在中间，于是第二次整轮零注入。\n",[35,1440,1438],{"__ignoreMap":33},[10,1442,1443],{},"排名是稳定的，分数会漂。所以不能用绝对分当门槛去「把关质量」。",[10,1445,1446],{},"阈值扫描：",[28,1448,1451],{"className":1449,"code":1450,"language":378},[376],"0.02~0.10 均 12\u002F12 零丢弃，0.15\u002F0.20 → 11\u002F12 丢 1 次\n",[35,1452,1450],{"__ignoreMap":33},[10,1454,1455],{},"最终取 0.05，并把职责写清楚：",[28,1457,1459],{"className":30,"code":1458,"language":32,"meta":33,"style":33},"\u002F**\n * 精排阈值。**它的职责只是扔掉垃圾，不是把关质量**——质量由排序保证：\n * 12 条查询里精排把正确目标全部放进了前 3 名（10 条第 1）。\n *\n * 取 0.05 而不是更高，是因为**精排的绝对分不稳定**……\n * 观测到的正确目标最低分 0.131，取 0.05 留约 60% 余量；\n * 垃圾档在 0.014~0.025，仍被干净滤掉。\n *\u002F\n",[35,1460,1461,1465,1470,1475,1479,1484,1489,1494],{"__ignoreMap":33},[38,1462,1463],{"class":40,"line":41},[38,1464,788],{"class":229},[38,1466,1467],{"class":40,"line":84},[38,1468,1469],{"class":229}," * 精排阈值。**它的职责只是扔掉垃圾，不是把关质量**——质量由排序保证：\n",[38,1471,1472],{"class":40,"line":115},[38,1473,1474],{"class":229}," * 12 条查询里精排把正确目标全部放进了前 3 名（10 条第 1）。\n",[38,1476,1477],{"class":40,"line":131},[38,1478,798],{"class":229},[38,1480,1481],{"class":40,"line":147},[38,1482,1483],{"class":229}," * 取 0.05 而不是更高，是因为**精排的绝对分不稳定**……\n",[38,1485,1486],{"class":40,"line":163},[38,1487,1488],{"class":229}," * 观测到的正确目标最低分 0.131，取 0.05 留约 60% 余量；\n",[38,1490,1491],{"class":40,"line":179},[38,1492,1493],{"class":229}," * 垃圾档在 0.014~0.025，仍被干净滤掉。\n",[38,1495,1496],{"class":40,"line":197},[38,1497,842],{"class":229},[10,1499,1500],{},"观测到的垃圾档在 0.014~0.025，正确目标最低 0.131。0.05 落在两者之间，两边都有余量。",[20,1502,1504],{"id":1503},"五超时静默降级最贵","五、超时：静默降级最贵",[10,1506,1507],{},"同一批里还修了超时。",[10,1509,1510],{},"精排失败会自动降级回向量序，不阻断聊天。这个设计是对的——但有代价：",[28,1512,1515],{"className":1513,"code":1514,"language":378},[376],"法律知识库 30 块共 4.5 万字，冷调用实测 2263ms，只剩 737ms 余量。\n完整测试里有一条查询当场超时降级。\n超时是静默的，日志不报错，用户侧表现为「有时准有时不准」，极难归因。\n",[35,1516,1514],{"__ignoreMap":33},[10,1518,1519],{},"「有时准有时不准」这句话在这一天的上下文里已经出现第二次了——早上是参考图，这里是检索。",[10,1521,1522],{},"超时从 3000 毫秒提到 8000，给冷调用 3.5 倍余量。真卡死仍有降级兜底。",[20,1524,1526],{"id":1525},"六三个阈值放在一起","六、三个阈值放在一起",[10,1528,1529,1530],{},"三条修正的形式不同，结论是同一条：",[207,1531,1532],{},"阈值不能推导，只能测量。",[1032,1534,1535,1551],{},[1035,1536,1537],{},[1038,1538,1539,1542,1545,1548],{},[1041,1540,1541],{},"参数",[1041,1543,1544],{},"推导值",[1041,1546,1547],{},"实测值",[1041,1549,1550],{},"推导错在哪",[1051,1552,1553,1567,1581],{},[1038,1554,1555,1558,1561,1564],{},[1056,1556,1557],{},"分块合并阈值",[1056,1559,1560],{},"1440（按比例 0.6）",[1056,1562,1563],{},"250",[1056,1565,1566],{},"假设「块越大越好」，而该模型偏好主题聚焦的小段",[1038,1568,1569,1572,1575,1578],{},[1056,1570,1571],{},"召回阈值",[1056,1573,1574],{},"0.5（惯例值）",[1056,1576,1577],{},"0.35",[1056,1579,1580],{},"分块改动后分数分布整体下移，旧门槛把命中全丢",[1038,1582,1583,1586,1589,1592],{},[1056,1584,1585],{},"精排阈值",[1056,1587,1588],{},"0.15（留余量）",[1056,1590,1591],{},"0.05",[1056,1593,1594],{},"绝对分本身会漂近一半，拿漂移量当门槛必然随机漏召",[10,1596,1597],{},"三个值都有实测数字支撑，也都配了守卫测试。其中两条测试写的是「不许回到某个值」，而不是「必须等于某值」：",[28,1599,1601],{"className":30,"code":1600,"language":32,"meta":33,"style":33},"it(\"向量阈值不得回到会整轮清零的 0.5\", () => {\n  expect(DEFAULT_KB_MIN_SCORE).toBeLessThanOrEqual(0.4);\n});\n\nit(\"精排阈值不得高到会随分数漂移漏召——观测最低正确分 0.131\", () => {\n  expect(DEFAULT_KB_RERANK_MIN_SCORE).toBeLessThanOrEqual(0.1);\n});\n",[35,1602,1603,1621,1644,1649,1653,1668,1688],{"__ignoreMap":33},[38,1604,1605,1608,1610,1613,1616,1619],{"class":40,"line":41},[38,1606,1607],{"class":55},"it",[38,1609,244],{"class":59},[38,1611,1612],{"class":87},"\"向量阈值不得回到会整轮清零的 0.5\"",[38,1614,1615],{"class":59},", () ",[38,1617,1618],{"class":44},"=>",[38,1620,81],{"class":59},[38,1622,1623,1626,1628,1631,1634,1637,1639,1642],{"class":40,"line":84},[38,1624,1625],{"class":55},"  expect",[38,1627,244],{"class":59},[38,1629,1630],{"class":48},"DEFAULT_KB_MIN_SCORE",[38,1632,1633],{"class":59},").",[38,1635,1636],{"class":55},"toBeLessThanOrEqual",[38,1638,244],{"class":59},[38,1640,1641],{"class":48},"0.4",[38,1643,725],{"class":59},[38,1645,1646],{"class":40,"line":115},[38,1647,1648],{"class":59},"});\n",[38,1650,1651],{"class":40,"line":131},[38,1652,267],{"emptyLinePlaceholder":266},[38,1654,1655,1657,1659,1662,1664,1666],{"class":40,"line":147},[38,1656,1607],{"class":55},[38,1658,244],{"class":59},[38,1660,1661],{"class":87},"\"精排阈值不得高到会随分数漂移漏召——观测最低正确分 0.131\"",[38,1663,1615],{"class":59},[38,1665,1618],{"class":44},[38,1667,81],{"class":59},[38,1669,1670,1672,1674,1677,1679,1681,1683,1686],{"class":40,"line":163},[38,1671,1625],{"class":55},[38,1673,244],{"class":59},[38,1675,1676],{"class":48},"DEFAULT_KB_RERANK_MIN_SCORE",[38,1678,1633],{"class":59},[38,1680,1636],{"class":55},[38,1682,244],{"class":59},[38,1684,1685],{"class":48},"0.1",[38,1687,725],{"class":59},[38,1689,1690],{"class":40,"line":179},[38,1691,1648],{"class":59},[10,1693,1694],{},"这样写是因为真正的风险不是「有人改了数值」，而是「有人又推了一遍公式」。测试防的是后者。",[10,1696,1697],{},"还有一条测试值得抄下来：",[28,1699,1701],{"className":30,"code":1700,"language":32,"meta":33,"style":33},"it(\"精排阈值必须低于向量阈值——两者不是同一个量纲\", () => {\n  \u002F\u002F 精排是 cross-encoder 分，向量是余弦分，拿同一个数去卡两边必然有一边错\n  expect(DEFAULT_KB_RERANK_MIN_SCORE).toBeLessThan(DEFAULT_KB_MIN_SCORE);\n});\n",[35,1702,1703,1718,1723,1742],{"__ignoreMap":33},[38,1704,1705,1707,1709,1712,1714,1716],{"class":40,"line":41},[38,1706,1607],{"class":55},[38,1708,244],{"class":59},[38,1710,1711],{"class":87},"\"精排阈值必须低于向量阈值——两者不是同一个量纲\"",[38,1713,1615],{"class":59},[38,1715,1618],{"class":44},[38,1717,81],{"class":59},[38,1719,1720],{"class":40,"line":84},[38,1721,1722],{"class":229},"  \u002F\u002F 精排是 cross-encoder 分，向量是余弦分，拿同一个数去卡两边必然有一边错\n",[38,1724,1725,1727,1729,1731,1733,1736,1738,1740],{"class":40,"line":115},[38,1726,1625],{"class":55},[38,1728,244],{"class":59},[38,1730,1676],{"class":48},[38,1732,1633],{"class":59},[38,1734,1735],{"class":55},"toBeLessThan",[38,1737,244],{"class":59},[38,1739,1630],{"class":48},[38,1741,725],{"class":59},[38,1743,1744],{"class":40,"line":131},[38,1745,1648],{"class":59},[10,1747,1748],{},"同一个数量级、看起来可比，实际是两个不同的量纲。这种错觉在阈值调参里很常见，能用一条断言拦下来比写在注释里可靠。",[20,1750,1752],{"id":1751},"七评测集的缺口","七、评测集的缺口",[10,1754,1755],{},"这一轮所有数字来自临时扫的查询集：5 篇文档 6 个查询、12 条查询。",[10,1757,1758,1759,1762,1763,1027],{},"仓库里确实有一个召回评测脚本（用户问题、期望命中的文档名、recall@K \u002F precision@K \u002F MRR 双模式），但它的标注集还是占位内容——两条用例，",[35,1760,1761],{},"kbIds"," 的值是字面量 ",[35,1764,1765],{},"\u003C替换为真实 kbId>",[10,1767,1768],{},"也就是说，这一轮的标定用的数据集没有入库。数字留在代码注释、配置和环境变量说明里，跑分的脚本和查询集没有留下。",[10,1770,1771,1772,1775],{},"这是这次工作里最该补上的一环。",[207,1773,1774],{},"阈值有实测依据，但依据本身不可复现。"," 下次有人想调整，只能重新扫一遍查询。",[10,1777,1778],{},"补的做法是明确的：把这一轮用的查询集和期望结果补进评测脚本的标注集，让它成为下一次调参的起点。参数变更时先跑评测再改值，改完之后把新的分数写进注释——注释里的数字应该来自一次可复现的运行，而不是一次性的手测。",[1097,1780,1781],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":33,"searchDepth":84,"depth":84,"links":1783},[1784,1785,1786,1787,1788,1789,1790],{"id":1138,"depth":84,"text":1139},{"id":1219,"depth":84,"text":1220},{"id":1383,"depth":84,"text":1384},{"id":1402,"depth":84,"text":1403},{"id":1503,"depth":84,"text":1504},{"id":1525,"depth":84,"text":1526},{"id":1751,"depth":84,"text":1752},"2026-08-31",{},"\u002F2026-08-31",{"title":1127,"description":1132},"2026-08-31-阈值不能推只能量","知识库检索质量的三次修正：分块阈值按比例推算算出了全场最差点，精排阈值设高了会随分数漂移随机漏召，超时余量不够会静默降级。三次都靠实测数据定值。",[1798,1799,1800,1801,1802,1803,1804],"知识库","RAG","分块","检索","cross-encoder","阈值标定","评测","VbN-ARGFFeAmQMRZf2jG-4SfdtN22uzMLyCrc3dCuA8",{"id":1807,"title":1808,"body":1809,"column":1108,"date":2429,"description":1813,"extension":1110,"hero_image":1111,"meta":2430,"navigation":266,"path":2431,"seo":2432,"series_id":1111,"severity":1111,"stem":2433,"summary":2434,"tags":2435,"__hash__":2442},"posts\u002F2026-08-20-画布这一步深链运行历史与续跑.md","画布这一步：深链、运行历史与续跑",{"type":7,"value":1810,"toc":2419},[1811,1814,1817,1821,1824,1827,1833,1836,1850,1863,1866,1870,1873,1879,1882,1889,1892,1901,1904,1908,1911,1916,1944,1947,1952,1972,1979,1982,1986,1989,2078,2081,2084,2088,2091,2109,2118,2121,2124,2128,2131,2134,2154,2157,2162,2165,2197,2200,2291,2294,2300,2307,2311,2314,2317,2355,2362,2365,2371,2374,2379,2383,2386,2395,2401,2407,2413,2416],[10,1812,1813],{},"画布是这套平台里最复杂的界面。节点、连线、参数、批量框、运行状态，任何一件事都要在同一个视口里表达清楚。",[10,1815,1816],{},"这一天补齐八项操作。每一项都不难，难的是它们之间不能互相打架。",[20,1818,1820],{"id":1819},"一url-是唯一事实来源","一、URL 是唯一事实来源",[10,1822,1823],{},"原先画布是个单页状态机：打开就是列表，点进去切到编辑器，刷新回列表。",[10,1825,1826],{},"改成路由：",[28,1828,1831],{"className":1829,"code":1830,"language":378},[376],"\u002Fcanvas            列表\n\u002Fcanvas\u002F:id        编辑器\n",[35,1832,1830],{"__ignoreMap":33},[10,1834,1835],{},"四个行为一起对齐：",[992,1837,1838,1841,1844,1847],{},[995,1839,1840],{},"深链直达编辑器。",[995,1842,1843],{},"刷新不回列表。",[995,1845,1846],{},"后退回列表，而不是退出画布模块。",[995,1848,1849],{},"打开画布写入地址栏。",[10,1851,1852,1855,1856,1012,1859,1862],{},[35,1853,1854],{},"popstate"," 监听已有的解析函数，",[35,1857,1858],{},"pushState",[35,1860,1861],{},"replaceState"," 写入。",[10,1864,1865],{},"这条改动看起来只是加了个路由，实际改变的是状态的归属：画布 ID 从组件内的 ref 变成了 URL 的一部分。后面几项都依赖它——运行历史要能链到具体的运行，批量框要能被分享，都要求「当前在看哪张画布」是一个可以从外部确定的量。",[20,1867,1869],{"id":1868},"二运行历史","二、运行历史",[10,1871,1872],{},"一个新面板，两个接口：",[28,1874,1877],{"className":1875,"code":1876,"language":378},[376],"GET \u002Fapi\u002Fcanvas-flow\u002Fruns?flowId=…      列表\nGET \u002Fapi\u002Fcanvas-flow\u002Fruns\u002F:id           快照\n",[35,1878,1876],{"__ignoreMap":33},[10,1880,1881],{},"列表默认取 10 条。点开某一条，用它的快照推导出画布上每个节点的状态，覆盖显示。",[10,1883,1884,1885,1888],{},"这个面板的数据来源和实时运行状态用的是同一套 ",[35,1886,1887],{},"nodeStates","——历史记录不是另一条平行显示，而是把画布切到那一次运行的视角。",[10,1890,1891],{},"登录态走单一来源：",[28,1893,1895],{"className":30,"code":1894,"language":32,"meta":33,"style":33},"\u002F\u002F 登录态只有 authToken() 一个来源（有守卫测试盯着，别直读 localStorage）\n",[35,1896,1897],{"__ignoreMap":33},[38,1898,1899],{"class":40,"line":41},[38,1900,1894],{"class":229},[10,1902,1903],{},"这条注释是守卫测试抓出来的结果，不是提前的设计。",[20,1905,1907],{"id":1906},"三复制粘贴","三、复制粘贴",[10,1909,1910],{},"复制粘贴里有两个决定值得记。",[10,1912,1913],{},[207,1914,1915],{},"用应用内剪贴板，不碰系统剪贴板。",[28,1917,1919],{"className":30,"code":1918,"language":32,"meta":33,"style":33},"let clipboard: FlowClipboardPayload | null = null;\n",[35,1920,1921],{"__ignoreMap":33},[38,1922,1923,1926,1929,1931,1934,1936,1938,1940,1942],{"class":40,"line":41},[38,1924,1925],{"class":44},"let",[38,1927,1928],{"class":59}," clipboard",[38,1930,52],{"class":44},[38,1932,1933],{"class":55}," FlowClipboardPayload",[38,1935,608],{"class":44},[38,1937,634],{"class":48},[38,1939,326],{"class":44},[38,1941,634],{"class":48},[38,1943,332],{"class":59},[10,1945,1946],{},"粘贴的内容是节点和边，格式带版本号。走系统剪贴板意味着把画布的 JSON 写进用户的剪贴板，用户去别处粘贴会看到一堆结构数据。应用内的模块级变量没有这个问题。",[10,1948,1949],{},[207,1950,1951],{},"没选节点时不拦截。",[28,1953,1955],{"className":30,"code":1954,"language":32,"meta":33,"style":33},"\u002F\u002F Cmd\u002FCtrl+C：复制选中的节点。\n\u002F\u002F 没选节点就不拦截——用户可能正在复制节点产物里的文字，抢了就是坏默认行为\n\u002F\u002F Cmd\u002FCtrl+V：粘贴。应用内剪贴板为空时同样放行系统默认行为\n",[35,1956,1957,1962,1967],{"__ignoreMap":33},[38,1958,1959],{"class":40,"line":41},[38,1960,1961],{"class":229},"\u002F\u002F Cmd\u002FCtrl+C：复制选中的节点。\n",[38,1963,1964],{"class":40,"line":84},[38,1965,1966],{"class":229},"\u002F\u002F 没选节点就不拦截——用户可能正在复制节点产物里的文字，抢了就是坏默认行为\n",[38,1968,1969],{"class":40,"line":115},[38,1970,1971],{"class":229},"\u002F\u002F Cmd\u002FCtrl+V：粘贴。应用内剪贴板为空时同样放行系统默认行为\n",[10,1973,1974,1975,1978],{},"这两条合起来是一个原则：",[207,1976,1977],{},"快捷键只在它有明确意图时生效","。用户按 Cmd+C 时可能是想复制提示词里的文字，此时抢过来是损失。",[10,1980,1981],{},"粘贴到画布时重新生成 ID（节点 ID 最多重试 20 次防碰撞），偏移 24 像素，粘贴的结果进入撤销栈并成为新的选中项。",[20,1983,1985],{"id":1984},"四一键整理布局","四、一键整理布局",[10,1987,1988],{},"按依赖深度分层的纯函数，不引布局引擎：",[28,1990,1992],{"className":30,"code":1991,"language":32,"meta":33,"style":33},"\u002F**\n * 一键整理布局：按依赖深度分层的纯函数。\n *\n * 不引 dagre\u002Felk——画布的图是小规模 DAG（上限 100 节点），\n * 「上游在左、下游在右、同层竖排」这一条规则就够读顺一张乱图，\n * 引一个布局引擎为它的边缘能力买单不值。\n *\u002F\nconst COLUMN_GAP = 380;\nconst ROW_GAP = 240;\nconst ORIGIN = { x: 40, y: 60 };\n",[35,1993,1994,1998,2003,2007,2012,2017,2022,2026,2040,2054],{"__ignoreMap":33},[38,1995,1996],{"class":40,"line":41},[38,1997,788],{"class":229},[38,1999,2000],{"class":40,"line":84},[38,2001,2002],{"class":229}," * 一键整理布局：按依赖深度分层的纯函数。\n",[38,2004,2005],{"class":40,"line":115},[38,2006,798],{"class":229},[38,2008,2009],{"class":40,"line":131},[38,2010,2011],{"class":229}," * 不引 dagre\u002Felk——画布的图是小规模 DAG（上限 100 节点），\n",[38,2013,2014],{"class":40,"line":147},[38,2015,2016],{"class":229}," * 「上游在左、下游在右、同层竖排」这一条规则就够读顺一张乱图，\n",[38,2018,2019],{"class":40,"line":163},[38,2020,2021],{"class":229}," * 引一个布局引擎为它的边缘能力买单不值。\n",[38,2023,2024],{"class":40,"line":179},[38,2025,842],{"class":229},[38,2027,2028,2030,2033,2035,2038],{"class":40,"line":197},[38,2029,45],{"class":44},[38,2031,2032],{"class":48}," COLUMN_GAP",[38,2034,326],{"class":44},[38,2036,2037],{"class":48}," 380",[38,2039,332],{"class":59},[38,2041,2042,2044,2047,2049,2052],{"class":40,"line":494},[38,2043,45],{"class":44},[38,2045,2046],{"class":48}," ROW_GAP",[38,2048,326],{"class":44},[38,2050,2051],{"class":48}," 240",[38,2053,332],{"class":59},[38,2055,2056,2058,2061,2063,2066,2069,2072,2075],{"class":40,"line":499},[38,2057,45],{"class":44},[38,2059,2060],{"class":48}," ORIGIN",[38,2062,326],{"class":44},[38,2064,2065],{"class":59}," { x: ",[38,2067,2068],{"class":48},"40",[38,2070,2071],{"class":59},", y: ",[38,2073,2074],{"class":48},"60",[38,2076,2077],{"class":59}," };\n",[10,2079,2080],{},"同列保持用户原有的上下相对顺序。这一条是这类功能能不能用的分界线——整理完之后用户还得能认出自己的图。",[10,2082,2083],{},"节点上限 100，单批最大 50 项，展开后的总任务上限 200。这几个数决定了上面那个判断成立：在这个规模内，一条规则够用。",[20,2085,2087],{"id":2086},"五便签不能做成节点","五、便签不能做成节点",[10,2089,2090],{},"画布上要有地方写注释。最自然的做法是加一个「便签节点」，但它的实现方式正好相反：",[28,2092,2094],{"className":30,"code":2093,"language":32,"meta":33,"style":33},"\u002F**\n * 画布便签：纯注释，不参与调度、连线与计费。\n *\u002F\n",[35,2095,2096,2100,2105],{"__ignoreMap":33},[38,2097,2098],{"class":40,"line":41},[38,2099,788],{"class":229},[38,2101,2102],{"class":40,"line":84},[38,2103,2104],{"class":229}," * 画布便签：纯注释，不参与调度、连线与计费。\n",[38,2106,2107],{"class":40,"line":115},[38,2108,842],{"class":229},[28,2110,2112],{"className":30,"code":2111,"language":32,"meta":33,"style":33},"\u002F\u002F 不是 xyflow 节点——它不参与连线、调度与计费，做成节点要在注册表、校验器、执行器三处逐一开豁免，成本远高于一张自绘卡片。\n",[35,2113,2114],{"__ignoreMap":33},[38,2115,2116],{"class":40,"line":41},[38,2117,2111],{"class":229},[10,2119,2120],{},"做成节点意味着它要在三处地方被显式排除。而排除逻辑每加一处，将来就多一处要维护的例外。做成一张画在流坐标系里的自绘卡片，这些例外一个都不需要。",[10,2122,2123],{},"契约里限制条数 100、单条 2000 字。",[20,2125,2127],{"id":2126},"六批量框","六、批量框",[10,2129,2130],{},"批量框是「框内的子图跑 N 遍」。它带来的第一件事是边界规则：",[10,2132,2133],{},"框内节点的输出不能接到框外，反过来可以。",[28,2135,2137],{"className":30,"code":2136,"language":32,"meta":33,"style":33},"\u002F\u002F 批量框边界规则：框内节点的输出不能接到框外（或另一个框）。\n\u002F\u002F 框内子图每份各跑一遍，往外接意味着下游要收 N 份——运行时不支持这种收束；\n\u002F\u002F 反方向（框外 → 框内）合法：同一上游共享给每份拷贝。\n",[35,2138,2139,2144,2149],{"__ignoreMap":33},[38,2140,2141],{"class":40,"line":41},[38,2142,2143],{"class":229},"\u002F\u002F 批量框边界规则：框内节点的输出不能接到框外（或另一个框）。\n",[38,2145,2146],{"class":40,"line":84},[38,2147,2148],{"class":229},"\u002F\u002F 框内子图每份各跑一遍，往外接意味着下游要收 N 份——运行时不支持这种收束；\n",[38,2150,2151],{"class":40,"line":115},[38,2152,2153],{"class":229},"\u002F\u002F 反方向（框外 → 框内）合法：同一上游共享给每份拷贝。\n",[10,2155,2156],{},"拒绝时的文案要给下一步：",[518,2158,2159],{},[10,2160,2161],{},"批量框内的节点不能连到框外：框内每份各跑一遍，产物会直接进素材库。要串联处理就把目标节点也拖进框里。",[10,2163,2164],{},"第二件事是删除节点的连带处理。节点被删掉之后，批量框里会留下幽灵成员，而运行创建时会拒绝整张图：",[28,2166,2168],{"className":30,"code":2167,"language":32,"meta":33,"style":33},"\u002F**\n * 从所有批量框成员里剔除已删除的节点；成员清空的框一并删除。\n *\n * 删除节点必须同步清理：残留的幽灵成员会让 run-create 直接拒绝整张画布\n * （「批量框引用了图中不存在的节点」），用户面对的是一张再也跑不起来的图。\n *\u002F\n",[35,2169,2170,2174,2179,2183,2188,2193],{"__ignoreMap":33},[38,2171,2172],{"class":40,"line":41},[38,2173,788],{"class":229},[38,2175,2176],{"class":40,"line":84},[38,2177,2178],{"class":229}," * 从所有批量框成员里剔除已删除的节点；成员清空的框一并删除。\n",[38,2180,2181],{"class":40,"line":115},[38,2182,798],{"class":229},[38,2184,2185],{"class":40,"line":131},[38,2186,2187],{"class":229}," * 删除节点必须同步清理：残留的幽灵成员会让 run-create 直接拒绝整张画布\n",[38,2189,2190],{"class":40,"line":147},[38,2191,2192],{"class":229}," * （「批量框引用了图中不存在的节点」），用户面对的是一张再也跑不起来的图。\n",[38,2194,2195],{"class":40,"line":163},[38,2196,842],{"class":229},[10,2198,2199],{},"第三件事是撤销栈。批量框要进快照：",[28,2201,2203],{"className":30,"code":2202,"language":32,"meta":33,"style":33},"export interface GraphSnapshot {\n  readonly nodes: readonly CanvasFlowNode[];\n  readonly edges: readonly CanvasFlowEdge[];\n  readonly selected: readonly string[];\n  \u002F** 批量框。撤销\u002F重做要连它一起回放，否则撤销删框后节点回来了框没了 *\u002F\n  readonly batchGroups: readonly CanvasFlowBatchGroup[];\n}\n",[35,2204,2205,2217,2235,2251,2266,2271,2287],{"__ignoreMap":33},[38,2206,2207,2209,2212,2215],{"class":40,"line":41},[38,2208,317],{"class":44},[38,2210,2211],{"class":44}," interface",[38,2213,2214],{"class":55}," GraphSnapshot",[38,2216,81],{"class":59},[38,2218,2219,2222,2225,2227,2229,2232],{"class":40,"line":84},[38,2220,2221],{"class":44},"  readonly",[38,2223,2224],{"class":599}," nodes",[38,2226,52],{"class":44},[38,2228,904],{"class":44},[38,2230,2231],{"class":55}," CanvasFlowNode",[38,2233,2234],{"class":59},"[];\n",[38,2236,2237,2239,2242,2244,2246,2249],{"class":40,"line":115},[38,2238,2221],{"class":44},[38,2240,2241],{"class":599}," edges",[38,2243,52],{"class":44},[38,2245,904],{"class":44},[38,2247,2248],{"class":55}," CanvasFlowEdge",[38,2250,2234],{"class":59},[38,2252,2253,2255,2258,2260,2262,2264],{"class":40,"line":131},[38,2254,2221],{"class":44},[38,2256,2257],{"class":599}," selected",[38,2259,52],{"class":44},[38,2261,904],{"class":44},[38,2263,605],{"class":48},[38,2265,2234],{"class":59},[38,2267,2268],{"class":40,"line":147},[38,2269,2270],{"class":229},"  \u002F** 批量框。撤销\u002F重做要连它一起回放，否则撤销删框后节点回来了框没了 *\u002F\n",[38,2272,2273,2275,2278,2280,2282,2285],{"class":40,"line":163},[38,2274,2221],{"class":44},[38,2276,2277],{"class":599}," batchGroups",[38,2279,52],{"class":44},[38,2281,904],{"class":44},[38,2283,2284],{"class":55}," CanvasFlowBatchGroup",[38,2286,2234],{"class":59},[38,2288,2289],{"class":40,"line":179},[38,2290,754],{"class":59},[10,2292,2293],{},"第四件事是状态聚合。批量框内一个节点对应多行执行状态，显示取哪个：",[28,2295,2298],{"className":2296,"code":2297,"language":378},[376],"failed > running > pending > cancelled > succeeded > idle\n",[35,2299,2297],{"__ignoreMap":33},[10,2301,2302,2303,2306],{},"原先只让 ",[35,2304,2305],{},"failed"," 优先。结果是第一份先成功、第二份还在跑的时候，节点就提前显示成「成功」。",[20,2308,2310],{"id":2309},"七续跑不重复扣费","七、续跑不重复扣费",[10,2312,2313],{},"这一项是这八项里唯一涉及钱的。",[10,2315,2316],{},"失败或被取消的运行，可以续跑。实现方式不是「重跑一遍」：",[28,2318,2320],{"className":30,"code":2319,"language":32,"meta":33,"style":33},"\u002F**\n * 单节点重试：给失败\u002F被取消的运行造一个「续跑」运行。成功节点的行原样回填\n * （产物、billingRef、时间戳都保留）——executor 的调度器见到 succeeded 行\n * 会直接当作上游已就绪，不会重新执行，也就不会重复扣费；其余节点\n * （failed \u002F cancelled \u002F pending）重置成全新的 pending 行，正常调度重跑。\n * 不修改原运行：重试是一条新的 CanvasFlowRun，历史记录保持完整。\n *\u002F\n",[35,2321,2322,2326,2331,2336,2341,2346,2351],{"__ignoreMap":33},[38,2323,2324],{"class":40,"line":41},[38,2325,788],{"class":229},[38,2327,2328],{"class":40,"line":84},[38,2329,2330],{"class":229}," * 单节点重试：给失败\u002F被取消的运行造一个「续跑」运行。成功节点的行原样回填\n",[38,2332,2333],{"class":40,"line":115},[38,2334,2335],{"class":229}," * （产物、billingRef、时间戳都保留）——executor 的调度器见到 succeeded 行\n",[38,2337,2338],{"class":40,"line":131},[38,2339,2340],{"class":229}," * 会直接当作上游已就绪，不会重新执行，也就不会重复扣费；其余节点\n",[38,2342,2343],{"class":40,"line":147},[38,2344,2345],{"class":229}," * （failed \u002F cancelled \u002F pending）重置成全新的 pending 行，正常调度重跑。\n",[38,2347,2348],{"class":40,"line":163},[38,2349,2350],{"class":229}," * 不修改原运行：重试是一条新的 CanvasFlowRun，历史记录保持完整。\n",[38,2352,2353],{"class":40,"line":179},[38,2354,842],{"class":229},[10,2356,2357,2358,2361],{},"关键在于复用判定落在行状态上，而不是「这次运行是新是旧」。所以续跑不需要额外的豁免逻辑：被判成功的节点带着原来的 ",[35,2359,2360],{},"billingRef","，调度器看到它就不再执行。",[10,2363,2364],{},"预估只算子集，余额检查同理。接口层有幂等入口（按用户 + 请求 ID 查重）和归属校验。不可重试的两种情形给出明确文案：",[28,2366,2369],{"className":2367,"code":2368,"language":378},[376],"这次运行已全部成功，没有可重试的节点\n运行还没结束，等它终结后再重试\n",[35,2370,2368],{"__ignoreMap":33},[10,2372,2373],{},"界面上的按钮从「重试」改成「重试失败节点」，带一句说明：",[518,2375,2376],{},[10,2377,2378],{},"成功节点的产物直接沿用，只有失败的节点会重新执行并计费。",[20,2380,2382],{"id":2381},"八八项之间的关系","八、八项之间的关系",[10,2384,2385],{},"单独看每一项，都是常规功能。放在一起时，出现了几条贯穿的取舍：",[10,2387,2388,2391,2392,2394],{},[207,2389,2390],{},"状态的归属要单一。"," 画布 ID 放 URL；运行状态用同一套 ",[35,2393,1887],{},"；撤销栈是唯一的变更入口。三处都收成一个来源之后，「刷新之后看到什么」才有确定答案。",[10,2396,2397,2400],{},[207,2398,2399],{},"例外要少。"," 便签不做成节点，就是为了避免在注册表、校验器、执行器三处开豁免。每开一处例外，就多一处将来会忘记的地方。",[10,2402,2403,2406],{},[207,2404,2405],{},"别抢用户的操作。"," 没选节点时不拦 Cmd+C；整理布局保留同列原有顺序；批量框拒绝时给下一步而不是只报错。",[10,2408,2409,2412],{},[207,2410,2411],{},"涉及钱的判定落在状态上。"," 续跑复用靠行状态，不靠运行的新旧；批量份数由框上显式配置，预估与执行读同一个函数。",[10,2414,2415],{},"最后一条是这一天唯一和故障档案有关的部分。同一天里，批量链路翻出一处行约定矛盾和一处从未命中的推断分支，两处的成因都是「两侧各写一份」。八项补齐之后，取值入口都比之前更集中——这不是巧合，是同一件事的两个方向。",[1097,2417,2418],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":33,"searchDepth":84,"depth":84,"links":2420},[2421,2422,2423,2424,2425,2426,2427,2428],{"id":1819,"depth":84,"text":1820},{"id":1868,"depth":84,"text":1869},{"id":1906,"depth":84,"text":1907},{"id":1984,"depth":84,"text":1985},{"id":2086,"depth":84,"text":2087},{"id":2126,"depth":84,"text":2127},{"id":2309,"depth":84,"text":2310},{"id":2381,"depth":84,"text":2382},"2026-08-20",{},"\u002F2026-08-20",{"title":1808,"description":1813},"2026-08-20-画布这一步深链运行历史与续跑","一次补齐八项画布操作：URL 深链、小地图、运行历史、复制粘贴、批量框、一键整理布局、便签，以及失败运行的单节点续跑。",[2436,2437,2438,2439,2440,2441],"画布","工作流","URL状态","撤销重做","批量执行","单节点重试","eA-RfotRTWBXXppeiZFtcguPXjtpnVRWrkWgI5LKfY0",{"id":4,"title":5,"body":2444,"column":1108,"date":1109,"description":12,"extension":1110,"hero_image":1111,"meta":3268,"navigation":266,"path":1113,"seo":3269,"series_id":1111,"severity":1111,"stem":1115,"summary":1116,"tags":3270,"__hash__":1123},{"type":7,"value":2445,"toc":3260},[2446,2448,2450,2452,2454,2456,2582,2584,2588,2592,2594,2654,2656,2658,2660,2662,2680,2682,2684,2688,2700,2702,2704,2706,2708,2713,2715,2829,2831,2837,2839,2843,3013,3015,3017,3023,3025,3027,3079,3081,3133,3135,3163,3165,3167,3169,3174,3178,3180,3184,3188,3190,3192,3208,3212,3214,3252,3256,3258],[10,2447,12],{},[10,2449,15],{},[10,2451,18],{},[20,2453,23],{"id":22},[10,2455,26],{},[28,2457,2458],{"className":30,"code":31,"language":32,"meta":33,"style":33},[35,2459,2460,2486,2508,2522,2536,2550,2564,2578],{"__ignoreMap":33},[38,2461,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484],{"class":40,"line":41},[38,2463,45],{"class":44},[38,2465,49],{"class":48},[38,2467,52],{"class":44},[38,2469,56],{"class":55},[38,2471,60],{"class":59},[38,2473,63],{"class":55},[38,2475,66],{"class":59},[38,2477,69],{"class":44},[38,2479,72],{"class":55},[38,2481,75],{"class":59},[38,2483,78],{"class":44},[38,2485,81],{"class":59},[38,2487,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506],{"class":40,"line":84},[38,2489,88],{"class":87},[38,2491,91],{"class":59},[38,2493,94],{"class":87},[38,2495,66],{"class":59},[38,2497,99],{"class":87},[38,2499,66],{"class":59},[38,2501,104],{"class":87},[38,2503,66],{"class":59},[38,2505,109],{"class":87},[38,2507,112],{"class":59},[38,2509,2510,2512,2514,2516,2518,2520],{"class":40,"line":115},[38,2511,118],{"class":87},[38,2513,91],{"class":59},[38,2515,94],{"class":87},[38,2517,66],{"class":59},[38,2519,99],{"class":87},[38,2521,112],{"class":59},[38,2523,2524,2526,2528,2530,2532,2534],{"class":40,"line":131},[38,2525,134],{"class":87},[38,2527,91],{"class":59},[38,2529,94],{"class":87},[38,2531,66],{"class":59},[38,2533,99],{"class":87},[38,2535,112],{"class":59},[38,2537,2538,2540,2542,2544,2546,2548],{"class":40,"line":147},[38,2539,150],{"class":87},[38,2541,91],{"class":59},[38,2543,94],{"class":87},[38,2545,66],{"class":59},[38,2547,99],{"class":87},[38,2549,112],{"class":59},[38,2551,2552,2554,2556,2558,2560,2562],{"class":40,"line":163},[38,2553,166],{"class":87},[38,2555,91],{"class":59},[38,2557,99],{"class":87},[38,2559,66],{"class":59},[38,2561,104],{"class":87},[38,2563,112],{"class":59},[38,2565,2566,2568,2570,2572,2574,2576],{"class":40,"line":179},[38,2567,182],{"class":87},[38,2569,91],{"class":59},[38,2571,187],{"class":87},[38,2573,66],{"class":59},[38,2575,192],{"class":87},[38,2577,112],{"class":59},[38,2579,2580],{"class":40,"line":197},[38,2581,200],{"class":59},[10,2583,203],{},[10,2585,2586,210],{},[207,2587,209],{},[10,2589,2590,216],{},[207,2591,215],{},[10,2593,219],{},[28,2595,2596],{"className":30,"code":222,"language":32,"meta":33,"style":33},[35,2597,2598,2602,2624,2628,2632],{"__ignoreMap":33},[38,2599,2600],{"class":40,"line":41},[38,2601,230],{"class":229},[38,2603,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622],{"class":40,"line":84},[38,2605,235],{"class":44},[38,2607,238],{"class":59},[38,2609,241],{"class":55},[38,2611,244],{"class":59},[38,2613,247],{"class":48},[38,2615,250],{"class":59},[38,2617,253],{"class":55},[38,2619,244],{"class":59},[38,2621,258],{"class":48},[38,2623,261],{"class":59},[38,2625,2626],{"class":40,"line":115},[38,2627,267],{"emptyLinePlaceholder":266},[38,2629,2630],{"class":40,"line":131},[38,2631,272],{"class":229},[38,2633,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652],{"class":40,"line":147},[38,2635,235],{"class":44},[38,2637,238],{"class":59},[38,2639,241],{"class":55},[38,2641,244],{"class":59},[38,2643,247],{"class":48},[38,2645,250],{"class":59},[38,2647,253],{"class":55},[38,2649,244],{"class":59},[38,2651,258],{"class":48},[38,2653,261],{"class":59},[10,2655,297],{},[20,2657,301],{"id":300},[10,2659,304],{},[10,2661,307],{},[28,2663,2664],{"className":30,"code":310,"language":32,"meta":33,"style":33},[35,2665,2666],{"__ignoreMap":33},[38,2667,2668,2670,2672,2674,2676,2678],{"class":40,"line":41},[38,2669,317],{"class":44},[38,2671,320],{"class":44},[38,2673,323],{"class":48},[38,2675,326],{"class":44},[38,2677,329],{"class":48},[38,2679,332],{"class":59},[10,2681,335],{},[10,2683,338],{},[10,2685,2686,344],{},[35,2687,343],{},[28,2689,2690],{"className":30,"code":347,"language":32,"meta":33,"style":33},[35,2691,2692,2696],{"__ignoreMap":33},[38,2693,2694],{"class":40,"line":41},[38,2695,354],{"class":229},[38,2697,2698],{"class":40,"line":84},[38,2699,359],{"class":229},[10,2701,362],{},[20,2703,366],{"id":365},[10,2705,369],{},[10,2707,372],{},[28,2709,2711],{"className":2710,"code":377,"language":378},[376],[35,2712,377],{"__ignoreMap":33},[10,2714,383],{},[28,2716,2717],{"className":30,"code":386,"language":32,"meta":33,"style":33},[35,2718,2719,2747,2757,2767,2777,2787,2797,2801,2811,2815],{"__ignoreMap":33},[38,2720,2721,2723,2725,2727,2729,2731,2733,2735,2737,2739,2741,2743,2745],{"class":40,"line":41},[38,2722,45],{"class":44},[38,2724,395],{"class":48},[38,2726,52],{"class":44},[38,2728,400],{"class":55},[38,2730,60],{"class":59},[38,2732,405],{"class":55},[38,2734,60],{"class":59},[38,2736,63],{"class":55},[38,2738,66],{"class":59},[38,2740,414],{"class":48},[38,2742,417],{"class":59},[38,2744,78],{"class":44},[38,2746,81],{"class":59},[38,2748,2749,2751,2753,2755],{"class":40,"line":84},[38,2750,182],{"class":87},[38,2752,428],{"class":59},[38,2754,431],{"class":48},[38,2756,434],{"class":59},[38,2758,2759,2761,2763,2765],{"class":40,"line":115},[38,2760,88],{"class":87},[38,2762,428],{"class":59},[38,2764,443],{"class":48},[38,2766,434],{"class":59},[38,2768,2769,2771,2773,2775],{"class":40,"line":131},[38,2770,118],{"class":87},[38,2772,428],{"class":59},[38,2774,443],{"class":48},[38,2776,434],{"class":59},[38,2778,2779,2781,2783,2785],{"class":40,"line":147},[38,2780,134],{"class":87},[38,2782,428],{"class":59},[38,2784,443],{"class":48},[38,2786,434],{"class":59},[38,2788,2789,2791,2793,2795],{"class":40,"line":163},[38,2790,150],{"class":87},[38,2792,428],{"class":59},[38,2794,474],{"class":48},[38,2796,434],{"class":59},[38,2798,2799],{"class":40,"line":179},[38,2800,481],{"class":229},[38,2802,2803,2805,2807,2809],{"class":40,"line":197},[38,2804,166],{"class":87},[38,2806,428],{"class":59},[38,2808,443],{"class":48},[38,2810,434],{"class":59},[38,2812,2813],{"class":40,"line":494},[38,2814,200],{"class":59},[38,2816,2817,2819,2821,2823,2825,2827],{"class":40,"line":499},[38,2818,317],{"class":44},[38,2820,320],{"class":44},[38,2822,506],{"class":48},[38,2824,326],{"class":44},[38,2826,511],{"class":48},[38,2828,332],{"class":59},[10,2830,516],{},[518,2832,2833],{},[10,2834,522,2835,526],{},[207,2836,525],{},[10,2838,529],{},[10,2840,532,2841,536],{},[35,2842,535],{},[28,2844,2845],{"className":30,"code":539,"language":32,"meta":33,"style":33},[35,2846,2847,2851,2865,2869,2881,2885,2925,2937,2971,2991,3009],{"__ignoreMap":33},[38,2848,2849],{"class":40,"line":41},[38,2850,546],{"class":229},[38,2852,2853,2855,2857,2859,2861,2863],{"class":40,"line":84},[38,2854,317],{"class":44},[38,2856,320],{"class":44},[38,2858,555],{"class":48},[38,2860,326],{"class":44},[38,2862,560],{"class":48},[38,2864,332],{"class":59},[38,2866,2867],{"class":40,"line":115},[38,2868,567],{"class":229},[38,2870,2871,2873,2875,2877,2879],{"class":40,"line":131},[38,2872,45],{"class":44},[38,2874,574],{"class":48},[38,2876,326],{"class":44},[38,2878,560],{"class":48},[38,2880,332],{"class":59},[38,2882,2883],{"class":40,"line":147},[38,2884,267],{"emptyLinePlaceholder":266},[38,2886,2887,2889,2891,2893,2895,2897,2899,2901,2903,2905,2907,2909,2911,2913,2915,2917,2919,2921,2923],{"class":40,"line":163},[38,2888,317],{"class":44},[38,2890,591],{"class":44},[38,2892,594],{"class":55},[38,2894,244],{"class":59},[38,2896,600],{"class":599},[38,2898,52],{"class":44},[38,2900,605],{"class":48},[38,2902,608],{"class":44},[38,2904,611],{"class":48},[38,2906,66],{"class":59},[38,2908,616],{"class":599},[38,2910,619],{"class":44},[38,2912,622],{"class":48},[38,2914,625],{"class":59},[38,2916,52],{"class":44},[38,2918,622],{"class":48},[38,2920,608],{"class":44},[38,2922,634],{"class":48},[38,2924,81],{"class":59},[38,2926,2927,2929,2931,2933,2935],{"class":40,"line":179},[38,2928,641],{"class":44},[38,2930,644],{"class":48},[38,2932,326],{"class":44},[38,2934,649],{"class":55},[38,2936,652],{"class":59},[38,2938,2939,2941,2943,2945,2947,2949,2951,2953,2955,2957,2959,2961,2963,2965,2967,2969],{"class":40,"line":197},[38,2940,657],{"class":44},[38,2942,660],{"class":59},[38,2944,663],{"class":44},[38,2946,666],{"class":59},[38,2948,669],{"class":44},[38,2950,672],{"class":44},[38,2952,675],{"class":59},[38,2954,678],{"class":55},[38,2956,681],{"class":59},[38,2958,669],{"class":44},[38,2960,686],{"class":59},[38,2962,689],{"class":44},[38,2964,692],{"class":48},[38,2966,695],{"class":59},[38,2968,235],{"class":44},[38,2970,700],{"class":59},[38,2972,2973,2975,2977,2979,2981,2983,2985,2987,2989],{"class":40,"line":494},[38,2974,641],{"class":44},[38,2976,707],{"class":48},[38,2978,326],{"class":44},[38,2980,238],{"class":59},[38,2982,714],{"class":55},[38,2984,717],{"class":59},[38,2986,720],{"class":44},[38,2988,555],{"class":48},[38,2990,725],{"class":59},[38,2992,2993,2995,2997,2999,3001,3003,3005,3007],{"class":40,"line":499},[38,2994,730],{"class":44},[38,2996,733],{"class":59},[38,2998,736],{"class":44},[38,3000,238],{"class":59},[38,3002,253],{"class":55},[38,3004,743],{"class":59},[38,3006,52],{"class":44},[38,3008,748],{"class":59},[38,3010,3011],{"class":40,"line":751},[38,3012,754],{"class":59},[10,3014,757],{},[20,3016,761],{"id":760},[10,3018,764,3019,768,3021,772],{},[35,3020,767],{},[35,3022,771],{},[10,3024,775],{},[10,3026,778],{},[28,3028,3029],{"className":30,"code":781,"language":32,"meta":33,"style":33},[35,3030,3031,3035,3039,3043,3047,3051,3055,3059,3063,3067,3071,3075],{"__ignoreMap":33},[38,3032,3033],{"class":40,"line":41},[38,3034,788],{"class":229},[38,3036,3037],{"class":40,"line":84},[38,3038,793],{"class":229},[38,3040,3041],{"class":40,"line":115},[38,3042,798],{"class":229},[38,3044,3045],{"class":40,"line":131},[38,3046,803],{"class":229},[38,3048,3049],{"class":40,"line":147},[38,3050,798],{"class":229},[38,3052,3053],{"class":40,"line":163},[38,3054,812],{"class":229},[38,3056,3057],{"class":40,"line":179},[38,3058,817],{"class":229},[38,3060,3061],{"class":40,"line":197},[38,3062,822],{"class":229},[38,3064,3065],{"class":40,"line":494},[38,3066,827],{"class":229},[38,3068,3069],{"class":40,"line":499},[38,3070,798],{"class":229},[38,3072,3073],{"class":40,"line":751},[38,3074,836],{"class":229},[38,3076,3077],{"class":40,"line":839},[38,3078,842],{"class":229},[10,3080,845],{},[28,3082,3083],{"className":30,"code":848,"language":32,"meta":33,"style":33},[35,3084,3085,3089,3093,3097,3101],{"__ignoreMap":33},[38,3086,3087],{"class":40,"line":41},[38,3088,788],{"class":229},[38,3090,3091],{"class":40,"line":84},[38,3092,859],{"class":229},[38,3094,3095],{"class":40,"line":115},[38,3096,864],{"class":229},[38,3098,3099],{"class":40,"line":131},[38,3100,842],{"class":229},[38,3102,3103,3105,3107,3109,3111,3113,3115,3117,3119,3121,3123,3125,3127,3129,3131],{"class":40,"line":147},[38,3104,317],{"class":44},[38,3106,591],{"class":44},[38,3108,877],{"class":55},[38,3110,244],{"class":59},[38,3112,882],{"class":599},[38,3114,52],{"class":44},[38,3116,887],{"class":87},[38,3118,608],{"class":44},[38,3120,892],{"class":87},[38,3122,66],{"class":59},[38,3124,897],{"class":599},[38,3126,625],{"class":59},[38,3128,52],{"class":44},[38,3130,904],{"class":44},[38,3132,907],{"class":59},[10,3134,910],{},[28,3136,3137],{"className":30,"code":913,"language":32,"meta":33,"style":33},[35,3138,3139,3143,3147,3151,3155,3159],{"__ignoreMap":33},[38,3140,3141],{"class":40,"line":41},[38,3142,788],{"class":229},[38,3144,3145],{"class":40,"line":84},[38,3146,924],{"class":229},[38,3148,3149],{"class":40,"line":115},[38,3150,929],{"class":229},[38,3152,3153],{"class":40,"line":131},[38,3154,934],{"class":229},[38,3156,3157],{"class":40,"line":147},[38,3158,939],{"class":229},[38,3160,3161],{"class":40,"line":163},[38,3162,842],{"class":229},[20,3164,947],{"id":946},[10,3166,950],{},[10,3168,953],{},[28,3170,3172],{"className":3171,"code":957,"language":378},[376],[35,3173,957],{"__ignoreMap":33},[10,3175,962,3176,966],{},[35,3177,965],{},[10,3179,969],{},[10,3181,972,3182,976],{},[35,3183,975],{},[10,3185,979,3186,983],{},[207,3187,982],{},[20,3189,987],{"id":986},[10,3191,990],{},[992,3193,3194,3200],{},[995,3195,997,3196,1001,3198,1005],{},[35,3197,1000],{},[35,3199,1004],{},[995,3201,1008,3202,1012,3204,1016,3206,1020],{},[35,3203,1011],{},[35,3205,1015],{},[35,3207,1019],{},[10,3209,1023,3210,1027],{},[207,3211,1026],{},[10,3213,1030],{},[1032,3215,3216,3226],{},[1035,3217,3218],{},[1038,3219,3220,3222,3224],{},[1041,3221,1043],{},[1041,3223,1046],{},[1041,3225,1049],{},[1051,3227,3228,3236,3244],{},[1038,3229,3230,3232,3234],{},[1056,3231,1058],{},[1056,3233,1061],{},[1056,3235,1064],{},[1038,3237,3238,3240,3242],{},[1056,3239,1069],{},[1056,3241,1072],{},[1056,3243,1075],{},[1038,3245,3246,3248,3250],{},[1056,3247,1080],{},[1056,3249,1083],{},[1056,3251,1086],{},[10,3253,1089,3254],{},[207,3255,1092],{},[10,3257,1095],{},[1097,3259,1099],{},{"title":33,"searchDepth":84,"depth":84,"links":3261},[3262,3263,3264,3265,3266,3267],{"id":22,"depth":84,"text":23},{"id":300,"depth":84,"text":301},{"id":365,"depth":84,"text":366},{"id":760,"depth":84,"text":761},{"id":946,"depth":84,"text":947},{"id":986,"depth":84,"text":987},{},{"title":5,"description":12},[1118,1119,1120,1121,1122],1789212573134]