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.
- dify_oapi/api/chat/v1/model/__init__.py +37 -0
- dify_oapi/api/chat/v1/model/agent_thought.py +69 -0
- dify_oapi/api/chat/v1/model/annotation_info.py +43 -0
- dify_oapi/api/{completion/v1/model/info → chat/v1/model}/app_info.py +6 -14
- dify_oapi/api/chat/v1/model/app_parameters.py +494 -0
- dify_oapi/api/chat/v1/model/chat_file.py +53 -0
- dify_oapi/api/chat/v1/model/chat_request_body.py +26 -13
- dify_oapi/api/chat/v1/model/chat_response.py +13 -2
- dify_oapi/api/chat/v1/model/chat_types.py +65 -0
- dify_oapi/api/chat/v1/model/configure_annotation_reply_request.py +37 -0
- dify_oapi/api/chat/v1/model/configure_annotation_reply_request_body.py +39 -0
- dify_oapi/api/chat/v1/model/configure_annotation_reply_response.py +10 -0
- dify_oapi/api/chat/v1/model/conversation_info.py +57 -0
- dify_oapi/api/chat/v1/model/conversation_variable.py +55 -0
- dify_oapi/api/chat/v1/model/create_annotation_request.py +30 -0
- dify_oapi/api/chat/v1/model/create_annotation_request_body.py +32 -0
- dify_oapi/api/chat/v1/model/create_annotation_response.py +9 -0
- dify_oapi/api/chat/v1/model/delete_annotation_request.py +26 -0
- dify_oapi/api/chat/v1/model/delete_annotation_response.py +7 -0
- dify_oapi/api/{completion/v1/model/feedback → chat/v1/model}/feedback_info.py +29 -7
- dify_oapi/api/{completion/v1/model/file → chat/v1/model}/file_info.py +6 -0
- dify_oapi/api/chat/v1/model/get_annotation_reply_status_request.py +32 -0
- dify_oapi/api/chat/v1/model/get_annotation_reply_status_response.py +11 -0
- dify_oapi/api/chat/v1/model/get_conversation_list_request.py +25 -24
- dify_oapi/api/chat/v1/model/get_conversation_list_response.py +4 -15
- dify_oapi/api/chat/v1/model/get_conversation_variables_request.py +54 -0
- dify_oapi/api/chat/v1/model/get_conversation_variables_response.py +10 -0
- dify_oapi/api/chat/v1/model/get_conversations_request.py +49 -0
- dify_oapi/api/chat/v1/model/get_suggested_questions_request.py +36 -0
- dify_oapi/api/chat/v1/model/{message_suggested_response.py → get_suggested_questions_response.py} +1 -1
- dify_oapi/api/chat/v1/model/list_annotations_request.py +32 -0
- dify_oapi/api/chat/v1/model/list_annotations_response.py +11 -0
- dify_oapi/api/chat/v1/model/message_file.py +46 -0
- dify_oapi/api/chat/v1/model/message_history_request.py +24 -24
- dify_oapi/api/chat/v1/model/message_history_response.py +4 -43
- dify_oapi/api/chat/v1/model/message_info.py +73 -0
- dify_oapi/api/chat/v1/model/pagination_info.py +44 -0
- dify_oapi/api/chat/v1/model/rename_conversation_request_body.py +1 -1
- dify_oapi/api/chat/v1/model/retriever_resource.py +64 -0
- dify_oapi/api/chat/v1/model/site_settings.py +92 -0
- dify_oapi/api/chat/v1/model/text_to_audio_response.py +7 -0
- dify_oapi/api/chat/v1/model/tool_icon.py +52 -0
- dify_oapi/api/chat/v1/model/update_annotation_request.py +34 -0
- dify_oapi/api/chat/v1/model/update_annotation_request_body.py +32 -0
- dify_oapi/api/chat/v1/model/update_annotation_response.py +9 -0
- dify_oapi/api/{completion/v1/model/file → chat/v1/model}/upload_file_request_body.py +9 -3
- dify_oapi/api/chat/v1/model/usage_info.py +84 -0
- dify_oapi/api/chat/v1/resource/__init__.py +1 -4
- dify_oapi/api/chat/v1/resource/annotation.py +87 -0
- dify_oapi/api/chat/v1/resource/chat.py +47 -25
- dify_oapi/api/chat/v1/resource/conversation.py +45 -33
- dify_oapi/api/chat/v1/resource/message.py +22 -20
- dify_oapi/api/chat/v1/version.py +18 -5
- dify_oapi/api/chatflow/__init__.py +0 -0
- dify_oapi/api/chatflow/service.py +8 -0
- dify_oapi/api/chatflow/v1/__init__.py +0 -0
- dify_oapi/api/chatflow/v1/model/__init__.py +0 -0
- dify_oapi/api/chatflow/v1/model/annotation_info.py +43 -0
- dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request.py +37 -0
- dify_oapi/api/chatflow/v1/model/annotation_reply_settings_request_body.py +33 -0
- dify_oapi/api/chatflow/v1/model/annotation_reply_settings_response.py +10 -0
- dify_oapi/api/chatflow/v1/model/annotation_reply_status_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/annotation_reply_status_response.py +11 -0
- dify_oapi/api/chatflow/v1/model/app_info.py +33 -0
- dify_oapi/api/chatflow/v1/model/app_parameters.py +276 -0
- dify_oapi/api/chatflow/v1/model/chat_file.py +40 -0
- dify_oapi/api/chatflow/v1/model/chat_message.py +88 -0
- dify_oapi/api/chatflow/v1/model/chatflow_types.py +210 -0
- dify_oapi/api/chatflow/v1/model/conversation_info.py +53 -0
- dify_oapi/api/chatflow/v1/model/conversation_variable.py +55 -0
- dify_oapi/api/chatflow/v1/model/create_annotation_request.py +30 -0
- dify_oapi/api/chatflow/v1/model/create_annotation_request_body.py +28 -0
- dify_oapi/api/chatflow/v1/model/create_annotation_response.py +9 -0
- dify_oapi/api/chatflow/v1/model/delete_annotation_request.py +28 -0
- dify_oapi/api/chatflow/v1/model/delete_annotation_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/delete_conversation_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/delete_conversation_request_body.py +21 -0
- dify_oapi/api/chatflow/v1/model/delete_conversation_response.py +17 -0
- dify_oapi/api/chatflow/v1/model/feedback_info.py +75 -0
- dify_oapi/api/chatflow/v1/model/file_info.py +53 -0
- dify_oapi/api/chatflow/v1/model/get_annotations_request.py +30 -0
- dify_oapi/api/chatflow/v1/model/get_annotations_response.py +13 -0
- dify_oapi/api/chatflow/v1/model/get_conversation_messages_request.py +38 -0
- dify_oapi/api/chatflow/v1/model/get_conversation_messages_response.py +33 -0
- dify_oapi/api/chatflow/v1/model/get_conversation_variables_request.py +44 -0
- dify_oapi/api/chatflow/v1/model/get_conversation_variables_response.py +33 -0
- dify_oapi/api/chatflow/v1/model/get_conversations_request.py +40 -0
- dify_oapi/api/chatflow/v1/model/get_conversations_response.py +33 -0
- dify_oapi/api/chatflow/v1/model/get_suggested_questions_request.py +32 -0
- dify_oapi/api/{completion/v1/model/feedback/message_feedback_response.py → chatflow/v1/model/get_suggested_questions_response.py} +2 -3
- dify_oapi/api/chatflow/v1/model/rename_conversation_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/rename_conversation_request_body.py +31 -0
- dify_oapi/api/chatflow/v1/model/rename_conversation_response.py +53 -0
- dify_oapi/api/chatflow/v1/model/retriever_resource.py +58 -0
- dify_oapi/api/chatflow/v1/model/send_chat_message_request.py +30 -0
- dify_oapi/api/chatflow/v1/model/send_chat_message_request_body.py +54 -0
- dify_oapi/api/chatflow/v1/model/send_chat_message_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/stop_chat_message_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/stop_chat_message_request_body.py +21 -0
- dify_oapi/api/{dify/v1/model/message_feedback_response.py → chatflow/v1/model/stop_chat_message_response.py} +1 -1
- dify_oapi/api/chatflow/v1/model/tool_icon.py +48 -0
- dify_oapi/api/chatflow/v1/model/update_annotation_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/update_annotation_request_body.py +28 -0
- dify_oapi/api/chatflow/v1/model/update_annotation_response.py +9 -0
- dify_oapi/api/chatflow/v1/model/usage_info.py +78 -0
- dify_oapi/api/chatflow/v1/model/user_input_form.py +141 -0
- dify_oapi/api/chatflow/v1/model/webapp_settings.py +88 -0
- dify_oapi/api/chatflow/v1/resource/__init__.py +0 -0
- dify_oapi/api/chatflow/v1/resource/annotation.py +87 -0
- dify_oapi/api/chatflow/v1/resource/chatflow.py +78 -0
- dify_oapi/api/chatflow/v1/resource/conversation.py +75 -0
- dify_oapi/api/chatflow/v1/version.py +22 -0
- dify_oapi/api/completion/v1/resource/__init__.py +0 -6
- dify_oapi/api/completion/v1/version.py +8 -6
- dify_oapi/api/{chat → dify}/v1/model/audio_to_text_request_body.py +2 -2
- dify_oapi/api/{completion/v1/model/feedback → dify/v1/model}/get_feedbacks_request.py +12 -12
- dify_oapi/api/dify/v1/model/get_feedbacks_response.py +26 -0
- dify_oapi/api/{workflow → dify}/v1/model/get_parameters_request.py +4 -0
- dify_oapi/api/dify/v1/model/{get_parameter_response.py → get_parameters_response.py} +6 -2
- dify_oapi/api/dify/v1/model/get_site_response.py +17 -0
- dify_oapi/api/dify/v1/model/submit_feedback_request.py +34 -0
- dify_oapi/api/dify/v1/model/submit_feedback_request_body.py +35 -0
- dify_oapi/api/dify/v1/model/submit_feedback_response.py +7 -0
- dify_oapi/api/dify/v1/resource/__init__.py +0 -6
- dify_oapi/api/dify/v1/resource/audio.py +12 -0
- dify_oapi/api/dify/v1/resource/feedback.py +31 -0
- dify_oapi/api/dify/v1/resource/info.py +34 -2
- dify_oapi/api/dify/v1/version.py +5 -4
- dify_oapi/api/knowledge/service.py +3 -3
- dify_oapi/api/knowledge/v1/model/create_document_by_file_request.py +1 -3
- dify_oapi/api/knowledge/v1/model/create_document_by_file_request_body.py +4 -46
- dify_oapi/api/knowledge/v1/model/create_document_by_file_request_body_data.py +73 -0
- dify_oapi/api/knowledge/v1/model/data_source_detail.py +23 -0
- dify_oapi/api/knowledge/v1/model/dataset_info.py +10 -9
- dify_oapi/api/knowledge/v1/model/dataset_metadata.py +14 -0
- dify_oapi/api/knowledge/v1/model/document_info.py +18 -6
- dify_oapi/api/knowledge/v1/model/external_knowledge_info.py +11 -1
- dify_oapi/api/knowledge/v1/model/external_retrieval_model.py +13 -0
- dify_oapi/api/knowledge/v1/model/knowledge_types.py +2 -2
- dify_oapi/api/knowledge/v1/model/process_rule.py +0 -6
- dify_oapi/api/knowledge/v1/model/retrieval_model.py +10 -4
- dify_oapi/api/knowledge/v1/model/update_document_by_file_request.py +1 -3
- dify_oapi/api/knowledge/v1/model/update_document_by_file_request_body.py +4 -41
- dify_oapi/api/knowledge/v1/model/update_document_by_file_request_body_data.py +68 -0
- dify_oapi/api/knowledge/v1/model/weights.py +27 -0
- dify_oapi/api/knowledge/v1/resource/__init__.py +0 -8
- dify_oapi/api/workflow/v1/model/chunk_workflow_event.py +74 -0
- dify_oapi/api/workflow/v1/model/input_file_object_workflow.py +76 -0
- dify_oapi/api/workflow/v1/model/node_finished_data.py +118 -0
- dify_oapi/api/workflow/v1/model/node_started_data.py +81 -0
- dify_oapi/api/workflow/v1/model/ping_data.py +28 -0
- dify_oapi/api/workflow/v1/model/run_workflow_request_body.py +1 -1
- dify_oapi/api/workflow/v1/model/text_chunk_data.py +39 -0
- dify_oapi/api/workflow/v1/model/tts_message_data.py +45 -0
- dify_oapi/api/workflow/v1/model/tts_message_end_data.py +45 -0
- dify_oapi/api/workflow/v1/model/workflow_completion_response.py +50 -0
- dify_oapi/api/workflow/v1/model/workflow_finished_data.py +93 -0
- dify_oapi/api/workflow/v1/model/workflow_started_data.py +51 -0
- dify_oapi/api/workflow/v1/model/workflow_types.py +27 -12
- dify_oapi/api/workflow/v1/resource/workflow.py +0 -34
- dify_oapi/api/workflow/v1/version.py +9 -0
- dify_oapi/client.py +49 -4
- dify_oapi/core/http/transport/__init__.py +2 -1
- dify_oapi/core/http/transport/async_transport.py +73 -50
- dify_oapi/core/http/transport/connection_pool.py +131 -0
- dify_oapi/core/http/transport/sync_transport.py +73 -50
- dify_oapi/core/model/config.py +10 -0
- dify_oapi2-1.0.0.dist-info/METADATA +365 -0
- {dify_oapi2-0.4.0.dist-info → dify_oapi2-1.0.0.dist-info}/RECORD +174 -98
- dify_oapi/api/chat/v1/model/chat_request_file.py +0 -46
- dify_oapi/api/chat/v1/model/message_suggested_request.py +0 -36
- dify_oapi/api/chat/v1/resource/audio.py +0 -17
- dify_oapi/api/completion/v1/model/audio/audio_info.py +0 -28
- dify_oapi/api/completion/v1/model/audio/text_to_audio_request.py +0 -32
- dify_oapi/api/completion/v1/model/audio/text_to_audio_request_body.py +0 -33
- dify_oapi/api/completion/v1/model/audio/text_to_audio_response.py +0 -9
- dify_oapi/api/completion/v1/model/feedback/get_feedbacks_response.py +0 -9
- dify_oapi/api/completion/v1/model/feedback/message_feedback_request.py +0 -38
- dify_oapi/api/completion/v1/model/feedback/message_feedback_request_body.py +0 -35
- dify_oapi/api/completion/v1/model/file/upload_file_request.py +0 -42
- dify_oapi/api/completion/v1/model/file/upload_file_response.py +0 -9
- dify_oapi/api/completion/v1/model/info/feature_config.py +0 -91
- dify_oapi/api/completion/v1/model/info/file_upload_config.py +0 -23
- dify_oapi/api/completion/v1/model/info/get_info_request.py +0 -24
- dify_oapi/api/completion/v1/model/info/get_info_response.py +0 -9
- dify_oapi/api/completion/v1/model/info/get_parameters_request.py +0 -24
- dify_oapi/api/completion/v1/model/info/get_parameters_response.py +0 -9
- dify_oapi/api/completion/v1/model/info/get_site_response.py +0 -9
- dify_oapi/api/completion/v1/model/info/parameters_info.py +0 -75
- dify_oapi/api/completion/v1/model/info/site_info.py +0 -90
- dify_oapi/api/completion/v1/model/info/system_parameters.py +0 -38
- dify_oapi/api/completion/v1/model/info/user_input_form.py +0 -158
- dify_oapi/api/completion/v1/resource/audio.py +0 -19
- dify_oapi/api/completion/v1/resource/feedback.py +0 -33
- dify_oapi/api/completion/v1/resource/file.py +0 -19
- dify_oapi/api/completion/v1/resource/info.py +0 -39
- dify_oapi/api/dify/v1/model/get_parameter_request.py +0 -30
- dify_oapi/api/dify/v1/model/message_feedback_request.py +0 -38
- dify_oapi/api/dify/v1/model/message_feedback_request_body.py +0 -30
- dify_oapi/api/dify/v1/resource/message.py +0 -21
- dify_oapi/api/dify/v1/resource/meta.py +0 -17
- dify_oapi/api/dify/v1/resource/parameter.py +0 -19
- dify_oapi/api/workflow/v1/model/get_info_request.py +0 -24
- dify_oapi/api/workflow/v1/model/get_info_response.py +0 -9
- dify_oapi/api/workflow/v1/model/get_parameters_response.py +0 -9
- dify_oapi/api/workflow/v1/model/get_site_request.py +0 -24
- dify_oapi/api/workflow/v1/model/get_site_response.py +0 -9
- dify_oapi/api/workflow/v1/model/upload_file_request.py +0 -42
- dify_oapi/api/workflow/v1/model/upload_file_response.py +0 -9
- dify_oapi2-0.4.0.dist-info/METADATA +0 -303
- /dify_oapi/api/{chat → dify}/v1/model/audio_to_text_request.py +0 -0
- /dify_oapi/api/{chat → dify}/v1/model/audio_to_text_response.py +0 -0
- /dify_oapi/api/{completion/v1/model/info → dify/v1/model}/get_site_request.py +0 -0
- {dify_oapi2-0.4.0.dist-info → dify_oapi2-1.0.0.dist-info}/LICENSE +0 -0
- {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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
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:
|
226
|
+
f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
|
206
227
|
)
|
207
|
-
|
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
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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:
|
229
|
+
f"in-request: retrying ({retry + 1}/{conf.max_retry_count}) {log_details}, exp: {err_msg}"
|
209
230
|
)
|
210
|
-
|
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
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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)
|
dify_oapi/core/model/config.py
CHANGED
@@ -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
|