feat: config field in /cypher/execute — node-level component override
- /cypher/execute now accepts separate `config` field:
{node_name: {component: "cmp_19e62efd", ...staticParams}}
- graph-builder reads config[node].component to override componentId
(supports cmp_ hash, rec_ hash, or canonical_id)
- config[node] other fields become node.data (static params merged at runtime)
- acr run now sends workflow.config as separate `config` (not flattened into context)
- context is now only --input dynamic params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,8 @@ export async function cmdRun(workflowName: string, options: RunOptions): Promise
|
|||||||
headers,
|
headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
triplets: workflow.flow,
|
triplets: workflow.flow,
|
||||||
context: { ...inputContext, ...(workflow.config ?? {}) },
|
config: workflow.config ?? {}, // node_name → {component, ...params}
|
||||||
|
context: inputContext, // --input key=value 傳入的動態參數
|
||||||
graph_id: workflow.name,
|
graph_id: workflow.name,
|
||||||
graph_name: workflow.name,
|
graph_name: workflow.name,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export async function handleCypherExecute(
|
|||||||
context: Record<string, unknown> | undefined,
|
context: Record<string, unknown> | undefined,
|
||||||
graphId: string,
|
graphId: string,
|
||||||
graphName: string,
|
graphName: string,
|
||||||
|
config: Record<string, Record<string, unknown>> | undefined,
|
||||||
env: Bindings,
|
env: Bindings,
|
||||||
waitUntil: (promise: Promise<void>) => void,
|
waitUntil: (promise: Promise<void>) => void,
|
||||||
): Promise<{ success: boolean; data?: unknown; error?: string; trace?: unknown; duration_ms: number; graph?: ExecutionGraph }> {
|
): 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);
|
const parseResult = graphSchema.safeParse(graph);
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
throw new Error('圖定義產生失敗');
|
throw new Error('圖定義產生失敗');
|
||||||
|
|||||||
@@ -2,22 +2,34 @@ import type { ParsedTriplets } from './triplet-parser';
|
|||||||
import { toEdgeType } from './triplet-parser';
|
import { toEdgeType } from './triplet-parser';
|
||||||
import type { SearchResult } from './search-nodes';
|
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(
|
export function buildExecutionGraph(
|
||||||
parsed: ParsedTriplets,
|
parsed: ParsedTriplets,
|
||||||
nodeResults: SearchResult['nodeResults'],
|
nodeResults: SearchResult['nodeResults'],
|
||||||
graphId: string,
|
graphId: string,
|
||||||
graphName: string,
|
graphName: string,
|
||||||
|
config?: Record<string, Record<string, unknown>>,
|
||||||
) {
|
) {
|
||||||
const nodes = [...parsed.nodeNames].map(name => {
|
const nodes = [...parsed.nodeNames].map(name => {
|
||||||
const nr = nodeResults[name]!;
|
const nr = nodeResults[name]!;
|
||||||
const id = name.toLowerCase().replace(/\s+/g, '-');
|
const id = name.toLowerCase().replace(/\s+/g, '-');
|
||||||
return {
|
const nodeConfig = config?.[name] ?? {};
|
||||||
id,
|
|
||||||
type: nr.type,
|
// config[name].component 可以是 hash 或 canonical_id,覆蓋自動偵測的 componentId
|
||||||
componentId: nr.componentId,
|
const componentId = (nodeConfig.component as string | undefined) ?? nr.componentId;
|
||||||
label: name,
|
|
||||||
};
|
// 其他 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 => ({
|
const edges = parsed.edges.map(e => ({
|
||||||
|
|||||||
@@ -38,7 +38,13 @@ cypherRouter.post('/cypher/search', async (c) => {
|
|||||||
|
|
||||||
// POST /cypher/execute — 三元組 → 一步執行(search + execute 合一)
|
// POST /cypher/execute — 三元組 → 一步執行(search + execute 合一)
|
||||||
cypherRouter.post('/cypher/execute', async (c) => {
|
cypherRouter.post('/cypher/execute', async (c) => {
|
||||||
const body = await c.req.json() as { triplets?: unknown; context?: Record<string, unknown>; graph_id?: string; graph_name?: string };
|
const body = await c.req.json() as {
|
||||||
|
triplets?: unknown;
|
||||||
|
context?: Record<string, unknown>;
|
||||||
|
config?: Record<string, Record<string, unknown>>; // node_name → {component, ...params}
|
||||||
|
graph_id?: string;
|
||||||
|
graph_name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (!Array.isArray(body?.triplets) || body.triplets.length === 0) {
|
if (!Array.isArray(body?.triplets) || body.triplets.length === 0) {
|
||||||
return c.json({ error: 'triplets 必須為非空字串陣列' }, 400);
|
return c.json({ error: 'triplets 必須為非空字串陣列' }, 400);
|
||||||
@@ -57,6 +63,7 @@ cypherRouter.post('/cypher/execute', async (c) => {
|
|||||||
body.context,
|
body.context,
|
||||||
graphId,
|
graphId,
|
||||||
graphName,
|
graphName,
|
||||||
|
body.config,
|
||||||
c.env,
|
c.env,
|
||||||
(p) => c.executionCtx.waitUntil(p),
|
(p) => c.executionCtx.waitUntil(p),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user