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,
|
||||
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,
|
||||
}),
|
||||
|
||||
@@ -32,6 +32,7 @@ export async function handleCypherExecute(
|
||||
context: Record<string, unknown> | undefined,
|
||||
graphId: string,
|
||||
graphName: string,
|
||||
config: Record<string, Record<string, unknown>> | undefined,
|
||||
env: Bindings,
|
||||
waitUntil: (promise: Promise<void>) => 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('圖定義產生失敗');
|
||||
|
||||
@@ -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<string, Record<string, unknown>>,
|
||||
) {
|
||||
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 => ({
|
||||
|
||||
@@ -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<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) {
|
||||
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),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user