Moved tools property out of workflows in block definitions, updated serializer & tests accordingly

This commit is contained in:
Waleed Latif
2025-01-19 01:29:48 -08:00
parent 9b59d68209
commit 2f3fa0c059
8 changed files with 125 additions and 80 deletions

View File

@@ -19,6 +19,26 @@ export const AgentBlock: BlockConfig = {
icon: AgentIcon,
category: 'basic',
},
tools: {
access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat'],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o';
if (!model) {
throw new Error('No model selected');
}
const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS];
if (!tool) {
throw new Error(`Invalid model selected: ${model}`);
}
return tool;
}
}
},
workflow: {
outputType: {
default: 'string',
@@ -30,26 +50,6 @@ export const AgentBlock: BlockConfig = {
}
}
},
tools: {
access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat'],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o';
if (!model) {
throw new Error('No model selected');
}
const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS];
if (!tool) {
throw new Error(`Invalid model selected: ${model}`);
}
return tool;
}
}
},
inputs: {
systemPrompt: 'string'
},

View File

@@ -10,11 +10,11 @@ export const ApiBlock: BlockConfig = {
icon: ApiIcon,
category: 'basic',
},
tools: {
access: ['http.request']
},
workflow: {
outputType: 'json',
tools: {
access: ['http.request']
},
inputs: {
url: 'string',
method: 'string',

View File

@@ -10,11 +10,11 @@ export const FunctionBlock: BlockConfig = {
icon: CodeIcon,
category: 'advanced',
},
tools: {
access: ['function']
},
workflow: {
outputType: 'json',
tools: {
access: ['function']
},
inputs: {
code: 'string'
},

View File

@@ -8,22 +8,33 @@ import { FunctionBlock } from './blocks/function'
// Export blocks for ease of use
export { AgentBlock, ApiBlock, FunctionBlock }
// Combined blocks registry
export const BLOCKS: BlockConfig[] = [
AgentBlock,
ApiBlock,
FunctionBlock,
]
// Registry of all block configurations
const blocks: Record<string, BlockConfig> = {
agent: AgentBlock,
api: ApiBlock,
function: FunctionBlock
}
// Build a reverse mapping of tools to block types
const toolToBlockType = Object.entries(blocks).reduce((acc, [blockType, config]) => {
config.tools.access.forEach(toolId => {
acc[toolId] = blockType
})
return acc
}, {} as Record<string, string>)
// Helper functions
export const getBlock = (type: string): BlockConfig | undefined =>
BLOCKS.find(block => block.type === type)
blocks[type]
export const getBlockTypeForTool = (toolId: string): string | undefined =>
toolToBlockType[toolId]
export const getBlocksByCategory = (category: 'basic' | 'advanced'): BlockConfig[] =>
BLOCKS.filter(block => block.toolbar.category === category)
Object.values(blocks).filter(block => block.toolbar.category === category)
export const getAllBlockTypes = (): string[] =>
BLOCKS.map(block => block.type)
Object.keys(blocks)
export const isValidBlockType = (type: string): type is string =>
BLOCKS.some(block => block.type === type)
type in blocks

View File

@@ -42,15 +42,15 @@ export interface BlockConfig {
icon: BlockIcon
category: BlockCategory
}
tools: {
access: string[]
config?: {
tool: (params: Record<string, any>) => string
}
}
workflow: {
outputType: OutputTypeConfig
subBlocks: SubBlockConfig[]
tools: {
access: string[]
config?: {
tool: (params: Record<string, any>) => string
}
}
inputs?: Record<string, ParamType>
}
}

View File

@@ -3,12 +3,14 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1'
'^@/(.*)$': '<rootDir>/$1',
// Mock CSS imports
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
testMatch: ['**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.json'
}]
'^.+\\.(ts|tsx)$': ['ts-jest', {
tsconfig: 'tsconfig.json',
}],
}
};

View File

