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_assistant_agent.py 33 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. import asyncio
  2. import json
  3. import logging
  4. from typing import Any, AsyncGenerator, List
  5. import pytest
  6. from autogen_agentchat import EVENT_LOGGER_NAME
  7. from autogen_agentchat.agents import AssistantAgent
  8. from autogen_agentchat.base import Handoff, TaskResult
  9. from autogen_agentchat.messages import (
  10. ChatMessage,
  11. HandoffMessage,
  12. MemoryQueryEvent,
  13. ModelClientStreamingChunkEvent,
  14. MultiModalMessage,
  15. TextMessage,
  16. ToolCallExecutionEvent,
  17. ToolCallRequestEvent,
  18. ToolCallSummaryMessage,
  19. )
  20. from autogen_core import ComponentModel, FunctionCall, Image
  21. from autogen_core.memory import ListMemory, Memory, MemoryContent, MemoryMimeType, MemoryQueryResult
  22. from autogen_core.model_context import BufferedChatCompletionContext
  23. from autogen_core.models import (
  24. AssistantMessage,
  25. CreateResult,
  26. FunctionExecutionResult,
  27. LLMMessage,
  28. RequestUsage,
  29. SystemMessage,
  30. UserMessage,
  31. )
  32. from autogen_core.models._model_client import ModelFamily
  33. from autogen_core.tools import FunctionTool
  34. from autogen_ext.models.openai import OpenAIChatCompletionClient
  35. from autogen_ext.models.replay import ReplayChatCompletionClient
  36. from openai.resources.chat.completions import AsyncCompletions
  37. from openai.types.chat.chat_completion import ChatCompletion, Choice
  38. from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
  39. from openai.types.chat.chat_completion_message import ChatCompletionMessage
  40. from openai.types.chat.chat_completion_message_tool_call import (
  41. ChatCompletionMessageToolCall,
  42. Function,
  43. )
  44. from openai.types.completion_usage import CompletionUsage
  45. from utils import FileLogHandler
  46. logger = logging.getLogger(EVENT_LOGGER_NAME)
  47. logger.setLevel(logging.DEBUG)
  48. logger.addHandler(FileLogHandler("test_assistant_agent.log"))
  49. class _MockChatCompletion:
  50. def __init__(self, chat_completions: List[ChatCompletion]) -> None:
  51. self._saved_chat_completions = chat_completions
  52. self.curr_index = 0
  53. self.calls: List[List[LLMMessage]] = []
  54. async def mock_create(
  55. self, *args: Any, **kwargs: Any
  56. ) -> ChatCompletion | AsyncGenerator[ChatCompletionChunk, None]:
  57. self.calls.append(kwargs["messages"]) # Save the call
  58. await asyncio.sleep(0.1)
  59. completion = self._saved_chat_completions[self.curr_index]
  60. self.curr_index += 1
  61. return completion
  62. def _pass_function(input: str) -> str:
  63. return "pass"
  64. async def _fail_function(input: str) -> str:
  65. return "fail"
  66. async def _echo_function(input: str) -> str:
  67. return input
  68. @pytest.mark.asyncio
  69. async def test_run_with_tools(monkeypatch: pytest.MonkeyPatch) -> None:
  70. model = "gpt-4o-2024-05-13"
  71. chat_completions = [
  72. ChatCompletion(
  73. id="id1",
  74. choices=[
  75. Choice(
  76. finish_reason="tool_calls",
  77. index=0,
  78. message=ChatCompletionMessage(
  79. content=None,
  80. tool_calls=[
  81. ChatCompletionMessageToolCall(
  82. id="1",
  83. type="function",
  84. function=Function(
  85. name="_pass_function",
  86. arguments=json.dumps({"input": "task"}),
  87. ),
  88. )
  89. ],
  90. role="assistant",
  91. ),
  92. )
  93. ],
  94. created=0,
  95. model=model,
  96. object="chat.completion",
  97. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  98. ),
  99. ChatCompletion(
  100. id="id2",
  101. choices=[
  102. Choice(
  103. finish_reason="stop",
  104. index=0,
  105. message=ChatCompletionMessage(content="pass", role="assistant"),
  106. )
  107. ],
  108. created=0,
  109. model=model,
  110. object="chat.completion",
  111. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  112. ),
  113. ChatCompletion(
  114. id="id2",
  115. choices=[
  116. Choice(
  117. finish_reason="stop",
  118. index=0,
  119. message=ChatCompletionMessage(content="TERMINATE", role="assistant"),
  120. )
  121. ],
  122. created=0,
  123. model=model,
  124. object="chat.completion",
  125. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  126. ),
  127. ]
  128. mock = _MockChatCompletion(chat_completions)
  129. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  130. agent = AssistantAgent(
  131. "tool_use_agent",
  132. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  133. tools=[
  134. _pass_function,
  135. _fail_function,
  136. FunctionTool(_echo_function, description="Echo"),
  137. ],
  138. )
  139. result = await agent.run(task="task")
  140. assert len(result.messages) == 4
  141. assert isinstance(result.messages[0], TextMessage)
  142. assert result.messages[0].models_usage is None
  143. assert isinstance(result.messages[1], ToolCallRequestEvent)
  144. assert result.messages[1].models_usage is not None
  145. assert result.messages[1].models_usage.completion_tokens == 5
  146. assert result.messages[1].models_usage.prompt_tokens == 10
  147. assert isinstance(result.messages[2], ToolCallExecutionEvent)
  148. assert result.messages[2].models_usage is None
  149. assert isinstance(result.messages[3], ToolCallSummaryMessage)
  150. assert result.messages[3].content == "pass"
  151. assert result.messages[3].models_usage is None
  152. # Test streaming.
  153. mock.curr_index = 0 # Reset the mock
  154. index = 0
  155. async for message in agent.run_stream(task="task"):
  156. if isinstance(message, TaskResult):
  157. assert message == result
  158. else:
  159. assert message == result.messages[index]
  160. index += 1
  161. # Test state saving and loading.
  162. state = await agent.save_state()
  163. agent2 = AssistantAgent(
  164. "tool_use_agent",
  165. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  166. tools=[_pass_function, _fail_function, FunctionTool(_echo_function, description="Echo")],
  167. )
  168. await agent2.load_state(state)
  169. state2 = await agent2.save_state()
  170. assert state == state2
  171. @pytest.mark.asyncio
  172. async def test_run_with_tools_and_reflection(monkeypatch: pytest.MonkeyPatch) -> None:
  173. model = "gpt-4o-2024-05-13"
  174. chat_completions = [
  175. ChatCompletion(
  176. id="id1",
  177. choices=[
  178. Choice(
  179. finish_reason="tool_calls",
  180. index=0,
  181. message=ChatCompletionMessage(
  182. content=None,
  183. tool_calls=[
  184. ChatCompletionMessageToolCall(
  185. id="1",
  186. type="function",
  187. function=Function(
  188. name="_pass_function",
  189. arguments=json.dumps({"input": "task"}),
  190. ),
  191. )
  192. ],
  193. role="assistant",
  194. ),
  195. )
  196. ],
  197. created=0,
  198. model=model,
  199. object="chat.completion",
  200. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  201. ),
  202. ChatCompletion(
  203. id="id2",
  204. choices=[
  205. Choice(finish_reason="stop", index=0, message=ChatCompletionMessage(content="Hello", role="assistant"))
  206. ],
  207. created=0,
  208. model=model,
  209. object="chat.completion",
  210. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  211. ),
  212. ChatCompletion(
  213. id="id2",
  214. choices=[
  215. Choice(
  216. finish_reason="stop", index=0, message=ChatCompletionMessage(content="TERMINATE", role="assistant")
  217. )
  218. ],
  219. created=0,
  220. model=model,
  221. object="chat.completion",
  222. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  223. ),
  224. ]
  225. mock = _MockChatCompletion(chat_completions)
  226. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  227. agent = AssistantAgent(
  228. "tool_use_agent",
  229. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  230. tools=[_pass_function, _fail_function, FunctionTool(_echo_function, description="Echo")],
  231. reflect_on_tool_use=True,
  232. )
  233. result = await agent.run(task="task")
  234. assert len(result.messages) == 4
  235. assert isinstance(result.messages[0], TextMessage)
  236. assert result.messages[0].models_usage is None
  237. assert isinstance(result.messages[1], ToolCallRequestEvent)
  238. assert result.messages[1].models_usage is not None
  239. assert result.messages[1].models_usage.completion_tokens == 5
  240. assert result.messages[1].models_usage.prompt_tokens == 10
  241. assert isinstance(result.messages[2], ToolCallExecutionEvent)
  242. assert result.messages[2].models_usage is None
  243. assert isinstance(result.messages[3], TextMessage)
  244. assert result.messages[3].content == "Hello"
  245. assert result.messages[3].models_usage is not None
  246. assert result.messages[3].models_usage.completion_tokens == 5
  247. assert result.messages[3].models_usage.prompt_tokens == 10
  248. # Test streaming.
  249. mock.curr_index = 0 # pyright: ignore
  250. index = 0
  251. async for message in agent.run_stream(task="task"):
  252. if isinstance(message, TaskResult):
  253. assert message == result
  254. else:
  255. assert message == result.messages[index]
  256. index += 1
  257. # Test state saving and loading.
  258. state = await agent.save_state()
  259. agent2 = AssistantAgent(
  260. "tool_use_agent",
  261. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  262. tools=[
  263. _pass_function,
  264. _fail_function,
  265. FunctionTool(_echo_function, description="Echo"),
  266. ],
  267. )
  268. await agent2.load_state(state)
  269. state2 = await agent2.save_state()
  270. assert state == state2
  271. @pytest.mark.asyncio
  272. async def test_run_with_parallel_tools(monkeypatch: pytest.MonkeyPatch) -> None:
  273. model = "gpt-4o-2024-05-13"
  274. chat_completions = [
  275. ChatCompletion(
  276. id="id1",
  277. choices=[
  278. Choice(
  279. finish_reason="tool_calls",
  280. index=0,
  281. message=ChatCompletionMessage(
  282. content=None,
  283. tool_calls=[
  284. ChatCompletionMessageToolCall(
  285. id="1",
  286. type="function",
  287. function=Function(
  288. name="_pass_function",
  289. arguments=json.dumps({"input": "task1"}),
  290. ),
  291. ),
  292. ChatCompletionMessageToolCall(
  293. id="2",
  294. type="function",
  295. function=Function(
  296. name="_pass_function",
  297. arguments=json.dumps({"input": "task2"}),
  298. ),
  299. ),
  300. ChatCompletionMessageToolCall(
  301. id="3",
  302. type="function",
  303. function=Function(
  304. name="_echo_function",
  305. arguments=json.dumps({"input": "task3"}),
  306. ),
  307. ),
  308. ],
  309. role="assistant",
  310. ),
  311. )
  312. ],
  313. created=0,
  314. model=model,
  315. object="chat.completion",
  316. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  317. ),
  318. ChatCompletion(
  319. id="id2",
  320. choices=[
  321. Choice(
  322. finish_reason="stop",
  323. index=0,
  324. message=ChatCompletionMessage(content="pass", role="assistant"),
  325. )
  326. ],
  327. created=0,
  328. model=model,
  329. object="chat.completion",
  330. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  331. ),
  332. ChatCompletion(
  333. id="id2",
  334. choices=[
  335. Choice(
  336. finish_reason="stop",
  337. index=0,
  338. message=ChatCompletionMessage(content="TERMINATE", role="assistant"),
  339. )
  340. ],
  341. created=0,
  342. model=model,
  343. object="chat.completion",
  344. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  345. ),
  346. ]
  347. mock = _MockChatCompletion(chat_completions)
  348. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  349. agent = AssistantAgent(
  350. "tool_use_agent",
  351. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  352. tools=[
  353. _pass_function,
  354. _fail_function,
  355. FunctionTool(_echo_function, description="Echo"),
  356. ],
  357. )
  358. result = await agent.run(task="task")
  359. assert len(result.messages) == 4
  360. assert isinstance(result.messages[0], TextMessage)
  361. assert result.messages[0].models_usage is None
  362. assert isinstance(result.messages[1], ToolCallRequestEvent)
  363. assert result.messages[1].content == [
  364. FunctionCall(id="1", arguments=r'{"input": "task1"}', name="_pass_function"),
  365. FunctionCall(id="2", arguments=r'{"input": "task2"}', name="_pass_function"),
  366. FunctionCall(id="3", arguments=r'{"input": "task3"}', name="_echo_function"),
  367. ]
  368. assert result.messages[1].models_usage is not None
  369. assert result.messages[1].models_usage.completion_tokens == 5
  370. assert result.messages[1].models_usage.prompt_tokens == 10
  371. assert isinstance(result.messages[2], ToolCallExecutionEvent)
  372. expected_content = [
  373. FunctionExecutionResult(call_id="1", content="pass", is_error=False),
  374. FunctionExecutionResult(call_id="2", content="pass", is_error=False),
  375. FunctionExecutionResult(call_id="3", content="task3", is_error=False),
  376. ]
  377. for expected in expected_content:
  378. assert expected in result.messages[2].content
  379. assert result.messages[2].models_usage is None
  380. assert isinstance(result.messages[3], ToolCallSummaryMessage)
  381. assert result.messages[3].content == "pass\npass\ntask3"
  382. assert result.messages[3].models_usage is None
  383. # Test streaming.
  384. mock.curr_index = 0 # Reset the mock
  385. index = 0
  386. async for message in agent.run_stream(task="task"):
  387. if isinstance(message, TaskResult):
  388. assert message == result
  389. else:
  390. assert message == result.messages[index]
  391. index += 1
  392. # Test state saving and loading.
  393. state = await agent.save_state()
  394. agent2 = AssistantAgent(
  395. "tool_use_agent",
  396. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  397. tools=[_pass_function, _fail_function, FunctionTool(_echo_function, description="Echo")],
  398. )
  399. await agent2.load_state(state)
  400. state2 = await agent2.save_state()
  401. assert state == state2
  402. @pytest.mark.asyncio
  403. async def test_handoffs(monkeypatch: pytest.MonkeyPatch) -> None:
  404. handoff = Handoff(target="agent2")
  405. model = "gpt-4o-2024-05-13"
  406. chat_completions = [
  407. ChatCompletion(
  408. id="id1",
  409. choices=[
  410. Choice(
  411. finish_reason="tool_calls",
  412. index=0,
  413. message=ChatCompletionMessage(
  414. content=None,
  415. tool_calls=[
  416. ChatCompletionMessageToolCall(
  417. id="1",
  418. type="function",
  419. function=Function(
  420. name=handoff.name,
  421. arguments=json.dumps({}),
  422. ),
  423. )
  424. ],
  425. role="assistant",
  426. ),
  427. )
  428. ],
  429. created=0,
  430. model=model,
  431. object="chat.completion",
  432. usage=CompletionUsage(prompt_tokens=42, completion_tokens=43, total_tokens=85),
  433. ),
  434. ]
  435. mock = _MockChatCompletion(chat_completions)
  436. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  437. tool_use_agent = AssistantAgent(
  438. "tool_use_agent",
  439. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  440. tools=[
  441. _pass_function,
  442. _fail_function,
  443. FunctionTool(_echo_function, description="Echo"),
  444. ],
  445. handoffs=[handoff],
  446. )
  447. assert HandoffMessage in tool_use_agent.produced_message_types
  448. result = await tool_use_agent.run(task="task")
  449. assert len(result.messages) == 4
  450. assert isinstance(result.messages[0], TextMessage)
  451. assert result.messages[0].models_usage is None
  452. assert isinstance(result.messages[1], ToolCallRequestEvent)
  453. assert result.messages[1].models_usage is not None
  454. assert result.messages[1].models_usage.completion_tokens == 43
  455. assert result.messages[1].models_usage.prompt_tokens == 42
  456. assert isinstance(result.messages[2], ToolCallExecutionEvent)
  457. assert result.messages[2].models_usage is None
  458. assert isinstance(result.messages[3], HandoffMessage)
  459. assert result.messages[3].content == handoff.message
  460. assert result.messages[3].target == handoff.target
  461. assert result.messages[3].models_usage is None
  462. # Test streaming.
  463. mock.curr_index = 0 # pyright: ignore
  464. index = 0
  465. async for message in tool_use_agent.run_stream(task="task"):
  466. if isinstance(message, TaskResult):
  467. assert message == result
  468. else:
  469. assert message == result.messages[index]
  470. index += 1
  471. @pytest.mark.asyncio
  472. async def test_multi_modal_task(monkeypatch: pytest.MonkeyPatch) -> None:
  473. model = "gpt-4o-2024-05-13"
  474. chat_completions = [
  475. ChatCompletion(
  476. id="id2",
  477. choices=[
  478. Choice(
  479. finish_reason="stop",
  480. index=0,
  481. message=ChatCompletionMessage(content="Hello", role="assistant"),
  482. )
  483. ],
  484. created=0,
  485. model=model,
  486. object="chat.completion",
  487. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  488. ),
  489. ]
  490. mock = _MockChatCompletion(chat_completions)
  491. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  492. agent = AssistantAgent(
  493. name="assistant",
  494. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  495. )
  496. # Generate a random base64 image.
  497. img_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC"
  498. result = await agent.run(task=MultiModalMessage(source="user", content=["Test", Image.from_base64(img_base64)]))
  499. assert len(result.messages) == 2
  500. @pytest.mark.asyncio
  501. async def test_invalid_model_capabilities() -> None:
  502. model = "random-model"
  503. model_client = OpenAIChatCompletionClient(
  504. model=model,
  505. api_key="",
  506. model_info={"vision": False, "function_calling": False, "json_output": False, "family": ModelFamily.UNKNOWN},
  507. )
  508. with pytest.raises(ValueError):
  509. agent = AssistantAgent(
  510. name="assistant",
  511. model_client=model_client,
  512. tools=[
  513. _pass_function,
  514. _fail_function,
  515. FunctionTool(_echo_function, description="Echo"),
  516. ],
  517. )
  518. await agent.run(task=TextMessage(source="user", content="Test"))
  519. with pytest.raises(ValueError):
  520. agent = AssistantAgent(name="assistant", model_client=model_client, handoffs=["agent2"])
  521. await agent.run(task=TextMessage(source="user", content="Test"))
  522. @pytest.mark.asyncio
  523. async def test_remove_images(monkeypatch: pytest.MonkeyPatch) -> None:
  524. model = "random-model"
  525. model_client_1 = OpenAIChatCompletionClient(
  526. model=model,
  527. api_key="",
  528. model_info={"vision": False, "function_calling": False, "json_output": False, "family": ModelFamily.UNKNOWN},
  529. )
  530. model_client_2 = OpenAIChatCompletionClient(
  531. model=model,
  532. api_key="",
  533. model_info={"vision": True, "function_calling": False, "json_output": False, "family": ModelFamily.UNKNOWN},
  534. )
  535. img_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC"
  536. messages: List[LLMMessage] = [
  537. SystemMessage(content="System.1"),
  538. UserMessage(content=["User.1", Image.from_base64(img_base64)], source="user.1"),
  539. AssistantMessage(content="Assistant.1", source="assistant.1"),
  540. UserMessage(content="User.2", source="assistant.2"),
  541. ]
  542. agent_1 = AssistantAgent(name="assistant_1", model_client=model_client_1)
  543. result = agent_1._get_compatible_context(messages) # type: ignore
  544. assert len(result) == 4
  545. assert isinstance(result[1].content, str)
  546. agent_2 = AssistantAgent(name="assistant_2", model_client=model_client_2)
  547. result = agent_2._get_compatible_context(messages) # type: ignore
  548. assert len(result) == 4
  549. assert isinstance(result[1].content, list)
  550. @pytest.mark.asyncio
  551. async def test_list_chat_messages(monkeypatch: pytest.MonkeyPatch) -> None:
  552. model = "gpt-4o-2024-05-13"
  553. chat_completions = [
  554. ChatCompletion(
  555. id="id1",
  556. choices=[
  557. Choice(
  558. finish_reason="stop",
  559. index=0,
  560. message=ChatCompletionMessage(content="Response to message 1", role="assistant"),
  561. )
  562. ],
  563. created=0,
  564. model=model,
  565. object="chat.completion",
  566. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15),
  567. ),
  568. ]
  569. mock = _MockChatCompletion(chat_completions)
  570. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  571. agent = AssistantAgent(
  572. "test_agent",
  573. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  574. )
  575. # Create a list of chat messages
  576. messages: List[ChatMessage] = [
  577. TextMessage(content="Message 1", source="user"),
  578. TextMessage(content="Message 2", source="user"),
  579. ]
  580. # Test run method with list of messages
  581. result = await agent.run(task=messages)
  582. assert len(result.messages) == 3 # 2 input messages + 1 response message
  583. assert isinstance(result.messages[0], TextMessage)
  584. assert result.messages[0].content == "Message 1"
  585. assert result.messages[0].source == "user"
  586. assert isinstance(result.messages[1], TextMessage)
  587. assert result.messages[1].content == "Message 2"
  588. assert result.messages[1].source == "user"
  589. assert isinstance(result.messages[2], TextMessage)
  590. assert result.messages[2].content == "Response to message 1"
  591. assert result.messages[2].source == "test_agent"
  592. assert result.messages[2].models_usage is not None
  593. assert result.messages[2].models_usage.completion_tokens == 5
  594. assert result.messages[2].models_usage.prompt_tokens == 10
  595. # Test run_stream method with list of messages
  596. mock.curr_index = 0 # Reset mock index using public attribute
  597. index = 0
  598. async for message in agent.run_stream(task=messages):
  599. if isinstance(message, TaskResult):
  600. assert message == result
  601. else:
  602. assert message == result.messages[index]
  603. index += 1
  604. @pytest.mark.asyncio
  605. async def test_model_context(monkeypatch: pytest.MonkeyPatch) -> None:
  606. model = "gpt-4o-2024-05-13"
  607. chat_completions = [
  608. ChatCompletion(
  609. id="id1",
  610. choices=[
  611. Choice(
  612. finish_reason="stop",
  613. index=0,
  614. message=ChatCompletionMessage(content="Response to message 3", role="assistant"),
  615. )
  616. ],
  617. created=0,
  618. model=model,
  619. object="chat.completion",
  620. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15),
  621. ),
  622. ]
  623. mock = _MockChatCompletion(chat_completions)
  624. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  625. model_context = BufferedChatCompletionContext(buffer_size=2)
  626. agent = AssistantAgent(
  627. "test_agent",
  628. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  629. model_context=model_context,
  630. )
  631. messages = [
  632. TextMessage(content="Message 1", source="user"),
  633. TextMessage(content="Message 2", source="user"),
  634. TextMessage(content="Message 3", source="user"),
  635. ]
  636. await agent.run(task=messages)
  637. # Check if the mock client is called with only the last two messages.
  638. assert len(mock.calls) == 1
  639. # 2 message from the context + 1 system message
  640. assert len(mock.calls[0]) == 3
  641. @pytest.mark.asyncio
  642. async def test_run_with_memory(monkeypatch: pytest.MonkeyPatch) -> None:
  643. model = "gpt-4o-2024-05-13"
  644. chat_completions = [
  645. ChatCompletion(
  646. id="id1",
  647. choices=[
  648. Choice(
  649. finish_reason="stop",
  650. index=0,
  651. message=ChatCompletionMessage(content="Hello", role="assistant"),
  652. )
  653. ],
  654. created=0,
  655. model=model,
  656. object="chat.completion",
  657. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=0),
  658. ),
  659. ]
  660. b64_image_str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC"
  661. mock = _MockChatCompletion(chat_completions)
  662. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  663. # Test basic memory properties and empty context
  664. memory = ListMemory(name="test_memory")
  665. assert memory.name == "test_memory"
  666. empty_context = BufferedChatCompletionContext(buffer_size=2)
  667. empty_results = await memory.update_context(empty_context)
  668. assert len(empty_results.memories.results) == 0
  669. # Test various content types
  670. memory = ListMemory()
  671. await memory.add(MemoryContent(content="text content", mime_type=MemoryMimeType.TEXT))
  672. await memory.add(MemoryContent(content={"key": "value"}, mime_type=MemoryMimeType.JSON))
  673. await memory.add(MemoryContent(content=Image.from_base64(b64_image_str), mime_type=MemoryMimeType.IMAGE))
  674. # Test query functionality
  675. query_result = await memory.query(MemoryContent(content="", mime_type=MemoryMimeType.TEXT))
  676. assert isinstance(query_result, MemoryQueryResult)
  677. # Should have all three memories we added
  678. assert len(query_result.results) == 3
  679. # Test clear and cleanup
  680. await memory.clear()
  681. empty_query = await memory.query(MemoryContent(content="", mime_type=MemoryMimeType.TEXT))
  682. assert len(empty_query.results) == 0
  683. await memory.close() # Should not raise
  684. # Test invalid memory type
  685. with pytest.raises(TypeError):
  686. AssistantAgent(
  687. "test_agent",
  688. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  689. memory="invalid", # type: ignore
  690. )
  691. # Test with agent
  692. memory2 = ListMemory()
  693. await memory2.add(MemoryContent(content="test instruction", mime_type=MemoryMimeType.TEXT))
  694. agent = AssistantAgent(
  695. "test_agent", model_client=OpenAIChatCompletionClient(model=model, api_key=""), memory=[memory2]
  696. )
  697. # Test dump and load component with memory
  698. agent_config: ComponentModel = agent.dump_component()
  699. assert agent_config.provider == "autogen_agentchat.agents.AssistantAgent"
  700. agent2 = AssistantAgent.load_component(agent_config)
  701. result = await agent2.run(task="test task")
  702. assert len(result.messages) > 0
  703. memory_event = next((msg for msg in result.messages if isinstance(msg, MemoryQueryEvent)), None)
  704. assert memory_event is not None
  705. assert len(memory_event.content) > 0
  706. assert isinstance(memory_event.content[0], MemoryContent)
  707. # Test memory protocol
  708. class BadMemory:
  709. pass
  710. assert not isinstance(BadMemory(), Memory)
  711. assert isinstance(ListMemory(), Memory)
  712. @pytest.mark.asyncio
  713. async def test_assistant_agent_declarative(monkeypatch: pytest.MonkeyPatch) -> None:
  714. model = "gpt-4o-2024-05-13"
  715. chat_completions = [
  716. ChatCompletion(
  717. id="id1",
  718. choices=[
  719. Choice(
  720. finish_reason="stop",
  721. index=0,
  722. message=ChatCompletionMessage(content="Response to message 3", role="assistant"),
  723. )
  724. ],
  725. created=0,
  726. model=model,
  727. object="chat.completion",
  728. usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15),
  729. ),
  730. ]
  731. mock = _MockChatCompletion(chat_completions)
  732. monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
  733. model_context = BufferedChatCompletionContext(buffer_size=2)
  734. agent = AssistantAgent(
  735. "test_agent",
  736. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  737. model_context=model_context,
  738. memory=[ListMemory(name="test_memory")],
  739. )
  740. agent_config: ComponentModel = agent.dump_component()
  741. assert agent_config.provider == "autogen_agentchat.agents.AssistantAgent"
  742. agent2 = AssistantAgent.load_component(agent_config)
  743. assert agent2.name == agent.name
  744. agent3 = AssistantAgent(
  745. "test_agent",
  746. model_client=OpenAIChatCompletionClient(model=model, api_key=""),
  747. model_context=model_context,
  748. tools=[
  749. _pass_function,
  750. _fail_function,
  751. FunctionTool(_echo_function, description="Echo"),
  752. ],
  753. )
  754. agent3_config = agent3.dump_component()
  755. assert agent3_config.provider == "autogen_agentchat.agents.AssistantAgent"
  756. @pytest.mark.asyncio
  757. async def test_model_client_stream() -> None:
  758. mock_client = ReplayChatCompletionClient(
  759. [
  760. "Response to message 3",
  761. ]
  762. )
  763. agent = AssistantAgent(
  764. "test_agent",
  765. model_client=mock_client,
  766. model_client_stream=True,
  767. )
  768. chunks: List[str] = []
  769. async for message in agent.run_stream(task="task"):
  770. if isinstance(message, TaskResult):
  771. assert message.messages[-1].content == "Response to message 3"
  772. elif isinstance(message, ModelClientStreamingChunkEvent):
  773. chunks.append(message.content)
  774. assert "".join(chunks) == "Response to message 3"
  775. @pytest.mark.asyncio
  776. async def test_model_client_stream_with_tool_calls() -> None:
  777. mock_client = ReplayChatCompletionClient(
  778. [
  779. CreateResult(
  780. content=[
  781. FunctionCall(id="1", name="_pass_function", arguments=r'{"input": "task"}'),
  782. FunctionCall(id="3", name="_echo_function", arguments=r'{"input": "task"}'),
  783. ],
  784. finish_reason="function_calls",
  785. usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
  786. cached=False,
  787. ),
  788. "Example response 2 to task",
  789. ]
  790. )
  791. mock_client._model_info["function_calling"] = True # pyright: ignore
  792. agent = AssistantAgent(
  793. "test_agent",
  794. model_client=mock_client,
  795. model_client_stream=True,
  796. reflect_on_tool_use=True,
  797. tools=[_pass_function, _echo_function],
  798. )
  799. chunks: List[str] = []
  800. async for message in agent.run_stream(task="task"):
  801. if isinstance(message, TaskResult):
  802. assert message.messages[-1].content == "Example response 2 to task"
  803. assert message.messages[1].content == [
  804. FunctionCall(id="1", name="_pass_function", arguments=r'{"input": "task"}'),
  805. FunctionCall(id="3", name="_echo_function", arguments=r'{"input": "task"}'),
  806. ]
  807. assert message.messages[2].content == [
  808. FunctionExecutionResult(call_id="1", content="pass", is_error=False),
  809. FunctionExecutionResult(call_id="3", content="task", is_error=False),
  810. ]
  811. elif isinstance(message, ModelClientStreamingChunkEvent):
  812. chunks.append(message.content)
  813. assert "".join(chunks) == "Example response 2 to task"