Skip to content

Commit

Permalink
Merge pull request #671 from astroseger/pyfuns
Browse files Browse the repository at this point in the history
Implementing py-tuple py-list py-dict py-chain
  • Loading branch information
Necr0x0Der authored Apr 27, 2024
2 parents 82130e8 + 073527d commit 5cd8235
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 193 deletions.
51 changes: 50 additions & 1 deletion python/hyperon/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os

from .atoms import ExpressionAtom, E, GroundedAtom, OperationAtom, ValueAtom, NoReduceError, AtomType, MatchableObject, \
G, S, Atoms, get_string_value
G, S, Atoms, get_string_value, GroundedObject, SymbolAtom
from .base import Tokenizer, SExprParser
from .ext import register_atoms, register_tokens
import hyperonpy as hp
Expand Down Expand Up @@ -205,3 +205,52 @@ def load_ascii_atom(space, name):
return {
r"load-ascii": loadAtom
}

def try_unwrap_python_object(a, is_symbol_to_str = False):
if isinstance(a, GroundedAtom):
# FIXME? Do we need to unwrap a grounded object if it is not GroundedObject?
return a.get_object().content if isinstance(a.get_object(), GroundedObject) else a.get_object()
if is_symbol_to_str and isinstance(a, SymbolAtom):
return a.get_name()
return a

# convert nested tuples to nested python tuples or lists
def _py_tuple_list(tuple_list, metta_tuple):
rez = []
for a in metta_tuple.get_children():
if isinstance(a, ExpressionAtom):
rez.append(_py_tuple_list(tuple_list, a))
else:
rez.append(try_unwrap_python_object(a))
return tuple_list(rez)

def py_tuple(metta_tuple):
return [ValueAtom(_py_tuple_list(tuple, metta_tuple))]

def py_list(metta_tuple):
return [ValueAtom(_py_tuple_list(list, metta_tuple))]

def tuple_to_keyvalue(a):
ac = a.get_children()
if len(ac) != 2:
raise Exception("Syntax error in tuple_to_keyvalue")
return try_unwrap_python_object(ac[0], is_symbol_to_str = True), try_unwrap_python_object(ac[1])

# convert pair of tuples to python dictionary
def py_dict(metta_tuple):
return [ValueAtom(dict([tuple_to_keyvalue(a) for a in metta_tuple.get_children()]))]

# chain python objects with | (syntactic sugar for langchain)
def py_chain(metta_tuple):
objects = [try_unwrap_python_object(a) for a in metta_tuple.get_children()]
result = objects[0]
for obj in objects[1:]:
result = result | obj
return [ValueAtom(result)]

@register_atoms()
def py_funs():
return {"py-tuple": OperationAtom("py-tuple", py_tuple, unwrap = False),
"py-list" : OperationAtom("py-list" , py_list , unwrap = False),
"py-dict" : OperationAtom("py-dict" , py_dict , unwrap = False),
"py-chain": OperationAtom("py-chain", py_chain, unwrap = False)}
11 changes: 5 additions & 6 deletions python/sandbox/simple_import/example_01.metta
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
!(import! &self simple_import)

!(import_from example_01 import simple_fun)
!(import_from example_01 import SimpleObject)
!(bind! simple_fun (py-atom example_01.simple_fun))
!(bind! SimpleObject (py-atom example_01.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) )
!(simple_fun 1 2 "3" (Kwargs (kwarg1 2) (obj so)) )

!(call_dot so method "arg1" "arg2" (arg3 3))
!( (py-dot so method) "arg1" "arg2" (Kwargs (arg3 3)) )
24 changes: 11 additions & 13 deletions python/sandbox/simple_import/example_02_numpy.metta
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
!(import! &self simple_import)
!(bind! np (py-atom numpy))

!(import_as numpy as np)
!(bind! a1 ( (py-dot np array) (py-atom (py-tuple (1 2 3)) )))
!(bind! a2 ( (py-dot a1 __mul__) 3))
!(bind! a3 ( (py-dot a1 __add__) a2))

!(bind! a1 (call_dot np array (ptuple 1 2 3) ))
!(bind! a2 (call_dot a1 __mul__ 3))
!(bind! a3 (call_dot a1 __add__ a2))

!(a1)
!(a2)
!(a3)

!(__unwrap a1)
!(__unwrap a2)
!(__unwrap a3)
!(bind! m1 ((py-dot np array) (py-atom (py-list ((1 2 3) (py-list (4 4 5)) (py-tuple (6 7 8))) ))))
!(bind! linalg (py-atom numpy.linalg))
!(bind! m1_inv ( (py-dot linalg inv) m1))

!(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))
!( (py-dot np matmul) m1 m1_inv)
23 changes: 10 additions & 13 deletions python/sandbox/simple_import/example_03_langchain.metta
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
!(import! &self simple_import)
!(bind! ChatOpenAI (py-atom langchain_openai.ChatOpenAI))
!(bind! ChatPromptTemplate (py-atom langchain_core.prompts.ChatPromptTemplate))
!(bind! StrOutputParser (py-atom langchain_core.output_parsers.StrOutputParser))

