mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Moved tools property out of workflows in block definitions, updated serializer & tests accordingly
This commit is contained in:
@@ -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'
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -10,11 +10,11 @@ export const FunctionBlock: BlockConfig = {
|
||||
icon: CodeIcon,
|
||||
category: 'advanced',
|
||||
},
|
||||
tools: {
|
||||
access: ['function']
|
||||
},
|
||||
workflow: {
|
||||
outputType: 'json',
|
||||
tools: {
|
||||
access: ['function']
|
||||
},
|
||||
inputs: {
|
||||
code: 'string'
|
||||
},
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
}],
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user