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.
- 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/chat/v1/model/app_info.py +35 -0
- dify_oapi/api/chat/v1/model/app_parameters.py +494 -0
- dify_oapi/api/chat/v1/model/audio_to_text_request_body.py +2 -2
- 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 +12 -2
- dify_oapi/api/chat/v1/model/chat_types.py +65 -0
- dify_oapi/api/chat/v1/model/configure_annotation_reply_request.py +36 -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 +29 -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/chat/v1/model/feedback_info.py +75 -0
- dify_oapi/api/chat/v1/model/file_info.py +59 -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_app_info_request.py +24 -0
- dify_oapi/api/chat/v1/model/get_app_info_response.py +9 -0
- dify_oapi/api/chat/v1/model/get_app_meta_request.py +24 -0
- dify_oapi/api/chat/v1/model/get_app_meta_response.py +9 -0
- dify_oapi/api/chat/v1/model/get_app_parameters_request.py +28 -0
- dify_oapi/api/chat/v1/model/get_app_parameters_response.py +9 -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_feedbacks_request.py +32 -0
- dify_oapi/api/chat/v1/model/get_feedbacks_response.py +7 -0
- dify_oapi/api/chat/v1/model/get_site_settings_request.py +24 -0
- dify_oapi/api/chat/v1/model/get_site_settings_response.py +9 -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/submit_feedback_request.py +34 -0
- dify_oapi/api/chat/v1/model/submit_feedback_request_body.py +33 -0
- dify_oapi/api/chat/v1/model/submit_feedback_response.py +5 -0
- dify_oapi/api/chat/v1/model/text_to_audio_request.py +32 -0
- dify_oapi/api/chat/v1/model/text_to_audio_request_body.py +33 -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 +33 -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/chat/v1/model/upload_file_request.py +54 -0
- dify_oapi/api/chat/v1/model/upload_file_request_body.py +29 -0
- dify_oapi/api/chat/v1/model/upload_file_response.py +10 -0
- dify_oapi/api/chat/v1/model/usage_info.py +84 -0
- dify_oapi/api/chat/v1/resource/__init__.py +7 -3
- dify_oapi/api/chat/v1/resource/annotation.py +77 -0
- dify_oapi/api/chat/v1/resource/app.py +47 -0
- dify_oapi/api/chat/v1/resource/audio.py +13 -0
- dify_oapi/api/chat/v1/resource/chat.py +14 -0
- dify_oapi/api/chat/v1/resource/conversation.py +55 -9
- dify_oapi/api/chat/v1/resource/feedback.py +31 -0
- dify_oapi/api/chat/v1/resource/file.py +25 -0
- dify_oapi/api/chat/v1/resource/message.py +19 -15
- dify_oapi/api/chat/v1/version.py +8 -2
- dify_oapi/api/chatflow/__init__.py +0 -0
- dify_oapi/api/chatflow/service.py +25 -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/audio_to_text_request.py +37 -0
- dify_oapi/api/chatflow/v1/model/audio_to_text_response.py +21 -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_app_feedbacks_request.py +30 -0
- dify_oapi/api/chatflow/v1/model/get_app_feedbacks_response.py +23 -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_info_request.py +22 -0
- dify_oapi/api/chatflow/v1/model/get_info_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/get_meta_request.py +22 -0
- dify_oapi/api/chatflow/v1/model/get_meta_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/get_parameters_request.py +22 -0
- dify_oapi/api/chatflow/v1/model/get_parameters_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/get_site_request.py +22 -0
- dify_oapi/api/chatflow/v1/model/get_site_response.py +7 -0
- dify_oapi/api/chatflow/v1/model/get_suggested_questions_request.py +32 -0
- dify_oapi/api/chatflow/v1/model/get_suggested_questions_response.py +6 -0
- dify_oapi/api/chatflow/v1/model/message_feedback_request.py +36 -0
- dify_oapi/api/chatflow/v1/model/message_feedback_request_body.py +33 -0
- dify_oapi/api/chatflow/v1/model/message_feedback_response.py +21 -0
- 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/chatflow/v1/model/stop_chat_message_response.py +5 -0
- dify_oapi/api/chatflow/v1/model/text_to_audio_request.py +30 -0
- dify_oapi/api/chatflow/v1/model/text_to_audio_request_body.py +36 -0
- dify_oapi/api/chatflow/v1/model/text_to_audio_response.py +19 -0
- 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/upload_file_request.py +41 -0
- dify_oapi/api/chatflow/v1/model/upload_file_response.py +11 -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 +122 -0
- dify_oapi/api/chatflow/v1/resource/application.py +76 -0
- dify_oapi/api/chatflow/v1/resource/chatflow.py +91 -0
- dify_oapi/api/chatflow/v1/resource/conversation.py +104 -0
- dify_oapi/api/chatflow/v1/resource/feedback.py +69 -0
- dify_oapi/api/chatflow/v1/resource/file.py +39 -0
- dify_oapi/api/chatflow/v1/resource/tts.py +29 -0
- dify_oapi/api/chatflow/v1/version.py +29 -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/retrieval_model.py +10 -4
- dify_oapi/api/knowledge/v1/model/weights.py +27 -0
- 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/client.py +45 -0
- 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-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/METADATA +44 -12
- {dify_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/RECORD +189 -34
- 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_oapi2-0.4.0.dist-info → dify_oapi2-0.5.0.dist-info}/LICENSE +0 -0
- {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
|
-
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: dify-oapi2
|
3
|
-
Version: 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
|
17
|
-
Requires-Dist: pydantic (>=
|
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
|
122
|
-
-
|
123
|
-
-
|
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
|
-
- [**
|
155
|
-
- [**
|
156
|
-
- [**
|
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
|