conversations.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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 import (
  7. WrappedBooleanResponse,
  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. """Create a new conversation.
  21. Returns:
  22. WrappedConversationResponse
  23. """
  24. data: dict[str, Any] = {}
  25. if name:
  26. data["name"] = name
  27. # Send JSON so that FastAPI body validation succeeds.
  28. response_dict = await self.client._make_request(
  29. "POST",
  30. "conversations",
  31. json=data,
  32. version="v3",
  33. )
  34. return WrappedConversationResponse(**response_dict)
  35. async def list(
  36. self,
  37. ids: Optional[list[str | UUID]] = None,
  38. offset: Optional[int] = 0,
  39. limit: Optional[int] = 100,
  40. ) -> WrappedConversationsResponse:
  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. WrappedConversationsResponse
  48. """
  49. params: dict = {
  50. "offset": offset,
  51. "limit": limit,
  52. }
  53. if ids:
  54. params["ids"] = ids
  55. response_dict = await self.client._make_request(
  56. "GET",
  57. "conversations",
  58. params=params,
  59. version="v3",
  60. )
  61. return WrappedConversationsResponse(**response_dict)
  62. async def retrieve(
  63. self,
  64. id: str | UUID,
  65. ) -> WrappedConversationMessagesResponse:
  66. """Get detailed information about a specific conversation.
  67. Args:
  68. id (str | UUID): The ID of the conversation to retrieve
  69. Returns:
  70. WrappedConversationMessagesResponse
  71. """
  72. response_dict = await self.client._make_request(
  73. "GET",
  74. f"conversations/{str(id)}",
  75. version="v3",
  76. )
  77. return WrappedConversationMessagesResponse(**response_dict)
  78. async def update(
  79. self,
  80. id: str | UUID,
  81. name: str,
  82. ) -> WrappedConversationResponse:
  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. WrappedConversationResponse
  89. """
  90. data: dict[str, Any] = {
  91. "name": name,
  92. }
  93. response_dict = await self.client._make_request(
  94. "POST",
  95. f"conversations/{str(id)}",
  96. json=data,
  97. version="v3",
  98. )
  99. return WrappedConversationResponse(**response_dict)
  100. async def delete(
  101. self,
  102. id: str | UUID,
  103. ) -> WrappedBooleanResponse:
  104. """Delete a conversation.
  105. Args:
  106. id (str | UUID): The ID of the conversation to delete
  107. Returns:
  108. WrappedBooleanResponse
  109. """
  110. response_dict = await self.client._make_request(
  111. "DELETE",
  112. f"conversations/{str(id)}",
  113. version="v3",
  114. )
  115. return WrappedBooleanResponse(**response_dict)
  116. async def add_message(
  117. self,
  118. id: str | UUID,
  119. content: str,
  120. role: str,
  121. metadata: Optional[dict] = None,
  122. parent_id: Optional[str] = None,
  123. ) -> WrappedMessageResponse:
  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. WrappedMessageResponse
  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. response_dict = await self.client._make_request(
  143. "POST",
  144. f"conversations/{str(id)}/messages",
  145. json=data,
  146. version="v3",
  147. )
  148. return WrappedMessageResponse(**response_dict)
  149. async def update_message(
  150. self,
  151. id: str | UUID,
  152. message_id: str,
  153. content: Optional[str] = None,
  154. metadata: Optional[dict] = None,
  155. ) -> WrappedMessageResponse:
  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. WrappedMessageResponse
  164. """
  165. data: dict[str, Any] = {"content": content}
  166. if metadata:
  167. data["metadata"] = metadata
  168. response_dict = await self.client._make_request(
  169. "POST",
  170. f"conversations/{str(id)}/messages/{message_id}",
  171. json=data,
  172. version="v3",
  173. )
  174. return WrappedMessageResponse(**response_dict)
  175. async def export(
  176. self,
  177. output_path: str | Path,
  178. columns: Optional[_list[str]] = None,
  179. filters: Optional[dict] = None,
  180. include_header: bool = True,
  181. ) -> None:
  182. """Export conversations to a CSV file, streaming the results directly
  183. to disk.
  184. Args:
  185. output_path (str | Path): Local path where the CSV file should be saved
  186. columns (Optional[list[str]]): Specific columns to export. If None, exports default columns
  187. filters (Optional[dict]): Optional filters to apply when selecting conversations
  188. include_header (bool): Whether to include column headers in the CSV (default: True)
  189. Returns:
  190. None
  191. """
  192. # Convert path to string if it's a Path object
  193. output_path = (
  194. str(output_path) if isinstance(output_path, Path) else output_path
  195. )
  196. # Prepare request data
  197. data: dict[str, Any] = {"include_header": include_header}
  198. if columns:
  199. data["columns"] = columns
  200. if filters:
  201. data["filters"] = filters
  202. # Stream response directly to file
  203. async with aiofiles.open(output_path, "wb") as f:
  204. async with self.client.session.post(
  205. f"{self.client.base_url}/v3/conversations/export",
  206. json=data,
  207. headers={
  208. "Accept": "text/csv",
  209. **self.client._get_auth_header(),
  210. },
  211. ) as response:
  212. if response.status != 200:
  213. raise ValueError(
  214. f"Export failed with status {response.status}",
  215. response,
  216. )
  217. async for chunk in response.content.iter_chunks():
  218. if chunk:
  219. await f.write(chunk[0])
  220. async def export_messages(
  221. self,
  222. output_path: str | Path,
  223. columns: Optional[_list[str]] = None,
  224. filters: Optional[dict] = None,
  225. include_header: bool = True,
  226. ) -> None:
  227. """Export messages to a CSV file, streaming the results directly to
  228. disk.
  229. Args:
  230. output_path (str | Path): Local path where the CSV file should be saved
  231. columns (Optional[list[str]]): Specific columns to export. If None, exports default columns
  232. filters (Optional[dict]): Optional filters to apply when selecting messages
  233. include_header (bool): Whether to include column headers in the CSV (default: True)
  234. Returns:
  235. None
  236. """
  237. # Convert path to string if it's a Path object
  238. output_path = (
  239. str(output_path) if isinstance(output_path, Path) else output_path
  240. )
  241. # Prepare request data
  242. data: dict[str, Any] = {"include_header": include_header}
  243. if columns:
  244. data["columns"] = columns
  245. if filters:
  246. data["filters"] = filters
  247. # Stream response directly to file
  248. async with aiofiles.open(output_path, "wb") as f:
  249. async with self.client.session.post(
  250. f"{self.client.base_url}/v3/conversations/export_messages",
  251. json=data,
  252. headers={
  253. "Accept": "text/csv",
  254. **self.client._get_auth_header(),
  255. },
  256. ) as response:
  257. if response.status != 200:
  258. raise ValueError(
  259. f"Export failed with status {response.status}",
  260. response,
  261. )
  262. async for chunk in response.content.iter_chunks():
  263. if chunk:
  264. await f.write(chunk[0])