dify-oapi2 0.4.0__py3-none-any.whl → 1.0.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 (215) 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/{completion/v1/model/info → chat/v1/model}/app_info.py +6 -14
  5. dify_oapi/api/chat/v1/model/app_parameters.py +494 -0
  6. dify_oapi/api/chat/v1/model/chat_file.py +53 -0
  7. dify_oapi/api/chat/v1/model/chat_request_body.py +26 -13
  8. dify_oapi/api/chat/v1/model/chat_response.py +13 -2
  9. dify_oapi/api/chat/v1/model/chat_types.py +65 -0
  10. dify_oapi/api/chat/v1/model/configure_annotation_reply_request.py +37 -0
  11. dify_oapi/api/chat/v1/model/configure_annotation_reply_request_body.py +39 -0
  12. dify_oapi/api/chat/v1/model/configure_annotation_reply_response.py +10 -0
  13. dify_oapi/api/chat/v1/model/conversation_info.py +57 -0
  14. dify_oapi/api/chat/v1/model/conversation_variable.py +55 -0
  15. dify_oapi/api/chat/v1/model/create_annotation_request.py +30 -0
  16. dify_oapi/api/chat/v1/model/create_annotation_request_body.py +32 -0
  17. dify_oapi/api/chat/v1/model/create_annotation_response.py +9 -0
  18. dify_oapi/api/chat/v1/model/delete_annotation_request.py +26 -0
  19. dify_oapi/api/chat/v1/model/delete_annotation_response.py +7 -0
  20. dify_oapi/api/{completion/v1/model/feedback → chat/v1/model}/feedback_info.py +29 -7
  21. dify_oapi/api/{completion/v1/model/file → chat/v1/model}/file_info.py +6 -0
  22. dify_oapi/api/chat/v1/model/get_annotation_reply_status_request.py +32 -0
  23. dify_oapi/api/chat/v1/model/get_annotation_reply_status_response.py +11 -0
  24. dify_oapi/api/chat/v1/model/get_conversation_list_request.py +25 -24
  25. dify_oapi/api/chat/v1/model/get_conversation_list_response.py +4 -15
  26. dify_oapi/api/chat/v1/model/get_conversation_variables_request.py +54 -0
  27. dify_oapi/api/chat/v1/model/get_conversation_variables_response.py +10 -0
  28. dify_oapi/api/chat/v1/model/get_conversations_request.py +49 -0
  29. dify_oapi/api/chat/v1/model/get_suggested_questions_request.py +36 -0
  30. dify_oapi/api/chat/v1/model/{message_suggested_response.py → get_suggested_questions_response.py} +1 -1
  31. dify_oapi/api/chat/v1/model/list_annotations_request.py +32 -0
  32. dify_oapi/api/chat/v1/model/list_annotations_response.py +11 -0
  33. dify_oapi/api/chat/v1/model/message_file.py +46 -0
  34. dify_oapi/api/chat/v1/model/message_history_request.py +24 -24
  35. dify_oapi/api/chat/v1/model/message_history_response.py +4 -43
  36. dify_oapi/api/chat/v1/model/message_info.py +73 -0
  37. dify_oapi/api/chat/v1/model/pagination_info.py +44 -0
  38. dify_oapi/api/chat/v1/model/rename_conversation_request_body.py +1 -1
  39. dify_oapi/api/chat/v1/model/retriever_resource.py +64 -0
  40. dify_oapi/api/chat/v1/model/site_settings.py +92 -0
  41. dify_oapi/api/chat/v1/model/text_to_audio_response.py +7 -0
  42. dify_oapi/api/chat/v1/model/tool_icon.py +52 -0
  43. dify_oapi/api/chat/v1/model/update_annotation_request.py +34 -0
  44. dify_oapi/api/chat/v1/model/update_annotation_request_body.py +32 -0
  45. dify_oapi/api/chat/v1/model/update_annotation_response.py +9 -0
  46. dify_oapi/api/{completion/v1/model/file → chat/v1/model}/upload_file_request_body.py +9 -3
  47. dify_oapi/api/chat/v1/model/usage_info.py +84 -0
  48. dify_oapi/api/chat/v1/resource/__init__.py +1 -4
  49. dify_oapi/api/chat/v1/resource/annotation.py +87 -0
  50. dify_oapi/api/chat/v1/resource/chat.py +47 -25
  51. dify_oapi/api/chat/v1/resource/conversation.py +45 -33
  52. dify_oapi/api/chat/v1/resource/message.py +22 -20
  53. dify_oapi/api/chat/v1/version.py +18 -5
  54. dify_oapi/api/chatflow/__init__.py +0 -0
  55. dify_oapi/api/chatflow/service.py +8 -0
  56. dify_oapi/api/chatflow/v1/__init__.py +0 -0
  57. dify_oapi/api/chatflow/v1/model/__init__.py +0 -0
  58. dify_oapi/api/chatflow/v1/model/annotation_info.py +43 -0
  59. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request.py +37 -0
  60. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request_body.py +33 -0
  61. dify_oapi/api/chatflow/v1/model/annotation_reply_settings_response.py +10 -0
  62. dify_oapi/api/chatflow/v1/model/annotation_reply_status_request.py +36 -0
  63. dify_oapi/api/chatflow/v1/model/annotation_reply_status_response.py +11 -0
  64. dify_oapi/api/chatflow/v1/model/app_info.py +33 -0
  65. dify_oapi/api/chatflow/v1/model/app_parameters.py +276 -0
  66. dify_oapi/api/chatflow/v1/model/chat_file.py +40 -0
  67. dify_oapi/api/chatflow/v1/model/chat_message.py +88 -0
  68. dify_oapi/api/chatflow/v1/model/chatflow_types.py +210 -0
  69. dify_oapi/api/chatflow/v1/model/conversation_info.py +53 -0
  70. dify_oapi/api/chatflow/v1/model/conversation_variable.py +55 -0
  71. dify_oapi/api/chatflow/v1/model/create_annotation_request.py +30 -0
  72. dify_oapi/api/chatflow/v1/model/create_annotation_request_body.py +28 -0
  73. dify_oapi/api/chatflow/v1/model/create_annotation_response.py +9 -0
  74. dify_oapi/api/chatflow/v1/model/delete_annotation_request.py +28 -0
  75. dify_oapi/api/chatflow/v1/model/delete_annotation_response.py +7 -0
  76. dify_oapi/api/chatflow/v1/model/delete_conversation_request.py +36 -0
  77. dify_oapi/api/chatflow/v1/model/delete_conversation_request_body.py +21 -0
  78. dify_oapi/api/chatflow/v1/model/delete_conversation_response.py +17 -0
  79. dify_oapi/api/chatflow/v1/model/feedback_info.py +75 -0
  80. dify_oapi/api/chatflow/v1/model/file_info.py +53 -0
  81. dify_oapi/api/chatflow/v1/model/get_annotations_request.py +30 -0
  82. dify_oapi/api/chatflow/v1/model/get_annotations_response.py +13 -0
  83. dify_oapi/api/chatflow/v1/model/get_conversation_messages_request.py +38 -0
  84. dify_oapi/api/chatflow/v1/model/get_conversation_messages_response.py +33 -0
  85. dify_oapi/api/chatflow/v1/model/get_conversation_variables_request.py +44 -0
  86. dify_oapi/api/chatflow/v1/model/get_conversation_variables_response.py +33 -0
  87. dify_oapi/api/chatflow/v1/model/get_conversations_request.py +40 -0
  88. dify_oapi/api/chatflow/v1/model/get_conversations_response.py +33 -0
  89. dify_oapi/api/chatflow/v1/model/get_suggested_questions_request.py +32 -0
  90. dify_oapi/api/{completion/v1/model/feedback/message_feedback_response.py → chatflow/v1/model/get_suggested_questions_response.py} +2 -3
  91. dify_oapi/api/chatflow/v1/model/rename_conversation_request.py +36 -0
  92. dify_oapi/api/chatflow/v1/model/rename_conversation_request_body.py +31 -0
  93. dify_oapi/api/chatflow/v1/model/rename_conversation_response.py +53 -0
  94. dify_oapi/api/chatflow/v1/model/retriever_resource.py +58 -0
  95. dify_oapi/api/chatflow/v1/model/send_chat_message_request.py +30 -0
  96. dify_oapi/api/chatflow/v1/model/send_chat_message_request_body.py +54 -0
  97. dify_oapi/api/chatflow/v1/model/send_chat_message_response.py +7 -0
  98. dify_oapi/api/chatflow/v1/model/stop_chat_message_request.py +36 -0
  99. dify_oapi/api/chatflow/v1/model/stop_chat_message_request_body.py +21 -0
  100. dify_oapi/api/{dify/v1/model/message_feedback_response.py → chatflow/v1/model/stop_chat_message_response.py} +1 -1
  101. dify_oapi/api/chatflow/v1/model/tool_icon.py +48 -0
  102. dify_oapi/api/chatflow/v1/model/update_annotation_request.py +36 -0
  103. dify_oapi/api/chatflow/v1/model/update_annotation_request_body.py +28 -0
  104. dify_oapi/api/chatflow/v1/model/update_annotation_response.py +9 -0
  105. dify_oapi/api/chatflow/v1/model/usage_info.py +78 -0
  106. dify_oapi/api/chatflow/v1/model/user_input_form.py +141 -0
  107. dify_oapi/api/chatflow/v1/model/webapp_settings.py +88 -0
  108. dify_oapi/api/chatflow/v1/resource/__init__.py +0 -0
  109. dify_oapi/api/chatflow/v1/resource/annotation.py +87 -0
  110. dify_oapi/api/chatflow/v1/resource/chatflow.py +78 -0
  111. dify_oapi/api/chatflow/v1/resource/conversation.py +75 -0
  112. dify_oapi/api/chatflow/v1/version.py +22 -0
  113. dify_oapi/api/completion/v1/resource/__init__.py +0 -6
  114. dify_oapi/api/completion/v1/version.py +8 -6
  115. dify_oapi/api/{chat → dify}/v1/model/audio_to_text_request_body.py +2 -2
  116. dify_oapi/api/{completion/v1/model/feedback → dify/v1/model}/get_feedbacks_request.py +12 -12
  117. dify_oapi/api/dify/v1/model/get_feedbacks_response.py +26 -0
  118. dify_oapi/api/{workflow → dify}/v1/model/get_parameters_request.py +4 -0
  119. dify_oapi/api/dify/v1/model/{get_parameter_response.py → get_parameters_response.py} +6 -2
  120. dify_oapi/api/dify/v1/model/get_site_response.py +17 -0
  121. dify_oapi/api/dify/v1/model/submit_feedback_request.py +34 -0
  122. dify_oapi/api/dify/v1/model/submit_feedback_request_body.py +35 -0
  123. dify_oapi/api/dify/v1/model/submit_feedback_response.py +7 -0
  124. dify_oapi/api/dify/v1/resource/__init__.py +0 -6
  125. dify_oapi/api/dify/v1/resource/audio.py +12 -0
  126. dify_oapi/api/dify/v1/resource/feedback.py +31 -0
  127. dify_oapi/api/dify/v1/resource/info.py +34 -2
  128. dify_oapi/api/dify/v1/version.py +5 -4
  129. dify_oapi/api/knowledge/service.py +3 -3
  130. dify_oapi/api/knowledge/v1/model/create_document_by_file_request.py +1 -3
  131. dify_oapi/api/knowledge/v1/model/create_document_by_file_request_body.py +4 -46
  132. dify_oapi/api/knowledge/v1/model/create_document_by_file_request_body_data.py +73 -0
  133. dify_oapi/api/knowledge/v1/model/data_source_detail.py +23 -0
  134. dify_oapi/api/knowledge/v1/model/dataset_info.py +10 -9
  135. dify_oapi/api/knowledge/v1/model/dataset_metadata.py +14 -0
  136. dify_oapi/api/knowledge/v1/model/document_info.py +18 -6
  137. dify_oapi/api/knowledge/v1/model/external_knowledge_info.py +11 -1
  138. dify_oapi/api/knowledge/v1/model/external_retrieval_model.py +13 -0
  139. dify_oapi/api/knowledge/v1/model/knowledge_types.py +2 -2
  140. dify_oapi/api/knowledge/v1/model/process_rule.py +0 -6
  141. dify_oapi/api/knowledge/v1/model/retrieval_model.py +10 -4
  142. dify_oapi/api/knowledge/v1/model/update_document_by_file_request.py +1 -3
  143. dify_oapi/api/knowledge/v1/model/update_document_by_file_request_body.py +4 -41
  144. dify_oapi/api/knowledge/v1/model/update_document_by_file_request_body_data.py +68 -0
  145. dify_oapi/api/knowledge/v1/model/weights.py +27 -0
  146. dify_oapi/api/knowledge/v1/resource/__init__.py +0 -8
  147. dify_oapi/api/workflow/v1/model/chunk_workflow_event.py +74 -0
  148. dify_oapi/api/workflow/v1/model/input_file_object_workflow.py +76 -0
  149. dify_oapi/api/workflow/v1/model/node_finished_data.py +118 -0
  150. dify_oapi/api/workflow/v1/model/node_started_data.py +81 -0
  151. dify_oapi/api/workflow/v1/model/ping_data.py +28 -0
  152. dify_oapi/api/workflow/v1/model/run_workflow_request_body.py +1 -1
  153. dify_oapi/api/workflow/v1/model/text_chunk_data.py +39 -0
  154. dify_oapi/api/workflow/v1/model/tts_message_data.py +45 -0
  155. dify_oapi/api/workflow/v1/model/tts_message_end_data.py +45 -0
  156. dify_oapi/api/workflow/v1/model/workflow_completion_response.py +50 -0
  157. dify_oapi/api/workflow/v1/model/workflow_finished_data.py +93 -0
  158. dify_oapi/api/workflow/v1/model/workflow_started_data.py +51 -0
  159. dify_oapi/api/workflow/v1/model/workflow_types.py +27 -12
  160. dify_oapi/api/workflow/v1/resource/workflow.py +0 -34
  161. dify_oapi/api/workflow/v1/version.py +9 -0
  162. dify_oapi/client.py +49 -4
  163. dify_oapi/core/http/transport/__init__.py +2 -1
  164. dify_oapi/core/http/transport/async_transport.py +73 -50
  165. dify_oapi/core/http/transport/connection_pool.py +131 -0
  166. dify_oapi/core/http/transport/sync_transport.py +73 -50
  167. dify_oapi/core/model/config.py +10 -0
  168. dify_oapi2-1.0.0.dist-info/METADATA +365 -0
  169. {dify_oapi2-0.4.0.dist-info → dify_oapi2-1.0.0.dist-info}/RECORD +174 -98
  170. dify_oapi/api/chat/v1/model/chat_request_file.py +0 -46
  171. dify_oapi/api/chat/v1/model/message_suggested_request.py +0 -36
  172. dify_oapi/api/chat/v1/resource/audio.py +0 -17
  173. dify_oapi/api/completion/v1/model/audio/audio_info.py +0 -28
  174. dify_oapi/api/completion/v1/model/audio/text_to_audio_request.py +0 -32
  175. dify_oapi/api/completion/v1/model/audio/text_to_audio_request_body.py +0 -33
  176. dify_oapi/api/completion/v1/model/audio/text_to_audio_response.py +0 -9
  177. dify_oapi/api/completion/v1/model/feedback/get_feedbacks_response.py +0 -9
  178. dify_oapi/api/completion/v1/model/feedback/message_feedback_request.py +0 -38
  179. dify_oapi/api/completion/v1/model/feedback/message_feedback_request_body.py +0 -35
  180. dify_oapi/api/completion/v1/model/file/upload_file_request.py +0 -42
  181. dify_oapi/api/completion/v1/model/file/upload_file_response.py +0 -9
  182. dify_oapi/api/completion/v1/model/info/feature_config.py +0 -91
  183. dify_oapi/api/completion/v1/model/info/file_upload_config.py +0 -23
  184. dify_oapi/api/completion/v1/model/info/get_info_request.py +0 -24
  185. dify_oapi/api/completion/v1/model/info/get_info_response.py +0 -9
  186. dify_oapi/api/completion/v1/model/info/get_parameters_request.py +0 -24
  187. dify_oapi/api/completion/v1/model/info/get_parameters_response.py +0 -9
  188. dify_oapi/api/completion/v1/model/info/get_site_response.py +0 -9
  189. dify_oapi/api/completion/v1/model/info/parameters_info.py +0 -75
  190. dify_oapi/api/completion/v1/model/info/site_info.py +0 -90
  191. dify_oapi/api/completion/v1/model/info/system_parameters.py +0 -38
  192. dify_oapi/api/completion/v1/model/info/user_input_form.py +0 -158
  193. dify_oapi/api/completion/v1/resource/audio.py +0 -19
  194. dify_oapi/api/completion/v1/resource/feedback.py +0 -33
  195. dify_oapi/api/completion/v1/resource/file.py +0 -19
  196. dify_oapi/api/completion/v1/resource/info.py +0 -39
  197. dify_oapi/api/dify/v1/model/get_parameter_request.py +0 -30
  198. dify_oapi/api/dify/v1/model/message_feedback_request.py +0 -38
  199. dify_oapi/api/dify/v1/model/message_feedback_request_body.py +0 -30
  200. dify_oapi/api/dify/v1/resource/message.py +0 -21
  201. dify_oapi/api/dify/v1/resource/meta.py +0 -17
  202. dify_oapi/api/dify/v1/resource/parameter.py +0 -19
  203. dify_oapi/api/workflow/v1/model/get_info_request.py +0 -24
  204. dify_oapi/api/workflow/v1/model/get_info_response.py +0 -9
  205. dify_oapi/api/workflow/v1/model/get_parameters_response.py +0 -9
  206. dify_oapi/api/workflow/v1/model/get_site_request.py +0 -24
  207. dify_oapi/api/workflow/v1/model/get_site_response.py +0 -9
  208. dify_oapi/api/workflow/v1/model/upload_file_request.py +0 -42
  209. dify_oapi/api/workflow/v1/model/upload_file_response.py +0 -9
  210. dify_oapi2-0.4.0.dist-info/METADATA +0 -303
  211. /dify_oapi/api/{chat → dify}/v1/model/audio_to_text_request.py +0 -0
  212. /dify_oapi/api/{chat → dify}/v1/model/audio_to_text_response.py +0 -0
  213. /dify_oapi/api/{completion/v1/model/info → dify/v1/model}/get_site_request.py +0 -0
  214. {dify_oapi2-0.4.0.dist-info → dify_oapi2-1.0.0.dist-info}/LICENSE +0 -0
  215. {dify_oapi2-0.4.0.dist-info → dify_oapi2-1.0.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