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_function_and_tool_calling.py 14 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import json
  2. from typing import Any, Callable, Dict, List
  3. import pytest
  4. from autogen.agentchat.conversable_agent import ConversableAgent
  5. def _tool_func_1(arg1: str, arg2: str) -> str:
  6. return f"_tool_func_1: {arg1} {arg2}"
  7. def _tool_func_2(arg1: str, arg2: str) -> str:
  8. return f"_tool_func_2: {arg1} {arg2}"
  9. def _tool_func_error(arg1: str, arg2: str) -> str:
  10. raise RuntimeError("Error in tool function")
  11. async def _a_tool_func_1(arg1: str, arg2: str) -> str:
  12. return f"_tool_func_1: {arg1} {arg2}"
  13. async def _a_tool_func_2(arg1: str, arg2: str) -> str:
  14. return f"_tool_func_2: {arg1} {arg2}"
  15. async def _a_tool_func_error(arg1: str, arg2: str) -> str:
  16. raise RuntimeError("Error in tool function")
  17. _tool_use_message_1 = {
  18. "role": "assistant",
  19. "content": None,
  20. "tool_calls": [
  21. {
  22. "id": "1",
  23. "type": "function",
  24. "function": {
  25. "name": "_tool_func_1",
  26. "arguments": json.dumps({"arg1": "value1", "arg2": "value2"}),
  27. },
  28. },
  29. {
  30. "id": "2",
  31. "type": "function",
  32. "function": {
  33. "name": "_tool_func_2",
  34. "arguments": json.dumps({"arg1": "value3", "arg2": "value4"}),
  35. },
  36. },
  37. ],
  38. }
  39. _tool_use_message_1_bad_json = {
  40. "role": "assistant",
  41. "content": None,
  42. "tool_calls": [
  43. {
  44. "id": "1",
  45. "type": "function",
  46. "function": {
  47. "name": "_tool_func_1",
  48. # add extra comma to make json invalid
  49. "arguments": json.dumps({"arg1": "value3", "arg2": "value4"})[:-1] + ",}",
  50. },
  51. },
  52. {
  53. "id": "2",
  54. "type": "function",
  55. "function": {
  56. "name": "_tool_func_2",
  57. "arguments": json.dumps({"arg1": "value3", "arg2": "value4"}),
  58. },
  59. },
  60. ],
  61. }
  62. _tool_use_message_1_expected_reply = {
  63. "role": "tool",
  64. "tool_responses": [
  65. {"tool_call_id": "1", "role": "tool", "content": "_tool_func_1: value1 value2"},
  66. {"tool_call_id": "2", "role": "tool", "content": "_tool_func_2: value3 value4"},
  67. ],
  68. # "content": "Tool Call Id: 1\n_tool_func_1: value1 value2\n\nTool Call Id: 2\n_tool_func_2: value3 value4",
  69. "content": "_tool_func_1: value1 value2\n\n_tool_func_2: value3 value4",
  70. }
  71. _tool_use_message_1_bad_json_expected_reply = {
  72. "role": "tool",
  73. "tool_responses": [
  74. {
  75. "tool_call_id": "1",
  76. "role": "tool",
  77. "content": "Error: Expecting property name enclosed in double quotes: line 1 column 37 (char 36)\n You argument should follow json format.",
  78. },
  79. {"tool_call_id": "2", "role": "tool", "content": "_tool_func_2: value3 value4"},
  80. ],
  81. "content": "Error: Expecting property name enclosed in double quotes: line 1 column 37 (char 36)\n You argument should follow json format.\n\n_tool_func_2: value3 value4",
  82. }
  83. _tool_use_message_1_error_expected_reply = {
  84. "role": "tool",
  85. "tool_responses": [
  86. {"tool_call_id": "1", "role": "tool", "content": "_tool_func_1: value1 value2"},
  87. {
  88. "tool_call_id": "2",
  89. "role": "tool",
  90. "content": "Error: Error in tool function",
  91. },
  92. ],
  93. "content": "_tool_func_1: value1 value2\n\nError: Error in tool function",
  94. }
  95. _tool_use_message_1_not_found_expected_reply = {
  96. "role": "tool",
  97. "tool_responses": [
  98. {"tool_call_id": "1", "role": "tool", "content": "_tool_func_1: value1 value2"},
  99. {
  100. "tool_call_id": "2",
  101. "role": "tool",
  102. "content": "Error: Function _tool_func_2 not found.",
  103. },
  104. ],
  105. "content": "_tool_func_1: value1 value2\n\nError: Function _tool_func_2 not found.",
  106. }
  107. _function_use_message_1 = {
  108. "role": "assistant",
  109. "content": None,
  110. "function_call": {
  111. "name": "_tool_func_1",
  112. "arguments": json.dumps({"arg1": "value1", "arg2": "value2"}),
  113. },
  114. }
  115. _function_use_message_1_bad_json = {
  116. "role": "assistant",
  117. "content": None,
  118. "function_call": {
  119. "name": "_tool_func_1",
  120. "arguments": json.dumps({"arg1": "value1", "arg2": "value2"})[:-1] + ",}",
  121. },
  122. }
  123. _function_use_message_1_expected_reply = {
  124. "name": "_tool_func_1",
  125. "role": "function",
  126. "content": "_tool_func_1: value1 value2",
  127. }
  128. _function_use_message_1_bad_json_expected_reply = {
  129. "name": "_tool_func_1",
  130. "role": "function",
  131. "content": "Error: Expecting property name enclosed in double quotes: line 1 column 37 (char 36)\n You argument should follow json format.",
  132. }
  133. _function_use_message_1_error_expected_reply = {
  134. "name": "_tool_func_1",
  135. "role": "function",
  136. "content": "Error: Error in tool function",
  137. }
  138. _function_use_message_1_not_found_expected_reply = {
  139. "name": "_tool_func_1",
  140. "role": "function",
  141. "content": "Error: Function _tool_func_1 not found.",
  142. }
  143. _text_message = {"content": "Hi!", "role": "user"}
  144. def _get_function_map(is_function_async: bool, drop_tool_2: bool = False) -> Dict[str, Callable[..., Any]]:
  145. if is_function_async:
  146. return (
  147. {
  148. "_tool_func_1": _a_tool_func_1,
  149. "_tool_func_2": _a_tool_func_2,
  150. }
  151. if not drop_tool_2
  152. else {
  153. "_tool_func_1": _a_tool_func_1,
  154. }
  155. )
  156. else:
  157. return (
  158. {
  159. "_tool_func_1": _tool_func_1,
  160. "_tool_func_2": _tool_func_2,
  161. }
  162. if not drop_tool_2
  163. else {
  164. "_tool_func_1": _tool_func_1,
  165. }
  166. )
  167. def _get_error_function_map(
  168. is_function_async: bool, error_on_tool_func_2: bool = True
  169. ) -> Dict[str, Callable[..., Any]]:
  170. if is_function_async:
  171. return {
  172. "_tool_func_1": _a_tool_func_1 if error_on_tool_func_2 else _a_tool_func_error,
  173. "_tool_func_2": _a_tool_func_error if error_on_tool_func_2 else _a_tool_func_2,
  174. }
  175. else:
  176. return {
  177. "_tool_func_1": _tool_func_1 if error_on_tool_func_2 else _tool_func_error,
  178. "_tool_func_2": _tool_func_error if error_on_tool_func_2 else _tool_func_2,
  179. }
  180. @pytest.mark.parametrize("is_function_async", [True, False])
  181. def test_generate_function_call_reply_on_function_call_message(is_function_async: bool) -> None:
  182. agent = ConversableAgent(name="agent", llm_config=False)
  183. # empty function_map
  184. agent._function_map = {}
  185. messages = [_function_use_message_1]
  186. finished, retval = agent.generate_function_call_reply(messages)
  187. assert (finished, retval) == (True, _function_use_message_1_not_found_expected_reply)
  188. # function map set
  189. agent._function_map = _get_function_map(is_function_async)
  190. # correct function call, multiple times to make sure cleanups are done properly
  191. for _ in range(3):
  192. messages = [_function_use_message_1]
  193. finished, retval = agent.generate_function_call_reply(messages)
  194. assert (finished, retval) == (True, _function_use_message_1_expected_reply)
  195. # bad JSON
  196. messages = [_function_use_message_1_bad_json]
  197. finished, retval = agent.generate_function_call_reply(messages)
  198. assert (finished, retval) == (True, _function_use_message_1_bad_json_expected_reply)
  199. # tool call
  200. messages = [_tool_use_message_1]
  201. finished, retval = agent.generate_function_call_reply(messages)
  202. assert (finished, retval) == (False, None)
  203. # text message
  204. messages: List[Dict[str, str]] = [_text_message]
  205. finished, retval = agent.generate_function_call_reply(messages)
  206. assert (finished, retval) == (False, None)
  207. # error in function (raises Exception)
  208. agent._function_map = _get_error_function_map(is_function_async, error_on_tool_func_2=False)
  209. messages = [_function_use_message_1]
  210. finished, retval = agent.generate_function_call_reply(messages)
  211. assert (finished, retval) == (True, _function_use_message_1_error_expected_reply)
  212. @pytest.mark.asyncio()
  213. @pytest.mark.parametrize("is_function_async", [True, False])
  214. async def test_a_generate_function_call_reply_on_function_call_message(is_function_async: bool) -> None:
  215. agent = ConversableAgent(name="agent", llm_config=False)
  216. # empty function_map
  217. agent._function_map = {}
  218. messages = [_function_use_message_1]
  219. finished, retval = await agent.a_generate_function_call_reply(messages)
  220. assert (finished, retval) == (True, _function_use_message_1_not_found_expected_reply)
  221. # function map set
  222. agent._function_map = _get_function_map(is_function_async)
  223. # correct function call, multiple times to make sure cleanups are done properly
  224. for _ in range(3):
  225. messages = [_function_use_message_1]
  226. finished, retval = await agent.a_generate_function_call_reply(messages)
  227. assert (finished, retval) == (True, _function_use_message_1_expected_reply)
  228. # bad JSON
  229. messages = [_function_use_message_1_bad_json]
  230. finished, retval = await agent.a_generate_function_call_reply(messages)
  231. assert (finished, retval) == (True, _function_use_message_1_bad_json_expected_reply)
  232. # tool call
  233. messages = [_tool_use_message_1]
  234. finished, retval = await agent.a_generate_function_call_reply(messages)
  235. assert (finished, retval) == (False, None)
  236. # text message
  237. messages: List[Dict[str, str]] = [_text_message]
  238. finished, retval = await agent.a_generate_function_call_reply(messages)
  239. assert (finished, retval) == (False, None)
  240. # error in function (raises Exception)
  241. agent._function_map = _get_error_function_map(is_function_async, error_on_tool_func_2=False)
  242. messages = [_function_use_message_1]
  243. finished, retval = await agent.a_generate_function_call_reply(messages)
  244. assert (finished, retval) == (True, _function_use_message_1_error_expected_reply)
  245. @pytest.mark.parametrize("is_function_async", [True, False])
  246. def test_generate_tool_calls_reply_on_function_call_message(is_function_async: bool) -> None:
  247. agent = ConversableAgent(name="agent", llm_config=False)
  248. # empty function_map
  249. agent._function_map = _get_function_map(is_function_async, drop_tool_2=True)
  250. messages = [_tool_use_message_1]
  251. finished, retval = agent.generate_tool_calls_reply(messages)
  252. assert (finished, retval) == (True, _tool_use_message_1_not_found_expected_reply)
  253. # function map set
  254. agent._function_map = _get_function_map(is_function_async)
  255. # correct function call, multiple times to make sure cleanups are done properly
  256. for _ in range(3):
  257. messages = [_tool_use_message_1]
  258. finished, retval = agent.generate_tool_calls_reply(messages)
  259. assert (finished, retval) == (True, _tool_use_message_1_expected_reply)
  260. # bad JSON
  261. messages = [_tool_use_message_1_bad_json]
  262. finished, retval = agent.generate_tool_calls_reply(messages)
  263. assert (finished, retval) == (True, _tool_use_message_1_bad_json_expected_reply)
  264. # function call
  265. messages = [_function_use_message_1]
  266. finished, retval = agent.generate_tool_calls_reply(messages)
  267. assert (finished, retval) == (False, None)
  268. # text message
  269. messages: List[Dict[str, str]] = [_text_message]
  270. finished, retval = agent.generate_tool_calls_reply(messages)
  271. assert (finished, retval) == (False, None)
  272. # error in function (raises Exception)
  273. agent._function_map = _get_error_function_map(is_function_async)
  274. messages = [_tool_use_message_1]
  275. finished, retval = agent.generate_tool_calls_reply(messages)
  276. assert (finished, retval) == (True, _tool_use_message_1_error_expected_reply)
  277. @pytest.mark.asyncio()
  278. @pytest.mark.parametrize("is_function_async", [True, False])
  279. async def test_a_generate_tool_calls_reply_on_function_call_message(is_function_async: bool) -> None:
  280. agent = ConversableAgent(name="agent", llm_config=False)
  281. # empty function_map
  282. agent._function_map = _get_function_map(is_function_async, drop_tool_2=True)
  283. messages = [_tool_use_message_1]
  284. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  285. assert (finished, retval) == (True, _tool_use_message_1_not_found_expected_reply)
  286. # function map set
  287. agent._function_map = _get_function_map(is_function_async)
  288. # correct function call, multiple times to make sure cleanups are done properly
  289. for _ in range(3):
  290. messages = [_tool_use_message_1]
  291. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  292. assert (finished, retval) == (True, _tool_use_message_1_expected_reply)
  293. # bad JSON
  294. messages = [_tool_use_message_1_bad_json]
  295. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  296. assert (finished, retval) == (True, _tool_use_message_1_bad_json_expected_reply)
  297. # function call
  298. messages = [_function_use_message_1]
  299. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  300. assert (finished, retval) == (False, None)
  301. # text message
  302. messages: List[Dict[str, str]] = [_text_message]
  303. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  304. assert (finished, retval) == (False, None)
  305. # error in function (raises Exception)
  306. agent._function_map = _get_error_function_map(is_function_async)
  307. messages = [_tool_use_message_1]
  308. finished, retval = await agent.a_generate_tool_calls_reply(messages)
  309. assert (finished, retval) == (True, _tool_use_message_1_error_expected_reply)