diff --git a/python/sandbox/simple_import/example_01.metta b/python/sandbox/simple_import/example_01.metta new file mode 100644 index 000000000..f2b14c383 --- /dev/null +++ b/python/sandbox/simple_import/example_01.metta @@ -0,0 +1,11 @@ +!(import! &self simple_import) + +!(import_from example_01 import simple_fun) +!(import_from example_01 import SimpleObject) + +!(bind! so (SimpleObject)) + +; it is important that obj will have type SimpleObject when passed to simple_fun! +!(simple_fun 1 2 "3" (kwarg1 2) (obj so) ) + +!(call_dot so method "arg1" "arg2" (arg3 3)) \ No newline at end of file diff --git a/python/sandbox/simple_import/example_01.py b/python/sandbox/simple_import/example_01.py new file mode 100644 index 000000000..24f4bf797 --- /dev/null +++ b/python/sandbox/simple_import/example_01.py @@ -0,0 +1,22 @@ +def print_args(*args, **kwargs): + print("arguments:") + for a in args: + print(a, type(a)) + print("keyword arguments:") + for k,v in kwargs.items(): + print(k, v, type(v)) + +def simple_fun(*args, **kwargs): + print("Call simple function") + print_args(*args, **kwargs) + print("") + return 0 + +class SimpleObject: + def method(self, *args, **kwargs): + print("Call Method of simple Object") + print_args(*args, **kwargs) + print("") + return "0" + def __str__(self): + return "simple_object" diff --git a/python/sandbox/simple_import/example_02_numpy.metta b/python/sandbox/simple_import/example_02_numpy.metta new file mode 100644 index 000000000..edf992a8c --- /dev/null +++ b/python/sandbox/simple_import/example_02_numpy.metta @@ -0,0 +1,18 @@ +!(import! &self simple_import) + +!(import_as numpy as np) + +!(bind! a1 (call_dot np array (ptuple 1 2 3) )) +!(bind! a2 (call_dot a1 __mul__ 3)) +!(bind! a3 (call_dot a1 __add__ a2)) + + +!(__unwrap a1) +!(__unwrap a2) +!(__unwrap a3) + +!(bind! m1 (call_dot np array (ptuple (1 2 3) (4 4 5) (6 7 8)) )) +!(import_as numpy.linalg as linalg) +!(bind! m1_inv (call_dot linalg inv m1)) + +!(__unwrap (call_dot np matmul m1 m1_inv)) diff --git a/python/sandbox/simple_import/example_03_langchain.metta b/python/sandbox/simple_import/example_03_langchain.metta new file mode 100644 index 000000000..1fe219d2d --- /dev/null +++ b/python/sandbox/simple_import/example_03_langchain.metta @@ -0,0 +1,21 @@ +!(import! &self simple_import) + +!(import_from langchain_openai import ChatOpenAI) +!(import_from langchain_core.prompts import ChatPromptTemplate) +!(import_from langchain_core.output_parsers import StrOutputParser) + + +!(bind! model (ChatOpenAI (temperature 0) (model "gpt-3.5-turbo"))) + +!(bind! prompt (call_dot ChatPromptTemplate from_template "tell me a joke about cat")) + +!(bind! chain1 (chain prompt model (StrOutputParser) )) + +!(__unwrap(call_dot chain1 invoke (pdict))) + +!(bind! prompt2 (call_dot ChatPromptTemplate from_messages (ptuple ("system" "You are very funny") ("user" "tell me joke about {foo}")))) + +!(bind! chain2 (chain prompt2 model (StrOutputParser) )) + +!(__unwrap(call_dot chain2 invoke (pdict (foo "dogs") ))) + diff --git a/python/sandbox/simple_import/simple_import.py b/python/sandbox/simple_import/simple_import.py new file mode 100644 index 000000000..0ba0c3509 --- /dev/null +++ b/python/sandbox/simple_import/simple_import.py @@ -0,0 +1,128 @@ +from hyperon.atoms import OperationAtom, OperationObject, GroundedAtom, ValueAtom, ExpressionAtom, SymbolAtom, ValueObject +from hyperon.ext import register_atoms +import os +import sys + +def groundedatom_to_python_object(a): + obj = a.get_object() + if isinstance(obj, ValueObject): + obj = obj.value + if isinstance(obj, OperationObject): + obj = obj.content + # At this point it is already python object + if isinstance(obj, PythonCaller): + obj = obj.obj + return obj + +def tuple_to_keyvalue(a): + ac = a.get_children() + if len(ac) != 2: + raise Exception("Syntax error in tuple_to_keyvalue") + return str(ac[0]), groundedatom_to_python_object(ac[1]) + +def atoms_to_args(*atoms): + args = [] + kwargs = {} + for a in atoms: + if isinstance(a, GroundedAtom): + args.append(groundedatom_to_python_object(a)) + elif isinstance(a, ExpressionAtom): + k,v = tuple_to_keyvalue(a) + kwargs[k] = v + else: + raise Exception(f"Unexpected error: {a}, {type(a)}") + return args, kwargs + +class PythonCaller: + def __init__(self, obj): + self.obj = obj + + def __call__(self, *atoms): + args, kwargs = atoms_to_args(*atoms) + return [ValueAtom(PythonCaller(self.obj(*args, **kwargs)))] + +def _import_and_create_operationatom(metta, import_str, obj): + + # we only need these 3 lines to import from the current directory + # TODO fix it somehow differently + current_directory = os.getcwd() + if current_directory not in sys.path: + sys.path.append(current_directory) + + local_scope = {} + exec(import_str, {}, local_scope) + oatom = OperationAtom(obj, PythonCaller(local_scope[obj]), unwrap = False) + metta.register_atom(obj, oatom) + + +def import_from(metta, lib, i, obj): + if str(i) != "import": + raise Exception("bad import syntax") + lib = str(lib) + obj = str(obj) + _import_and_create_operationatom(metta, f"from {lib} import {obj}", obj) + return [] + +def import_as(metta, lib, a, obj): + if str(a) != "as": + raise Exception("bad import syntax") + lib = str(lib) + obj = str(obj) + _import_and_create_operationatom(metta, f"import {lib} as {obj}", obj) + return [] + +def call_with_dot(*atoms): + if len(atoms) < 2: + raise Exception("Syntax error") + obj = groundedatom_to_python_object(atoms[0]) + method = str(atoms[1]) + atoms = atoms[2:] + args, kwargs = atoms_to_args(*atoms) + rez = getattr(obj, method)(*args, **kwargs) + return [ValueAtom(PythonCaller(rez))] + +def __unwrap(obj): + return obj.obj + +@register_atoms(pass_metta=True) +def my_atoms(metta): + return {'import_from': OperationAtom('import_from', lambda *args: import_from(metta, *args), unwrap = False), + 'import_as': OperationAtom('import_as', lambda *args: import_as (metta, *args), unwrap = False)} + +@register_atoms() +def my_atoms2(): + return {'__unwrap': OperationAtom('__unwrap', __unwrap), + "call_dot": OperationAtom("call_dot", call_with_dot, unwrap = False)} + +# The functions which are not required for import, but nice for examples + +# convert nested tuples to nested python tuples +def _ptuple(*atoms): + rez = [] + for a in atoms: + if isinstance(a, GroundedAtom): + rez.append(groundedatom_to_python_object(a)) + elif isinstance(a, ExpressionAtom): + rez.append(_ptuple(*a.get_children())) + return tuple(rez) + +def ptuple(*atoms): + return [ValueAtom(_ptuple(*atoms))] + +# convert pair of tuples to python dictionary +def pdict(*atoms): + return [ValueAtom(dict([tuple_to_keyvalue(a) for a in atoms]))] + +# chain python objects with | (syntactic sugar for langchain) +def chain(*atoms): + objects = [groundedatom_to_python_object(a) for a in atoms] + result = objects[0] + for obj in objects[1:]: + result = result | obj + return [ValueAtom(PythonCaller(result))] + +@register_atoms() +def my_atoms3(): + return {"ptuple": OperationAtom("ptuple", ptuple, unwrap = False), + "pdict": OperationAtom("pdict", pdict, unwrap = False), + "chain": OperationAtom("chain", chain, unwrap = False)}