Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

client.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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", json=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: dict,
  59. query: str,
  60. user: str,
  61. response_mode: str = "blocking",
  62. conversation_id: str | None = None,
  63. files: dict | None = 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: str, 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: str, user: str):
  86. data = {"user": user}
  87. return self._send_request("POST", f"/chat-messages/{task_id}/stop", data)
  88. def get_conversations(
  89. self,
  90. user: str,
  91. last_id: str | None = None,
  92. limit: int | None = None,
  93. pinned: bool | None = None
  94. ):
  95. params = {"user": user, "last_id": last_id,
  96. "limit": limit, "pinned": pinned}
  97. return self._send_request("GET", "/conversations", params=params)
  98. def get_conversation_messages(
  99. self,
  100. user: str,
  101. conversation_id: str | None = None,
  102. first_id: str | None = None,
  103. limit: int | None = None
  104. ):
  105. params = {"user": user}
  106. if conversation_id:
  107. params["conversation_id"] = conversation_id
  108. if first_id:
  109. params["first_id"] = first_id
  110. if limit:
  111. params["limit"] = limit
  112. return self._send_request("GET", "/messages", params=params)
  113. def rename_conversation(
  114. self, conversation_id: str, name: str, auto_generate: bool, user: str
  115. ):
  116. data = {"name": name, "auto_generate": auto_generate, "user": user}
  117. return self._send_request(
  118. "POST", f"/conversations/{conversation_id}/name", data
  119. )
  120. def delete_conversation(self, conversation_id: str, user: str):
  121. data = {"user": user}
  122. return self._send_request("DELETE", f"/conversations/{conversation_id}", data)
  123. def audio_to_text(self, audio_file: dict, user: str):
  124. data = {"user": user}
  125. files = {"audio_file": audio_file}
  126. return self._send_request_with_files("POST", "/audio-to-text", data, files)
  127. class WorkflowClient(DifyClient):
  128. def run(
  129. self, inputs: dict, response_mode: str = "streaming", user: str = "abc-123"
  130. ):
  131. data = {"inputs": inputs, "response_mode": response_mode, "user": user}
  132. return self._send_request("POST", "/workflows/run", data)
  133. def stop(self, task_id, user):
  134. data = {"user": user}
  135. return self._send_request("POST", f"/workflows/tasks/{task_id}/stop", data)
  136. def get_result(self, workflow_run_id):
  137. return self._send_request("GET", f"/workflows/run/{workflow_run_id}")
  138. class KnowledgeBaseClient(DifyClient):
  139. def __init__(
  140. self,
  141. api_key,
  142. base_url: str = "https://api.dify.ai/v1",
  143. dataset_id: str | None = None,
  144. ):
  145. """
  146. Construct a KnowledgeBaseClient object.
  147. Args:
  148. api_key (str): API key of Dify.
  149. base_url (str, optional): Base URL of Dify API. Defaults to 'https://api.dify.ai/v1'.
  150. dataset_id (str, optional): ID of the dataset. Defaults to None. You don't need this if you just want to
  151. create a new dataset. or list datasets. otherwise you need to set this.
  152. """
  153. super().__init__(api_key=api_key, base_url=base_url)
  154. self.dataset_id = dataset_id
  155. def _get_dataset_id(self):
  156. if self.dataset_id is None:
  157. raise ValueError("dataset_id is not set")
  158. return self.dataset_id
  159. def create_dataset(self, name: str, **kwargs):
  160. return self._send_request("POST", "/datasets", {"name": name}, **kwargs)
  161. def list_datasets(self, page: int = 1, page_size: int = 20, **kwargs):
  162. return self._send_request(
  163. "GET", f"/datasets?page={page}&limit={page_size}", **kwargs
  164. )
  165. def create_document_by_text(
  166. self, name, text, extra_params: dict | None = None, **kwargs
  167. ):
  168. """
  169. Create a document by text.
  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. "indexing_technique": "high_quality",
  194. "process_rule": {"mode": "automatic"},
  195. "name": name,
  196. "text": text,
  197. }
  198. if extra_params is not None and isinstance(extra_params, dict):
  199. data.update(extra_params)
  200. url = f"/datasets/{self._get_dataset_id()}/document/create_by_text"
  201. return self._send_request("POST", url, json=data, **kwargs)
  202. def update_document_by_text(
  203. self, document_id, name, text, extra_params: dict | None = None, **kwargs
  204. ):
  205. """
  206. Update a document by text.
  207. :param document_id: ID of the document
  208. :param name: Name of the document
  209. :param text: Text content of the document
  210. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  211. e.g.
  212. {
  213. 'indexing_technique': 'high_quality',
  214. 'process_rule': {
  215. 'rules': {
  216. 'pre_processing_rules': [
  217. {'id': 'remove_extra_spaces', 'enabled': True},
  218. {'id': 'remove_urls_emails', 'enabled': True}
  219. ],
  220. 'segmentation': {
  221. 'separator': '\n',
  222. 'max_tokens': 500
  223. }
  224. },
  225. 'mode': 'custom'
  226. }
  227. }
  228. :return: Response from the API
  229. """
  230. data = {"name": name, "text": text}
  231. if extra_params is not None and isinstance(extra_params, dict):
  232. data.update(extra_params)
  233. url = (
  234. f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_text"
  235. )
  236. return self._send_request("POST", url, json=data, **kwargs)
  237. def create_document_by_file(
  238. self, file_path, original_document_id=None, extra_params: dict | None = None
  239. ):
  240. """
  241. Create a document by file.
  242. :param file_path: Path to the file
  243. :param original_document_id: pass this ID if you want to replace the original document (optional)
  244. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  245. e.g.
  246. {
  247. 'indexing_technique': 'high_quality',
  248. 'process_rule': {
  249. 'rules': {
  250. 'pre_processing_rules': [
  251. {'id': 'remove_extra_spaces', 'enabled': True},
  252. {'id': 'remove_urls_emails', 'enabled': True}
  253. ],
  254. 'segmentation': {
  255. 'separator': '\n',
  256. 'max_tokens': 500
  257. }
  258. },
  259. 'mode': 'custom'
  260. }
  261. }
  262. :return: Response from the API
  263. """
  264. files = {"file": open(file_path, "rb")}
  265. data = {
  266. "process_rule": {"mode": "automatic"},
  267. "indexing_technique": "high_quality",
  268. }
  269. if extra_params is not None and isinstance(extra_params, dict):
  270. data.update(extra_params)
  271. if original_document_id is not None:
  272. data["original_document_id"] = original_document_id
  273. url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
  274. return self._send_request_with_files(
  275. "POST", url, {"data": json.dumps(data)}, files
  276. )
  277. def update_document_by_file(
  278. self, document_id, file_path, extra_params: dict | None = None
  279. ):
  280. """
  281. Update a document by file.
  282. :param document_id: ID of the document
  283. :param file_path: Path to the file
  284. :param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
  285. e.g.
  286. {
  287. 'indexing_technique': 'high_quality',
  288. 'process_rule': {
  289. 'rules': {
  290. 'pre_processing_rules': [
  291. {'id': 'remove_extra_spaces', 'enabled': True},
  292. {'id': 'remove_urls_emails', 'enabled': True}
  293. ],
  294. 'segmentation': {
  295. 'separator': '\n',
  296. 'max_tokens': 500
  297. }
  298. },
  299. 'mode': 'custom'
  300. }
  301. }
  302. :return:
  303. """
  304. files = {"file": open(file_path, "rb")}
  305. data = {}
  306. if extra_params is not None and isinstance(extra_params, dict):
  307. data.update(extra_params)
  308. url = (
  309. f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
  310. )
  311. return self._send_request_with_files(
  312. "POST", url, {"data": json.dumps(data)}, files
  313. )
  314. def batch_indexing_status(self, batch_id: str, **kwargs):
  315. """
  316. Get the status of the batch indexing.
  317. :param batch_id: ID of the batch uploading
  318. :return: Response from the API
  319. """
  320. url = f"/datasets/{self._get_dataset_id()}/documents/{batch_id}/indexing-status"
  321. return self._send_request("GET", url, **kwargs)
  322. def delete_dataset(self):
  323. """
  324. Delete this dataset.
  325. :return: Response from the API
  326. """
  327. url = f"/datasets/{self._get_dataset_id()}"
  328. return self._send_request("DELETE", url)
  329. def delete_document(self, document_id):
  330. """
  331. Delete a document.
  332. :param document_id: ID of the document
  333. :return: Response from the API
  334. """
  335. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}"
  336. return self._send_request("DELETE", url)
  337. def list_documents(
  338. self,
  339. page: int | None = None,
  340. page_size: int | None = None,
  341. keyword: str | None = None,
  342. **kwargs,
  343. ):
  344. """
  345. Get a list of documents in this dataset.
  346. :return: Response from the API
  347. """
  348. params = {}
  349. if page is not None:
  350. params["page"] = page
  351. if page_size is not None:
  352. params["limit"] = page_size
  353. if keyword is not None:
  354. params["keyword"] = keyword
  355. url = f"/datasets/{self._get_dataset_id()}/documents"
  356. return self._send_request("GET", url, params=params, **kwargs)
  357. def add_segments(self, document_id, segments, **kwargs):
  358. """
  359. Add segments to a document.
  360. :param document_id: ID of the document
  361. :param segments: List of segments to add, example: [{"content": "1", "answer": "1", "keyword": ["a"]}]
  362. :return: Response from the API
  363. """
  364. data = {"segments": segments}
  365. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
  366. return self._send_request("POST", url, json=data, **kwargs)
  367. def query_segments(
  368. self,
  369. document_id,
  370. keyword: str | None = None,
  371. status: str | None = None,
  372. **kwargs,
  373. ):
  374. """
  375. Query segments in this document.
  376. :param document_id: ID of the document
  377. :param keyword: query keyword, optional
  378. :param status: status of the segment, optional, e.g. completed
  379. """
  380. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
  381. params = {}
  382. if keyword is not None:
  383. params["keyword"] = keyword
  384. if status is not None:
  385. params["status"] = status
  386. if "params" in kwargs:
  387. params.update(kwargs["params"])
  388. return self._send_request("GET", url, params=params, **kwargs)
  389. def delete_document_segment(self, document_id, segment_id):
  390. """
  391. Delete a segment from a document.
  392. :param document_id: ID of the document
  393. :param segment_id: ID of the segment
  394. :return: Response from the API
  395. """
  396. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
  397. return self._send_request("DELETE", url)
  398. def update_document_segment(self, document_id, segment_id, segment_data, **kwargs):
  399. """
  400. Update a segment in a document.
  401. :param document_id: ID of the document
  402. :param segment_id: ID of the segment
  403. :param segment_data: Data of the segment, example: {"content": "1", "answer": "1", "keyword": ["a"], "enabled": True}
  404. :return: Response from the API
  405. """
  406. data = {"segment": segment_data}
  407. url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
  408. return self._send_request("POST", url, json=data, **kwargs)