1+ """
2+ Pydify - Dify 网站API交互
3+
4+ 此模块提供与Dify网站API交互的工具。
5+ """
6+ import requests
7+ import webbrowser
8+
9+ # Dify应用模式的枚举类,用于创建应用时指定应用类型
10+ class DifyAppMode :
11+ """
12+ Dify应用模式的枚举类,定义了Dify支持的所有应用类型
13+ """
14+ CHAT = "chat" # 聊天助手chatbot
15+ AGENT_CHAT = "agent-chat" # Agent - 代理模式
16+ COMPLETION = "completion" # 文本生成应用
17+ ADVANCED_CHAT = "advanced-chat" # Chatflow - 高级聊天流
18+ WORKFLOW = "workflow" # 工作流应用
19+
20+ class DifySite :
21+ """
22+ Dify网站API交互类,提供与Dify平台管理API的交互功能
23+
24+ 此类封装了Dify平台的所有管理API,包括登录认证、应用管理、API密钥管理等功能。
25+ 初始化时会自动登录并获取访问令牌,后续所有API调用都会使用此令牌进行认证。
26+ """
27+
28+ def __init__ (self , base_url , email , password ):
29+ """
30+ 初始化DifySite实例并自动登录获取访问令牌
31+
32+ Args:
33+ base_url (str): Dify平台的基础URL,例如 "http://sandanapp.com:11080"
34+ email (str): 登录邮箱账号
35+ password (str): 登录密码
36+
37+ Raises:
38+ Exception: 登录失败时抛出异常,包含错误信息
39+ """
40+ self .base_url = base_url
41+ self .email = email
42+ self .password = password
43+ self .access_token = None
44+ self .refresh_token = None
45+
46+ # 自动登录并获取访问令牌
47+ self ._login ()
48+
49+ def _login (self ):
50+ """
51+ 登录Dify平台并获取访问令牌
52+
53+ Raises:
54+ Exception: 登录失败时抛出异常,包含错误信息
55+ """
56+ url = f"{ self .base_url } /console/api/login"
57+ data = {
58+ "email" : self .email ,
59+ "language" : "zh-CN" ,
60+ "password" : self .password ,
61+ "remember_me" : True
62+ }
63+ response = requests .post (url , json = data )
64+ if response .status_code != 200 :
65+ raise Exception (f"登录失败: { response .text } " )
66+
67+ response_data = response .json ()['data' ]
68+ self .access_token = response_data ['access_token' ]
69+ self .refresh_token = response_data ['refresh_token' ]
70+
71+ def fetch_apps (self , page = 1 , limit = 100 , name = "" , is_created_by_me = False , keywords = "" , tagIDs = []):
72+ """
73+ 获取Dify平台中的应用列表,支持分页和过滤条件
74+
75+ Args:
76+ page (int, optional): 页码,从1开始. 默认为1.
77+ limit (int, optional): 每页返回的应用数量上限. 默认为100.
78+ name (str, optional): 按应用名称过滤. 默认为空字符串,不过滤.
79+ is_created_by_me (bool, optional): 是否只查询当前用户创建的应用. 默认为False(查询所有).
80+ keywords (str, optional): 关键词搜索. 默认为空字符串,不过滤.
81+ tagIDs (list, optional): 标签ID列表,按标签过滤. 默认为空列表,不过滤.
82+
83+ Raises:
84+ Exception: 获取应用列表失败时抛出异常,包含错误信息
85+
86+ Returns:
87+ dict: 应用列表的响应数据,包含以下字段:
88+ - page (int): 当前页码
89+ - limit (int): 每页数量
90+ - total (int): 应用总数
91+ - has_more (bool): 是否有更多页
92+ - data (list): 应用列表,每个应用包含以下字段:
93+ - id (str): 应用ID
94+ - name (str): 应用名称
95+ - description (str): 应用描述
96+ - mode (str): 应用模式,如chat、completion、workflow、agent-chat等
97+ - icon_type (str): 图标类型
98+ - icon (str): 图标
99+ - icon_background (str): 图标背景色
100+ - icon_url (str): 图标URL
101+ - model_config (dict): 模型配置
102+ - workflow (dict): 工作流配置
103+ - created_by (str): 创建者ID
104+ - created_at (int): 创建时间戳
105+ - updated_by (str): 更新者ID
106+ - updated_at (int): 更新时间戳
107+ - tags (list): 标签列表
108+ """
109+ # 处理关键词中的空格,转换为URL编码
110+ keywords = keywords .replace (" " , "+" )
111+ # 处理标签ID列表,转换为分号分隔的字符串
112+ tagIDs = "%3B" .join (tagIDs )
113+
114+ # 构建URL参数
115+ params = []
116+ if page : params .append (f"page={ page } " )
117+ if limit : params .append (f"limit={ limit } " )
118+ if name : params .append (f"name={ name } " )
119+ if is_created_by_me : params .append (f"is_created_by_me={ is_created_by_me } " )
120+ if keywords : params .append (f"keywords={ keywords } " )
121+ if tagIDs : params .append (f"tagIDs={ tagIDs } " )
122+
123+ # 构建完整的API URL
124+ url = f"{ self .base_url } /console/api/apps?" + "&" .join (params )
125+
126+ # 发送请求
127+ response = requests .get (url , headers = {"Authorization" : f"Bearer { self .access_token } " })
128+ if response .status_code != 200 :
129+ raise Exception (f"获取应用失败: { response .text } " )
130+ return response .json ()
131+
132+ def fetch_all_apps (self ):
133+ """
134+ 获取Dify平台中的所有应用列表
135+
136+ Returns:
137+ list: 所有应用的列表,每个应用包含详细信息
138+ """
139+ all_apps = []
140+ for page in range (1 , 100 ):
141+ resp = self .fetch_apps (page = page , limit = 100 )
142+ all_apps .extend (resp ['data' ])
143+ if not resp ['has_more' ]:
144+ break
145+ return all_apps
146+
147+ def fetch_app_dsl (self , app_id ):
148+ """
149+ 获取指定应用的DSL配置
150+
151+ Args:
152+ app_id (str): 要获取DSL的应用ID
153+
154+ Raises:
155+ Exception: 获取DSL失败时抛出异常,包含错误信息
156+
157+ Returns:
158+ str: YAML格式的DSL内容
159+ """
160+ export_url = f"{ self .base_url } /console/api/apps/{ app_id } /export?include_secret=false"
161+ response = requests .get (export_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
162+ if response .status_code != 200 :
163+ raise Exception (f"获取DSL失败: { response .text } " )
164+ return response .json ()['data' ]
165+
166+ def import_app_dsl (self , dsl , app_id = None ):
167+ """
168+ 将DSL配置导入为新应用
169+
170+ Args:
171+ dsl (str): YAML格式的DSL配置内容
172+ app_id (str, optional): 要导入DSL的应用ID. 默认为None(创建新应用).
173+
174+ Raises:
175+ Exception: 导入DSL失败时抛出异常,包含错误信息
176+
177+ Returns:
178+ dict: 导入成功后的响应数据,包含新创建应用的信息:
179+ 新创建的应用信息,包含id、name等字段
180+ """
181+ import_url = f"{ self .base_url } /console/api/apps/imports"
182+ payload = {
183+ 'mode' : 'yaml-content' ,
184+ 'yaml_content' : dsl
185+ }
186+
187+ if app_id :
188+ payload ['app_id' ] = app_id
189+ response = requests .post (import_url , headers = {"Authorization" : f"Bearer { self .access_token } " }, json = payload )
190+ if response .status_code != 200 :
191+ raise Exception (f"导入DSL失败: { response .text } " )
192+ return response .json ()
193+
194+ def create_app (self , name , description , mode ):
195+ """
196+ 创建新的Dify应用
197+
198+ Args:
199+ name (str): 应用名称
200+ description (str): 应用描述
201+ mode (str): 应用模式,从DifyAppMode类中选择,如DifyAppMode.CHAT
202+
203+ Raises:
204+ Exception: 创建应用失败时抛出异常,包含错误信息
205+
206+ Returns:
207+ dict: 创建应用成功后的响应,包含以下字段:
208+ - id (str): 应用ID,如"8aa70316-9c2e-4d6e-8588-617ed91b6b5c"
209+ - name (str): 应用名称
210+ - description (str): 应用描述
211+ - mode (str): 应用模式
212+ - icon (str): 应用图标
213+ - icon_background (str): 图标背景色
214+ - status (str): 应用状态
215+ - api_status (str): API状态
216+ - api_rpm (int): API每分钟请求数限制
217+ - api_rph (int): API每小时请求数限制
218+ - is_demo (bool): 是否为演示应用
219+ - created_at (int): 创建时间戳
220+ """
221+ create_url = f"{ self .base_url } /console/api/apps"
222+ payload = {
223+ 'name' : name ,
224+ 'description' : description ,
225+ 'mode' : mode ,
226+ 'icon' : '🤖' ,
227+ 'icon_background' : '#FFEAD5' ,
228+ 'icon_type' : 'emoji' ,
229+ }
230+ response = requests .post (create_url , headers = {"Authorization" : f"Bearer { self .access_token } " }, json = payload )
231+ if response .status_code != 201 :
232+ raise Exception (f"创建应用失败: { response .text } " )
233+ return response .json ()
234+
235+ def fetch_app (self , app_id ):
236+ """
237+ 获取指定应用的详细信息
238+
239+ Args:
240+ app_id (str): 要获取的应用ID
241+
242+ Raises:
243+ Exception: 获取应用信息失败时抛出异常,包含错误信息
244+
245+ Returns:
246+ dict: 应用的详细信息,包含以下字段:
247+ - id (str): 应用ID
248+ - name (str): 应用名称
249+ - description (str): 应用描述
250+ - mode (str): 应用模式(chat, completion, workflow等)
251+ - icon_type (str): 图标类型
252+ - icon (str): 图标内容
253+ - icon_background (str): 图标背景色
254+ - icon_url (str): 图标URL
255+ - enable_site (bool): 是否启用网站
256+ - enable_api (bool): 是否启用API
257+ - model_config (dict): 模型配置
258+ - workflow (dict): 工作流配置(仅workflow模式)
259+ - site (dict): 网站配置
260+ - api_base_url (str): API基础URL
261+ - use_icon_as_answer_icon (bool): 是否使用应用图标作为回答图标
262+ - created_by (str): 创建者ID
263+ - created_at (int): 创建时间戳
264+ - updated_by (str): 更新者ID
265+ - updated_at (int): 更新时间戳
266+ - deleted_tools (list): 已删除的工具列表
267+ """
268+ get_url = f"{ self .base_url } /console/api/apps/{ app_id } "
269+ response = requests .get (get_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
270+
271+ if response .status_code != 200 :
272+ raise Exception (f"获取应用信息失败: { response .text } " )
273+
274+ return response .json ()
275+
276+ def create_app_api_key (self , app_id ):
277+ """
278+ 为指定应用创建API密钥
279+
280+ Args:
281+ app_id (str): 要创建API密钥的应用ID
282+
283+ Raises:
284+ Exception: 创建API密钥失败时抛出异常,包含错误信息
285+
286+ Returns:
287+ dict: 创建的API密钥信息,包含以下字段:
288+ - id (str): API密钥ID
289+ - type (str): 密钥类型,通常为"app"
290+ - token (str): API密钥令牌,例如"app-QGNv5nH4Zk9gKPCDwRklvlkp"
291+ - last_used_at (str|null): 最后使用时间,首次创建为null
292+ - created_at (int): 创建时间戳
293+ """
294+ create_url = f"{ self .base_url } /console/api/apps/{ app_id } /api-keys"
295+ response = requests .post (create_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
296+ if response .status_code != 201 :
297+ raise Exception (f"创建API密钥失败: { response .text } " )
298+ return response .json ()
299+
300+ def fetch_app_api_keys (self , app_id ):
301+ """
302+ 获取指定应用的所有API密钥列表
303+
304+ Args:
305+ app_id (str): 要获取API密钥的应用ID
306+
307+ Raises:
308+ Exception: 获取API密钥列表失败时抛出异常,包含错误信息
309+
310+ Returns:
311+ list: API密钥列表,每个密钥包含以下字段:
312+ - id (str): API密钥ID
313+ - type (str): 密钥类型,通常为"app"
314+ - token (str): API密钥令牌
315+ - last_used_at (str|null): 最后使用时间,如果未使用过则为null
316+ - created_at (int): 创建时间戳
317+ """
318+ get_url = f"{ self .base_url } /console/api/apps/{ app_id } /api-keys"
319+ response = requests .get (get_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
320+ if response .status_code != 200 :
321+ raise Exception (f"获取API密钥列表失败: { response .text } " )
322+ return response .json ()['data' ]
323+
324+ def delete_app_api_key (self , app_id , api_key_id ):
325+ """
326+ 删除指定应用的API密钥
327+
328+ Args:
329+ app_id (str): 应用ID
330+ api_key_id (str): 要删除的API密钥ID
331+
332+ Raises:
333+ Exception: 删除API密钥失败时抛出异常,包含错误信息
334+
335+ Returns:
336+ dict: 删除操作的响应数据,如果删除成功,通常返回空对象{}
337+ """
338+ delete_url = f"{ self .base_url } /console/api/apps/{ app_id } /api-keys/{ api_key_id } "
339+ response = requests .delete (delete_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
340+ if response .status_code != 204 :
341+ raise Exception (f"删除API密钥失败: { response .text } " )
342+ return response .json ()
343+
344+ def jump_to_app (self , app_id , app_mode ):
345+ """
346+ 在浏览器中打开指定应用的控制台页面
347+
348+ Args:
349+ app_id (str): 要打开的应用ID
350+ app_mode (str): 应用模式,应与应用创建时的模式一致
351+ """
352+ url = f"{ self .base_url } /console/apps/{ app_id } /{ app_mode } "
353+ # 使用默认浏览器打开
354+ webbrowser .open (url )
355+
356+ def delete_app (self , app_id ):
357+ """
358+ 删除指定应用
359+
360+ Args:
361+ app_id (str): 要删除的应用ID
362+
363+ Raises:
364+ Exception: 删除应用失败时抛出异常,包含错误信息
365+
366+ Returns:
367+ dict: 删除操作的响应数据,如果删除成功,通常返回空对象{}
368+ """
369+ delete_url = f"{ self .base_url } /console/api/apps/{ app_id } "
370+ response = requests .delete (delete_url , headers = {"Authorization" : f"Bearer { self .access_token } " })
371+ if response .status_code != 204 :
372+ raise Exception (f"删除应用失败: { response .text } " )
373+ return response .json ()
374+
0 commit comments