client.py 15 KB

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