w-dispatch-ai 1.0.2 → 1.0.4

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.
Files changed (45) hide show
  1. package/README.md +123 -35
  2. package/dist/w-dispatch-ai.umd.js +2 -2
  3. package/dist/w-dispatch-ai.umd.js.map +1 -1
  4. package/docs/WDispatchAi.mjs.html +8 -4
  5. package/docs/adapters.mjs.html +43 -7
  6. package/docs/dispatchAi.mjs.html +2 -2
  7. package/docs/dispatchAiFallback.mjs.html +42 -8
  8. package/docs/dispatchAiWkf.mjs.html +197 -0
  9. package/docs/dispatchAntigravity.mjs.html +2 -2
  10. package/docs/dispatchApiOpenaiCompat.mjs.html +516 -0
  11. package/docs/dispatchClaude.mjs.html +2 -2
  12. package/docs/dispatchCodex.mjs.html +2 -2
  13. package/docs/dispatchOpencode.mjs.html +2 -2
  14. package/docs/getCliArgs.mjs.html +2 -2
  15. package/docs/getErrorResult.mjs.html +2 -2
  16. package/docs/global.html +5768 -1469
  17. package/docs/index.html +2 -2
  18. package/docs/wkf_callAiWithFallback.mjs.html +282 -0
  19. package/docs/wkf_extractJsonLoose.mjs.html +180 -0
  20. package/docs/wkf_runFanout.mjs.html +227 -0
  21. package/docs/wkf_runFanoutPipeline.mjs.html +178 -0
  22. package/docs/wkf_runRolePipeline.mjs.html +195 -0
  23. package/g.mjs +41 -25
  24. package/package.json +1 -1
  25. package/src/WDispatchAi.mjs +6 -2
  26. package/src/adapters.mjs +41 -5
  27. package/src/dispatchAiFallback.mjs +40 -6
  28. package/src/dispatchAiWkf.mjs +125 -0
  29. package/src/dispatchApiOpenaiCompat.mjs +444 -0
  30. package/src/wkf/callAiWithFallback.mjs +210 -0
  31. package/src/wkf/extractJsonLoose.mjs +108 -0
  32. package/src/wkf/runFanout.mjs +155 -0
  33. package/src/wkf/runFanoutPipeline.mjs +106 -0
  34. package/src/wkf/runRolePipeline.mjs +123 -0
  35. package/test/tools/fakeServerForApiTest.mjs +151 -0
  36. package/test/unit-WDispatchAi.test.mjs +13 -6
  37. package/test/unit-adapters.test.mjs +5 -3
  38. package/test/unit-callAiWithFallback.test.mjs +146 -0
  39. package/test/unit-dispatchAi.test.mjs +1 -1
  40. package/test/unit-dispatchAiWkf.test.mjs +118 -0
  41. package/test/unit-dispatchApiOpenaiCompat.test.mjs +264 -0
  42. package/test/unit-extractJsonLoose.test.mjs +78 -0
  43. package/test/unit-runFanout.test.mjs +166 -0
  44. package/test/unit-runFanoutPipeline.test.mjs +86 -0
  45. package/test/unit-runRolePipeline.test.mjs +163 -0
