diff --git a/cli/src/commands/run.ts b/cli/src/commands/run.ts index db77fb0..8e0d85d 100644 --- a/cli/src/commands/run.ts +++ b/cli/src/commands/run.ts @@ -64,7 +64,8 @@ export async function cmdRun(workflowName: string, options: RunOptions): Promise headers, body: JSON.stringify({ triplets: workflow.flow, - context: { ...inputContext, ...(workflow.config ?? {}) }, + config: workflow.config ?? {}, // node_name → {component, ...params} + context: inputContext, // --input key=value 傳入的動態參數 graph_id: workflow.name, graph_name: workflow.name, }), diff --git a/cypher-executor/src/actions/cypher-handlers.ts b/cypher-executor/src/actions/cypher-handlers.ts index bcdcfdd..4354b68 100644 --- a/cypher-executor/src/actions/cypher-handlers.ts +++ b/cypher-executor/src/actions/cypher-handlers.ts @@ -32,6 +32,7 @@ export async function handleCypherExecute( context: Record | undefined, graphId: string, graphName: string, + config: Record> | undefined, env: Bindings, waitUntil: (promise: Promise) => void, ): Promise<{ success: boolean; data?: unknown; error?: string; trace?: unknown; duration_ms: number; graph?: ExecutionGraph }> { @@ -49,7 +50,7 @@ export async function handleCypherExecute( ); } - const graph = buildExecutionGraph(parsed, nodeResults, graphId, graphName); + const graph = buildExecutionGraph(parsed, nodeResults, graphId, graphName, config); const parseResult = graphSchema.safeParse(graph); if (!parseResult.success) { throw new Error('圖定義產生失敗'); diff --git a/cypher-executor/src/actions/graph-builder.ts b/cypher-executor/src/actions/graph-builder.ts index e1bd3c7..cd1eb84 100644 --- a/cypher-executor/src/actions/graph-builder.ts +++ b/cypher-executor/src/actions/graph-builder.ts @@ -2,22 +2,34 @@ import type { ParsedTriplets } from './triplet-parser'; import { toEdgeType } from './triplet-parser'; import type { SearchResult } from './search-nodes'; -/** 從 nodeResults + parsed 組成可直接送入 /execute 的 ExecutionGraph */ +/** 從 nodeResults + parsed 組成可直接送入 /execute 的 ExecutionGraph + * + * config 格式(來自 workflow YAML 的 config 欄位): + * { node_name: { component: "cmp_xxxxxxxx" | "rec_xxxxxxxx" | canonical_id, ...params } } + * + * 若 config[node].component 存在,以它覆蓋 searchNodes 偵測到的 componentId。 + * config[node] 的其他欄位作為節點靜態參數(node.data),合併進執行 context。 + */ export function buildExecutionGraph( parsed: ParsedTriplets, nodeResults: SearchResult['nodeResults'], graphId: string, graphName: string, + config?: Record>, ) { const nodes = [...parsed.nodeNames].map(name => { const nr = nodeResults[name]!; const id = name.toLowerCase().replace(/\s+/g, '-'); - return { - id, - type: nr.type, - componentId: nr.componentId, - label: name, - }; + const nodeConfig = config?.[name] ?? {}; + + // config[name].component 可以是 hash 或 canonical_id,覆蓋自動偵測的 componentId + const componentId = (nodeConfig.component as string | undefined) ?? nr.componentId; + + // 其他 config 欄位作為 node.data(靜態參數) + const { component: _component, ...staticParams } = nodeConfig; + const data = Object.keys(staticParams).length > 0 ? staticParams : undefined; + + return { id, type: nr.type, componentId, label: name, data }; }); const edges = parsed.edges.map(e => ({ diff --git a/cypher-executor/src/routes/cypher.ts b/cypher-executor/src/routes/cypher.ts index 7f638ba..6cef099 100644 --- a/cypher-executor/src/routes/cypher.ts +++ b/cypher-executor/src/routes/cypher.ts @@ -38,7 +38,13 @@ cypherRouter.post('/cypher/search', async (c) => { // POST /cypher/execute — 三元組 → 一步執行(search + execute 合一) cypherRouter.post('/cypher/execute', async (c) => { - const body = await c.req.json() as { triplets?: unknown; context?: Record; graph_id?: string; graph_name?: string }; + const body = await c.req.json() as { + triplets?: unknown; + context?: Record; + config?: Record>; // node_name → {component, ...params} + graph_id?: string; + graph_name?: string; + }; if (!Array.isArray(body?.triplets) || body.triplets.length === 0) { return c.json({ error: 'triplets 必須為非空字串陣列' }, 400); @@ -57,6 +63,7 @@ cypherRouter.post('/cypher/execute', async (c) => { body.context, graphId, graphName, + body.config, c.env, (p) => c.executionCtx.waitUntil(p), );