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_tool_agent.py 6.3 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import asyncio
  2. import json
  3. from typing import Any, AsyncGenerator, List
  4. import pytest
  5. from openai.resources.chat.completions import AsyncCompletions
  6. from openai.types.chat.chat_completion import ChatCompletion, Choice
  7. from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
  8. from openai.types.chat.chat_completion_message import ChatCompletionMessage
  9. from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function
  10. from openai.types.completion_usage import CompletionUsage
  11. from agnext.application import SingleThreadedAgentRuntime
  12. from agnext.components import FunctionCall
  13. from agnext.components.tool_agent import (
  14. InvalidToolArgumentsException,
  15. ToolAgent,
  16. ToolExecutionException,
  17. ToolNotFoundException,
  18. tool_agent_caller_loop,
  19. )
  20. from agnext.components.tools import FunctionTool, Tool
  21. from agnext.core import CancellationToken, AgentId
  22. from agnext.components.models import (
  23. AssistantMessage,
  24. FunctionExecutionResult,
  25. FunctionExecutionResultMessage,
  26. OpenAIChatCompletionClient,
  27. UserMessage,
  28. )
  29. from agnext.components.tools import FunctionTool
  30. def _pass_function(input: str) -> str:
  31. return "pass"
  32. def _raise_function(input: str) -> str:
  33. raise Exception("raise")
  34. async def _async_sleep_function(input: str) -> str:
  35. await asyncio.sleep(10)
  36. return "pass"
  37. class _MockChatCompletion:
  38. def __init__(self, model: str = "gpt-4o") -> None:
  39. self._saved_chat_completions: List[ChatCompletion] = [
  40. ChatCompletion(
  41. id="id1",
  42. choices=[
  43. Choice(
  44. finish_reason="tool_calls",
  45. index=0,
  46. message=ChatCompletionMessage(
  47. content=None,
  48. tool_calls=[
  49. ChatCompletionMessageToolCall(
  50. id="1",
  51. type="function",
  52. function=Function(
  53. name="pass",
  54. arguments=json.dumps({"input": "pass"}),
  55. ),
  56. )
  57. ],
  58. role="assistant",
  59. ),
  60. )
  61. ],
  62. created=0,
  63. model=model,
  64. object="chat.completion",
  65. usage=CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
  66. ),
  67. ChatCompletion(
  68. id="id2",
  69. choices=[
  70. Choice(
  71. finish_reason="stop", index=0, message=ChatCompletionMessage(content="Hello", role="assistant")
  72. )
  73. ],
  74. created=0,
  75. model=model,
  76. object="chat.completion",
  77. usage=CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
  78. ),
  79. ]
  80. self._curr_index = 0
  81. async def mock_create(
  82. self, *args: Any, **kwargs: Any
  83. ) -> ChatCompletion | AsyncGenerator[ChatCompletionChunk, None]:
  84. await asyncio.sleep(0.1)
  85. completion = self._saved_chat_completions[self._curr_index]
  86. self._curr_index += 1
  87. return completion
  88. @pytest.mark.asyncio
  89. async def test_tool_agent() -> None:
  90. runtime = SingleThreadedAgentRuntime()
  91. await runtime.register(
  92. "tool_agent",
  93. lambda: ToolAgent(
  94. description="Tool agent",
  95. tools=[
  96. FunctionTool(_pass_function, name="pass", description="Pass function"),
  97. FunctionTool(_raise_function, name="raise", description="Raise function"),
  98. FunctionTool(_async_sleep_function, name="sleep", description="Sleep function"),
  99. ],
  100. ),
  101. )
  102. agent = AgentId("tool_agent", "default")
  103. runtime.start()
  104. # Test pass function
  105. result = await runtime.send_message(
  106. FunctionCall(id="1", arguments=json.dumps({"input": "pass"}), name="pass"), agent
  107. )
  108. assert result == FunctionExecutionResult(call_id="1", content="pass")
  109. # Test raise function
  110. with pytest.raises(ToolExecutionException):
  111. await runtime.send_message(FunctionCall(id="2", arguments=json.dumps({"input": "raise"}), name="raise"), agent)
  112. # Test invalid tool name
  113. with pytest.raises(ToolNotFoundException):
  114. await runtime.send_message(FunctionCall(id="3", arguments=json.dumps({"input": "pass"}), name="invalid"), agent)
  115. # Test invalid arguments
  116. with pytest.raises(InvalidToolArgumentsException):
  117. await runtime.send_message(FunctionCall(id="3", arguments="invalid json /xd", name="pass"), agent)
  118. # Test sleep and cancel.
  119. token = CancellationToken()
  120. result_future = runtime.send_message(
  121. FunctionCall(id="3", arguments=json.dumps({"input": "sleep"}), name="sleep"), agent, cancellation_token=token
  122. )
  123. token.cancel()
  124. with pytest.raises(asyncio.CancelledError):
  125. await result_future
  126. await runtime.stop()
  127. @pytest.mark.asyncio
  128. async def test_caller_loop(monkeypatch: pytest.MonkeyPatch) -> None:
  129. mock = _MockChatCompletion(model="gpt-4o-2024-05-13")
  130. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  131. client = OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="api_key")
  132. tools : List[Tool] = [FunctionTool(_pass_function, name="pass", description="Pass function")]
  133. runtime = SingleThreadedAgentRuntime()
  134. await runtime.register(
  135. "tool_agent",
  136. lambda: ToolAgent(
  137. description="Tool agent",
  138. tools=tools,
  139. ),
  140. )
  141. agent = AgentId("tool_agent", "default")
  142. runtime.start()
  143. messages = await tool_agent_caller_loop(
  144. runtime,
  145. agent,
  146. client,
  147. [UserMessage(content="Hello", source="user")],
  148. tool_schema=tools
  149. )
  150. assert len(messages) == 3
  151. assert isinstance(messages[0], AssistantMessage)
  152. assert isinstance(messages[1], FunctionExecutionResultMessage)
  153. assert isinstance(messages[2], AssistantMessage)
  154. await runtime.stop()