conversations.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. from builtins import list as _list
  2. from pathlib import Path
  3. from typing import Any, Optional
  4. from uuid import UUID
  5. import aiofiles
  6. from shared.api.models.base import WrappedBooleanResponse
  7. from shared.api.models.management.responses import (
  8. WrappedConversationMessagesResponse,
  9. WrappedConversationResponse,
  10. WrappedConversationsResponse,
  11. WrappedMessageResponse,
  12. )
  13. class ConversationsSDK:
  14. def __init__(self, client):
  15. self.client = client
  16. async def create(
  17. self,
  18. name: Optional[str] = None,
  19. ) -> WrappedConversationResponse:
  20. """
  21. Create a new conversation.
  22. Returns:
  23. dict: Created conversation information
  24. """
  25. data: dict[str, Any] = {}
  26. if name:
  27. data["name"] = name
  28. return await self.client._make_request(
  29. "POST",
  30. "conversations",
  31. data=data,
  32. version="v3",
  33. )
  34. async def list(
  35. self,
  36. ids: Optional[list[str | UUID]] = None,
  37. offset: Optional[int] = 0,
  38. limit: Optional[int] = 100,
  39. ) -> WrappedConversationsResponse:
  40. """
  41. List conversations with pagination and sorting options.
  42. Args:
  43. ids (Optional[list[str | UUID]]): List of conversation IDs to retrieve
  44. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  45. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  46. Returns:
  47. dict: List of conversations and pagination information
  48. """
  49. params: dict = {
  50. "offset": offset,
  51. "limit": limit,
  52. }
  53. if ids:
  54. params["ids"] = ids
  55. return await self.client._make_request(
  56. "GET",
  57. "conversations",
  58. params=params,
  59. version="v3",
  60. )
  61. async def retrieve(
  62. self,
  63. id: str | UUID,
  64. ) -> WrappedConversationMessagesResponse:
  65. """
  66. Get detailed information about a specific conversation.
  67. Args:
  68. id (str | UUID): The ID of the conversation to retrieve
  69. Returns:
  70. dict: Detailed conversation information
  71. """
  72. return await self.client._make_request(
  73. "GET",
  74. f"conversations/{str(id)}",
  75. version="v3",
  76. )
  77. async def update(
  78. self,
  79. id: str | UUID,
  80. name: str,
  81. ) -> WrappedConversationResponse:
  82. """
  83. Update an existing conversation.
  84. Args:
  85. id (str | UUID): The ID of the conversation to update
  86. name (str): The new name of the conversation
  87. Returns:
  88. dict: The updated conversation
  89. """
  90. data: dict[str, Any] = {
  91. "name": name,
  92. }
  93. return await self.client._make_request(
  94. "POST",
  95. f"conversations/{str(id)}",
  96. json=data,
  97. version="v3",
  98. )
  99. async def delete(
  100. self,
  101. id: str | UUID,
  102. ) -> WrappedBooleanResponse:
  103. """
  104. Delete a conversation.
  105. Args:
  106. id (str | UUID): The ID of the conversation to delete
  107. Returns:
  108. bool: True if deletion was successful
  109. """
  110. return await self.client._make_request(
  111. "DELETE",
  112. f"conversations/{str(id)}",
  113. version="v3",
  114. )
  115. async def add_message(
  116. self,
  117. id: str | UUID,
  118. content: str,
  119. role: str,
  120. metadata: Optional[dict] = None,
  121. parent_id: Optional[str] = None,
  122. ) -> WrappedMessageResponse:
  123. """
  124. Add a new message to a conversation.
  125. Args:
  126. id (str | UUID): The ID of the conversation to add the message to
  127. content (str): The content of the message
  128. role (str): The role of the message (e.g., "user" or "assistant")
  129. parent_id (Optional[str]): The ID of the parent message
  130. metadata (Optional[dict]): Additional metadata to attach to the message
  131. Returns:
  132. dict: Result of the operation, including the new message ID
  133. """
  134. data: dict[str, Any] = {
  135. "content": content,
  136. "role": role,
  137. }
  138. if parent_id:
  139. data["parent_id"] = parent_id
  140. if metadata:
  141. data["metadata"] = metadata
  142. return await self.client._make_request(
  143. "POST",
  144. f"conversations/{str(id)}/messages",
  145. json=data,
  146. version="v3",
  147. )
  148. async def update_message(
  149. self,
  150. id: str | UUID,
  151. message_id: str,
  152. content: Optional[str] = None,
  153. metadata: Optional[dict] = None,
  154. ) -> dict:
  155. """
  156. Update an existing message in a conversation.
  157. Args:
  158. id (str | UUID): The ID of the conversation containing the message
  159. message_id (str): The ID of the message to update
  160. content (str): The new content of the message
  161. metadata (dict): Additional metadata to attach to the message
  162. Returns:
  163. dict: Result of the operation, including the new message ID and branch ID
  164. """
  165. data: dict[str, Any] = {"content": content}
  166. if metadata:
  167. data["metadata"] = metadata
  168. return await self.client._make_request(
  169. "POST",
  170. f"conversations/{str(id)}/messages/{message_id}",
  171. json=data,
  172. version="v3",
  173. )
  174. async def export(
  175. self,
  176. output_path: str | Path,
  177. columns: Optional[_list[str]] = None,
  178. filters: Optional[dict] = None,
  179. include_header: bool = True,
  180. ) -> None:
  181. """
  182. Export conversations to a CSV file, streaming the results directly to disk.
  183. Args:
  184. output_path (str | Path): Local path where the CSV file should be saved
  185. columns (Optional[list[str]]): Specific columns to export. If None, exports default columns
  186. filters (Optional[dict]): Optional filters to apply when selecting conversations
  187. include_header (bool): Whether to include column headers in the CSV (default: True)
  188. """
  189. # Convert path to string if it's a Path object
  190. output_path = (
  191. str(output_path) if isinstance(output_path, Path) else output_path
  192. )
  193. # Prepare request data
  194. data: dict[str, Any] = {"include_header": include_header}
  195. if columns:
  196. data["columns"] = columns
  197. if filters:
  198. data["filters"] = filters
  199. # Stream response directly to file
  200. async with aiofiles.open(output_path, "wb") as f:
  201. async with self.client.session.post(
  202. f"{self.client.base_url}/v3/conversations/export",
  203. json=data,
  204. headers={
  205. "Accept": "text/csv",
  206. **self.client._get_auth_headers(),
  207. },
  208. ) as response:
  209. if response.status != 200:
  210. raise ValueError(
  211. f"Export failed with status {response.status}",
  212. response,
  213. )
  214. async for chunk in response.content.iter_chunks():
  215. if chunk:
  216. await f.write(chunk[0])
  217. async def export_messages(
  218. self,
  219. output_path: str | Path,
  220. columns: Optional[_list[str]] = None,
  221. filters: Optional[dict] = None,
  222. include_header: bool = True,
  223. ) -> None:
  224. """
  225. Export messages to a CSV file, streaming the results directly to disk.
  226. Args:
  227. output_path (str | Path): Local path where the CSV file should be saved
  228. columns (Optional[list[str]]): Specific columns to export. If None, exports default columns
  229. filters (Optional[dict]): Optional filters to apply when selecting messages
  230. include_header (bool): Whether to include column headers in the CSV (default: True)
  231. """
  232. # Convert path to string if it's a Path object
  233. output_path = (
  234. str(output_path) if isinstance(output_path, Path) else output_path
  235. )
  236. # Prepare request data
  237. data: dict[str, Any] = {"include_header": include_header}
  238. if columns:
  239. data["columns"] = columns
  240. if filters:
  241. data["filters"] = filters
  242. # Stream response directly to file
  243. async with aiofiles.open(output_path, "wb") as f:
  244. async with self.client.session.post(
  245. f"{self.client.base_url}/v3/conversations/export_messages",
  246. json=data,
  247. headers={
  248. "Accept": "text/csv",
  249. **self.client._get_auth_headers(),
  250. },
  251. ) as response:
  252. if response.status != 200:
  253. raise ValueError(
  254. f"Export failed with status {response.status}",
  255. response,
  256. )
  257. async for chunk in response.content.iter_chunks():
  258. if chunk:
  259. await f.write(chunk[0])