Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing py-tuple py-list py-dict py-chain #671

Merged
merged 14 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, ValueObject, OperationObject, 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()
Loading