test_conversations.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import time
  2. import contextlib
  3. import uuid
  4. import pytest
  5. from r2r import R2RClient, R2RException
  6. @pytest.fixture
  7. def test_conversation(client: R2RClient):
  8. """Create and yield a test conversation, then clean up."""
  9. conv_resp = client.conversations.create()
  10. conversation_id = conv_resp.results.id
  11. yield conversation_id
  12. with contextlib.suppress(R2RException):
  13. client.conversations.delete(id=conversation_id)
  14. def test_create_conversation(client: R2RClient):
  15. conv_id = client.conversations.create().results.id
  16. assert conv_id is not None, "No conversation_id returned"
  17. # Cleanup
  18. client.conversations.delete(id=conv_id)
  19. def test_list_conversations(client: R2RClient, test_conversation):
  20. results = client.conversations.list(offset=0, limit=10).results
  21. # Just ensure at least one conversation is listed
  22. assert len(results) >= 1, "Expected at least one conversation, none found"
  23. def test_retrieve_conversation(client: R2RClient, test_conversation):
  24. # Retrieve the conversation just created
  25. retrieved = client.conversations.retrieve(id=test_conversation).results
  26. # A new conversation might have no messages, so results should be an empty list
  27. assert isinstance(retrieved, list), "Expected list of messages"
  28. assert len(retrieved) == 0, (
  29. "Expected empty message list for a new conversation")
  30. def test_delete_conversation(client: R2RClient):
  31. # Create a conversation and delete it
  32. conv_id = client.conversations.create().results.id
  33. client.conversations.delete(id=conv_id)
  34. # Verify retrieval fails
  35. with pytest.raises(R2RException) as exc_info:
  36. client.conversations.retrieve(id=conv_id)
  37. assert exc_info.value.status_code == 404, (
  38. "Wrong error code retrieving deleted conversation")
  39. def test_add_message(client: R2RClient, test_conversation):
  40. # Add a message to the conversation
  41. msg_id = client.conversations.add_message(
  42. id=test_conversation,
  43. content="Hello",
  44. role="user",
  45. ).results.id
  46. assert msg_id, "No message ID returned after adding a message"
  47. # Retrieve conversation and verify message is present
  48. retrieved = client.conversations.retrieve(id=test_conversation).results
  49. found = any(str(msg.id) == str(msg_id) for msg in retrieved)
  50. assert found, "Added message not found in conversation"
  51. def test_retrieve_non_existent_conversation(client: R2RClient):
  52. bad_id = str(uuid.uuid4())
  53. with pytest.raises(R2RException) as exc_info:
  54. client.conversations.retrieve(id=bad_id)
  55. assert exc_info.value.status_code == 404, (
  56. "Wrong error code for non-existent conversation")
  57. def test_delete_non_existent_conversation(client: R2RClient):
  58. bad_id = str(uuid.uuid4())
  59. with pytest.raises(R2RException) as exc_info:
  60. client.conversations.delete(id=bad_id)
  61. assert exc_info.value.status_code == 404, (
  62. "Wrong error code for delete non-existent")
  63. def test_add_message_to_non_existent_conversation(client: R2RClient):
  64. bad_id = str(uuid.uuid4())
  65. with pytest.raises(R2RException) as exc_info:
  66. client.conversations.add_message(
  67. id=bad_id,
  68. content="Hi",
  69. role="user",
  70. )
  71. # Expected a 404 since conversation doesn't exist
  72. assert exc_info.value.status_code == 404, (
  73. "Wrong error code for adding message to non-existent conversation")
  74. def test_update_message(client: R2RClient, test_conversation):
  75. # Add a message first
  76. original_msg_id = client.conversations.add_message(
  77. id=test_conversation,
  78. content="Original content",
  79. role="user",
  80. ).results.id
  81. # Update the message
  82. update_resp = client.conversations.update_message(
  83. id=test_conversation,
  84. message_id=original_msg_id,
  85. content="Updated content",
  86. metadata={
  87. "new_key": "new_value"
  88. },
  89. ).results
  90. assert update_resp.message is not None, "No message returned after update"
  91. assert update_resp.metadata is not None, (
  92. "No metadata returned after update")
  93. assert update_resp.id is not None, "No metadata returned after update"
  94. # Retrieve the conversation with the new branch
  95. updated_conv = client.conversations.retrieve(id=test_conversation).results
  96. assert updated_conv, "No conversation returned after update"
  97. assert updated_conv[0].message.content == "Updated content", (
  98. "Message content not updated")
  99. # found_updated = any(msg["id"] == new_message_id and msg["message"]["content"] == "Updated content" for msg in updated_conv)
  100. # assert found_updated, "Updated message not found in the new branch"
  101. def test_update_non_existent_message(client: R2RClient, test_conversation):
  102. fake_msg_id = str(uuid.uuid4())
  103. with pytest.raises(R2RException) as exc_info:
  104. client.conversations.update_message(id=test_conversation,
  105. message_id=fake_msg_id,
  106. content="Should fail")
  107. assert exc_info.value.status_code == 404, (
  108. "Wrong error code for updating non-existent message")
  109. def test_add_message_with_empty_content(client: R2RClient, test_conversation):
  110. with pytest.raises(R2RException) as exc_info:
  111. client.conversations.add_message(
  112. id=test_conversation,
  113. content="",
  114. role="user",
  115. )
  116. # Check for 400 or a relevant error code depending on server validation
  117. assert exc_info.value.status_code == 400, (
  118. "Wrong error code or no error for empty content message")
  119. def test_add_message_invalid_role(client: R2RClient, test_conversation):
  120. with pytest.raises(R2RException) as exc_info:
  121. client.conversations.add_message(
  122. id=test_conversation,
  123. content="Hello",
  124. role="invalid_role",
  125. )
  126. assert exc_info.value.status_code == 400, (
  127. "Wrong error code or no error for invalid role")
  128. def test_add_message_to_deleted_conversation(client: R2RClient):
  129. # Create a conversation and delete it
  130. conv_id = client.conversations.create().results.id
  131. client.conversations.delete(id=conv_id)
  132. # Try adding a message to the deleted conversation
  133. with pytest.raises(R2RException) as exc_info:
  134. client.conversations.add_message(
  135. id=conv_id,
  136. content="Should fail",
  137. role="user",
  138. )
  139. assert exc_info.value.status_code == 404, (
  140. "Wrong error code for adding message to deleted conversation")
  141. def test_update_message_with_additional_metadata(client: R2RClient,
  142. test_conversation):
  143. # Add a message with initial metadata
  144. original_msg_id = client.conversations.add_message(
  145. id=test_conversation,
  146. content="Initial content",
  147. role="user",
  148. metadata={
  149. "initial_key": "initial_value"
  150. },
  151. ).results.id
  152. # Update the message with new content and additional metadata
  153. update_resp = client.conversations.update_message(
  154. id=test_conversation,
  155. message_id=original_msg_id,
  156. content="Updated content",
  157. metadata={
  158. "new_key": "new_value"
  159. },
  160. ).results
  161. # Retrieve the conversation from the new branch
  162. updated_conv = client.conversations.retrieve(id=test_conversation).results
  163. # Find the updated message
  164. updated_message = next(
  165. (msg for msg in updated_conv if str(msg.id) == str(original_msg_id)),
  166. None,
  167. )
  168. assert updated_message is not None, (
  169. "Updated message not found in conversation")
  170. # Check that metadata includes old keys, new keys, and 'edited': True
  171. msg_metadata = updated_message.metadata
  172. assert msg_metadata.get("initial_key") == "initial_value", (
  173. "Old metadata not preserved")
  174. assert msg_metadata.get("new_key") == "new_value", "New metadata not added"
  175. assert msg_metadata.get("edited") is True, (
  176. "'edited' flag not set in metadata")
  177. assert updated_message.message.content == "Updated content", (
  178. "Message content not updated")
  179. def test_new_conversation_gets_named_after_first_agent_interaction(client: R2RClient):
  180. """Test that a new conversation is automatically named after the first agent interaction."""
  181. # Create a new conversation
  182. conv_resp = client.conversations.create()
  183. conversation_id = conv_resp.results.id
  184. try:
  185. # Verify it has no name initially
  186. conv_overview = client.conversations.list(
  187. offset=0,
  188. limit=10,
  189. # conversation_ids=[conversation_id]
  190. )
  191. target_conv = next((c for c in conv_overview.results if str(c.id) == str(conversation_id)), None)
  192. assert target_conv is not None, "Test conversation not found"
  193. assert target_conv.name is None, "New conversation already had a name"
  194. # Add a message via the agent method which should trigger naming
  195. response = client.retrieval.agent(
  196. message={"role": "user", "content": "Hello, this is a test message"},
  197. conversation_id=conversation_id,
  198. )
  199. time.sleep(5) # sleep while name is fetched
  200. # Verify the conversation now has a name
  201. conv_overview = client.conversations.list(
  202. offset=0,
  203. limit=10,
  204. # conversation_ids=[conversation_id]
  205. )
  206. target_conv = next((c for c in conv_overview.results if str(c.id) == str(conversation_id)), None)
  207. assert target_conv is not None, "Test conversation not found"
  208. assert target_conv.name is not None and target_conv.name != "", "Conversation was not automatically named"
  209. finally:
  210. # Cleanup
  211. client.conversations.delete(id=conversation_id)
  212. def test_existing_named_conversation_preserves_name_after_agent_interaction(client: R2RClient):
  213. """Test that an existing conversation with a name preserves that name after agent interaction."""
  214. # Create a new conversation
  215. conv_resp = client.conversations.create()
  216. conversation_id = conv_resp.results.id
  217. try:
  218. # Set a specific name for the conversation
  219. custom_name = f"Custom Conversation Name {uuid.uuid4()}"
  220. client.conversations.update(
  221. id=conversation_id,
  222. name=custom_name
  223. )
  224. # Verify the name was set correctly
  225. conv_overview = client.conversations.list(
  226. offset=0,
  227. limit=10,
  228. # conversation_ids=[conversation_id]
  229. )
  230. target_conv = next((c for c in conv_overview.results if str(c.id) == str(conversation_id)), None)
  231. assert target_conv is not None, "Test conversation not found"
  232. assert target_conv.name == custom_name, "Custom name not set correctly"
  233. # Add a message via the agent method
  234. response = client.retrieval.agent(
  235. message={"role": "user", "content": "Hello, this is a test message"},
  236. conversation_id=conversation_id,
  237. )
  238. # Verify the conversation still has the same name
  239. conv_overview = client.conversations.list(
  240. offset=0,
  241. limit=100,
  242. # conversation_ids=[conversation_id]
  243. )
  244. target_conv = next((c for c in conv_overview.results if str(c.id) == str(conversation_id)), None)
  245. assert target_conv is not None, "Test conversation not found"
  246. assert target_conv.name == custom_name, "Conversation name was changed after agent interaction"
  247. finally:
  248. # Cleanup
  249. client.conversations.delete(id=conversation_id)