Skip to content

Commit

Permalink
Merge pull request #652 from astroseger/simple_python_import
Browse files Browse the repository at this point in the history
Simple python import
  • Loading branch information
Necr0x0Der authored Apr 9, 2024
2 parents ca18fdd + 741f024 commit 34f9794
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
11 changes: 11 additions & 0 deletions python/sandbox/simple_import/example_01.metta
Original file line number Diff line number Diff line change
@@ -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))
22 changes: 22 additions & 0 deletions python/sandbox/simple_import/example_01.py
Original file line number Diff line number Diff line change
@@ -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"
18 changes: 18 additions & 0 deletions python/sandbox/simple_import/example_02_numpy.metta
Original file line number Diff line number Diff line change
@@ -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))
21 changes: 21 additions & 0 deletions python/sandbox/simple_import/example_03_langchain.metta
Original file line number Diff line number Diff line change
@@ -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") )))

128 changes: 128 additions & 0 deletions python/sandbox/simple_import/simple_import.py
Original file line number Diff line number Diff line change
@@ -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)}

0 comments on commit 34f9794

Please sign in to comment.