@@ -0,0 +1,227 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>wkf/runFanout.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">wkf/runFanout.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import isearr from 'wsemi/src/isearr.mjs'
50
+ import isestr from 'wsemi/src/isestr.mjs'
51
+ import isfun from 'wsemi/src/isfun.mjs'
52
+ import ispint from 'wsemi/src/ispint.mjs'
53
+ import cint from 'wsemi/src/cint.mjs'
54
+ import callAiWithFallback from './callAiWithFallback.mjs'
55
+
56
+
57
+ // runFanout.mjs — Fanout工作流: 多開執行+單點整合收斂
58
+ //
59
+ // 【結構】前段(fanout)並行開N個AI名額執行同一任務, 各名額可指定主模型與自帶fallback;
60
+ // 後段開單一AI名額把成功候選整合成最終版; 整合成果即工作流成果。
61
+ //
62
+ // 【部分接受】個別名額失敗不炸整輪: 成功候選達minCandidates才進整合;
63
+ // 未達門檻(含恰為1份)時不硬整合, 直接以首位成功候選為成果(integrated:false)——
64
+ // 單稿無從「整合」, 硬呼叫整合者只是空耗一次額度。
65
+ // 全部失敗才回ok:false, 且已成功候選仍完整回傳(便於接續重試)。
66
+ //
67
+ // 【實測依據(2026-08-10評比)】整合者是本流程的單點故障——端點不穩的模型
68
+ // (如偶發靜默空回者)當整合者時, 靠spec.fallback遞補或maxRetries調高才能保住整條鏈。
69
+
70
+
71
+ /**
72
+ * 預設整合提示詞模板:把成功候選JSON併入整合任務
73
+ *
74
+ * @param {Array} candidates 輸入成功候選物件陣列
75
+ * @param {Object} [opt={}] 輸入設定物件(取schema作為輸出格式示意),預設{}
76
+ * @returns {String} 回傳整合提示詞字串
77
+ */
78
+ function defaultIntegratePrompt(candidates, opt = {}) {
79
+ let schema = get(opt, 'schema', '')
80
+ let schemaLine = isestr(schema) ? `\n只回覆 JSON 物件,不要任何其他說明文字,格式與候選相同:\n${schema}\n` : '\n只回覆 JSON 物件,不要任何其他說明文字,格式與候選相同。\n'
81
+ return `你是整合者。以下是同一任務由 ${candidates.length} 個獨立執行產生的候選結果(JSON),請整合成單一最佳版本:擇優合併、去重、保留最完整的證據標注與爭議呈現,不可加入候選中沒有的數字或結論。
82
+ ${schemaLine}
83
+ ${candidates.map((c, i) => `【候選 ${i + 1}】\n${JSON.stringify(c)}`).join('\n\n')}`
84
+ }
85
+
86
+
87
+ /**
88
+ * 執行Fanout工作流:多開執行與單點整合
89
+ *
90
+ * 特點:
91
+ * 前段各名額並行執行同一任務,各名額可指定主模型(use)與自帶遞補鏈(fallback);
92
+ * 後段為單一整合名額,同樣可帶遞補鏈;
93
+ * 個別名額失敗不中斷整輪,成功候選未達minCandidates時以首位候選為成果(integrated:false)不硬整合;
94
+ * 成功候選完整保留於回傳(部分接受、便於接續重試整合);
95
+ * 本函數不會reject
96
+ *
97
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
98
+ * @param {Object} opt.providers 輸入provider定義表物件(名稱 → 條目),透傳callAiWithFallback
99
+ * @param {String} opt.task 輸入前段各名額共用之任務提示詞字串
100
+ * @param {Array} opt.agents 輸入前段名額規格陣列,各元素{ use, fallback, maxRetries?, timeoutMs? }等(除use/fallback外之鍵覆寫該名額呼叫設定)
101
+ * @param {Object} opt.integrate 輸入整合名額規格物件{ use, fallback, prompt?, ... },prompt可為(candidates)=>String自訂整合提示詞,省略用預設模板
102
+ * @param {Function} [opt.check=null] 輸入候選與終稿共用之檢核函數(json)=>Boolean,預設null
103
+ * @param {String} [opt.schema=''] 輸入輸出格式示意字串,供預設整合模板嵌入,預設''
104
+ * @param {Number} [opt.minCandidates=2] 輸入進入整合所需之最少成功候選數正整數,未達門檻以首位候選為成果,預設2
105
+ * @param {Object} [opt.callOpt={}] 輸入透傳callAiWithFallback之共用設定(cwd、store、onEvent、timeoutMs、promptPrefix等),預設{}
106
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(工作流成果)、integrated(是否經過整合布林值)、agents(各名額完整結果陣列)、candidates(成功候選陣列)、integrateDetail(整合呼叫完整結果)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
107
+ * @example
108
+ * //need cli in system PATH
109
+ *
110
+ * import runFanout from './src/wkf/runFanout.mjs'
111
+ *
112
+ * let providers = {
113
+ * 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
114
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
115
+ * }
116
+ *
117
+ * let test = async () => {
118
+ *
119
+ * let r = await runFanout({
120
+ * providers,
121
+ * task: '分析並只回覆JSON: {"essence":"..."}',
122
+ * agents: [
123
+ * { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
124
+ * { use: 'claude:sonnet' },
125
+ * ],
126
+ * integrate: { use: 'claude:sonnet' },
127
+ * check: (j) => !!j.essence,
128
+ * })
129
+ * console.log(r.ok, r.integrated, r.candidates.length)
130
+ * // => true true 2
131
+ *
132
+ * }
133
+ * await test()
134
+ * .catch((err) => {
135
+ * console.log(err)
136
+ * })
137
+ *
138
+ */
139
+ async function runFanout(opt = {}) {
140
+ let t0 = Date.now()
141
+ let providers = get(opt, 'providers', null)
142
+ let task = get(opt, 'task', '')
143
+ let agents = get(opt, 'agents', null)
144
+ let integrate = get(opt, 'integrate', null)
145
+ let check = get(opt, 'check', null)
146
+ let callOpt = get(opt, 'callOpt', {})
147
+
148
+ if (!isestr(task)) {
149
+ return { ok: false, result: null, integrated: false, agents: [], candidates: [], totalMs: 0, error: 'task must be a non-empty string' }
150
+ }
151
+ if (!isearr(agents)) {
152
+ return { ok: false, result: null, integrated: false, agents: [], candidates: [], totalMs: 0, error: 'agents must be a non-empty array' }
153
+ }
154
+
155
+ let minCandidates = get(opt, 'minCandidates', null)
156
+ if (!ispint(minCandidates)) {
157
+ minCandidates = 2
158
+ }
159
+ else {
160
+ minCandidates = cint(minCandidates)
161
+ }
162
+
163
+ //前段: 並行多開, 個別失敗不炸整輪
164
+ let rsAgents = await Promise.all(agents.map((spec) => {
165
+ let { use, fallback, ...overrides } = spec
166
+ return callAiWithFallback(task, { ...callOpt, ...overrides, providers, spec: { use, fallback }, check })
167
+ }))
168
+ let candidates = rsAgents.filter((r) => r.ok).map((r) => r.json)
169
+
170
+ //全部失敗
171
+ if (candidates.length === 0) {
172
+ return { ok: false, result: null, integrated: false, agents: rsAgents, candidates, totalMs: Date.now() - t0, error: 'all agents failed' }
173
+ }
174
+
175
+ //未達整合門檻(含恰為1份): 不硬整合, 以首位成功候選為成果
176
+ if (candidates.length === 1 || candidates.length &lt; minCandidates) {
177
+ return { ok: true, result: candidates[0], integrated: false, agents: rsAgents, candidates, totalMs: Date.now() - t0, error: '' }
178
+ }
179
+
180
+ //後段: 整合
181
+ if (!integrate || !isestr(get(integrate, 'use', ''))) {
182
+ return { ok: false, result: null, integrated: false, agents: rsAgents, candidates, totalMs: Date.now() - t0, error: 'integrate spec (with use) is required' }
183
+ }
184
+ let { use, fallback, prompt: intPromptFn, ...intOverrides } = integrate
185
+ let intPrompt = isfun(intPromptFn) ? intPromptFn(candidates) : defaultIntegratePrompt(candidates, opt)
186
+ let rInt = await callAiWithFallback(intPrompt, { ...callOpt, ...intOverrides, providers, spec: { use, fallback }, check })
187
+
188
+ return {
189
+ ok: rInt.ok,
190
+ result: rInt.ok ? rInt.json : null,
191
+ integrated: rInt.ok,
192
+ agents: rsAgents,
193
+ candidates, //即使整合失敗, 成功候選仍完整回傳, 供接續重試整合(只重跑整合段)
194
+ integrateDetail: rInt,
195
+ totalMs: Date.now() - t0,
196
+ error: rInt.ok ? '' : `integrate failed: ${rInt.error}`,
197
+ }
198
+ }
199
+
200
+
201
+ export default runFanout
202
+ export { defaultIntegratePrompt }
203
+ </code></pre>
204
+ </article>
205
+ </section>
206
+
207
+
208
+
209
+
210
+
211
+
212
+ </div>
213
+
214
+ <br class="clear">
215
+
216
+ <footer>
217
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
218
+ </footer>
219
+
220
+ <script>prettyPrint();</script>
221
+ <script src="scripts/polyfill.js"></script>
222
+ <script src="scripts/linenumber.js"></script>
223
+
224
+
225
+
226
+ </body>
227
+ </html>
@@ -0,0 +1,178 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>wkf/runFanoutPipeline.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">wkf/runFanoutPipeline.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import runFanout from './runFanout.mjs'
50
+ import runRolePipeline from './runRolePipeline.mjs'
51
+
52
+
53
+ // runFanoutPipeline.mjs — FanoutPipeline工作流(Fanout+RolePipeline): 多開收斂成果接串行角色鏈
54
+ //
55
+ // 【結構】先跑runFanout(多開 → 整合), 其成果作為runRolePipeline的input
56
+ // (各階段以ctx.input取用), 最末階段回傳即工作流成果。
57
+ //
58
+ // 【實測依據(2026-08-10評比)】Fanout+RolePipeline是品質天花板: 前段的多樣性擇優給出最豐底稿、
59
+ // 審計鏈再修幻覺與證據標注; 六模型的歷史最高品質全部出現在此組合(或與純RolePipeline並列)。
60
+ //
61
+ // 【部分接受】前段失敗即回(附前段完整明細, 含已成功候選);
62
+ // 後段失敗回傳前段成果與後段已完成階段——呼叫端可只重跑失敗段。
63
+
64
+
65
+ /**
66
+ * 執行FanoutPipeline工作流(Fanout+RolePipeline):多開+整合+串行角色鏈
67
+ *
68
+ * 特點:
69
+ * 前段同runFanout(agents各名額可自帶fallback、integrate單點整合);
70
+ * 後段同runRolePipeline(stages各階段可自帶AI/fallback/提示詞),其input即前段成果;
71
+ * 本函數不會reject
72
+ *
73
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
74
+ * @param {Object} opt.providers 輸入provider定義表物件(名稱 → 條目)
75
+ * @param {String} opt.task 輸入前段各名額共用之任務提示詞字串
76
+ * @param {Array} opt.agents 輸入前段名額規格陣列(同runFanout)
77
+ * @param {Object} opt.integrate 輸入前段整合名額規格物件(同runFanout)
78
+ * @param {Array} opt.stages 輸入後段階段規格陣列(同runRolePipeline),各階段以ctx.input取得前段成果
79
+ * @param {Function} [opt.check=null] 輸入前段共用檢核函數,後段各階段自帶check,預設null
80
+ * @param {String} [opt.schema=''] 輸入輸出格式示意字串(供前段預設整合模板),預設''
81
+ * @param {Number} [opt.minCandidates=2] 輸入前段整合門檻正整數,預設2
82
+ * @param {Object} [opt.callOpt={}] 輸入透傳兩段之共用呼叫設定,預設{}
83
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(工作流成果)、A(前段runFanout完整結果)、B(後段runRolePipeline完整結果)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
84
+ * @example
85
+ * //need cli in system PATH
86
+ *
87
+ * import runFanoutPipeline from './src/wkf/runFanoutPipeline.mjs'
88
+ *
89
+ * let providers = {
90
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
91
+ * 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
92
+ * }
93
+ *
94
+ * let test = async () => {
95
+ *
96
+ * let r = await runFanoutPipeline({
97
+ * providers,
98
+ * task: '分析並只回覆JSON: {"essence":"..."}',
99
+ * agents: [{ use: 'claude:sonnet' }, { use: 'codex:gpt-5.6-luna' }],
100
+ * integrate: { use: 'claude:sonnet' },
101
+ * stages: [
102
+ * { id: 'audit', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
103
+ * ],
104
+ * check: (j) => !!j.essence,
105
+ * })
106
+ * console.log(r.ok, r.A.integrated, r.B.order)
107
+ * // => true true [ 'audit' ]
108
+ *
109
+ * }
110
+ * await test()
111
+ * .catch((err) => {
112
+ * console.log(err)
113
+ * })
114
+ *
115
+ */
116
+ async function runFanoutPipeline(opt = {}) {
117
+ let t0 = Date.now()
118
+
119
+ //前段: Fanout(多開 → 整合)
120
+ let rA = await runFanout({
121
+ providers: get(opt, 'providers', null),
122
+ task: get(opt, 'task', ''),
123
+ agents: get(opt, 'agents', null),
124
+ integrate: get(opt, 'integrate', null),
125
+ check: get(opt, 'check', null),
126
+ schema: get(opt, 'schema', ''),
127
+ minCandidates: get(opt, 'minCandidates', null),
128
+ callOpt: get(opt, 'callOpt', {}),
129
+ })
130
+ if (!rA.ok) {
131
+ return { ok: false, result: null, A: rA, B: null, totalMs: Date.now() - t0, error: `A failed: ${rA.error}` }
132
+ }
133
+
134
+ //後段: RolePipeline, input即前段成果
135
+ let rB = await runRolePipeline({
136
+ providers: get(opt, 'providers', null),
137
+ input: rA.result,
138
+ stages: get(opt, 'stages', null),
139
+ callOpt: get(opt, 'callOpt', {}),
140
+ })
141
+
142
+ return {
143
+ ok: rB.ok,
144
+ result: rB.ok ? rB.result : null,
145
+ A: rA, //前段成果與明細一律回傳——後段失敗時可據此只重跑後段
146
+ B: rB,
147
+ totalMs: Date.now() - t0,
148
+ error: rB.ok ? '' : `B failed: ${rB.error}`,
149
+ }
150
+ }
151
+
152
+
153
+ export default runFanoutPipeline
154
+ </code></pre>
155
+ </article>
156
+ </section>
157
+
158
+
159
+
160
+
161
+
162
+
163
+ </div>
164
+
165
+ <br class="clear">
166
+
167
+ <footer>
168
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
169
+ </footer>
170
+
171
+ <script>prettyPrint();</script>
172
+ <script src="scripts/polyfill.js"></script>
173
+ <script src="scripts/linenumber.js"></script>
174
+
175
+
176
+
177
+ </body>
178
+ </html>
@@ -0,0 +1,195 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>wkf/runRolePipeline.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">wkf/runRolePipeline.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import omit from 'lodash-es/omit.js'
50
+ import isearr from 'wsemi/src/isearr.mjs'
51
+ import isestr from 'wsemi/src/isestr.mjs'
52
+ import isfun from 'wsemi/src/isfun.mjs'
53
+ import callAiWithFallback from './callAiWithFallback.mjs'
54
+
55
+
56
+ // runRolePipeline.mjs — RolePipeline工作流: 多角色串行鏈(stage1 → … → stageN)
57
+ //
58
+ // 【結構】各階段可各自指定AI(use)、遞補鏈(fallback)、提示詞(角色與任務)與檢核;
59
+ // 前一階段的成果傳給下一階段、一路傳到最末階段, 其成果即工作流成果。
60
+ //
61
+ // 【提示詞為函數】stage.prompt收ctx = { input, prev, results, index }:
62
+ // input=工作流輸入(如原始任務或Fanout的整合稿)、prev=上一階段成果、
63
+ // results=已完成各階段成果(依id查詢, 例如修訂階段要同時引用初稿與審計意見)。
64
+ //
65
+ // 【部分接受】某階段(含遞補全敗)失敗即中止後續, 但已完成階段之成果完整回傳
66
+ // (failedStage標明斷點), 呼叫端可據此只重跑失敗段而非整條鏈。
67
+ //
68
+ // 【實測依據(2026-08-10評比)】審計類角色鏈曾出現「審計刪過頭」——
69
+ // 修訂/終審提示詞應包含護欄「意見未涉及的內容不可刪除」; 此屬提示詞設計,
70
+ // 本函數不代寫角色提示詞, 由呼叫端(或上層預設模板)自理。
71
+
72
+
73
+ //各階段規格自用之鍵, 其餘鍵覆寫該階段之呼叫設定
74
+ let STAGE_KEYS = ['id', 'use', 'fallback', 'prompt', 'check']
75
+
76
+
77
+ /**
78
+ * 執行RolePipeline工作流:多角色串行鏈
79
+ *
80
+ * 特點:
81
+ * 各階段可各自指定use/fallback/prompt/check(含rawText純文字階段);
82
+ * 階段成果依序傳遞,最末階段成果即工作流成果;
83
+ * 失敗即止但已完成成果完整回傳(部分接受、便於接續重跑失敗段);
84
+ * 本函數不會reject
85
+ *
86
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
87
+ * @param {Object} opt.providers 輸入provider定義表物件(名稱 → 條目)
88
+ * @param {*} [opt.input=null] 輸入工作流輸入(原始任務字串或前一工作流之成果物件),提供給各階段ctx.input,預設null
89
+ * @param {Array} opt.stages 輸入階段規格陣列,各元素{ id, use, fallback, prompt:(ctx)=>String, check?, rawText?, maxRetries?, timeoutMs? }等
90
+ * @param {Object} [opt.callOpt={}] 輸入透傳callAiWithFallback之共用設定,預設{}
91
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(最末階段成果)、stages(id對階段完整呼叫結果之物件)、results(id對階段成果之物件)、order(階段id順序陣列)、failedStage(失敗階段id,無失敗為null)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
92
+ * @example
93
+ * //need cli in system PATH
94
+ *
95
+ * import runRolePipeline from './src/wkf/runRolePipeline.mjs'
96
+ *
97
+ * let providers = {
98
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
99
+ * 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
100
+ * }
101
+ *
102
+ * let test = async () => {
103
+ *
104
+ * let r = await runRolePipeline({
105
+ * providers,
106
+ * input: '原始任務',
107
+ * stages: [
108
+ * { id: 'draft', use: 'claude:sonnet', prompt: (ctx) => `就「${ctx.input}」寫初稿, 只回覆JSON: {"text":"..."}` },
109
+ * { id: 'review', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審閱並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.prev)}` },
110
+ * ],
111
+ * })
112
+ * console.log(r.ok, r.order, r.failedStage)
113
+ * // => true [ 'draft', 'review' ] null
114
+ *
115
+ * }
116
+ * await test()
117
+ * .catch((err) => {
118
+ * console.log(err)
119
+ * })
120
+ *
121
+ */
122
+ async function runRolePipeline(opt = {}) {
123
+ let t0 = Date.now()
124
+ let providers = get(opt, 'providers', null)
125
+ let input = get(opt, 'input', null)
126
+ let stages = get(opt, 'stages', null)
127
+ let callOpt = get(opt, 'callOpt', {})
128
+
129
+ if (!isearr(stages)) {
130
+ return { ok: false, result: null, stages: {}, results: {}, order: [], failedStage: null, totalMs: 0, error: 'stages must be a non-empty array' }
131
+ }
132
+
133
+ let results = {} //id → 成果(json或文字)
134
+ let details = {} //id → 完整呼叫結果
135
+ let order = []
136
+ let prev = null
137
+
138
+ for (let i = 0; i &lt; stages.length; i++) {
139
+ let stage = stages[i]
140
+ let id = isestr(get(stage, 'id', '')) ? stage.id : `stage${i + 1}`
141
+ order.push(id)
142
+
143
+ let promptFn = get(stage, 'prompt', null)
144
+ if (!isfun(promptFn)) {
145
+ details[id] = { ok: false, error: `stage[${id}].prompt must be a function` }
146
+ return { ok: false, result: null, stages: details, results, order, failedStage: id, totalMs: Date.now() - t0, error: details[id].error }
147
+ }
148
+ let prompt = promptFn({ input, prev, results, index: i })
149
+ if (!isestr(prompt)) {
150
+ details[id] = { ok: false, error: `stage[${id}].prompt returned empty` }
151
+ return { ok: false, result: null, stages: details, results, order, failedStage: id, totalMs: Date.now() - t0, error: details[id].error }
152
+ }
153
+
154
+ //overrides, 剔除階段自用鍵後其餘鍵覆寫該階段呼叫設定(rawText、maxRetries、timeoutMs等)
155
+ let overrides = omit(stage, STAGE_KEYS)
156
+ let r = await callAiWithFallback(prompt, { ...callOpt, ...overrides, providers, spec: { use: get(stage, 'use', ''), fallback: get(stage, 'fallback', null) }, check: get(stage, 'check', null) })
157
+ details[id] = r
158
+ if (!r.ok) {
159
+ //失敗即止: 已完成階段成果保留於results, 供呼叫端接續重跑
160
+ return { ok: false, result: null, stages: details, results, order, failedStage: id, totalMs: Date.now() - t0, error: `stage[${id}] failed: ${r.error}` }
161
+ }
162
+ results[id] = r.json
163
+ prev = r.json
164
+ }
165
+
166
+ return { ok: true, result: prev, stages: details, results, order, failedStage: null, totalMs: Date.now() - t0, error: '' }
167
+ }
168
+
169
+
170
+ export default runRolePipeline
171
+ </code></pre>
172
+ </article>
173
+ </section>
174
+
175
+
176
+
177
+
178
+
179
+
180
+ </div>
181
+
182
+ <br class="clear">
183
+
184
+ <footer>
185
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
186
+ </footer>
187
+
188
+ <script>prettyPrint();</script>
189
+ <script src="scripts/polyfill.js"></script>
190
+ <script src="scripts/linenumber.js"></script>
191
+
192
+
193
+
194
+ </body>
195
+ </html>