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_websurfer_agent.py 7.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import asyncio
  2. import json
  3. import logging
  4. from datetime import datetime
  5. from typing import Any, AsyncGenerator, List
  6. import pytest
  7. from autogen_agentchat import EVENT_LOGGER_NAME
  8. from autogen_agentchat.messages import (
  9. MultiModalMessage,
  10. TextMessage,
  11. )
  12. from autogen_ext.agents.web_surfer import MultimodalWebSurfer
  13. from autogen_ext.models.openai import OpenAIChatCompletionClient
  14. from openai.resources.chat.completions import AsyncCompletions
  15. from openai.types.chat.chat_completion import ChatCompletion, Choice
  16. from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
  17. from openai.types.chat.chat_completion_message import ChatCompletionMessage
  18. from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function
  19. from openai.types.completion_usage import CompletionUsage
  20. from pydantic import BaseModel
  21. class FileLogHandler(logging.Handler):
  22. def __init__(self, filename: str) -> None:
  23. super().__init__()
  24. self.filename = filename
  25. self.file_handler = logging.FileHandler(filename)
  26. def emit(self, record: logging.LogRecord) -> None:
  27. ts = datetime.fromtimestamp(record.created).isoformat()
  28. if isinstance(record.msg, BaseModel):
  29. record.msg = json.dumps(
  30. {
  31. "timestamp": ts,
  32. "message": record.msg.model_dump(),
  33. "type": record.msg.__class__.__name__,
  34. },
  35. )
  36. self.file_handler.emit(record)
  37. class _MockChatCompletion:
  38. def __init__(self, chat_completions: List[ChatCompletion]) -> None:
  39. self._saved_chat_completions = chat_completions
  40. self._curr_index = 0
  41. async def mock_create(
  42. self, *args: Any, **kwargs: Any
  43. ) -> ChatCompletion | AsyncGenerator[ChatCompletionChunk, None]:
  44. await asyncio.sleep(0.1)
  45. completion = self._saved_chat_completions[self._curr_index]
  46. self._curr_index += 1
  47. return completion
  48. logger = logging.getLogger(EVENT_LOGGER_NAME)
  49. logger.setLevel(logging.DEBUG)
  50. logger.addHandler(FileLogHandler("test_websurfer_agent.log"))
  51. @pytest.mark.asyncio
  52. async def test_run_websurfer(monkeypatch: pytest.MonkeyPatch) -> None:
  53. model = "gpt-4o-2024-05-13"
  54. chat_completions = [
  55. ChatCompletion(
  56. id="id2",
  57. choices=[
  58. Choice(finish_reason="stop", index=0, message=ChatCompletionMessage(content="Hello", role="assistant"))
  59. ],
  60. created=0,
  61. model=model,
  62. object="chat.completion",
  63. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  64. ),
  65. ChatCompletion(
  66. id="id2",
  67. choices=[
  68. Choice(
  69. finish_reason="tool_calls",
  70. index=0,
  71. message=ChatCompletionMessage(
  72. content=None,
  73. tool_calls=[
  74. ChatCompletionMessageToolCall(
  75. id="1",
  76. type="function",
  77. function=Function(
  78. name="sleep",
  79. arguments=json.dumps({"reasoning": "sleep is important"}),
  80. ),
  81. )
  82. ],
  83. role="assistant",
  84. ),
  85. )
  86. ],
  87. created=0,
  88. model=model,
  89. object="chat.completion",
  90. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  91. ),
  92. ]
  93. mock = _MockChatCompletion(chat_completions)
  94. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  95. agent = MultimodalWebSurfer(
  96. "WebSurfer", model_client=OpenAIChatCompletionClient(model=model, api_key=""), use_ocr=False
  97. )
  98. # Before lazy init
  99. assert agent._name == "WebSurfer" # pyright: ignore[reportPrivateUsage]
  100. assert agent._playwright is None # pyright: ignore[reportPrivateUsage]
  101. # After lazy init
  102. result = await agent.run(task="task")
  103. assert agent._playwright is not None # pyright: ignore[reportPrivateUsage]
  104. assert agent._page is not None # pyright: ignore[reportPrivateUsage]
  105. # now check result object
  106. assert len(result.messages) == 3
  107. # user message
  108. assert isinstance(result.messages[0], TextMessage)
  109. assert result.messages[0].models_usage is None
  110. # inner message
  111. assert isinstance(result.messages[1], TextMessage)
  112. # final return
  113. assert isinstance(result.messages[2], TextMessage)
  114. assert result.messages[2].models_usage is not None
  115. assert result.messages[2].models_usage.completion_tokens == 5
  116. assert result.messages[2].models_usage.prompt_tokens == 10
  117. assert result.messages[2].content == "Hello"
  118. # check internal web surfer state
  119. assert len(agent._chat_history) == 2 # pyright: ignore[reportPrivateUsage]
  120. assert agent._chat_history[0].content == "task" # pyright: ignore[reportPrivateUsage]
  121. assert agent._chat_history[1].content == "Hello" # pyright: ignore[reportPrivateUsage]
  122. url_after_no_tool = agent._page.url # pyright: ignore[reportPrivateUsage]
  123. # run again
  124. result = await agent.run(task="task")
  125. assert len(result.messages) == 3
  126. assert isinstance(result.messages[2], MultiModalMessage)
  127. assert (
  128. result.messages[2] # type: ignore
  129. .content[0] # type: ignore
  130. .startswith( # type: ignore
  131. "I am waiting a short period of time before taking further action."
  132. )
  133. ) # type: ignore
  134. url_after_sleep = agent._page.url # type: ignore
  135. assert url_after_no_tool == url_after_sleep
  136. @pytest.mark.asyncio
  137. async def test_run_websurfer_declarative(monkeypatch: pytest.MonkeyPatch) -> None:
  138. model = "gpt-4o-2024-05-13"
  139. chat_completions = [
  140. ChatCompletion(
  141. id="id1",
  142. choices=[
  143. Choice(
  144. finish_reason="stop",
  145. index=0,
  146. message=ChatCompletionMessage(content="Response to message 3", role="assistant"),
  147. )
  148. ],
  149. created=0,
  150. model=model,
  151. object="chat.completion",
  152. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15),
  153. ),
  154. ]
  155. mock = _MockChatCompletion(chat_completions)
  156. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  157. agent = MultimodalWebSurfer(
  158. "WebSurfer", model_client=OpenAIChatCompletionClient(model=model, api_key=""), use_ocr=False
  159. )
  160. agent_config = agent.dump_component()
  161. assert agent_config.provider == "autogen_ext.agents.web_surfer.MultimodalWebSurfer"
  162. assert agent_config.config["name"] == "WebSurfer"
  163. loaded_agent = MultimodalWebSurfer.load_component(agent_config)
  164. assert isinstance(loaded_agent, MultimodalWebSurfer)
  165. assert loaded_agent.name == "WebSurfer"