client.py 15 KB

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