Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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