@@ -4,42 +4,71 @@ import { SerializedWorkflow } from '../types';
import { BlockState } from '@/stores/workflow/types';
import { OutputType } from '@/blocks/types';
// Mock icons
jest.mock('@/components/icons', () => ({
AgentIcon: () => 'AgentIcon',
ApiIcon: () => 'ApiIcon',
CodeIcon: () => 'CodeIcon',
}));
// Mock blocks
jest.mock('@/blocks', () => ({
getBlock: (type: string) => {
if (type === 'http') {
if (type === 'api') {
return {
type,
toolbar: {
title: 'API',
description: 'Use any API',
bgColor: '#2F55FF',
icon: () => null,
category: 'basic',
},
tools: {
access: ['http.request']
},
workflow: {
tools: {
access: ['http.request'],
config: {
tool: () => 'http.request'
}
},
outputType: 'json',
inputs: {
url: 'string',
method: 'string'
},
outputType: 'json'
subBlocks: []
}
};
}
// Default agent block config
return {
type,
toolbar: {
title: 'Agent',
description: 'Use any LLM',
bgColor: '#7F2FFF',
icon: () => null,
category: 'basic',
},
tools: {
access: ['openai.chat'],
config: {
tool: () => 'openai.chat'
}
},
workflow: {
tools: {
access: ['openai.chat'],
config: {
tool: () => 'openai.chat'
}
},
outputType: 'string',
inputs: {
prompt: 'string'
},
outputType: 'string'
subBlocks: []
}
};
},
getBlockTypeForTool: (toolId: string) => {
const toolToType: Record<string, string> = {
'openai.chat': 'agent',
'http.request': 'api',
'function': 'function'
};
return toolToType[toolId];
}
}));
@@ -79,7 +108,7 @@ describe('Serializer', () => {
},
'http-1': {
id: 'http-1',
type: 'http',
type: 'api',
name: 'API Call',
position: { x: 400, y: 100 },
subBlocks: {
@@ -165,7 +194,7 @@ describe('Serializer', () => {
const blocks: Record<string, BlockState> = {
'input-1': {
id: 'input-1',
type: 'http',
type: 'api',
name: 'Data Input',
position: { x: 100, y: 100 },
subBlocks: {
@@ -203,7 +232,7 @@ describe('Serializer', () => {
},
'output-1': {
id: 'output-1',
type: 'http',
type: 'api',
name: 'Data Output',
position: { x: 500, y: 100 },
subBlocks: {
@@ -321,7 +350,7 @@ describe('Serializer', () => {
const { blocks } = serializer.deserializeWorkflow(workflow);
const block = blocks['agent-1'];
expect(block.type).toBe('openai.chat');
expect(block.type).toBe('agent');
expect(block.subBlocks.model.value).toBe('gpt-4o');
expect(block.subBlocks.systemPrompt.value).toBe('You are helpful');
expect(block.outputType).toBe('string');

View File

@@ -1,7 +1,7 @@
import { BlockState, SubBlockState } from '@/stores/workflow/types';
import { Edge } from 'reactflow';
import { SerializedBlock, SerializedConnection, SerializedWorkflow } from './types';
import { getBlock } from '@/blocks';
import { getBlock, getBlockTypeForTool } from '@/blocks';
import { OutputType, SubBlockType } from '@/blocks/types';
export class Serializer {
@@ -25,7 +25,7 @@ export class Serializer {
}
// Get the tool ID from the block's configuration
const tools = blockConfig.workflow.tools;
const tools = blockConfig.tools;
if (!tools?.access || tools.access.length === 0) {
throw new Error(`No tools specified for block type: ${block.type}`);
}
@@ -83,15 +83,28 @@ export class Serializer {
}
private deserializeBlock(serialized: SerializedBlock): BlockState {
const toolId = serialized.config.tool;
const blockType = getBlockTypeForTool(toolId);
if (!blockType) {
throw new Error(`Could not determine block type for tool: ${toolId}`);
}
const blockConfig = getBlock(blockType);
if (!blockConfig) {
throw new Error(`Block configuration not found for type: ${blockType}`);
}
return {
id: serialized.id,
type: serialized.config.tool,
name: `${serialized.config.tool} Block`,
type: blockType,
name: `${blockType} Block`,
position: serialized.position,
subBlocks: Object.entries(serialized.config.params).reduce((acc, [key, value]) => {
const subBlock = blockConfig.workflow.subBlocks?.find(sb => sb.id === key);
acc[key] = {
id: key,
type: this.inferSubBlockType(value),
type: subBlock?.type || 'short-input',
value: value
};
return acc;
@@ -99,14 +112,4 @@ export class Serializer {
outputType: serialized.config.interface.outputs.output as OutputType
};
}
private inferSubBlockType(value: any): SubBlockType {
if (Array.isArray(value) && Array.isArray(value[0])) {
return 'table';
}
if (typeof value === 'string' && value.length > 100) {
return 'long-input';
}
return 'short-input';
}
}