dify-oapi2 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. dify_oapi/api/chat/v1/model/__init__.py +37 -0
  2. dify_oapi/api/chat/v1/model/agent_thought.py +69 -0
  3. dify_oapi/api/chat/v1/model/annotation_info.py +43 -0
  4. dify_oapi/api/chat/v1/model/app_info.py +35 -0
  5. dify_oapi/api/chat/v1/model/app_parameters.py +494 -0
  6. dify_oapi/api/chat/v1/model/audio_to_text_request_body.py +2 -2
  7. dify_oapi/api/chat/v1/model/chat_file.py +53 -0
  8. dify_oapi/api/chat/v1/model/chat_request_body.py +26 -13
  9. dify_oapi/api/chat/v1/model/chat_response.py +12 -2
  10. dify_oapi/api/chat/v1/model/chat_types.py +65 -0
  11. dify_oapi/api/chat/v1/model/configure_annotation_reply_request.py +36 -0
  12. dify_oapi/api/chat/v1/model/configure_annotation_reply_request_body.py +39 -0
  13. dify_oapi/api/chat/v1/model/configure_annotation_reply_response.py +10 -0
  14. dify_oapi/api/chat/v1/model/conversation_info.py +57 -0
  15. dify_oapi/api/chat/v1/model/conversation_variable.py +55 -0
  16. dify_oapi/api/chat/v1/model/create_annotation_request.py +29 -0
  17. dify_oapi/api/chat/v1/model/create_annotation_request_body.py +32 -0
  18. dify_oapi/api/chat/v1/model/create_annotation_response.py +9 -0
  19. dify_oapi/api/chat/v1/model/delete_annotation_request.py +26 -0
  20. dify_oapi/api/chat/v1/model/delete_annotation_response.py +7 -0
  21. dify_oapi/api/chat/v1/model/feedback_info.py +75 -0
  22. dify_oapi/api/chat/v1/model/file_info.py +59 -0
  23. dify_oapi/api/chat/v1/model/get_annotation_reply_status_request.py +32 -0
  24. dify_oapi/api/chat/v1/model/get_annotation_reply_status_response.py +11 -0
  25. dify_oapi/api/chat/v1/model/get_app_info_request.py +24 -0
  26. dify_oapi/api/chat/v1/model/get_app_info_response.py +9 -0
  27. dify_oapi/api/chat/v1/model/get_app_meta_request.py +24 -0
  28. dify_oapi/api/chat/v1/model/get_app_meta_response.py +9 -0
  29. dify_oapi/api/chat/v1/model/get_app_parameters_request.py +28 -0
  30. dify_oapi/api/chat/v1/model/get_app_parameters_response.py +9 -0
  31. dify_oapi/api/chat/v1/model/get_conversation_list_request.py +25 -24
  32. dify_oapi/api/chat/v1/model/get_conversation_list_response.py +4 -15
  33. dify_oapi/api/chat/v1/model/get_conversation_variables_request.py +54 -0
  34. dify_oapi/api/chat/v1/model/get_conversation_variables_response.py +10 -0
  35. dify_oapi/api/chat/v1/model/get_feedbacks_request.py +32 -0
  36. dify_oapi/api/chat/v1/model/get_feedbacks_response.py +7 -0
  37. dify_oapi/api/chat/v1/model/get_site_settings_request.py +24 -0
  38. dify_oapi/api/chat/v1/model/get_site_settings_response.py +9 -0
  39. dify_oapi/api/chat/v1/model/get_suggested_questions_request.py +36 -0
  40. dify_oapi/api/chat/v1/model/{message_suggested_response.py → get_suggested_questions_response.py} +1 -1
  41. dify_oapi/api/chat/v1/model/list_annotations_request.py +32 -0
  42. dify_oapi/api/chat/v1/model/list_annotations_response.py +11 -0
  43. dify_oapi/api/chat/v1/model/message_file.py +46 -0
  44. dify_oapi/api/chat/v1/model/message_history_request.py +24 -24
  45. dify_oapi/api/chat/v1/model/message_history_response.py +4 -43
  46. dify_oapi/api/chat/v1/model/message_info.py +73 -0
  47. dify_oapi/api/chat/v1/model/pagination_info.py +44 -0
  48. dify_oapi/api/chat/v1/model/rename_conversation_request_body.py +1 -1
  49. dify_oapi/api/chat/v1/model/retriever_resource.py +64 -0
  50. dify_oapi/api/chat/v1/model/site_settings.py +92 -0
  51. dify_oapi/api/chat/v1/model/submit_feedback_request.py +34 -0
  52. dify_oapi/api/chat/v1/model/submit_feedback_request_body.py +33 -0
  53. dify_oapi/api/chat/v1/model/submit_feedback_response.py +5 -0
  54. dify_oapi/api/chat/v1/model/text_to_audio_request.py +32 -0
  55. dify_oapi/api/chat/v1/model/text_to_audio_request_body.py +33 -0
  56. dify_oapi/api/chat/v1/model/text_to_audio_response.py +7 -0
  57. dify_oapi/api/chat/v1/model/tool_icon.py +52 -0
  58. dify_oapi/api/chat/v1/model/update_annotation_request.py +33 -0
  59. dify_oapi/api/chat/v1/model/update_annotation_request_body.py +32 -0
  60. dify_oapi/api/chat/v1/model/update_annotation_response.py +9 -0
  61. dify_oapi/api/chat/v1/model/upload_file_request.py +54 -0
  62. dify_oapi/api/chat/v1/model/upload_file_request_body.py +29 -0
  63. dify_oapi/api/chat/v1/model/upload_file_response.py +10 -0
  64. dify_oapi/api/chat/v1/model/usage_info.py +84 -0
  65. dify_oapi/api/chat/v1/resource/__init__.py +7 -3
  66. dify_oapi/api/chat/v1/resource/annotation.py +77 -0
  67. dify_oapi/api/chat/v1/resource/app.py +47 -0
  68. dify_oapi/api/chat/v1/resource/audio.py +13 -0
  69. dify_oapi/api/chat/v1/resource/chat.py +14 -0
  70. dify_oapi/api/chat/v1/resource/conversation.py +55 -9
  71. dify_oapi/api/chat/v1/resource/feedback.py +31 -0
  72. dify_oapi/api/chat/v1/resource/file.py +25 -0
  73. dify_oapi/api/chat/v1/resource/message.py +19 -15
  74. dify_oapi/api/chat/v1/version.py +8 -2
  75. dify_oapi/api/chatflow/__init__.py +0 -0
  76. dify_oapi/api/chatflow/service.py +25 -0
  77. dify_oapi/api/chatflow/v1/__init__.py +0 -0
  78. dify_oapi/api/chatflow/v1/model/__init__.py +0 -0
  79. dify_oapi/api/chatflow/v1/model/annotation_info.py +43 -0
  80. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request.py +37 -0
  81. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request_body.py +33 -0
  82. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_response.py +10 -0
  83. dify_oapi/api/chatflow/v1/model/annotation_reply_status_request.py +36 -0
  84. dify_oapi/api/chatflow/v1/model/annotation_reply_status_response.py +11 -0
  85. dify_oapi/api/chatflow/v1/model/app_info.py +33 -0
  86. dify_oapi/api/chatflow/v1/model/app_parameters.py +276 -0
  87. dify_oapi/api/chatflow/v1/model/audio_to_text_request.py +37 -0
  88. dify_oapi/api/chatflow/v1/model/audio_to_text_response.py +21 -0
  89. dify_oapi/api/chatflow/v1/model/chat_file.py +40 -0
  90. dify_oapi/api/chatflow/v1/model/chat_message.py +88 -0
  91. dify_oapi/api/chatflow/v1/model/chatflow_types.py +210 -0
  92. dify_oapi/api/chatflow/v1/model/conversation_info.py +53 -0
  93. dify_oapi/api/chatflow/v1/model/conversation_variable.py +55 -0
  94. dify_oapi/api/chatflow/v1/model/create_annotation_request.py +30 -0
  95. dify_oapi/api/chatflow/v1/model/create_annotation_request_body.py +28 -0
  96. dify_oapi/api/chatflow/v1/model/create_annotation_response.py +9 -0
  97. dify_oapi/api/chatflow/v1/model/delete_annotation_request.py +28 -0
  98. dify_oapi/api/chatflow/v1/model/delete_annotation_response.py +7 -0
  99. dify_oapi/api/chatflow/v1/model/delete_conversation_request.py +36 -0
  100. dify_oapi/api/chatflow/v1/model/delete_conversation_request_body.py +21 -0
  101. dify_oapi/api/chatflow/v1/model/delete_conversation_response.py +17 -0
  102. dify_oapi/api/chatflow/v1/model/feedback_info.py +75 -0
  103. dify_oapi/api/chatflow/v1/model/file_info.py +53 -0
  104. dify_oapi/api/chatflow/v1/model/get_annotations_request.py +30 -0
  105. dify_oapi/api/chatflow/v1/model/get_annotations_response.py +13 -0
  106. dify_oapi/api/chatflow/v1/model/get_app_feedbacks_request.py +30 -0
  107. dify_oapi/api/chatflow/v1/model/get_app_feedbacks_response.py +23 -0
  108. dify_oapi/api/chatflow/v1/model/get_conversation_messages_request.py +38 -0
  109. dify_oapi/api/chatflow/v1/model/get_conversation_messages_response.py +33 -0
  110. dify_oapi/api/chatflow/v1/model/get_conversation_variables_request.py +44 -0
  111. dify_oapi/api/chatflow/v1/model/get_conversation_variables_response.py +33 -0
  112. dify_oapi/api/chatflow/v1/model/get_conversations_request.py +40 -0
  113. dify_oapi/api/chatflow/v1/model/get_conversations_response.py +33 -0
  114. dify_oapi/api/chatflow/v1/model/get_info_request.py +22 -0
  115. dify_oapi/api/chatflow/v1/model/get_info_response.py +7 -0
  116. dify_oapi/api/chatflow/v1/model/get_meta_request.py +22 -0
  117. dify_oapi/api/chatflow/v1/model/get_meta_response.py +7 -0
  118. dify_oapi/api/chatflow/v1/model/get_parameters_request.py +22 -0
  119. dify_oapi/api/chatflow/v1/model/get_parameters_response.py +7 -0
  120. dify_oapi/api/chatflow/v1/model/get_site_request.py +22 -0
  121. dify_oapi/api/chatflow/v1/model/get_site_response.py +7 -0
  122. dify_oapi/api/chatflow/v1/model/get_suggested_questions_request.py +32 -0
  123. dify_oapi/api/chatflow/v1/model/get_suggested_questions_response.py +6 -0
  124. dify_oapi/api/chatflow/v1/model/message_feedback_request.py +36 -0
  125. dify_oapi/api/chatflow/v1/model/message_feedback_request_body.py +33 -0
  126. dify_oapi/api/chatflow/v1/model/message_feedback_response.py +21 -0
  127. dify_oapi/api/chatflow/v1/model/rename_conversation_request.py +36 -0
  128. dify_oapi/api/chatflow/v1/model/rename_conversation_request_body.py +31 -0
  129. dify_oapi/api/chatflow/v1/model/rename_conversation_response.py +53 -0
  130. dify_oapi/api/chatflow/v1/model/retriever_resource.py +58 -0
  131. dify_oapi/api/chatflow/v1/model/send_chat_message_request.py +30 -0
  132. dify_oapi/api/chatflow/v1/model/send_chat_message_request_body.py +54 -0
  133. dify_oapi/api/chatflow/v1/model/send_chat_message_response.py +7 -0
  134. dify_oapi/api/chatflow/v1/model/stop_chat_message_request.py +36 -0
  135. dify_oapi/api/chatflow/v1/model/stop_chat_message_request_body.py +21 -0
  136. dify_oapi/api/chatflow/v1/model/stop_chat_message_response.py +5 -0
  137. dify_oapi/api/chatflow/v1/model/text_to_audio_request.py +30 -0
  138. dify_oapi/api/chatflow/v1/model/text_to_audio_request_body.py +36 -0
  139. dify_oapi/api/chatflow/v1/model/text_to_audio_response.py +19 -0
  140. dify_oapi/api/chatflow/v1/model/tool_icon.py +48 -0
  141. dify_oapi/api/chatflow/v1/model/update_annotation_request.py +36 -0
  142. dify_oapi/api/chatflow/v1/model/update_annotation_request_body.py +28 -0
  143. dify_oapi/api/chatflow/v1/model/update_annotation_response.py +9 -0
  144. dify_oapi/api/chatflow/v1/model/upload_file_request.py +41 -0
  145. dify_oapi/api/chatflow/v1/model/upload_file_response.py +11 -0
  146. dify_oapi/api/chatflow/v1/model/usage_info.py +78 -0
  147. dify_oapi/api/chatflow/v1/model/user_input_form.py +141 -0
  148. dify_oapi/api/chatflow/v1/model/webapp_settings.py +88 -0
  149. dify_oapi/api/chatflow/v1/resource/__init__.py +0 -0
  150. dify_oapi/api/chatflow/v1/resource/annotation.py +122 -0
  151. dify_oapi/api/chatflow/v1/resource/application.py +76 -0
  152. dify_oapi/api/chatflow/v1/resource/chatflow.py +91 -0
  153. dify_oapi/api/chatflow/v1/resource/conversation.py +104 -0
  154. dify_oapi/api/chatflow/v1/resource/feedback.py +69 -0
  155. dify_oapi/api/chatflow/v1/resource/file.py +39 -0
  156. dify_oapi/api/chatflow/v1/resource/tts.py +29 -0
  157. dify_oapi/api/chatflow/v1/version.py +29 -0
  158. dify_oapi/api/knowledge/v1/model/data_source_detail.py +23 -0
  159. dify_oapi/api/knowledge/v1/model/dataset_info.py +10 -9
  160. dify_oapi/api/knowledge/v1/model/dataset_metadata.py +14 -0
  161. dify_oapi/api/knowledge/v1/model/document_info.py +18 -6
  162. dify_oapi/api/knowledge/v1/model/external_knowledge_info.py +11 -1
  163. dify_oapi/api/knowledge/v1/model/external_retrieval_model.py +13 -0
  164. dify_oapi/api/knowledge/v1/model/knowledge_types.py +2 -2
  165. dify_oapi/api/knowledge/v1/model/retrieval_model.py +10 -4
  166. dify_oapi/api/knowledge/v1/model/weights.py +27 -0
  167. dify_oapi/api/workflow/v1/model/chunk_workflow_event.py +74 -0
  168. dify_oapi/api/workflow/v1/model/input_file_object_workflow.py +76 -0
  169. dify_oapi/api/workflow/v1/model/node_finished_data.py +118 -0
  170. dify_oapi/api/workflow/v1/model/node_started_data.py +81 -0
  171. dify_oapi/api/workflow/v1/model/ping_data.py +28 -0
  172. dify_oapi/api/workflow/v1/model/run_workflow_request_body.py +1 -1
  173. dify_oapi/api/workflow/v1/model/text_chunk_data.py +39 -0
  174. dify_oapi/api/workflow/v1/model/tts_message_data.py +45 -0
  175. dify_oapi/api/workflow/v1/model/tts_message_end_data.py +45 -0
  176. dify_oapi/api/workflow/v1/model/workflow_completion_response.py +50 -0
  177. dify_oapi/api/workflow/v1/model/workflow_finished_data.py +93 -0
  178. dify_oapi/api/workflow/v1/model/workflow_started_data.py +51 -0
  179. dify_oapi/api/workflow/v1/model/workflow_types.py +27 -12
  180. dify_oapi/client.py +45 -0
  181. dify_oapi/core/http/transport/__init__.py +2 -1
  182. dify_oapi/core/http/transport/async_transport.py +73 -50
  183. dify_oapi/core/http/transport/connection_pool.py +131 -0
  184. dify_oapi/core/http/transport/sync_transport.py +73 -50
  185. dify_oapi/core/model/config.py +10 -0
  186. {dify_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/METADATA +44 -12
  187. {dify_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/RECORD +189 -34
  188. dify_oapi/api/chat/v1/model/chat_request_file.py +0 -46
  189. dify_oapi/api/chat/v1/model/message_suggested_request.py +0 -36
  190. {dify_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/LICENSE +0 -0
  191. {dify_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/WHEEL +0 -0
@@ -16,6 +16,7 @@ from dify_oapi.core.model.request_option import RequestOption
16
16
  from dify_oapi.core.type import T
17
17
 
18
18
  from ._misc import _build_header, _build_url, _get_sleep_time, _merge_dicts, _unmarshaller
19
+ from .connection_pool import connection_pool
19
20
 
20
21
 
21
22
  def _format_log_details(
@@ -33,6 +34,12 @@ def _format_log_details(
33
34
  return ", ".join(details)
34
35
 
35
36
 
37
+ def _update_response_mode(body_data: dict | None, stream: bool) -> None:
38
+ """Update response_mode field in request body based on stream parameter"""
39
+ if body_data and "response_mode" in body_data:
40
+ body_data["response_mode"] = "streaming" if stream else "blocking"
41
+
42
+
36
43
  async def _handle_async_stream_error(response: httpx.Response) -> bytes:
37
44
  """Handle async streaming response errors"""
38
45
  try:
@@ -58,6 +65,16 @@ async def _async_stream_generator(
58
65
  method_name = http_method.name
59
66
  body_data = _merge_dicts(json_, files, data)
60
67
 
68
+ # Use connection pool for async streaming requests
69
+ client = connection_pool.get_async_client(
70
+ conf.domain or "",
71
+ conf.timeout,
72
+ getattr(conf, "max_keepalive_connections", 20),
73
+ getattr(conf, "max_connections", 100),
74
+ getattr(conf, "keepalive_expiry", 30.0),
75
+ getattr(conf, "verify_ssl", True),
76
+ )
77
+
61
78
  for retry in range(conf.max_retry_count + 1):
62
79
  if retry > 0:
63
80
  sleep_time = _get_sleep_time(retry)
@@ -65,19 +82,16 @@ async def _async_stream_generator(
65
82
  await asyncio.sleep(sleep_time)
66
83
 
67
84
  try:
68
- async with (
69
- httpx.AsyncClient() as client,
70
- client.stream(
71
- method_name,
72
- url,
73
- headers=headers,
74
- params=tuple(req.queries),
75
- json=json_,
76
- data=data,
77
- files=files,
78
- timeout=conf.timeout,
79
- ) as response,
80
- ):
85
+ async with client.stream(
86
+ method_name,
87
+ url,
88
+ headers=headers,
89
+ params=tuple(req.queries),
90
+ json=json_,
91
+ data=data,
92
+ files=files,
93
+ timeout=conf.timeout,
94
+ ) as response:
81
95
  logger.debug(
82
96
  f"{_format_log_details(method_name, url, headers, req.queries, body_data)}, stream response"
83
97
  )
@@ -162,8 +176,10 @@ class ATransport:
162
176
  files = req.files
163
177
  if req.body is not None:
164
178
  data = json.loads(JSON.marshal(req.body))
179
+ _update_response_mode(data, stream)
165
180
  elif req.body is not None:
166
181
  json_ = json.loads(JSON.marshal(req.body))
182
+ _update_response_mode(json_, stream)
167
183
 
168
184
  if stream:
169
185
  return _async_stream_generator(
@@ -173,45 +189,52 @@ class ATransport:
173
189
  method_name = req.http_method.name
174
190
  body_data = _merge_dicts(json_, files, data)
175
191
 
176
- async with httpx.AsyncClient() as client:
177
- for retry in range(conf.max_retry_count + 1):
178
- if retry > 0:
179
- sleep_time = _get_sleep_time(retry)
180
- logger.info(f"in-request: sleep {sleep_time}s")
181
- await asyncio.sleep(sleep_time)
192
+ # Use connection pool for async regular requests
193
+ client = connection_pool.get_async_client(
194
+ conf.domain or "",
195
+ conf.timeout,
196
+ getattr(conf, "max_keepalive_connections", 20),
197
+ getattr(conf, "max_connections", 100),
198
+ getattr(conf, "keepalive_expiry", 30.0),
199
+ getattr(conf, "verify_ssl", True),
200
+ )
201
+
202
+ for retry in range(conf.max_retry_count + 1):
203
+ if retry > 0:
204
+ sleep_time = _get_sleep_time(retry)
205
+ logger.info(f"in-request: sleep {sleep_time}s")
206
+ await asyncio.sleep(sleep_time)
207
+
208
+ try:
209
+ response = await client.request(
210
+ method_name,
211
+ url,
212
+ headers=headers,
213
+ params=tuple(req.queries),
214
+ json=json_,
215
+ data=data,
216
+ files=files,
217
+ timeout=conf.timeout,
218
+ )
219
+ break
220
+ except httpx.RequestError as e:
221
+ err_msg = f"{e.__class__.__name__}: {e!r}"
222
+ log_details = _format_log_details(method_name, url, headers, req.queries, body_data)
182
223
 
183
- try:
184
- response = await client.request(
185
- method_name,
186
- url,
187
- headers=headers,
188
- params=tuple(req.queries),
189
- json=json_,
190
- data=data,
191
- files=files,
192
- timeout=conf.timeout,
193
- )
194
- break
195
- except httpx.RequestError as e:
196
- err_msg = f"{e.__class__.__name__}: {e!r}"
197
- log_details = _format_log_details(method_name, url, headers, req.queries, body_data)
198
-
199
- if retry < conf.max_retry_count:
200
- logger.info(
201
- f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
202
- )
203
- continue
224
+ if retry < conf.max_retry_count:
204
225
  logger.info(
205
- f"in-request: request failed, retried ({retry}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
226
+ f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
206
227
  )
207
- raise
228
+ continue
229
+ logger.info(
230
+ f"in-request: request failed, retried ({retry}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
231
+ )
232
+ raise
208
233
 
209
- logger.debug(
210
- f"{_format_log_details(method_name, url, headers, req.queries, body_data)} {response.status_code}"
211
- )
234
+ logger.debug(f"{_format_log_details(method_name, url, headers, req.queries, body_data)} {response.status_code}")
212
235
 
213
- raw_resp = RawResponse()
214
- raw_resp.status_code = response.status_code
215
- raw_resp.headers = dict(response.headers)
216
- raw_resp.content = response.content
217
- return _unmarshaller(raw_resp, unmarshal_as)
236
+ raw_resp = RawResponse()
237
+ raw_resp.status_code = response.status_code
238
+ raw_resp.headers = dict(response.headers)
239
+ raw_resp.content = response.content
240
+ return _unmarshaller(raw_resp, unmarshal_as)
@@ -0,0 +1,131 @@
1
+ """HTTP connection pool management for efficient TCP connection reuse."""
2
+
3
+ import threading
4
+ from typing import Optional
5
+
6
+ import httpx
7
+
8
+
9
+ class ConnectionPoolManager:
10
+ """Manages HTTP connection pools to reduce TCP connection overhead."""
11
+
12
+ _instance: Optional["ConnectionPoolManager"] = None
13
+ _lock = threading.Lock()
14
+
15
+ def __new__(cls) -> "ConnectionPoolManager":
16
+ if cls._instance is None:
17
+ with cls._lock:
18
+ if cls._instance is None:
19
+ cls._instance = super().__new__(cls)
20
+ return cls._instance
21
+
22
+ def __init__(self):
23
+ if not hasattr(self, "_initialized"):
24
+ self._sync_clients: dict[str, httpx.Client] = {}
25
+ self._async_clients: dict[str, httpx.AsyncClient] = {}
26
+ self._client_lock = threading.Lock()
27
+ self._initialized = True
28
+
29
+ def get_sync_client(
30
+ self,
31
+ domain: str,
32
+ timeout: Optional[float] = None,
33
+ max_keepalive: int = 20,
34
+ max_connections: int = 100,
35
+ keepalive_expiry: float = 30.0,
36
+ verify_ssl: bool = True,
37
+ ) -> httpx.Client:
38
+ """Get or create a sync HTTP client for the given domain."""
39
+ client_key = f"{domain}:{timeout}:{max_keepalive}:{max_connections}:{keepalive_expiry}:{verify_ssl}"
40
+
41
+ with self._client_lock:
42
+ if client_key not in self._sync_clients:
43
+ # Configure connection limits to prevent excessive connections
44
+ limits = httpx.Limits(
45
+ max_keepalive_connections=max_keepalive,
46
+ max_connections=max_connections,
47
+ keepalive_expiry=keepalive_expiry,
48
+ )
49
+
50
+ self._sync_clients[client_key] = httpx.Client(
51
+ timeout=timeout,
52
+ limits=limits,
53
+ verify=verify_ssl,
54
+ # Note: HTTP/2 disabled to avoid h2 dependency requirement
55
+ )
56
+
57
+ return self._sync_clients[client_key]
58
+
59
+ def get_async_client(
60
+ self,
61
+ domain: str,
62
+ timeout: Optional[float] = None,
63
+ max_keepalive: int = 20,
64
+ max_connections: int = 100,
65
+ keepalive_expiry: float = 30.0,
66
+ verify_ssl: bool = True,
67
+ ) -> httpx.AsyncClient:
68
+ """Get or create an async HTTP client for the given domain."""
69
+ client_key = f"{domain}:{timeout}:{max_keepalive}:{max_connections}:{keepalive_expiry}:{verify_ssl}"
70
+
71
+ with self._client_lock:
72
+ if client_key not in self._async_clients:
73
+ # Configure connection limits to prevent excessive connections
74
+ limits = httpx.Limits(
75
+ max_keepalive_connections=max_keepalive,
76
+ max_connections=max_connections,
77
+ keepalive_expiry=keepalive_expiry,
78
+ )
79
+
80
+ self._async_clients[client_key] = httpx.AsyncClient(
81
+ timeout=timeout,
82
+ limits=limits,
83
+ verify=verify_ssl,
84
+ # Note: HTTP/2 disabled to avoid h2 dependency requirement
85
+ )
86
+
87
+ return self._async_clients[client_key]
88
+
89
+ def close_all(self):
90
+ """Close all HTTP clients and clean up connections."""
91
+ with self._client_lock:
92
+ # Close sync clients
93
+ for client in self._sync_clients.values():
94
+ try:
95
+ client.close()
96
+ except Exception:
97
+ pass
98
+ self._sync_clients.clear()
99
+
100
+ # Close async clients
101
+ for _client in self._async_clients.values():
102
+ try:
103
+ # Note: This should be called from an async context in practice
104
+ # For cleanup purposes, we'll just clear the dict
105
+ pass
106
+ except Exception:
107
+ pass
108
+ self._async_clients.clear()
109
+
110
+ async def aclose_all(self):
111
+ """Async version of close_all for proper async client cleanup."""
112
+ with self._client_lock:
113
+ # Close sync clients
114
+ for sync_client in self._sync_clients.values():
115
+ try:
116
+ sync_client.close()
117
+ except Exception:
118
+ pass
119
+ self._sync_clients.clear()
120
+
121
+ # Close async clients properly
122
+ for async_client in self._async_clients.values():
123
+ try:
124
+ await async_client.aclose()
125
+ except Exception:
126
+ pass
127
+ self._async_clients.clear()
128
+
129
+
130
+ # Global connection pool manager instance
131
+ connection_pool = ConnectionPoolManager()
@@ -17,6 +17,7 @@ from dify_oapi.core.model.request_option import RequestOption
17
17
  from dify_oapi.core.type import T
18
18
 
19
19
  from ._misc import _build_header, _build_url, _get_sleep_time, _merge_dicts, _unmarshaller
20
+ from .connection_pool import connection_pool
20
21
 
21
22
 
22
23
  def _format_log_details(
@@ -45,6 +46,12 @@ def _handle_stream_error(response: httpx.Response) -> bytes:
45
46
  return f"data: [ERROR] {error_message}\n\n".encode()
46
47
 
47
48
 
49
+ def _update_response_mode(body_data: dict | None, stream: bool) -> None:
50
+ """Update response_mode field in request body based on stream parameter"""
51
+ if body_data and "response_mode" in body_data:
52
+ body_data["response_mode"] = "streaming" if stream else "blocking"
53
+
54
+
48
55
  def _stream_generator(
49
56
  conf: Config,
50
57
  req: BaseRequest,
@@ -59,6 +66,16 @@ def _stream_generator(
59
66
  method_name = http_method.name
60
67
  body_data = _merge_dicts(json_, files, data)
61
68
 
69
+ # Use connection pool for streaming requests
70
+ client = connection_pool.get_sync_client(
71
+ conf.domain or "",
72
+ conf.timeout,
73
+ getattr(conf, "max_keepalive_connections", 20),
74
+ getattr(conf, "max_connections", 100),
75
+ getattr(conf, "keepalive_expiry", 30.0),
76
+ getattr(conf, "verify_ssl", True),
77
+ )
78
+
62
79
  for retry in range(conf.max_retry_count + 1):
63
80
  if retry > 0:
64
81
  sleep_time = _get_sleep_time(retry)
@@ -66,19 +83,16 @@ def _stream_generator(
66
83
  time.sleep(sleep_time)
67
84
 
68
85
  try:
69
- with (
70
- httpx.Client() as client,
71
- client.stream(
72
- method_name,
73
- url,
74
- headers=headers,
75
- params=tuple(req.queries),
76
- json=json_,
77
- data=data,
78
- files=files,
79
- timeout=conf.timeout,
80
- ) as response,
81
- ):
86
+ with client.stream(
87
+ method_name,
88
+ url,
89
+ headers=headers,
90
+ params=tuple(req.queries),
91
+ json=json_,
92
+ data=data,
93
+ files=files,
94
+ timeout=conf.timeout,
95
+ ) as response:
82
96
  logger.debug(
83
97
  f"{_format_log_details(method_name, url, headers, req.queries, body_data)}, stream response"
84
98
  )
@@ -160,8 +174,10 @@ class Transport:
160
174
  files = req.files
161
175
  if req.body is not None:
162
176
  data = json.loads(JSON.marshal(req.body))
177
+ _update_response_mode(data, stream)
163
178
  elif req.body is not None:
164
179
  json_ = json.loads(JSON.marshal(req.body))
180
+ _update_response_mode(json_, stream)
165
181
 
166
182
  if stream:
167
183
  return _stream_generator(
@@ -176,45 +192,52 @@ class Transport:
176
192
  method_name = req.http_method.name
177
193
  body_data = _merge_dicts(json_, files, data)
178
194
 
179
- with httpx.Client() as client:
180
- for retry in range(conf.max_retry_count + 1):
181
- if retry > 0:
182
- sleep_time = _get_sleep_time(retry)
183
- logger.info(f"in-request: sleep {sleep_time}s")
184
- time.sleep(sleep_time)
195
+ # Use connection pool for regular requests
196
+ client = connection_pool.get_sync_client(
197
+ conf.domain or "",
198
+ conf.timeout,
199
+ getattr(conf, "max_keepalive_connections", 20),
200
+ getattr(conf, "max_connections", 100),
201
+ getattr(conf, "keepalive_expiry", 30.0),
202
+ getattr(conf, "verify_ssl", True),
203
+ )
204
+
205
+ for retry in range(conf.max_retry_count + 1):
206
+ if retry > 0:
207
+ sleep_time = _get_sleep_time(retry)
208
+ logger.info(f"in-request: sleep {sleep_time}s")
209
+ time.sleep(sleep_time)
210
+
211
+ try:
212
+ response = client.request(
213
+ method_name,
214
+ url,
215
+ headers=headers,
216
+ params=tuple(req.queries),
217
+ json=json_,
218
+ data=data,
219
+ files=files,
220
+ timeout=conf.timeout,
221
+ )
222
+ break
223
+ except httpx.RequestError as e:
224
+ err_msg = f"{e.__class__.__name__}: {e!r}"
225
+ log_details = _format_log_details(method_name, url, headers, req.queries, body_data)
185
226
 
186
- try:
187
- response = client.request(
188
- method_name,
189
- url,
190
- headers=headers,
191
- params=tuple(req.queries),
192
- json=json_,
193
- data=data,
194
- files=files,
195
- timeout=conf.timeout,
196
- )
197
- break
198
- except httpx.RequestError as e:
199
- err_msg = f"{e.__class__.__name__}: {e!r}"
200
- log_details = _format_log_details(method_name, url, headers, req.queries, body_data)
201
-
202
- if retry < conf.max_retry_count:
203
- logger.info(
204
- f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
205
- )
206
- continue
227
+ if retry < conf.max_retry_count:
207
228
  logger.info(
208
- f"in-request: request failed, retried ({retry}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
229
+ f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
209
230
  )
210
- raise
231
+ continue
232
+ logger.info(
233
+ f"in-request: request failed, retried ({retry}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
234
+ )
235
+ raise
211
236
 
212
- logger.debug(
213
- f"{_format_log_details(method_name, url, headers, req.queries, body_data)} {response.status_code}"
214
- )
237
+ logger.debug(f"{_format_log_details(method_name, url, headers, req.queries, body_data)} {response.status_code}")
215
238
 
216
- raw_resp = RawResponse()
217
- raw_resp.status_code = response.status_code
218
- raw_resp.headers = dict(response.headers)
219
- raw_resp.content = response.content
220
- return _unmarshaller(raw_resp, unmarshal_as)
239
+ raw_resp = RawResponse()
240
+ raw_resp.status_code = response.status_code
241
+ raw_resp.headers = dict(response.headers)
242
+ raw_resp.content = response.content
243
+ return _unmarshaller(raw_resp, unmarshal_as)
@@ -1,3 +1,5 @@
1
+ import ssl
2
+
1
3
  from dify_oapi.core.enum import LogLevel
2
4
 
3
5
 
@@ -7,3 +9,11 @@ class Config:
7
9
  self.timeout: float | None = None # Client timeout in seconds, default is no timeout
8
10
  self.log_level: LogLevel = LogLevel.WARNING # Log level, default is WARNING
9
11
  self.max_retry_count: int = 3 # Maximum retry count after request failure. Default is 3
12
+
13
+ # Connection pool settings
14
+ self.max_keepalive_connections: int = 20 # Max keepalive connections per pool
15
+ self.max_connections: int = 100 # Max total connections per pool
16
+ self.keepalive_expiry: float = 30.0 # Keepalive connection expiry time in seconds
17
+
18
+ # SSL settings
19
+ self.verify_ssl: ssl.SSLContext | str | bool = True # SSL certificate verification
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dify-oapi2
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: A package for interacting with the Dify Service-API
5
5
  License: MIT
6
6
  Keywords: dify,nlp,ai,language-processing
@@ -13,8 +13,8 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
- Requires-Dist: httpx (>=0.24,<1.0)
17
- Requires-Dist: pydantic (>=1.10,<3.0.0)
16
+ Requires-Dist: httpx (>=0,<1)
17
+ Requires-Dist: pydantic (>=2,<3)
18
18
  Project-URL: Homepage, https://github.com/nodite/dify-oapi2
19
19
  Project-URL: Source, https://github.com/nodite/dify-oapi2
20
20
  Description-Content-Type: text/markdown
@@ -31,13 +31,14 @@ A Python SDK for interacting with the Dify Service-API. This library provides a
31
31
 
32
32
  ## ✨ Features
33
33
 
34
- - **Multiple API Services**: Chat, Completion, Knowledge Base (33 APIs), Workflow, and Core Dify APIs
34
+ - **Multiple API Services**: Chat (22 APIs), Completion (15 APIs), Knowledge Base (33 APIs), Chatflow (17 APIs), Workflow, and Core Dify APIs
35
35
  - **Builder Pattern**: Fluent, chainable interface for constructing requests
36
36
  - **Sync & Async Support**: Both synchronous and asynchronous operations
37
37
  - **Streaming Responses**: Real-time streaming for chat and completion
38
38
  - **Type Safety**: Comprehensive type hints with Pydantic validation
39
39
  - **File Upload**: Support for images and documents
40
40
  - **Modern HTTP Client**: Built on httpx for reliable API communication
41
+ - **Connection Pool Optimization**: Efficient TCP connection reuse to reduce resource overhead
41
42
 
42
43
  ## 📦 Installation
43
44
 
@@ -116,11 +117,16 @@ asyncio.run(async_chat())
116
117
 
117
118
  ## 🔧 API Services
118
119
 
119
- ### Chat API
120
- - Interactive conversations with AI assistants
121
- - File upload support (images, documents)
122
- - Conversation and message history management
123
- - Streaming and blocking response modes
120
+ ### Chat API (22 APIs)
121
+ - **Chat Messages**: Interactive conversations with AI assistants (3 APIs)
122
+ - **File Management**: Upload and manage images and documents (1 API)
123
+ - **Feedback Management**: Collect and analyze user feedback (2 APIs)
124
+ - **Conversation Management**: Complete conversation lifecycle management (5 APIs)
125
+ - **Audio Processing**: Speech-to-text and text-to-speech capabilities (2 APIs)
126
+ - **Application Information**: App configuration and metadata retrieval (4 APIs)
127
+ - **Annotation Management**: Create and manage annotations with reply settings (6 APIs)
128
+ - **Streaming Support**: Real-time streaming for chat and completion
129
+ - **Type Safety**: Comprehensive type hints with strict Literal types
124
130
 
125
131
  ### Completion API (15 APIs)
126
132
  - **Message Processing**: Send messages and control responses
@@ -138,6 +144,17 @@ asyncio.run(async_chat())
138
144
  - **Tag Management**: 7 APIs for metadata and knowledge type tags
139
145
  - **Model Management**: 1 API for embedding model information
140
146
 
147
+ ### Chatflow API (17 APIs)
148
+ - **Advanced Chat**: 3 APIs for enhanced chat functionality with workflow events
149
+ - **File Management**: 1 API for multimodal file upload and processing
150
+ - **Feedback System**: 2 APIs for comprehensive feedback collection and analysis
151
+ - **Conversation Management**: 5 APIs for complete conversation lifecycle management
152
+ - **TTS Integration**: 2 APIs for speech-to-text and text-to-speech capabilities
153
+ - **Application Configuration**: 4 APIs for app settings and metadata management
154
+ - **Annotation System**: 6 APIs for annotation management and reply settings
155
+ - **Streaming Support**: Real-time streaming with comprehensive event handling
156
+ - **Type Safety**: Strict Literal types for all predefined values
157
+
141
158
  ### Workflow API
142
159
  - Automated workflow execution
143
160
  - Parameter configuration
@@ -151,9 +168,13 @@ asyncio.run(async_chat())
151
168
  Explore comprehensive examples in the [examples directory](./examples):
152
169
 
153
170
  ### Chat Examples
154
- - [**Blocking Response**](./examples/chat/blocking_response.py) - Standard chat interactions
155
- - [**Streaming Response**](./examples/chat/streaming_response.py) - Real-time streaming chat
156
- - [**Conversation Management**](./examples/chat/conversation_management.py) - Managing chat history
171
+ - [**Chat Messages**](./examples/chat/chat/) - Send messages, stop generation, get suggestions
172
+ - [**File Management**](./examples/chat/file/) - Upload and manage files
173
+ - [**Feedback Management**](./examples/chat/feedback/) - Submit and retrieve feedback
174
+ - [**Conversation Management**](./examples/chat/conversation/) - Complete conversation operations
175
+ - [**Audio Processing**](./examples/chat/audio/) - Speech-to-text and text-to-speech
176
+ - [**Application Information**](./examples/chat/app/) - App configuration and settings
177
+ - [**Annotation Management**](./examples/chat/annotation/) - Annotation CRUD and reply settings
157
178
 
158
179
  ### Completion Examples
159
180
  - [**Basic Completion**](./examples/completion/basic_completion.py) - Text generation
@@ -164,6 +185,15 @@ Explore comprehensive examples in the [examples directory](./examples):
164
185
  - [**Content Organization**](./examples/knowledge/segment/) - Segment and chunk management
165
186
  - [**Tag Management**](./examples/knowledge/tag/) - Metadata and tagging system
166
187
 
188
+ ### Chatflow Examples
189
+ - [**Advanced Chat**](./examples/chatflow/chatflow/) - Enhanced chat with streaming and workflow events
190
+ - [**File Operations**](./examples/chatflow/file/) - Multimodal file upload and processing
191
+ - [**Feedback Management**](./examples/chatflow/feedback/) - Comprehensive feedback collection
192
+ - [**Conversation Management**](./examples/chatflow/conversation/) - Complete conversation operations
193
+ - [**TTS Operations**](./examples/chatflow/tts/) - Speech-to-text and text-to-speech
194
+ - [**Application Configuration**](./examples/chatflow/application/) - App settings and metadata
195
+ - [**Annotation Management**](./examples/chatflow/annotation/) - Annotation CRUD and reply settings
196
+
167
197
  For detailed examples and usage patterns, see the [examples README](./examples/README.md).
168
198
 
169
199
  ## 🛠️ Development
@@ -253,6 +283,7 @@ dify-oapi/
253
283
  │ │ ├── completion/ # Completion API
254
284
  │ │ ├── dify/ # Core Dify API
255
285
  │ │ ├── knowledge/ # Knowledge Base API (33 APIs)
286
+ │ │ ├── chatflow/ # Chatflow API (17 APIs)
256
287
  │ │ └── workflow/ # Workflow API
257
288
  │ ├── core/ # Core functionality
258
289
  │ │ ├── http/ # HTTP transport layer
@@ -268,6 +299,7 @@ dify-oapi/
268
299
  ## 📖 Documentation
269
300
 
270
301
  - [**Project Overview**](./docs/overview.md) - Architecture and technical details
302
+ - [**TCP Connection Optimization**](./docs/tcp-optimization.md) - Connection pool configuration and performance tuning
271
303
  - [**Completion APIs**](./docs/completion/apis.md) - Complete completion API documentation
272
304
  - [**Knowledge Base APIs**](./docs/knowledge/apis.md) - Complete knowledge base API documentation
273
305
  - [**Examples**](./examples/README.md) - Usage examples and patterns