client.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import json
  2. import requests
  3. class DifyClient:
  4. def __init__(self, api_key, base_url: str = "https://api.dify.ai/v1"):
  5. self.api_key = api_key
  6. self.base_url = base_url
  7. def _send_request(self, method, endpoint, json=None, params=None, stream=False):
  8. headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
  9. url = f"{self.base_url}{endpoint}"
  10. response = requests.request(method, url, json=json, params=params, headers=headers, stream=stream)
  11. return response
  12. def _send_request_with_files(self, method, endpoint, data, files):
  13. headers = {"Authorization": f"Bearer {self.api_key}"}
  14. url = f"{self.base_url}{endpoint}"
  15. response = requests.request(method, url, data=data, headers=headers, files=files)
  16. return response
  17. def message_feedback(self, message_id, rating, user):
  18. data = {"rating": rating, "user": user}
  19. return self._send_request("POST", f"/messages/{message_id}/feedbacks", data)
  20. def get_application_parameters(self, user):
  21. params = {"user": user}
  22. return self._send_request("GET", "/parameters", params=params)
  23. def file_upload(self, user, files):
  24. data = {"user": user}
  25. return self._send_request_with_files("POST", "/files/upload", data=data, files=files)
  26. def text_to_audio(self, text: str, user: str, streaming: bool = False):
  27. data = {"text": text, "user": user, "streaming": streaming}
  28. return self._send_request("POST", "/text-to-audio", data=data)
  29. def get_meta(self, user):
  30. params = {"user": user}
  31. return self._send_request("GET", "/meta", params=params)
  32. class CompletionClient(DifyClient):
  33. def create_completion_message(self, inputs, response_mode, user, files=None):
  34. data = {"inputs": inputs, "response_mode": response_mode, "user": user, "files": files}
  35. return self._send_request(
  36. "POST", "/completion-messages", data, stream=True if response_mode == "streaming" else False
  37. )
  38. class ChatClient(DifyClient):
  39. def create_chat_message(self, inputs, query, user, response_mode="blocking", conversation_id=None, files=None):
  40. data = {"inputs": inputs, "query": query, "user": user, "response_mode": response_mode, "files": files}
  41. if conversation_id:
  42. data["conversation_id"] = conversation_id
  43. return self._send_request(
  44. "POST", "/chat-messages", data, stream=True if response_mode == "streaming" else False
  45. )
  46. def get_suggested(self, message_id, user: str):
  47. params = {"user": user}
  48. return self._send_request("GET", f"/messages/{message_id}/suggested", params=params)
  49. def stop_message(self, task_id, user):
  50. data = {"user": user}
  51. return self._send_request("POST", f"/chat-messages/{task_id}/stop", data)
  52. def get_conversations(self, user, last_id=None, limit=None, pinned=None):
  53. params = {"user": user, "last_id": last_id, "limit": limit, "pinned": pinned}
  54. return self._send_request("GET", "/conversations", params=params)
  55. def get_conversation_messages(self, user, conversation_id=None, first_id=None, limit=None):
  56. params = {"user": user}
  57. if conversation_id:
  58. params["conversation_id"] = conversation_id
  59. if first_id:
  60. params["first_id"] = first_id
  61. if limit:
  62. params["limit"] = limit
  63. return self._send_request("GET", "/messages", params=params)
  64. def rename_conversation(self, conversation_id, name, auto_generate: bool, user: str):
  65. data = {"name": name, "auto_generate": auto_generate, "user": user}
  66. return self._send_request("POST", f"/conversations/{conversation_id}/name", data)
  67. def delete_conversation(self, conversation_id, user):
  68. data = {"user": user}
  69. return self._send_request("DELETE", f"/conversations/{conversation_id}", data)
  70. def audio_to_text(self, audio_file, user):
  71. data = {"user": user}
  72. files = {"audio_file": audio_file}
  73. return self._send_request_with_files("POST", "/audio-to-text", data, files)
  74. class WorkflowClient(DifyClient):
  75. def run(self, inputs: dict, response_mode: str = "streaming", user: str = "abc-123"):
  76. data = {"inputs": inputs, "response_mode": response_mode, "user": user}
  77. return self._send_request("POST", "/workflows/run", data)
  78. def stop(self, task_id, user):
  79. data = {"user": user}
  80. return self._send_request("POST", f"/workflows/tasks/{task_id}/stop", data)
  81. def get_result(self, workflow_run_id):
  82. return self._send_request("GET", f"/workflows/run/{workflow_run_id}")
  83. class KnowledgeBaseClient(DifyClient):
  84. def __init__(self, api_key, base_url: str = "https://api.dify.ai/v1", dataset_id: str = None):
  85. """
  86. Construct a KnowledgeBaseClient object.
  87. Args:
  88. api_key (str): API key of Dify.
  89. base_url (str, optional): Base URL of Dify API. Defaults to 'https://api.dify.ai/v1'.
  90. dataset_id (str, optional): ID of the dataset. Defaults to None. You don't need this if you just want to
  91. create a new dataset. or list datasets. otherwise you need to set this.
  92. """
  93. super().__init__(api_key=api_key, base_url=base_url)
  94. self.dataset_id = dataset_id
  95. def _get_dataset_id(self):
  96. if self.dataset_id is None:
  97. raise ValueError("dataset_id is not set")
  98. return self.dataset_id
  99. def create_dataset(self, name: str, **kwargs):
  100. return self._send_request("POST", "/datasets", {"name": name}, **kwargs)
  101. def list_datasets(self, page: int = 1, page_size: int = 20, **kwargs):
  102. return self._send_request("GET", f"/datasets?page={page}&limit={page_size}", **kwargs)
  103. def create_document_by_text(self, name, text, extra_params: dict = None, **kwargs):
  104. """
  105. Create a document by text.
  106. :param name: Name of the document
  107. :param text: Text content of the document
  108. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  109. e.g.
  110. {
  111. 'indexing_technique': 'high_quality',
  112. 'process_rule': {
  113. 'rules': {
  114. 'pre_processing_rules': [
  115. {'id': 'remove_extra_spaces', 'enabled': True},
  116. {'id': 'remove_urls_emails', 'enabled': True}
  117. ],
  118. 'segmentation': {
  119. 'separator': '\n',
  120. 'max_tokens': 500
  121. }
  122. },
  123. 'mode': 'custom'
  124. }
  125. }
  126. :return: Response from the API
  127. """
  128. data = {"indexing_technique": "high_quality", "process_rule": {"mode": "automatic"}, "name": name, "text": text}
  129. if extra_params is not None and isinstance(extra_params, dict):
  130. data.update(extra_params)
  131. url = f"/datasets/{self._get_dataset_id()}/document/create_by_text"
  132. return self._send_request("POST", url, json=data, **kwargs)
  133. def update_document_by_text(self, document_id, name, text, extra_params: dict = None, **kwargs):
  134. """
  135. Update a document by text.
  136. :param document_id: ID of the document
  137. :param name: Name of the document
  138. :param text: Text content of the document
  139. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  140. e.g.
  141. {
  142. 'indexing_technique': 'high_quality',
  143. 'process_rule': {
  144. 'rules': {
  145. 'pre_processing_rules': [
  146. {'id': 'remove_extra_spaces', 'enabled': True},
  147. {'id': 'remove_urls_emails', 'enabled': True}
  148. ],
  149. 'segmentation': {
  150. 'separator': '\n',
  151. 'max_tokens': 500
  152. }
  153. },
  154. 'mode': 'custom'
  155. }
  156. }
  157. :return: Response from the API
  158. """
  159. data = {"name": name, "text": text}
  160. if extra_params is not None and isinstance(extra_params, dict):
  161. data.update(extra_params)
  162. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_text"
  163. return self._send_request("POST", url, json=data, **kwargs)
  164. def create_document_by_file(self, file_path, original_document_id=None, extra_params: dict = None):
  165. """
  166. Create a document by file.
  167. :param file_path: Path to the file
  168. :param original_document_id: pass this ID if you want to replace the original document (optional)
  169. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  170. e.g.
  171. {
  172. 'indexing_technique': 'high_quality',
  173. 'process_rule': {
  174. 'rules': {
  175. 'pre_processing_rules': [
  176. {'id': 'remove_extra_spaces', 'enabled': True},
  177. {'id': 'remove_urls_emails', 'enabled': True}
  178. ],
  179. 'segmentation': {
  180. 'separator': '\n',
  181. 'max_tokens': 500
  182. }
  183. },
  184. 'mode': 'custom'
  185. }
  186. }
  187. :return: Response from the API
  188. """
  189. files = {"file": open(file_path, "rb")}
  190. data = {"process_rule": {"mode": "automatic"}, "indexing_technique": "high_quality"}
  191. if extra_params is not None and isinstance(extra_params, dict):
  192. data.update(extra_params)
  193. if original_document_id is not None:
  194. data["original_document_id"] = original_document_id
  195. url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
  196. return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
  197. def update_document_by_file(self, document_id, file_path, extra_params: dict = None):
  198. """
  199. Update a document by file.
  200. :param document_id: ID of the document
  201. :param file_path: Path to the file
  202. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  203. e.g.
  204. {
  205. 'indexing_technique': 'high_quality',
  206. 'process_rule': {
  207. 'rules': {
  208. 'pre_processing_rules': [
  209. {'id': 'remove_extra_spaces', 'enabled': True},
  210. {'id': 'remove_urls_emails', 'enabled': True}
  211. ],
  212. 'segmentation': {
  213. 'separator': '\n',
  214. 'max_tokens': 500
  215. }
  216. },
  217. 'mode': 'custom'
  218. }
  219. }
  220. :return:
  221. """
  222. files = {"file": open(file_path, "rb")}
  223. data = {}
  224. if extra_params is not None and isinstance(extra_params, dict):
  225. data.update(extra_params)
  226. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
  227. return self._send_request_with_files("POST", url, {"data": json.dumps(data)}, files)
  228. def batch_indexing_status(self, batch_id: str, **kwargs):
  229. """
  230. Get the status of the batch indexing.
  231. :param batch_id: ID of the batch uploading
  232. :return: Response from the API
  233. """
  234. url = f"/datasets/{self._get_dataset_id()}/documents/{batch_id}/indexing-status"
  235. return self._send_request("GET", url, **kwargs)
  236. def delete_dataset(self):
  237. """
  238. Delete this dataset.
  239. :return: Response from the API
  240. """
  241. url = f"/datasets/{self._get_dataset_id()}"
  242. return self._send_request("DELETE", url)
  243. def delete_document(self, document_id):
  244. """
  245. Delete a document.
  246. :param document_id: ID of the document
  247. :return: Response from the API
  248. """
  249. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}"
  250. return self._send_request("DELETE", url)
  251. def list_documents(self, page: int = None, page_size: int = None, keyword: str = None, **kwargs):
  252. """
  253. Get a list of documents in this dataset.
  254. :return: Response from the API
  255. """
  256. params = {}
  257. if page is not None:
  258. params["page"] = page
  259. if page_size is not None:
  260. params["limit"] = page_size
  261. if keyword is not None:
  262. params["keyword"] = keyword
  263. url = f"/datasets/{self._get_dataset_id()}/documents"
  264. return self._send_request("GET", url, params=params, **kwargs)
  265. def add_segments(self, document_id, segments, **kwargs):
  266. """
  267. Add segments to a document.
  268. :param document_id: ID of the document
  269. :param segments: List of segments to add, example: [{"content": "1", "answer": "1", "keyword": ["a"]}]
  270. :return: Response from the API
  271. """
  272. data = {"segments": segments}
  273. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
  274. return self._send_request("POST", url, json=data, **kwargs)
  275. def query_segments(self, document_id, keyword: str = None, status: str = None, **kwargs):
  276. """
  277. Query segments in this document.
  278. :param document_id: ID of the document
  279. :param keyword: query keyword, optional
  280. :param status: status of the segment, optional, e.g. completed
  281. """
  282. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
  283. params = {}
  284. if keyword is not None:
  285. params["keyword"] = keyword
  286. if status is not None:
  287. params["status"] = status
  288. if "params" in kwargs:
  289. params.update(kwargs["params"])
  290. return self._send_request("GET", url, params=params, **kwargs)
  291. def delete_document_segment(self, document_id, segment_id):
  292. """
  293. Delete a segment from a document.
  294. :param document_id: ID of the document
  295. :param segment_id: ID of the segment
  296. :return: Response from the API
  297. """
  298. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
  299. return self._send_request("DELETE", url)
  300. def update_document_segment(self, document_id, segment_id, segment_data, **kwargs):
  301. """
  302. Update a segment in a document.
  303. :param document_id: ID of the document
  304. :param segment_id: ID of the segment
  305. :param segment_data: Data of the segment, example: {"content": "1", "answer": "1", "keyword": ["a"], "enabled": True}
  306. :return: Response from the API
  307. """
  308. data = {"segment": segment_data}
  309. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
  310. return self._send_request("POST", url, json=data, **kwargs)