Files
Arcrun/registry/examples/graph-neighbors/workflow.yaml
T
Leo 313aeb13bf feat(cypher-executor): 同步查詢 trigger + graph_neighbors 查詢面 workflow 示範
Part 1(框架,cypher-executor webhooks-named.ts):加同步查詢 trigger。
現有 named webhook /trigger 回 {success,data,trace,duration_ms} 信封、只有 POST;
查詢面(console/MCP 打 graph neighbors/traverse)要 GET + 直接拿最終節點輸出當 response。
新端點同步 await 執行 workflow graph → 回 result.data(最終節點輸出)本身當 body(非 202):
  - GET  /q/:ns/:name                    (namespace 走 path,input 走 query string)
  - GET  /webhooks/named/:name/query     (X-Arcrun-API-Key header,input 走 query string)
  - POST /webhooks/named/:name/query     (header,input 走 body)
  - POST /webhooks/named/:ns/:name/query (namespace 走 path,input 走 body)
認證沿用 X-Arcrun-API-Key。誠實(mindset §7):節點失敗回 error+trace(500,非假綠);
paused 工作流無法同步回答 → 409 明講;輸出 5 MiB 硬上限(超過 413);duration 走 header 不污染 body。

Part 2(A 類 workflow.yaml):registry/examples/graph-neighbors/。
http_request 打 base custom domain kbdb.finally.click(避 CF 1042)撈 triplet records
→ code 零件記憶體 BFS(對照 kbdb-graph-plugin graph-traverse.ts)→ 同步回鄰居。
把 graph plugin 內建 GET /graph/neighbors 泛化成查詢面 workflow 的示範。

測試:cypher-executor/tests/query-trigger.test.ts(7 測,全綠)——同步回輸出(非 202)、
GET/POST × header/path 四端點、節點失敗回錯+trace、缺 key 401、不存在 404。
用內建 comp_uppercase(純記憶體)證明 Part 1 同步 trigger 機制本身可用。

待驗:graph_neighbors 需 code 零件部署 leo21c 後才能 live 端到端(另線處理);
triplet template id 上線前對一次(workflow 已參數化未寫死)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015d5jDbuqT5Htwv3Q88XXKk
2026-07-07 08:16:17 +00:00