!(import_from langchain_openai import ChatOpenAI)
!(import_from langchain_core.prompts import ChatPromptTemplate)
!(import_from langchain_core.output_parsers import StrOutputParser)
!(bind! model (ChatOpenAI (Kwargs (temperature 0) (model "gpt-3.5-turbo"))))

!(bind! prompt ( (py-dot ChatPromptTemplate from_template) "tell me a joke about cat"))

!(bind! model (ChatOpenAI (temperature 0) (model "gpt-3.5-turbo")))
!(bind! chain1 (py-chain (prompt model (StrOutputParser)) ))

!(bind! prompt (call_dot ChatPromptTemplate from_template "tell me a joke about cat"))
!( (py-dot chain1 invoke) (py-dict ()))

!(bind! chain1 (chain prompt model (StrOutputParser) ))
!(bind! prompt2 ( (py-dot ChatPromptTemplate from_messages ) (py-tuple (("system" "You are very funny") ("user" "tell me joke about {foo}")))))

!(__unwrap(call_dot chain1 invoke (pdict)))
!(bind! chain2 (py-chain (prompt2 model (StrOutputParser)) ))

!(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") )))
!((py-dot chain2 invoke) (py-dict (("foo" "dogs"))))

18 changes: 5 additions & 13 deletions python/sandbox/simple_import/example_04_numpy_simple_import.metta
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
!(import! &self simple_import)
!(bind! linalg (py-atom numpy.linalg))
!(bind! numpy (py-atom numpy))

; with simple "import" it is rather common that we import something
; twice because of submodules in python
; So let's import twice to make sure that it does not cause any problems
!(import numpy)
!(import numpy)
!(import numpy.linalg)


!(bind! m1 (call_dot2 numpy random rand 3 3 ))
!(bind! m1_inv (call_dot2 numpy linalg inv m1))

!(__unwrap (call_dot numpy matmul m1 m1_inv))
!(bind! m1 ((py-dot numpy random.rand) 3 3 ))
!(bind! m1_inv ( (py-dot linalg inv) m1))
!( (py-dot numpy matmul) m1 m1_inv)
147 changes: 0 additions & 147 deletions python/sandbox/simple_import/simple_import.py

This file was deleted.

31 changes: 31 additions & 0 deletions python/tests/test_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,37 @@ def test_regex(self):

self.assertEqual(metta.run('!(intent "Hi")', True), [])

def test_py_list_tuple(self):
metta = MeTTa(env_builder=Environment.test_env())
self.assertEqual(metta.run('!(py-list ())'), [[ValueAtom( [] )]])
self.assertEqual(metta.run('!(py-tuple ())'), [[ValueAtom( () )]])
self.assertEqual(metta.run('!(py-dict ())'), [[ValueAtom( {} )]])
self.assertEqual(metta.run('!(py-tuple (1 (2 (3 "3")) (py-atom list)))'), [[ValueAtom((1,(2,(3, "3")), list))]])
self.assertEqual(metta.run('!(py-list (1 2 (4.5 3)))'), [[ValueAtom( [1,2,[4.5,3]] )]])
self.assertEqual(metta.run('!(py-list (1 2 (py-tuple (3 4))))'), [[ValueAtom( [1,2, (3,4)] )]])

self.assertEqual(metta.run('!(py-dict ((a "b") ("b" "c")))'), [[ValueAtom( {"a":"b", "b":"c"} )]])

self.assertEqual(str(metta.run('!(py-list (a b c))')[0][0].get_object().content[2]), "c")

# We need py-chain for langchain, but we test with bitwise operation | (1 | 2 | 3 | 4 = 7)
self.assertEqual(metta.run('!(py-chain (1 2 3 4))'), [[ValueAtom( 7 )]])

# test when we except errors (just in case we reset metta after each exception)
self.assertRaises(Exception, metta.run('!(py-dict (("a" "b" "c") ("b" "c")))'))
metta = MeTTa(env_builder=Environment.test_env())

self.assertRaises(Exception, metta.run('!(py-dict (("a") ("b" "c")))'))
metta = MeTTa(env_builder=Environment.test_env())

self.assertRaises(Exception, metta.run('!(py-dict ("a" "b") ("b" "c"))'))
metta = MeTTa(env_builder=Environment.test_env())

self.assertRaises(Exception, metta.run('!(py-list 1 2)'))
metta = MeTTa(env_builder=Environment.test_env())

self.assertRaises(Exception, metta.run('!(py-list 1)'))
metta = MeTTa(env_builder=Environment.test_env())

if __name__ == "__main__":
unittest.main()

0 comments on commit 5cd8235

Please sign in to comment.