Files
AgentBlock/src/workflowModel.ts
T
2026-05-11 01:39:12 +08:00

341 lines
9.4 KiB
TypeScript

export type WorkflowNodeKind =
| 'environment'
| 'space'
| 'agent'
| 'behavior'
| 'output';
export type WorkflowPortType =
| 'ENV_CONTEXT'
| 'SPACE_CONTEXT'
| 'AGENT_CONTEXT'
| 'OBSERVABLE_INFO'
| 'BEHAVIOR_OUTPUT'
| 'REPORT';
export type WorkflowNodeStatus = 'ready' | 'warning' | 'error' | 'success';
export interface WorkflowPort {
id: string;
label: string;
type: WorkflowPortType;
}
export interface WorkflowNode {
id: string;
title: string;
subtitle: string;
kind: WorkflowNodeKind;
x: number;
y: number;
status: WorkflowNodeStatus;
inputs: WorkflowPort[];
outputs: WorkflowPort[];
config: Array<{ label: string; value: string }>;
}
export interface WorkflowEdge {
id: string;
source: string;
target: string;
type: WorkflowPortType;
label: string;
}
export type AttributeScope = 'endogenous' | 'exogenous' | 'derived';
export interface WorkflowParameter {
id: string;
nodeId: string;
name: string;
type: 'Float' | 'Integer' | 'Boolean' | 'String' | 'Coordinate';
scope: AttributeScope;
source: string;
value: string;
status: 'ok' | 'warning' | 'error';
description: string;
}
export type BehaviorSectionKind = 'observable_pool' | 'input' | 'calculation' | 'output';
export interface BehaviorSection {
id: string;
nodeId: string;
kind: BehaviorSectionKind;
title: string;
description: string;
items: Array<{ label: string; source: string; value: string }>;
}
export interface WorkflowModel {
nodes: WorkflowNode[];
edges: WorkflowEdge[];
parameters: WorkflowParameter[];
behaviorSections: BehaviorSection[];
}
export type InitialSpaceType = 'grid' | 'network' | 'continuous' | 'gis';
function createSpaceNode(spaceType: InitialSpaceType): WorkflowNode {
const spaceConfig: Record<InitialSpaceType, { title: string; subtitle: string; config: Array<{ label: string; value: string }> }> = {
grid: {
title: 'Grid Space',
subtitle: '40 x 40 grid for spatial interaction',
config: [
{ label: 'width', value: '40' },
{ label: 'height', value: '40' },
{ label: 'horizontal wrap', value: 'true' },
{ label: 'vertical wrap', value: 'true' },
{ label: 'agent capacity/cell', value: '1' },
],
},
network: {
title: 'Network Space',
subtitle: 'Nodes and edges for relationship-based interaction',
config: [
{ label: 'network type', value: 'undirected' },
{ label: 'default degree', value: '4' },
{ label: 'rewiring', value: 'false' },
],
},
continuous: {
title: 'Continuous Space',
subtitle: 'Coordinate plane for movement and distance-based sensing',
config: [
{ label: 'width', value: '100' },
{ label: 'height', value: '100' },
{ label: 'boundary', value: 'bounded' },
],
},
gis: {
title: 'GIS Space',
subtitle: 'Map-coordinate space for real geography',
config: [
{ label: 'projection', value: 'WGS84' },
{ label: 'basemap', value: 'not selected' },
{ label: 'boundary layer', value: 'required' },
],
},
};
const selected = spaceConfig[spaceType];
return {
id: `${spaceType}-space`,
title: selected.title,
subtitle: selected.subtitle,
kind: 'space',
x: 100,
y: 80,
status: spaceType === 'grid' ? 'warning' : 'ready',
inputs: [{ id: 'env', label: 'environment', type: 'ENV_CONTEXT' }],
outputs: [{ id: 'space', label: 'space state', type: 'SPACE_CONTEXT' }],
config: selected.config,
};
}
export function createDefaultWorkflow(spaceType?: InitialSpaceType): WorkflowModel {
const nodes: WorkflowNode[] = [
{
id: 'environment',
title: 'Environment',
subtitle: 'Global model container and shared context',
kind: 'environment',
x: -220,
y: -110,
status: 'ready',
inputs: [],
outputs: [{ id: 'env', label: 'environment', type: 'ENV_CONTEXT' }],
config: [
{ label: 'model type', value: 'ABM + Game Model' },
{ label: 'default agent', value: 'Example Agent' },
],
},
{
id: 'example-agent',
title: 'Example Agent',
subtitle: 'Default agent with attributes and behaviors',
kind: 'agent',
x: 120,
y: -95,
status: 'ready',
inputs: [
{ id: 'space', label: 'located in grid', type: 'SPACE_CONTEXT' },
{ id: 'env', label: 'environment context', type: 'ENV_CONTEXT' },
],
outputs: [{ id: 'agent', label: 'agent state', type: 'AGENT_CONTEXT' }],
config: [
{ label: 'attributes', value: 'endogenous + exogenous' },
{ label: 'behavior', value: 'on_sensing' },
],
},
];
if (spaceType) {
nodes.splice(1, 0, createSpaceNode(spaceType));
}
const edges: WorkflowEdge[] = [
{ id: 'e1', source: 'environment', target: 'example-agent', type: 'ENV_CONTEXT', label: 'default context' },
];
if (spaceType) {
const spaceId = `${spaceType}-space`;
edges.splice(
0,
1,
{ id: 'e1', source: 'environment', target: spaceId, type: 'ENV_CONTEXT', label: 'contains space' },
{ id: 'e2', source: spaceId, target: 'example-agent', type: 'SPACE_CONTEXT', label: 'locates agent' },
);
}
return {
nodes,
edges,
parameters: [
{
id: 'a1',
nodeId: 'example-agent',
name: 'race',
type: 'String',
scope: 'endogenous',
source: 'initialization',
value: 'A | B',
status: 'ok',
description: 'Agent group identity used by segregation behavior',
},
{
id: 'a2',
nodeId: 'example-agent',
name: 'position',
type: 'Coordinate',
scope: 'derived',
source: 'Grid Space connection',
value: '(x, y)',
status: 'ok',
description: 'Automatically added when agent is connected to grid space',
},
{
id: 'a3',
nodeId: 'example-agent',
name: 'happiness',
type: 'Boolean',
scope: 'endogenous',
source: 'on_sensing output',
value: '--',
status: 'warning',
description: 'Assigned by behavior after observing neighbors',
},
{
id: 's1',
nodeId: 'grid-space',
name: 'cell_capacity',
type: 'Integer',
scope: 'exogenous',
source: 'space config',
value: '1',
status: 'ok',
description: 'How many agents can occupy one grid cell',
},
{
id: 's2',
nodeId: 'grid-space',
name: 'neighbor_radius',
type: 'Integer',
scope: 'exogenous',
source: 'space config',
value: '--',
status: 'error',
description: 'Required for on_sensing neighborhood lookup',
},
{
id: 'n1',
nodeId: 'neighbor-agent',
name: 'race',
type: 'String',
scope: 'endogenous',
source: 'peer attribute',
value: 'visible',
status: 'ok',
description: 'Selectable observable attribute for Example Agent',
},
],
behaviorSections: [
{
id: 'b1',
nodeId: 'example-agent',
kind: 'observable_pool',
title: 'Observable Information Pool',
description: 'All information the behavior may use, grouped by self and observed subjects.',
items: [
{ label: 'self.race', source: 'Example Agent', value: 'String' },
{ label: 'self.position', source: 'Grid Space', value: 'Coordinate' },
{ label: 'neighbor.race', source: 'Neighbor Agent', value: 'String' },
{ label: 'space.neighbor_count', source: 'Grid Space', value: 'Integer' },
],
},
{
id: 'b2',
nodeId: 'example-agent',
kind: 'input',
title: 'Behavior Inputs',
description: 'Selected inputs passed into this behavior.',
items: [
{ label: 'self.position', source: 'Observable pool', value: 'selected' },
{ label: 'neighbor.race', source: 'Observable pool', value: 'selected' },
],
},
{
id: 'b3',
nodeId: 'example-agent',
kind: 'calculation',
title: 'Calculation or LLM Call',
description: 'How inputs are transformed into a decision.',
items: [
{ label: 'mode', source: 'user choice', value: 'formula' },
{ label: 'rule', source: 'behavior editor', value: 'same_race_neighbors / total_neighbors >= threshold' },
],
},
{
id: 'b4',
nodeId: 'example-agent',
kind: 'output',
title: 'Output Assignment',
description: 'Where the return value is saved.',
items: [
{ label: 'return', source: 'calculation', value: 'Boolean' },
{ label: 'assign to', source: 'Example Agent', value: 'self.happiness' },
],
},
],
};
}
export function getParametersForNode(workflow: WorkflowModel, nodeId: string) {
return workflow.parameters.filter((parameter) => parameter.nodeId === nodeId);
}
export function getBehaviorSectionsForNode(workflow: WorkflowModel, nodeId: string) {
return workflow.behaviorSections.filter((section) => section.nodeId === nodeId);
}
export const NODE_CARD_SIZE = {
width: 176,
height: 128,
};
export function getNodeConnectionAnchor({
x,
y,
side,
}: {
x: number;
y: number;
side: 'left' | 'right';
}) {
return {
x: side === 'left' ? x : x + NODE_CARD_SIZE.width,
y: y + NODE_CARD_SIZE.height / 2,
};
}