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

[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
[Core] [Tool Call] adjust conversable agent to support tool_calls (#974) * adjust conversable and compressible agents to support tool_calls * split out tools into their own reply def * copilot typo * address review comments * revert compressible_agent and token_count_utils calls * cleanup terminate check and remove unnecessary code * doc search and update * return function/tool calls as interrupted when user provides a reply to a tool call request * fix tool name reference * fix formatting * fix initiate receiving a dict * missed changed roled * ignore incoming role, more similiar to existing code * consistency * redundant to_dict * fix todo comment * uneeded change * handle dict reply in groupchat * Fix generate_tool_call_calls_reply_comment * change method annotation for register_for_llm from functions to tools * typo autogen/agentchat/conversable_agent.py Co-authored-by: Chi Wang <wang.chi@microsoft.com> * add deprecation comments for function_call * tweak doc strings * switch to ToolFunction type * update the return to * fix generate_init_message return type * Revert "fix generate_init_message return type" This reverts commit 645ba8b76afa06f160223ecdac6f3dc1822fd249. * undo force init to dict * fix notebooks and groupchat tool handling * fix type * use get for key error * fix teachable to pull content from dict * change single message tool response * cleanup unnessary changes * little better tool response concatenation * update tools tests * add skip openai check to tools tests * fix nits * move func name normalization to oai_reply and assert configured names * fix whitespace * remove extra normalize * tool name is now normalized in the generate_reply function, so will not be incorrect when sent to receive * validate function names in init and expand comments for validation methods * fix dict comprehension * Dummy llm config for unit tests * handle tool_calls set to None * fix tool name reference * method operates on responses not calls --------- Co-authored-by: Yiran Wu <32823396+kevin666aa@users.noreply.github.com> Co-authored-by: Chi Wang <wang.chi@microsoft.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import asyncio
  2. import inspect
  3. import unittest.mock
  4. from typing import Dict, List, Literal, Optional, Tuple
  5. import pytest
  6. from pydantic import BaseModel, Field
  7. from typing_extensions import Annotated
  8. from autogen._pydantic import PYDANTIC_V1, model_dump
  9. from autogen.function_utils import (
  10. get_default_values,
  11. get_function_schema,
  12. get_load_param_if_needed_function,
  13. get_missing_annotations,
  14. get_param_annotations,
  15. get_parameter_json_schema,
  16. get_parameters,
  17. get_required_params,
  18. get_typed_annotation,
  19. get_typed_return_annotation,
  20. get_typed_signature,
  21. load_basemodels_if_needed,
  22. serialize_to_str,
  23. )
  24. def f(a: Annotated[str, "Parameter a"], b: int = 2, c: Annotated[float, "Parameter c"] = 0.1, *, d):
  25. pass
  26. def g(
  27. a: Annotated[str, "Parameter a"],
  28. b: int = 2,
  29. c: Annotated[float, "Parameter c"] = 0.1,
  30. *,
  31. d: Dict[str, Tuple[Optional[int], List[float]]],
  32. ) -> str:
  33. pass
  34. async def a_g(
  35. a: Annotated[str, "Parameter a"],
  36. b: int = 2,
  37. c: Annotated[float, "Parameter c"] = 0.1,
  38. *,
  39. d: Dict[str, Tuple[Optional[int], List[float]]],
  40. ) -> str:
  41. pass
  42. def test_get_typed_annotation() -> None:
  43. globalns = getattr(f, "__globals__", {})
  44. assert get_typed_annotation(str, globalns) == str
  45. assert get_typed_annotation("float", globalns) == float
  46. def test_get_typed_signature() -> None:
  47. assert get_typed_signature(f).parameters == inspect.signature(f).parameters
  48. assert get_typed_signature(g).parameters == inspect.signature(g).parameters
  49. def test_get_typed_return_annotation() -> None:
  50. assert get_typed_return_annotation(f) is None
  51. assert get_typed_return_annotation(g) == str
  52. def test_get_parameter_json_schema() -> None:
  53. assert get_parameter_json_schema("c", str, {}) == {"type": "string", "description": "c"}
  54. assert get_parameter_json_schema("c", str, {"c": "ccc"}) == {"type": "string", "description": "c", "default": "ccc"}
  55. assert get_parameter_json_schema("a", Annotated[str, "parameter a"], {}) == {
  56. "type": "string",
  57. "description": "parameter a",
  58. }
  59. assert get_parameter_json_schema("a", Annotated[str, "parameter a"], {"a": "3.14"}) == {
  60. "type": "string",
  61. "description": "parameter a",
  62. "default": "3.14",
  63. }
  64. class B(BaseModel):
  65. b: float
  66. c: str
  67. expected = {
  68. "description": "b",
  69. "properties": {"b": {"title": "B", "type": "number"}, "c": {"title": "C", "type": "string"}},
  70. "required": ["b", "c"],
  71. "title": "B",
  72. "type": "object",
  73. }
  74. assert get_parameter_json_schema("b", B, {}) == expected
  75. expected["default"] = B(b=1.2, c="3.4")
  76. assert get_parameter_json_schema("b", B, {"b": B(b=1.2, c="3.4")}) == expected
  77. def test_get_required_params() -> None:
  78. assert get_required_params(inspect.signature(f)) == ["a", "d"]
  79. assert get_required_params(inspect.signature(g)) == ["a", "d"]
  80. def test_get_default_values() -> None:
  81. assert get_default_values(inspect.signature(f)) == {"b": 2, "c": 0.1}
  82. assert get_default_values(inspect.signature(g)) == {"b": 2, "c": 0.1}
  83. def test_get_param_annotations() -> None:
  84. def f(a: Annotated[str, "Parameter a"], b=1, c: Annotated[float, "Parameter c"] = 1.0):
  85. pass
  86. expected = {"a": Annotated[str, "Parameter a"], "c": Annotated[float, "Parameter c"]}
  87. typed_signature = get_typed_signature(f)
  88. param_annotations = get_param_annotations(typed_signature)
  89. assert param_annotations == expected, param_annotations
  90. def test_get_missing_annotations() -> None:
  91. def _f1(a: str, b=2):
  92. pass
  93. missing, unannotated_with_default = get_missing_annotations(get_typed_signature(_f1), ["a"])
  94. assert missing == set()
  95. assert unannotated_with_default == {"b"}
  96. def _f2(a: str, b) -> str:
  97. "ok"
  98. missing, unannotated_with_default = get_missing_annotations(get_typed_signature(_f2), ["a", "b"])
  99. assert missing == {"b"}
  100. assert unannotated_with_default == set()
  101. def _f3() -> None:
  102. pass
  103. missing, unannotated_with_default = get_missing_annotations(get_typed_signature(_f3), [])
  104. assert missing == set()
  105. assert unannotated_with_default == set()
  106. def test_get_parameters() -> None:
  107. def f(a: Annotated[str, "Parameter a"], b=1, c: Annotated[float, "Parameter c"] = 1.0):
  108. pass
  109. typed_signature = get_typed_signature(f)
  110. param_annotations = get_param_annotations(typed_signature)
  111. required = get_required_params(typed_signature)
  112. default_values = get_default_values(typed_signature)
  113. expected = {
  114. "type": "object",
  115. "properties": {
  116. "a": {"type": "string", "description": "Parameter a"},
  117. "c": {"type": "number", "description": "Parameter c", "default": 1.0},
  118. },
  119. "required": ["a"],
  120. }
  121. actual = model_dump(get_parameters(required, param_annotations, default_values))
  122. assert actual == expected, actual
  123. def test_get_function_schema_no_return_type() -> None:
  124. def f(a: Annotated[str, "Parameter a"], b: int, c: float = 0.1):
  125. pass
  126. expected = (
  127. "The return type of the function 'f' is not annotated. Although annotating it is "
  128. + "optional, the function should return either a string, a subclass of 'pydantic.BaseModel'."
  129. )
  130. with unittest.mock.patch("autogen.function_utils.logger.warning") as mock_logger_warning:
  131. get_function_schema(f, description="function g")
  132. mock_logger_warning.assert_called_once_with(expected)
  133. def test_get_function_schema_unannotated_with_default() -> None:
  134. with unittest.mock.patch("autogen.function_utils.logger.warning") as mock_logger_warning:
  135. def f(
  136. a: Annotated[str, "Parameter a"], b=2, c: Annotated[float, "Parameter c"] = 0.1, d="whatever", e=None
  137. ) -> str:
  138. return "ok"
  139. get_function_schema(f, description="function f")
  140. mock_logger_warning.assert_called_once_with(
  141. "The following parameters of the function 'f' with default values are not annotated: 'b', 'd', 'e'."
  142. )
  143. def test_get_function_schema_missing() -> None:
  144. def f(a: Annotated[str, "Parameter a"], b, c: Annotated[float, "Parameter c"] = 0.1) -> float:
  145. pass
  146. expected = (
  147. "All parameters of the function 'f' without default values must be annotated. "
  148. + "The annotations are missing for the following parameters: 'b'"
  149. )
  150. with pytest.raises(TypeError) as e:
  151. get_function_schema(f, description="function f")
  152. assert str(e.value) == expected, e.value
  153. def test_get_function_schema() -> None:
  154. expected_v2 = {
  155. "type": "function",
  156. "function": {
  157. "description": "function g",
  158. "name": "fancy name for g",
  159. "parameters": {
  160. "type": "object",
  161. "properties": {
  162. "a": {"type": "string", "description": "Parameter a"},
  163. "b": {"type": "integer", "description": "b", "default": 2},
  164. "c": {"type": "number", "description": "Parameter c", "default": 0.1},
  165. "d": {
  166. "additionalProperties": {
  167. "maxItems": 2,
  168. "minItems": 2,
  169. "prefixItems": [
  170. {"anyOf": [{"type": "integer"}, {"type": "null"}]},
  171. {"items": {"type": "number"}, "type": "array"},
  172. ],
  173. "type": "array",
  174. },
  175. "type": "object",
  176. "description": "d",
  177. },
  178. },
  179. "required": ["a", "d"],
  180. },
  181. },
  182. }
  183. # the difference is that the v1 version does not handle Union types (Optional is Union[T, None])
  184. expected_v1 = {
  185. "type": "function",
  186. "function": {
  187. "description": "function g",
  188. "name": "fancy name for g",
  189. "parameters": {
  190. "type": "object",
  191. "properties": {
  192. "a": {"type": "string", "description": "Parameter a"},
  193. "b": {"type": "integer", "description": "b", "default": 2},
  194. "c": {"type": "number", "description": "Parameter c", "default": 0.1},
  195. "d": {
  196. "type": "object",
  197. "additionalProperties": {
  198. "type": "array",
  199. "minItems": 2,
  200. "maxItems": 2,
  201. "items": [{"type": "integer"}, {"type": "array", "items": {"type": "number"}}],
  202. },
  203. "description": "d",
  204. },
  205. },
  206. "required": ["a", "d"],
  207. },
  208. },
  209. }
  210. actual = get_function_schema(g, description="function g", name="fancy name for g")
  211. if PYDANTIC_V1:
  212. assert actual == expected_v1, actual
  213. else:
  214. assert actual == expected_v2, actual
  215. actual = get_function_schema(a_g, description="function g", name="fancy name for g")
  216. if PYDANTIC_V1:
  217. assert actual == expected_v1, actual
  218. else:
  219. assert actual == expected_v2, actual
  220. CurrencySymbol = Literal["USD", "EUR"]
  221. class Currency(BaseModel):
  222. currency: Annotated[CurrencySymbol, Field(..., description="Currency code")]
  223. amount: Annotated[float, Field(100.0, description="Amount of money in the currency")]
  224. def test_get_function_schema_pydantic() -> None:
  225. def currency_calculator(
  226. base: Annotated[Currency, "Base currency: amount and currency symbol"],
  227. quote_currency: Annotated[CurrencySymbol, "Quote currency symbol (default: 'EUR')"] = "EUR",
  228. ) -> Currency:
  229. pass
  230. expected = {
  231. "type": "function",
  232. "function": {
  233. "description": "Currency exchange calculator.",
  234. "name": "currency_calculator",
  235. "parameters": {
  236. "type": "object",
  237. "properties": {
  238. "base": {
  239. "properties": {
  240. "currency": {
  241. "description": "Currency code",
  242. "enum": ["USD", "EUR"],
  243. "title": "Currency",
  244. "type": "string",
  245. },
  246. "amount": {
  247. "default": 100.0,
  248. "description": "Amount of money in the currency",
  249. "title": "Amount",
  250. "type": "number",
  251. },
  252. },
  253. "required": ["currency"],
  254. "title": "Currency",
  255. "type": "object",
  256. "description": "Base currency: amount and currency symbol",
  257. },
  258. "quote_currency": {
  259. "enum": ["USD", "EUR"],
  260. "type": "string",
  261. "default": "EUR",
  262. "description": "Quote currency symbol (default: 'EUR')",
  263. },
  264. },
  265. "required": ["base"],
  266. },
  267. },
  268. }
  269. actual = get_function_schema(
  270. currency_calculator, description="Currency exchange calculator.", name="currency_calculator"
  271. )
  272. assert actual == expected, actual
  273. def test_get_load_param_if_needed_function() -> None:
  274. assert get_load_param_if_needed_function(CurrencySymbol) is None
  275. assert get_load_param_if_needed_function(Currency)({"currency": "USD", "amount": 123.45}, Currency) == Currency(
  276. currency="USD", amount=123.45
  277. )
  278. f = get_load_param_if_needed_function(Annotated[Currency, "amount and a symbol of a currency"])
  279. actual = f({"currency": "USD", "amount": 123.45}, Currency)
  280. expected = Currency(currency="USD", amount=123.45)
  281. assert actual == expected, actual
  282. def test_load_basemodels_if_needed_sync() -> None:
  283. @load_basemodels_if_needed
  284. def f(
  285. base: Annotated[Currency, "Base currency"],
  286. quote_currency: Annotated[CurrencySymbol, "Quote currency"] = "EUR",
  287. ) -> Tuple[Currency, CurrencySymbol]:
  288. return base, quote_currency
  289. assert not inspect.iscoroutinefunction(f)
  290. actual = f(base={"currency": "USD", "amount": 123.45}, quote_currency="EUR")
  291. assert isinstance(actual[0], Currency)
  292. assert actual[0].amount == 123.45
  293. assert actual[0].currency == "USD"
  294. assert actual[1] == "EUR"
  295. @pytest.mark.asyncio
  296. async def test_load_basemodels_if_needed_async() -> None:
  297. @load_basemodels_if_needed
  298. async def f(
  299. base: Annotated[Currency, "Base currency"],
  300. quote_currency: Annotated[CurrencySymbol, "Quote currency"] = "EUR",
  301. ) -> Tuple[Currency, CurrencySymbol]:
  302. return base, quote_currency
  303. assert inspect.iscoroutinefunction(f)
  304. actual = await f(base={"currency": "USD", "amount": 123.45}, quote_currency="EUR")
  305. assert isinstance(actual[0], Currency)
  306. assert actual[0].amount == 123.45
  307. assert actual[0].currency == "USD"
  308. assert actual[1] == "EUR"
  309. def test_serialize_to_json():
  310. assert serialize_to_str("abc") == "abc"
  311. assert serialize_to_str(123) == "123"
  312. assert serialize_to_str([123, 456]) == "[123, 456]"
  313. assert serialize_to_str({"a": 1, "b": 2.3}) == '{"a": 1, "b": 2.3}'
  314. class A(BaseModel):
  315. a: int
  316. b: float
  317. c: str
  318. assert serialize_to_str(A(a=1, b=2.3, c="abc")) == '{"a":1,"b":2.3,"c":"abc"}'