47c6aaea03
SDD: workflow-discovery 3.12/3.13(不是新規格;3.12 已 confirmed 並實作完成) ## 1) workers_ai_chat 種子(新) Cloudflare Workers AI 走 env.AI binding ⇒ 用戶不必填任何 API 金鑰就能問答。 放種子表而非產品安裝器:「裝好後預設有哪些 recipe」是平台能力(rule 07 薄殼原則)。 換模型/換供應商=改這一筆 recipe,workflow 不動。 選型實測(1.4.4 實例,真實長度 RAG prompt,每個模型連跑 2 次): llama-4-scout-17b 2373/2173 ms ✅ 答案最完整、引用正確 ← 選它 llama-3.3-70b-fp8-fast 3261/2147 ms ✅ 可用但波動較大 mistral-small-3.1-24b 3560/3631 ms qwen2.5-coder-32b 3572/3353 ms gpt-oss-120b 1971/2295 ms ❌ 回應形狀不同,response 取不到文字 gemma-3-12b-it ❌ 5018 帳號無權限 對照舊路徑 Gemini gemma-4-31b-it:同型提問 16.87 s,且吐整段英文思考草稿。 ## 2) 修 /init/seed 靜默吃掉 3.12 欄位 3.12 給 RecipeDefinition 加了 body_template/response_map/auth/binding_name, 但 /init/seed 是**列舉欄位重建** recipe record ⇒ 不在名單上的欄位被丟掉。 最惡劣的地方是「哪裡都不會紅」:recipe 查得到、endpoint 對,只有跑起來像沒設定過。 與 08-02 syncManifest 吃掉 manifest.daemon 欄同型(教訓:東西還在不在也要進機械閘)。 加 tests/init-seed-recipe-fields.test.ts:拿掉修復會紅、補回會綠(已實測會擋)。 ## 3) 修 D1 LIKE pattern 50 bytes 上限造成的 500 /entries/search?q=… 只要 q 超過 48 bytes 就回 HTTP 500,沒有錯誤訊息。 逐 byte 二分:48→200/49→500;中文 16 字→200/17 字→500。 判別實驗:q 固定 48 bytes、其他 filter 全塞滿讓 SQL 變很長 → 仍 200 ⇒ 爆的是 LIKE 的 pattern('%'+q+'%' = 50),不是 statement 長度。 中文問句超過 16 字是常態,而 rag_chat 用整句問題當 q ⇒ 聊天對正常問句等於不能用。 (=InkStoneCo status.md 待辦第 1 條「KBDB keyword 長查詢會炸」的根因。) 修法:q ≤ 48 bytes 走原路(行為逐字不變),超過才拆詞/切 UTF-8 邊界片段。 kbdb 全套 83 測全綠(含新增 8 項)。 ## 4) 順手 - 移除被 commit 進 repo 的 node_modules 壞 symlink(指向 leo Mac 的絕對路徑, 害任何 fresh clone 裝不起來、切分支還會把裝好的蓋掉——本次撞了兩次)。 - pending-changes.md 加 P2 提案(fan-out 並行執行)+等裁決,未動引擎。 驗證:cypher-executor 新增測試 17/17 綠;tsc 與基線逐字相同; 全套測試失敗集合與基線**逐字相同**(基線 14 個失敗,本分支 t173 既有,非本次引入)。
175 lines
8.3 KiB
TypeScript
175 lines
8.3 KiB
TypeScript
/**
|
||
* api-recipe-seeds.ts(server 端,唯一真相)
|
||
*
|
||
* 現役 API recipe 的種子定義。self-host 新帳號裝好後,POST /init/seed 端點把這些灌進空的 RECIPES KV。
|
||
*
|
||
* API recipe = http_request + 固定設定(endpoint/method 模板)。
|
||
* 不需 deploy Worker,cypher-executor 執行時直接 fetch(見 routes/recipes.ts)。
|
||
*
|
||
* 為何放在 cypher-executor/src(薄殼原則 rule 07 + 壓測 §5.5):
|
||
* - 「裝好之後預設有哪些 recipe」是 API 的能力,不是 CLI 的。seed 應由 API 端點完成
|
||
* (POST /init/seed),CLI/MCP 只呼叫一次。種子資料是這個能力的一部分,故放 server。
|
||
* - 種子的 endpoint 字串(sheets.googleapis.com 等)是 recipe 的「資料欄位」(宣告打哪),
|
||
* 非 TS 裡的呼叫實作;真正呼叫仍走零件 / http_request。rule 02 §2.2 hook 已對本檔加例外
|
||
* (richblack 2026-06-06 授權,與 auth-recipe-seeds.ts 同理由)。
|
||
*
|
||
* 來源:2026-06-01 從 prod cypher.arcrun.dev/recipes 逐一查得的現役定義。
|
||
* 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §5
|
||
*
|
||
* KBDB recipe(kbdb_*)採 Supabase 模式(richblack 2026-06-02):
|
||
* 進 seed = 展示能力(引子)。使用者要用 → 去 arcrun 取統一 API Key 當 credential。
|
||
* FOLLOW-UP(KBDB 端):endpoint 現為 kbdb.finally.click,KBDB 應改用統一對外網址;
|
||
* KBDB 改網址後同步更新此處。seed 先照現況進。
|
||
*/
|
||
|
||
import type { ResponseMap } from './recipe-payload';
|
||
|
||
export interface ApiRecipeSeed {
|
||
canonical_id: string;
|
||
display_name: string;
|
||
description?: string;
|
||
/** HTTP recipe=要打的網址;`auth: 'binding'` 型=要呼叫的資源名(如 Workers AI 的模型 id)。 */
|
||
endpoint: string;
|
||
method: string;
|
||
auth_service?: string;
|
||
// ── payload/回應/binding 三層(3.12):全選填,既有種子不帶=行為完全不變 ──
|
||
body_template?: Record<string, unknown>;
|
||
response_map?: ResponseMap;
|
||
auth?: 'static_key' | 'service_account' | 'oauth2' | 'binding';
|
||
binding_name?: string;
|
||
}
|
||
|
||
export const API_RECIPE_SEEDS: ApiRecipeSeed[] = [
|
||
// ── KBDB(Supabase 模式,auth_service=kbdb static_key)──
|
||
{
|
||
canonical_id: 'kbdb_get',
|
||
display_name: 'KBDB Get',
|
||
description: 'GET 讀取 block / 查詢。_path 帶查詢路徑。auth: kbdb static_key。',
|
||
endpoint: 'https://kbdb.finally.click{{_path}}',
|
||
method: 'GET',
|
||
auth_service: 'kbdb',
|
||
},
|
||
{
|
||
canonical_id: 'kbdb_create_block',
|
||
display_name: 'KBDB Create Block',
|
||
description: 'POST /blocks 建立 block。body 帶 block 欄位(content/type/page_name/source/user_id 等)。auth: kbdb static_key。',
|
||
endpoint: 'https://kbdb.finally.click/blocks',
|
||
method: 'POST',
|
||
auth_service: 'kbdb',
|
||
},
|
||
{
|
||
canonical_id: 'kbdb_patch_block',
|
||
display_name: 'KBDB Patch Block',
|
||
description: 'PATCH /blocks/:id 局部更新。_path 帶 /blocks/{id},body 帶要改的欄位。auth: kbdb static_key。',
|
||
endpoint: 'https://kbdb.finally.click{{_path}}',
|
||
method: 'PATCH',
|
||
auth_service: 'kbdb',
|
||
},
|
||
{
|
||
canonical_id: 'kbdb_delete',
|
||
display_name: 'KBDB Delete',
|
||
description: 'DELETE /blocks/:id 刪除 block。_path 帶 /blocks/{id}。auth: kbdb static_key。',
|
||
endpoint: 'https://kbdb.finally.click{{_path}}',
|
||
method: 'DELETE',
|
||
auth_service: 'kbdb',
|
||
},
|
||
{
|
||
canonical_id: 'kbdb_ingest',
|
||
display_name: 'KBDB Ingest',
|
||
description: 'POST /blocks/ingest 批次寫入。body 帶 input。auth: kbdb static_key。',
|
||
endpoint: 'https://kbdb.finally.click/blocks/ingest',
|
||
method: 'POST',
|
||
auth_service: 'kbdb',
|
||
},
|
||
|
||
// ── Google(service_account)──
|
||
{
|
||
canonical_id: 'gmail_send',
|
||
display_name: 'Gmail Send',
|
||
description: '寄 Gmail。POST messages/send,body 帶 raw(base64url MIME)。auth: google service_account。',
|
||
endpoint: 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
||
method: 'POST',
|
||
auth_service: 'google_gmail_sa',
|
||
},
|
||
{
|
||
canonical_id: 'google_sheets_append',
|
||
display_name: 'Google Sheets Append',
|
||
// 壓測階段 12 修正:append 官方 API 是 POST .../values/{range}:append(PUT 是 values.update 覆寫的動詞),
|
||
// 種子寫死 PUT 導致每個 self-host 用戶 seed 到壞 recipe(PUT :append → Google 400)。
|
||
// body 形狀屬工作流,泛用種子不寫死欄位 → 由工作流的 _path + body 處理(body_from 機制待 §13.4 補)。
|
||
description: '追加一列到 Sheets。POST .../values/{range}:append?valueInputOption=RAW,body 帶 {values:[[...]]}。auth: google service_account。',
|
||
endpoint: 'https://sheets.googleapis.com{{_path}}',
|
||
method: 'POST',
|
||
auth_service: 'google_sheets_sa',
|
||
},
|
||
{
|
||
canonical_id: 'google_sheets_read',
|
||
display_name: 'Google Sheets Read',
|
||
description: '讀 Sheets。GET values。_path 帶完整路徑。auth: google service_account。',
|
||
endpoint: 'https://sheets.googleapis.com{{_path}}',
|
||
method: 'GET',
|
||
auth_service: 'google_sheets_sa',
|
||
},
|
||
|
||
// ── 訊息(static_key)──
|
||
{
|
||
canonical_id: 'telegram_send',
|
||
display_name: 'Telegram Send',
|
||
description: 'Telegram sendMessage。token 在 URL path({{auth.bot_token}}),body 帶 chat_id+text。auth: static_key path 注入。',
|
||
endpoint: 'https://api.telegram.org/bot{{auth.bot_token}}/sendMessage',
|
||
method: 'POST',
|
||
auth_service: 'telegram',
|
||
},
|
||
{
|
||
canonical_id: 'line_notify_send',
|
||
display_name: 'LINE Notify',
|
||
description: 'LINE Notify 推訊息。POST notify,body 帶 message(form-urlencoded)。auth: static_key Bearer line token。',
|
||
endpoint: 'https://notify-api.line.me/api/notify',
|
||
method: 'POST',
|
||
auth_service: 'line_notify',
|
||
},
|
||
|
||
// ── LLM 對話(binding=免金鑰,3.12 第四型認證的第一個真實案例)──
|
||
//
|
||
// 為什麼進種子(而非寫在某個產品的安裝器裡):「裝好之後預設有哪些 recipe」是平台能力,
|
||
// 與本檔其餘種子同理由(見檔頭)。裝完 /init/seed 就有 ⇒ **用戶不填任何金鑰就能問答**。
|
||
//
|
||
// 換模型/換供應商=**改這一筆 recipe**(endpoint + body_template + response_map),
|
||
// workflow 的 ask_llm 節點不動——這正是「換源=換 recipe 不是換引擎」。
|
||
//
|
||
// 選型實測(2026-08-03,在 1.4.4 實例上跑真實長度的 RAG prompt,每個模型連跑 2 次):
|
||
// @cf/meta/llama-4-scout-17b-16e-instruct 2373/2173 ms ✅ 答案最完整、引用正確
|
||
// @cf/meta/llama-3.3-70b-instruct-fp8-fast 3261/2147 ms ✅ 可用但波動較大
|
||
// @cf/mistralai/mistral-small-3.1-24b-instruct 3560/3631 ms
|
||
// @cf/qwen/qwen2.5-coder-32b-instruct 3572/3353 ms
|
||
// @cf/openai/gpt-oss-120b 1971/2295 ms ❌ 回應形狀不同,response 取不到文字
|
||
// @cf/google/gemma-3-12b-it ❌ 5018 This account is not allowed to access this model
|
||
// 對照舊路徑(Gemini `gemma-4-31b-it`):同型提問 **16.87 s**,且吐整段英文思考草稿
|
||
// ⇒ 選 llama-4-scout:**快 7 倍以上,且不需要淨化思考草稿**。
|
||
{
|
||
canonical_id: 'workers_ai_chat',
|
||
display_name: 'Workers AI 對話(免金鑰)',
|
||
description:
|
||
'Cloudflare Workers AI 文字生成,走 env.AI binding ⇒ 不需要任何 API 金鑰。'
|
||
+ 'ctx 帶 prompt,回應正規化成 text(含【答】標記與前綴淨化)。'
|
||
+ '換模型=改本 recipe 的 endpoint,workflow 不動。',
|
||
endpoint: '@cf/meta/llama-4-scout-17b-16e-instruct',
|
||
method: 'POST',
|
||
auth: 'binding',
|
||
binding_name: 'AI',
|
||
body_template: {
|
||
messages: [{ role: 'user', content: '{{prompt}}' }],
|
||
max_tokens: 1024,
|
||
temperature: 0.2,
|
||
},
|
||
response_map: {
|
||
// Workers AI chat 回應:{ response: "…" }(另有 OpenAI 相容的 choices,取 response 最穩)
|
||
text_path: 'response',
|
||
// 提示詞要求答案以【答】開頭;模型偶爾會在前面多帶一行 ⇒ 取最後一個標記之後
|
||
answer_marker: '【答】',
|
||
// 前綴組合順序不定,循環剝殼(規則見 recipe-payload.ts sanitize)
|
||
strip_prefixes: ['*', '-', '•', '>', '#', '"', '「', '【答】', 'Answer:', 'Draft:'],
|
||
},
|
||
},
|
||
];
|