You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_user_defined_functions.py 7.5 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import tempfile
  2. import pytest
  3. from autogen.coding.base import CodeBlock
  4. from autogen.coding.local_commandline_code_executor import LocalCommandLineCodeExecutor
  5. try:
  6. import pandas
  7. except ImportError:
  8. skip = True
  9. else:
  10. skip = False
  11. from autogen.coding.func_with_reqs import FunctionWithRequirements, with_requirements
  12. classes_to_test = [LocalCommandLineCodeExecutor]
  13. def add_two_numbers(a: int, b: int) -> int:
  14. """Add two numbers together."""
  15. return a + b
  16. @with_requirements(python_packages=["pandas"], global_imports=["pandas"])
  17. def load_data() -> "pandas.DataFrame":
  18. """Load some sample data.
  19. Returns:
  20. pandas.DataFrame: A DataFrame with the following columns: name(str), location(str), age(int)
  21. """
  22. data = {
  23. "name": ["John", "Anna", "Peter", "Linda"],
  24. "location": ["New York", "Paris", "Berlin", "London"],
  25. "age": [24, 13, 53, 33],
  26. }
  27. return pandas.DataFrame(data)
  28. @with_requirements(global_imports=["NOT_A_REAL_PACKAGE"])
  29. def function_incorrect_import() -> "pandas.DataFrame":
  30. return pandas.DataFrame()
  31. @with_requirements(python_packages=["NOT_A_REAL_PACKAGE"])
  32. def function_incorrect_dep() -> "pandas.DataFrame":
  33. return pandas.DataFrame()
  34. def function_missing_reqs() -> "pandas.DataFrame":
  35. return pandas.DataFrame()
  36. @pytest.mark.parametrize("cls", classes_to_test)
  37. @pytest.mark.skipif(skip, reason="pandas not installed")
  38. def test_can_load_function_with_reqs(cls) -> None:
  39. with tempfile.TemporaryDirectory() as temp_dir:
  40. executor = cls(work_dir=temp_dir, functions=[load_data])
  41. code = f"""from {executor.functions_module} import load_data
  42. import pandas
  43. # Get first row's name
  44. print(load_data().iloc[0]['name'])"""
  45. result = executor.execute_code_blocks(
  46. code_blocks=[
  47. CodeBlock(language="python", code=code),
  48. ]
  49. )
  50. assert result.output == "John\n"
  51. assert result.exit_code == 0
  52. @pytest.mark.parametrize("cls", classes_to_test)
  53. @pytest.mark.skipif(skip, reason="pandas not installed")
  54. def test_can_load_function(cls) -> None:
  55. with tempfile.TemporaryDirectory() as temp_dir:
  56. executor = cls(work_dir=temp_dir, functions=[add_two_numbers])
  57. code = f"""from {executor.functions_module} import add_two_numbers
  58. print(add_two_numbers(1, 2))"""
  59. result = executor.execute_code_blocks(
  60. code_blocks=[
  61. CodeBlock(language="python", code=code),
  62. ]
  63. )
  64. assert result.output == "3\n"
  65. assert result.exit_code == 0
  66. # TODO - only run this test for containerized executors, as the environment is not guaranteed to have pandas installed
  67. # It is common for the local environment to have pandas installed, so this test will not work as expected
  68. # @pytest.mark.parametrize("cls", classes_to_test)
  69. # @pytest.mark.skipif(skip, reason="pandas not installed")
  70. # def test_fails_for_missing_reqs(cls) -> None:
  71. # with tempfile.TemporaryDirectory() as temp_dir:
  72. # executor = cls(work_dir=temp_dir, functions=[function_missing_reqs])
  73. # code = f"""from {executor.functions_module} import function_missing_reqs
  74. # function_missing_reqs()"""
  75. # with pytest.raises(ValueError):
  76. # executor.execute_code_blocks(
  77. # code_blocks=[
  78. # CodeBlock(language="python", code=code),
  79. # ]
  80. # )
  81. @pytest.mark.parametrize("cls", classes_to_test)
  82. @pytest.mark.skipif(skip, reason="pandas not installed")
  83. def test_fails_for_function_incorrect_import(cls) -> None:
  84. with tempfile.TemporaryDirectory() as temp_dir:
  85. executor = cls(work_dir=temp_dir, functions=[function_incorrect_import])
  86. code = f"""from {executor.functions_module} import function_incorrect_import
  87. function_incorrect_import()"""
  88. with pytest.raises(ValueError):
  89. executor.execute_code_blocks(
  90. code_blocks=[
  91. CodeBlock(language="python", code=code),
  92. ]
  93. )
  94. @pytest.mark.parametrize("cls", classes_to_test)
  95. @pytest.mark.skipif(skip, reason="pandas not installed")
  96. def test_fails_for_function_incorrect_dep(cls) -> None:
  97. with tempfile.TemporaryDirectory() as temp_dir:
  98. executor = cls(work_dir=temp_dir, functions=[function_incorrect_dep])
  99. code = f"""from {executor.functions_module} import function_incorrect_dep
  100. function_incorrect_dep()"""
  101. with pytest.raises(ValueError):
  102. executor.execute_code_blocks(
  103. code_blocks=[
  104. CodeBlock(language="python", code=code),
  105. ]
  106. )
  107. @pytest.mark.parametrize("cls", classes_to_test)
  108. def test_formatted_prompt(cls) -> None:
  109. with tempfile.TemporaryDirectory() as temp_dir:
  110. executor = cls(work_dir=temp_dir, functions=[add_two_numbers])
  111. result = executor.format_functions_for_prompt()
  112. assert (
  113. '''def add_two_numbers(a: int, b: int) -> int:
  114. """Add two numbers together."""
  115. '''
  116. in result
  117. )
  118. @pytest.mark.parametrize("cls", classes_to_test)
  119. def test_formatted_prompt_str_func(cls) -> None:
  120. with tempfile.TemporaryDirectory() as temp_dir:
  121. func = FunctionWithRequirements.from_str(
  122. '''
  123. def add_two_numbers(a: int, b: int) -> int:
  124. """Add two numbers together."""
  125. return a + b
  126. '''
  127. )
  128. executor = cls(work_dir=temp_dir, functions=[func])
  129. result = executor.format_functions_for_prompt()
  130. assert (
  131. '''def add_two_numbers(a: int, b: int) -> int:
  132. """Add two numbers together."""
  133. '''
  134. in result
  135. )
  136. @pytest.mark.parametrize("cls", classes_to_test)
  137. def test_can_load_str_function_with_reqs(cls) -> None:
  138. with tempfile.TemporaryDirectory() as temp_dir:
  139. func = FunctionWithRequirements.from_str(
  140. '''
  141. def add_two_numbers(a: int, b: int) -> int:
  142. """Add two numbers together."""
  143. return a + b
  144. '''
  145. )
  146. executor = cls(work_dir=temp_dir, functions=[func])
  147. code = f"""from {executor.functions_module} import add_two_numbers
  148. print(add_two_numbers(1, 2))"""
  149. result = executor.execute_code_blocks(
  150. code_blocks=[
  151. CodeBlock(language="python", code=code),
  152. ]
  153. )
  154. assert result.output == "3\n"
  155. assert result.exit_code == 0
  156. @pytest.mark.parametrize("cls", classes_to_test)
  157. def test_cant_load_broken_str_function_with_reqs(cls) -> None:
  158. with pytest.raises(ValueError):
  159. _ = FunctionWithRequirements.from_str(
  160. '''
  161. invaliddef add_two_numbers(a: int, b: int) -> int:
  162. """Add two numbers together."""
  163. return a + b
  164. '''
  165. )
  166. @pytest.mark.parametrize("cls", classes_to_test)
  167. def test_cant_run_broken_str_function_with_reqs(cls) -> None:
  168. with tempfile.TemporaryDirectory() as temp_dir:
  169. func = FunctionWithRequirements.from_str(
  170. '''
  171. def add_two_numbers(a: int, b: int) -> int:
  172. """Add two numbers together."""
  173. return a + b
  174. '''
  175. )
  176. executor = cls(work_dir=temp_dir, functions=[func])
  177. code = f"""from {executor.functions_module} import add_two_numbers
  178. print(add_two_numbers(object(), False))"""
  179. result = executor.execute_code_blocks(
  180. code_blocks=[
  181. CodeBlock(language="python", code=code),
  182. ]
  183. )
  184. assert "TypeError: unsupported operand type(s) for +:" in result.output
  185. assert result.exit_code == 1