113 lines
5.8 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: graph_neighbors
description: >
同步查詢:給一個節點 → 從 KBDB triplet 記錄建鄰接表 → 記憶體 BFS 找 N 跳鄰居 → 同步回鄰居清單。
這是「查詢面工作流」示範:用同步查詢 triggerGET /q/:ns/graph_neighbors 或
POST /webhooks/named/:name/query),把 workflow 最終節點輸出直接當 HTTP response 拿回,
取代 graph plugin 內建的 GET /graph/neighbors。對照 kbdb-graph-plugin graph-traverse.ts 的記憶體 BFS。
# ── 為什麼走 base custom domainkbdb.finally.click)而非 workers.dev ──
# cypher-executor 執行 http_request 節點時對 kbdb 發 fetch。若打 *.uncle6-me.workers.dev 同 zone
# 會踩 CF 1042same-zone self-fetch)。打 base 的對外 custom domain kbdb.finally.click 屬跨 zone
# 前門公網進出,避開 1042(同 credential-primitives-wasm Phase 7 的 global_fetch_strictly_public 精神)。
# ── 觸發(同步查詢 trigger,非 202)──
# GET https://cypher.arcrun.dev/q/{namespace}/graph_neighbors?node=Arcrun&depth=2&template=graph_triplet&namespace={namespace}
# POST https://cypher.arcrun.dev/webhooks/named/graph_neighbors/query
# -H "X-Arcrun-API-Key: {namespace}"
# -d '{"node":"Arcrun","depth":2,"template":"graph_triplet","namespace":"{namespace}"}'
# → 直接回 { success, start, depth, directed, neighbors:[...], count }(最終節點輸出本身)。
flow:
- "input >> ON_SUCCESS >> fetch_triplets"
- "fetch_triplets >> ON_SUCCESS >> bfs_neighbors"
config:
# 1) 撈本租戶的 triplet 記錄。triplet = base 萬用表的一個 templategraph plugin 寫入),
# slots = subject / predicate / object。base 端點:GET /records/by-template/:template?owner_id=
# 回 { success, records:[{ record_id, values:{subject,predicate,object} }], count }。
# ⚠️ template 名(此處 {{input.template}},預設由呼叫者帶 graph_triplet)以實際部署的
# kbdb-graph-plugin triplet template id 為準——上線前對一次。
fetch_triplets:
component: http_request
method: GET
url: "https://kbdb.finally.click/records/by-template/{{input.template}}?owner_id={{input.namespace}}"
headers:
Accept: "application/json"
# 2) ★ 記憶體 BFS(通用 code 零件,sandbox inline JS,無 LLM、無 fs/網路,stdin→stdout JSON)。
# 對照 kbdb-graph-plugin graph-traverse.ts:23-51 的記憶體 BFS:把 triplet 當有向邊
# subject --predicate--> object 建鄰接表,從 start 逐跳擴張到 depth 上限,收集新訪節點當鄰居。
# directed=false(預設)時把邊當雙向(無向圖鄰居);directed=true 只走 subject→object。
bfs_neighbors:
component: code
code: |
// graph_neighbors — 記憶體 BFS 找 N 跳鄰居(純函式、決定性、零 token)。
// input(由下方 input: 映射解析後注入):
// records[] : triplet 記錄({ values:{subject,predicate,object} } 或扁平 {subject,predicate,object}
// start : 起點節點名(字串)
// depth : 最大跳數(字串或數字,來自 query string 時是字串)
// directed : "true" 只走 subject→object;否則當無向
const records = Array.isArray(input.records) ? input.records : [];
const start = String(input.start == null ? '' : input.start);
const maxDepth = Math.max(1, parseInt(String(input.depth == null ? 1 : input.depth), 10) || 1);
const directed = String(input.directed == null ? '' : input.directed) === 'true';
if (!start) {
return { success: false, error: 'graph_neighbors 缺 startnode)參數' };
}
// 建鄰接表:subject --predicate--> object。無向時同時加反向邊。
const adj = new Map();
function addEdge(from, to, predicate) {
if (!adj.has(from)) adj.set(from, []);
adj.get(from).push({ node: to, predicate: predicate });
}
for (const r of records) {
const v = (r && typeof r === 'object' && r.values && typeof r.values === 'object') ? r.values : r;
if (!v || typeof v !== 'object') continue;
const s = v.subject, p = v.predicate, o = v.object;
if (!s || !o) continue;
addEdge(s, o, p);
if (!directed) addEdge(o, s, p);
}
// BFS:一層一跳,收集首次訪到的節點當鄰居(記 depth / 來源 / 關係)。
const visited = new Set([start]);
let frontier = [start];
const neighbors = [];
for (let d = 1; d <= maxDepth; d++) {
const next = [];
for (const cur of frontier) {
const outs = adj.get(cur) || [];
for (const e of outs) {
if (visited.has(e.node)) continue;
visited.add(e.node);
neighbors.push({ node: e.node, predicate: e.predicate, from: cur, depth: d });
next.push(e.node);
}
}
frontier = next;
if (frontier.length === 0) break;
}
return {
success: true,
start: start,
depth: maxDepth,
directed: directed,
neighbors: neighbors,
count: neighbors.length,
};
# input 映射:{{...}} 對 workflow context 展開後注入 code 沙箱的 `input` 變數。
# {{input.X}} 的 input = 上游 input 節點輸出(=觸發 context);{{fetch_triplets.data.records}}
# = http_request 回應 body 的 records 陣列(單一 ref pass-through 保留陣列型別)。
input:
records: "{{fetch_triplets.data.records}}"
start: "{{input.node}}"
depth: "{{input.depth}}"
directed: "{{input.directed}}"
limits:
timeout_ms: 3000 # 純 CPU BFS,充裕
max_output_bytes: 2097152 # 鄰居清單上限 2 MiB(呼叫端同步查詢輸出也有 5 MiB 硬上限)