From 40382b85a2885a11e75ca05c29005477a0ad06f7 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Fri, 26 Jan 2024 11:22:15 +0800 Subject: [PATCH 1/4] update dynaport logic if parameter is passed from a non-literal component --- xai_components/base.py | 97 +++++++++++++++++++++------------- xircuits/compiler/generator.py | 32 +++++------ 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/xai_components/base.py b/xai_components/base.py index 953e1321..da22d735 100644 --- a/xai_components/base.py +++ b/xai_components/base.py @@ -1,41 +1,29 @@ from argparse import Namespace -from typing import TypeVar, Generic, Tuple, NamedTuple, List +from typing import TypeVar, Generic, Tuple, NamedTuple, Callable, List T = TypeVar('T') - class InArg(Generic[T]): - value: T - - def __init__(self, value: T) -> None: - self.value = value + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: + self._value = value + self._getter = getter - @classmethod - def empty(cls): - return InArg(None) + @property + def value(self): + return self._getter(self._value) + @value.setter + def value(self, value: T): + self._value = value class OutArg(Generic[T]): - value: T - - def __init__(self, value: T) -> None: + def __init__(self, value: T = None, *args, **kwargs) -> None: self.value = value - @classmethod - def empty(cls): - return OutArg(None) - class InCompArg(Generic[T]): - value: T - - def __init__(self, value: T) -> None: + def __init__(self, value: T = None, *args, **kwargs) -> None: self.value = value - @classmethod - def empty(cls): - return InCompArg(None) - - def xai_component(*args, **kwargs): # Passthrough element without any changes. # This is used for parser metadata only. @@ -54,19 +42,24 @@ class ExecutionContext: def __init__(self, args: Namespace): self.args = args - class BaseComponent: - def __init__(self): all_ports = self.__annotations__ for key, type_arg in all_ports.items(): - if isinstance(type_arg, InArg[any].__class__): - setattr(self, key, InArg.empty()) - elif isinstance(type_arg, InCompArg[any].__class__): - setattr(self, key, InCompArg.empty()) - elif isinstance(type_arg, OutArg[any].__class__): - setattr(self, key, OutArg.empty()) - elif type_arg == str(self.__class__): + port_class = type_arg.__origin__ + port_type = type_arg.__args__[0] + if port_class in (InArg, InCompArg, OutArg): + if hasattr(port_type, 'initial_value'): + port_value = port_type.initial_value() + else: + port_value = None + + if hasattr(port_type, 'getter'): + port_getter = port_type.getter + else: + port_getter = lambda x: x + setattr(self, key, port_class(port_value, port_getter)) + else: setattr(self, key, None) @classmethod @@ -79,7 +72,6 @@ def execute(self, ctx) -> None: def do(self, ctx) -> Tuple[bool, 'BaseComponent']: pass - class Component(BaseComponent): next: BaseComponent @@ -132,7 +124,6 @@ def __init__(self, value): def get_value(self): return self.__value - class message(NamedTuple): role: str content: str @@ -141,13 +132,43 @@ class chat(NamedTuple): messages: List[message] class dynalist: - def __init__(self, value): - self.value = value + @staticmethod + def initial_value(): + return [] + + @staticmethod + def getter(x): + return [item.value if isinstance(item, (InArg, OutArg)) else item for item in x] class dynatuple: def __init__(self, value): self.value = value + @staticmethod + def initial_value(): + return tuple() + + @staticmethod + def getter(x): + def resolve(item): + if isinstance(item, (InArg, OutArg)): + return item.value + elif isinstance(item, str): + return item + else: + return item + + return tuple(resolve(item) for item in x) + class dynadict: def __init__(self, value): - self.value = value \ No newline at end of file + self.value = value + + @staticmethod + def initial_value(): + return {} + + @staticmethod + def getter(x): + # Check for InArg and OutArg instances in the dictionary and extract their values + return {key: (val.value if isinstance(val, (InArg, OutArg)) else val) for key, val in x.items()} \ No newline at end of file diff --git a/xircuits/compiler/generator.py b/xircuits/compiler/generator.py index 54a84d10..717eb8b2 100644 --- a/xircuits/compiler/generator.py +++ b/xircuits/compiler/generator.py @@ -165,16 +165,15 @@ def main(args): ports_by_varName[port.varName] = [] ports_by_varName[port.varName].append(port) - # Iterate through grouped ports and append values for varName, ports in ports_by_varName.items(): appended_list = [] merged_dict = {} - convert_to_tuple = False + convert_to_tuple = False for port in ports: if port.source.id not in named_nodes: if port.source.type == "string": - value = port.sourceLabel + value = repr(port.sourceLabel) # Use repr to ensure strings are quoted elif port.source.type == "list": value = json.loads("[" + port.sourceLabel + "]") elif port.source.type == "dict": @@ -183,34 +182,37 @@ def main(args): value = list(eval(port.sourceLabel)) else: value = eval(port.sourceLabel) - else: - value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) + value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) # Variable reference if port.dataType == 'dynadict': merged_dict.update(value) else: appended_list.append(value) - + if port.dataType == 'dynatuple': convert_to_tuple = True - # Create a single AST node for each unique varName and append to code assignment_target = "%s.%s.value" % (named_nodes[ports[0].target.id], ports[0].varName) - - assignment_value = '' + + assignment_value_elements = [] + for item in appended_list: + if isinstance(item, str) and '.' in item: # Assuming '.' indicates a variable reference + assignment_value_elements.append(item) # Add variable reference as is + else: + assignment_value_elements.append(repr(item)) # Use repr for other items to ensure proper representation + + assignment_value = '[' + ', '.join(assignment_value_elements) + ']' + if convert_to_tuple: + assignment_value = '(' + assignment_value.strip('[]') + ',)' if merged_dict: assignment_value = repr(merged_dict) - elif appended_list: - if convert_to_tuple: - assignment_value = repr(tuple(appended_list)) - else: - assignment_value = repr(appended_list) - + tpl = ast.parse("%s = %s" % (assignment_target, assignment_value)) code.append(tpl) + # Set up control flow for node in component_nodes: has_next = False From dd58b39dd516ad3d9c2866fb6d97de9fcb5df6f5 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Tue, 30 Jan 2024 14:40:26 +0800 Subject: [PATCH 2/4] fix dynaports logic if parameter is passed from a non-literal --- xai_components/base.py | 70 ++++++++++++++++++++-------------- xircuits/compiler/generator.py | 50 +++++++++++++----------- 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/xai_components/base.py b/xai_components/base.py index da22d735..38245379 100644 --- a/xai_components/base.py +++ b/xai_components/base.py @@ -17,12 +17,30 @@ def value(self, value: T): self._value = value class OutArg(Generic[T]): - def __init__(self, value: T = None, *args, **kwargs) -> None: + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: self.value = value + self._getter = getter + + @property + def value(self): + return self._getter(self._value) + + @value.setter + def value(self, value: T): + self._value = value class InCompArg(Generic[T]): - def __init__(self, value: T = None, *args, **kwargs) -> None: + def __init__(self, value: T = None, getter: Callable[[T], any] = lambda x: x) -> None: self.value = value + self._getter = getter + + @property + def value(self): + return self._getter(self._value) + + @value.setter + def value(self, value: T): + self._value = value def xai_component(*args, **kwargs): # Passthrough element without any changes. @@ -123,7 +141,7 @@ def __init__(self, value): def get_value(self): return self.__value - + class message(NamedTuple): role: str content: str @@ -131,44 +149,40 @@ class message(NamedTuple): class chat(NamedTuple): messages: List[message] -class dynalist: - @staticmethod - def initial_value(): - return [] +class dynalist(list): + def __init__(self, *args): + super().__init__(args) @staticmethod def getter(x): + if x is None: + return [] return [item.value if isinstance(item, (InArg, OutArg)) else item for item in x] -class dynatuple: - def __init__(self, value): - self.value = value - - @staticmethod - def initial_value(): - return tuple() - +class dynatuple(tuple): + def __init__(self, *args): + super().__init__(args) @staticmethod def getter(x): + if x is None: + return tuple() def resolve(item): - if isinstance(item, (InArg, OutArg)): + if isinstance(item, (InArg, InCompArg,OutArg)): return item.value - elif isinstance(item, str): - return item else: return item - return tuple(resolve(item) for item in x) - -class dynadict: - def __init__(self, value): - self.value = value - @staticmethod - def initial_value(): - return {} +class dynadict(dict): + def __init__(self, *args): + super().__init__(args) @staticmethod def getter(x): - # Check for InArg and OutArg instances in the dictionary and extract their values - return {key: (val.value if isinstance(val, (InArg, OutArg)) else val) for key, val in x.items()} \ No newline at end of file + if x is None: + return {} + + if isinstance(x, (InArg, InCompArg, OutArg)): + x = x.value + + return {key: (val.value if isinstance(val, (InArg, OutArg)) else val) for key, val in x.items()} diff --git a/xircuits/compiler/generator.py b/xircuits/compiler/generator.py index 717eb8b2..3c760026 100644 --- a/xircuits/compiler/generator.py +++ b/xircuits/compiler/generator.py @@ -157,7 +157,7 @@ def main(args): # Handle dynamic connections dynaports = [p for p in node.ports if p.direction == 'in' and p.type != 'triangle-link' and p.dataType in DYNAMIC_PORTS] - ports_by_varName = {} + ports_by_varName = {} # Group ports by varName for port in dynaports: @@ -167,52 +167,56 @@ def main(args): for varName, ports in ports_by_varName.items(): appended_list = [] - merged_dict = {} + merged_dicts = [] # Use a list to store dictionaries for direct merge or variable references for unpacking convert_to_tuple = False for port in ports: if port.source.id not in named_nodes: if port.source.type == "string": - value = repr(port.sourceLabel) # Use repr to ensure strings are quoted + value = port.sourceLabel elif port.source.type == "list": value = json.loads("[" + port.sourceLabel + "]") elif port.source.type == "dict": value = json.loads("{" + port.sourceLabel + "}") elif port.source.type == "tuple": - value = list(eval(port.sourceLabel)) + value = tuple(eval(port.sourceLabel)) else: value = eval(port.sourceLabel) + if port.dataType == 'dynadict' and isinstance(value, dict): + merged_dicts.append(value) # Append the dictionary for direct merge else: value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) # Variable reference - - if port.dataType == 'dynadict': - merged_dict.update(value) - else: + if port.dataType == 'dynadict': + merged_dicts.append(value) # Append the variable reference for unpacking + if port.dataType != 'dynadict': appended_list.append(value) - if port.dataType == 'dynatuple': convert_to_tuple = True assignment_target = "%s.%s.value" % (named_nodes[ports[0].target.id], ports[0].varName) - - assignment_value_elements = [] - for item in appended_list: - if isinstance(item, str) and '.' in item: # Assuming '.' indicates a variable reference - assignment_value_elements.append(item) # Add variable reference as is + if merged_dicts: + if all(isinstance(item, dict) for item in merged_dicts): # All items are actual dictionaries + combined_dict = {} + for d in merged_dicts: + combined_dict.update(d) + assignment_value = repr(combined_dict) + else: # At least one item is a variable reference + dict_items_to_unpack = ', '.join('**{%s}' % item if isinstance(item, str) else '**' + repr(item) for item in merged_dicts) + assignment_value = '{' + dict_items_to_unpack + '}' + elif convert_to_tuple: + tuple_elements = [item if isinstance(item, str) and '.' in item else repr(item) for item in appended_list] + if len(tuple_elements) == 1: + assignment_value = '(' + tuple_elements[0] + ',)' else: - assignment_value_elements.append(repr(item)) # Use repr for other items to ensure proper representation - - assignment_value = '[' + ', '.join(assignment_value_elements) + ']' - if convert_to_tuple: - assignment_value = '(' + assignment_value.strip('[]') + ',)' - - if merged_dict: - assignment_value = repr(merged_dict) + assignment_value = '(' + ', '.join(tuple_elements) + ')' + + else: + list_elements = [item if isinstance(item, str) and '.' in item else repr(item) for item in appended_list] + assignment_value = '[' + ', '.join(list_elements) + ']' tpl = ast.parse("%s = %s" % (assignment_target, assignment_value)) code.append(tpl) - # Set up control flow for node in component_nodes: has_next = False From 4bb2c14f639c974fc50d36e5b3f86c8a445e09fb Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Tue, 30 Jan 2024 19:43:42 +0800 Subject: [PATCH 3/4] remove dynadicts as a dynaport, implement as a component --- src/components/port/CustomDynaPortModel.tsx | 7 +-- src/components/port/CustomPortLabel.tsx | 1 - xai_components/base.py | 16 +---- xai_components/xai_utils/utils.py | 67 ++++++++++++++++++++- xircuits/compiler/generator.py | 38 ++++-------- xircuits/compiler/port.py | 2 +- 6 files changed, 81 insertions(+), 50 deletions(-) diff --git a/src/components/port/CustomDynaPortModel.tsx b/src/components/port/CustomDynaPortModel.tsx index 100cd3ac..dea8135b 100644 --- a/src/components/port/CustomDynaPortModel.tsx +++ b/src/components/port/CustomDynaPortModel.tsx @@ -3,7 +3,7 @@ import { CustomPortModel, CustomPortModelOptions } from './CustomPortModel'; import { CustomNodeModel } from '../CustomNodeModel'; export const DYNAMIC_PARAMETER_NODE_TYPES = [ - 'dynalist', 'dynadict', 'dynatuple' + 'dynalist', 'dynatuple' ]; export interface DynaPortRef { @@ -59,11 +59,6 @@ export class CustomDynaPortModel extends CustomPortModel { return true; // Accepts anything } - // if thisLinkedPortType is dynadict, accept only dict - if (thisLinkedPortType === 'dynadict' && thisNodeModelType !== 'dict') { - return false; - } - // default check return super.isTypeCompatible(thisNodeModelType, thisLinkedPortType); } diff --git a/src/components/port/CustomPortLabel.tsx b/src/components/port/CustomPortLabel.tsx index fa129eb9..a914366a 100644 --- a/src/components/port/CustomPortLabel.tsx +++ b/src/components/port/CustomPortLabel.tsx @@ -79,7 +79,6 @@ export class CustomPortLabel extends React.Component { "dict": '{ }', "dynalist": '«[]»', "dynatuple": '«()»', - "dynadict": '«{}»', "union": ' U', "secret": '🗝️', "chat": '🗨', diff --git a/xai_components/base.py b/xai_components/base.py index 38245379..5eb27d74 100644 --- a/xai_components/base.py +++ b/xai_components/base.py @@ -171,18 +171,4 @@ def resolve(item): return item.value else: return item - return tuple(resolve(item) for item in x) - -class dynadict(dict): - def __init__(self, *args): - super().__init__(args) - - @staticmethod - def getter(x): - if x is None: - return {} - - if isinstance(x, (InArg, InCompArg, OutArg)): - x = x.value - - return {key: (val.value if isinstance(val, (InArg, OutArg)) else val) for key, val in x.items()} + return tuple(resolve(item) for item in x) \ No newline at end of file diff --git a/xai_components/xai_utils/utils.py b/xai_components/xai_utils/utils.py index 6e09226b..19df3910 100644 --- a/xai_components/xai_utils/utils.py +++ b/xai_components/xai_utils/utils.py @@ -1,4 +1,4 @@ -from xai_components.base import InArg, OutArg, InCompArg, Component, xai_component +from xai_components.base import InArg, OutArg, InCompArg, Component, xai_component, dynalist, dynatuple import os import sys @@ -166,4 +166,67 @@ def execute(self, ctx) -> None: sleep_timer = self.sleep_timer.value if self.sleep_timer.value else 5.0 print("Sleeping for " + str(sleep_timer) + " seconds.") - time.sleep(sleep_timer) \ No newline at end of file + time.sleep(sleep_timer) + +@xai_component(color="grey") +class MakeList(Component): + """ + A component that takes values from its dynamic list port and output as a normal list. + ##### inPorts: + - list_values: Dynamic list port that can take any vars and append it in a list. + + ##### outPorts: + - output_list: The constructed list from the dynamic list inPorts. + """ + list_values: InArg[dynalist] + output_list: OutArg[list] + + def execute(self, ctx) -> None: + + self.output_list.value = self.list_values.value + print("Constructed List:", self.output_list.value) + +@xai_component(color="grey") +class MakeTuple(Component): + """ + A component that takes values from its dynamic tuple port and output as a normal tuple. + ##### inPorts: + - tuple_values: Dynamic tuple port that can take any vars and append it in a tuple. + + ##### outPorts: + - output_tuple: The constructed tuple from the dynamic tuple inPorts. + """ + tuple_values: InArg[dynatuple] + output_tuple: OutArg[tuple] + + def execute(self, ctx) -> None: + + self.output_tuple.value = self.tuple_values.value + print("Constructed Tuple:", self.output_tuple.value) + +@xai_component(color="grey") +class MakeDict(Component): + """ + A component that takes two dynamic lists (dynalists) as inputs - one for keys and one for values, + and constructs a dictionary from these lists. If there are more keys than values, the unmatched keys + will have None as their value. + + ##### inPorts: + - keys_list: Dynamic list of keys for the dictionary. + - values_list: Dynamic list of values for the dictionary. + + ##### outPorts: + - output_dict: The constructed dictionary from the provided keys and values. + """ + keys_list: InArg[dynalist] + values_list: InArg[dynalist] + output_dict: OutArg[dict] + + def execute(self, ctx) -> None: + keys = self.keys_list.value + values = self.values_list.value + + constructed_dict = {key: values[i] if i < len(values) else None for i, key in enumerate(keys)} + + self.output_dict.value = constructed_dict + print("Constructed Dictionary:", self.output_dict.value) diff --git a/xircuits/compiler/generator.py b/xircuits/compiler/generator.py index 3c760026..f794366a 100644 --- a/xircuits/compiler/generator.py +++ b/xircuits/compiler/generator.py @@ -1,5 +1,6 @@ import ast import itertools +from collections import namedtuple import re import json import sys @@ -156,9 +157,10 @@ def main(args): # Handle dynamic connections dynaports = [p for p in node.ports if p.direction == 'in' and p.type != 'triangle-link' and p.dataType in DYNAMIC_PORTS] - ports_by_varName = {} + RefOrValue = namedtuple('RefOrValue', ['value', 'is_ref']) # Renamed to RefOrValue + # Group ports by varName for port in dynaports: if port.varName not in ports_by_varName: @@ -166,12 +168,11 @@ def main(args): ports_by_varName[port.varName].append(port) for varName, ports in ports_by_varName.items(): - appended_list = [] - merged_dicts = [] # Use a list to store dictionaries for direct merge or variable references for unpacking - convert_to_tuple = False + dynaport_values = [] for port in ports: if port.source.id not in named_nodes: + # Handle Literals if port.source.type == "string": value = port.sourceLabel elif port.source.type == "list": @@ -182,41 +183,28 @@ def main(args): value = tuple(eval(port.sourceLabel)) else: value = eval(port.sourceLabel) - if port.dataType == 'dynadict' and isinstance(value, dict): - merged_dicts.append(value) # Append the dictionary for direct merge + dynaport_values.append(RefOrValue(value, False)) else: + # Handle named node references value = "%s.%s" % (named_nodes[port.source.id], port.sourceLabel) # Variable reference - if port.dataType == 'dynadict': - merged_dicts.append(value) # Append the variable reference for unpacking - if port.dataType != 'dynadict': - appended_list.append(value) - if port.dataType == 'dynatuple': - convert_to_tuple = True + dynaport_values.append(RefOrValue(value, True)) assignment_target = "%s.%s.value" % (named_nodes[ports[0].target.id], ports[0].varName) - if merged_dicts: - if all(isinstance(item, dict) for item in merged_dicts): # All items are actual dictionaries - combined_dict = {} - for d in merged_dicts: - combined_dict.update(d) - assignment_value = repr(combined_dict) - else: # At least one item is a variable reference - dict_items_to_unpack = ', '.join('**{%s}' % item if isinstance(item, str) else '**' + repr(item) for item in merged_dicts) - assignment_value = '{' + dict_items_to_unpack + '}' - elif convert_to_tuple: - tuple_elements = [item if isinstance(item, str) and '.' in item else repr(item) for item in appended_list] + + if ports[0].dataType == 'dynatuple': + tuple_elements = [item.value if item.is_ref else repr(item.value) for item in dynaport_values] if len(tuple_elements) == 1: assignment_value = '(' + tuple_elements[0] + ',)' else: assignment_value = '(' + ', '.join(tuple_elements) + ')' - else: - list_elements = [item if isinstance(item, str) and '.' in item else repr(item) for item in appended_list] + list_elements = [item.value if item.is_ref else repr(item.value) for item in dynaport_values] assignment_value = '[' + ', '.join(list_elements) + ']' tpl = ast.parse("%s = %s" % (assignment_target, assignment_value)) code.append(tpl) + # Set up control flow for node in component_nodes: has_next = False diff --git a/xircuits/compiler/port.py b/xircuits/compiler/port.py index 303f95ec..c99f5647 100644 --- a/xircuits/compiler/port.py +++ b/xircuits/compiler/port.py @@ -13,4 +13,4 @@ class Port: sourceLabel: str direction: str -DYNAMIC_PORTS = ['dynalist', 'dynadict', 'dynatuple'] +DYNAMIC_PORTS = ['dynalist', 'dynatuple'] From 710dec4faa0e46c504b6e73e350754c8e5b9e160 Mon Sep 17 00:00:00 2001 From: MFA-X-AI Date: Wed, 31 Jan 2024 01:20:42 +0800 Subject: [PATCH 4/4] allow literal chats to be connected to list inPorts --- src/components/port/CustomPortModel.ts | 34 +++++++++++++++++++------- xai_components/base.py | 7 +----- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/components/port/CustomPortModel.ts b/src/components/port/CustomPortModel.ts index 89afd5b7..2b833946 100644 --- a/src/components/port/CustomPortModel.ts +++ b/src/components/port/CustomPortModel.ts @@ -154,19 +154,35 @@ export class CustomPortModel extends DefaultPortModel { return PARAMETER_NODE_TYPES.includes(nodeModelType); } + static typeCompatibilityMap = { + "chat": ["list"], + "secret": ["string", "int", "float"], + }; + isTypeCompatible(thisNodeModelType, dataType) { - if(thisNodeModelType !== dataType){ - // Skip 'any' type check - if(dataType === 'any'){ - return true; - } - // if multiple types are accepted by target node port, check if source port type is among them - if(dataType.includes(thisNodeModelType)) { + // Check for direct compatibility or 'any' type + if (thisNodeModelType === dataType || dataType === 'any') { + return true; + } + + // Check if the thisNodeModelType exists in the compatibility map + if (CustomPortModel.typeCompatibilityMap.hasOwnProperty(thisNodeModelType)) { + // Get the array of compatible data types for thisNodeModelType + const compatibleDataTypes = CustomPortModel.typeCompatibilityMap[thisNodeModelType]; + + // Check if dataType is in the array of compatible types + if (compatibleDataTypes.includes(dataType)) { return true; } - return false; // types are incompatible } - return true; + + // If multiple types are accepted by target node port, check if source port type is among them + if (dataType.includes(thisNodeModelType)) { + return true; + } + + // If none of the above checks pass, the types are incompatible + return false; } canTriangleLinkToTriangle = (thisPort, port) => { diff --git a/xai_components/base.py b/xai_components/base.py index 5eb27d74..d3810ea2 100644 --- a/xai_components/base.py +++ b/xai_components/base.py @@ -135,12 +135,7 @@ def execute_graph(args: Namespace, start: BaseComponent, ctx) -> None: class secret: - - def __init__(self, value): - self.__value = value - - def get_value(self): - return self.__value + pass class message(NamedTuple): role: str