dify_plugin 0.1.0__tar.gz → 0.2.0__tar.gz

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 (114) hide show
  1. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/LICENSE +1 -1
  2. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/PKG-INFO +2 -2
  3. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/README.md +1 -1
  4. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/invocation.py +1 -0
  5. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/plugin/setup.py +1 -0
  6. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/plugin_executor.py +1 -1
  7. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/plugin_registration.py +2 -2
  8. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/runtime.py +10 -6
  9. dify_plugin-0.2.0/dify_plugin/core/server/__base/request_reader.py +114 -0
  10. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/serverless/request_reader.py +4 -2
  11. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/stdio/request_reader.py +10 -2
  12. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/tcp/request_reader.py +14 -8
  13. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/agent.py +2 -1
  14. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/endpoint.py +1 -1
  15. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/__init__.py +1 -0
  16. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/llm.py +33 -4
  17. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/message.py +2 -1
  18. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/tool.py +2 -2
  19. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/agent/__init__.py +11 -1
  20. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/ai_model.py +5 -6
  21. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/large_language_model.py +0 -2
  22. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/llm.py +9 -13
  23. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/tool/__init__.py +3 -3
  24. dify_plugin-0.2.0/dify_plugin/invocations/app/__init__.py +26 -0
  25. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/llm.py +0 -1
  26. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/text_embedding.py +1 -1
  27. dify_plugin-0.2.0/dify_plugin/invocations/storage.py +89 -0
  28. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/pyproject.toml +1 -1
  29. dify_plugin-0.2.0/tests/entities/models/test_llm.py +54 -0
  30. dify_plugin-0.2.0/tests/interfaces/agent/test_agent.py +22 -0
  31. dify_plugin-0.2.0/tests/invocations/test_storage.py +78 -0
  32. dify_plugin-0.2.0/tests/test_llm_result.py +43 -0
  33. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/tests/test_tool_call_model_init.py +5 -9
  34. dify_plugin-0.1.0/dify_plugin/core/server/__base/request_reader.py +0 -66
  35. dify_plugin-0.1.0/dify_plugin/invocations/storage.py +0 -51
  36. dify_plugin-0.1.0/tests/__init__.py +0 -0
  37. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/__init__.py +14 -14
  38. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/config/__init__.py +0 -0
  39. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/config/config.py +0 -0
  40. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/config/logger_format.py +0 -0
  41. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/__init__.py +0 -0
  42. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/__init__.py +0 -0
  43. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/message.py +0 -0
  44. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/plugin/__init__.py +0 -0
  45. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/plugin/io.py +0 -0
  46. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/plugin/parameter_type.py +0 -0
  47. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/entities/plugin/request.py +0 -0
  48. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/__base/__init__.py +0 -0
  49. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/__base/filter_reader.py +0 -0
  50. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/__base/response_writer.py +0 -0
  51. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/__base/writer_entities.py +0 -0
  52. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/__init__.py +0 -0
  53. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/io_server.py +0 -0
  54. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/router.py +0 -0
  55. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/serverless/__init__.py +0 -0
  56. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/serverless/response_writer.py +0 -0
  57. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/stdio/__init__.py +0 -0
  58. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/stdio/response_writer.py +0 -0
  59. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/server/tcp/__init__.py +0 -0
  60. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/utils/__init__.py +0 -0
  61. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/utils/class_loader.py +0 -0
  62. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/utils/http_parser.py +0 -0
  63. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/utils/position_helper.py +0 -0
  64. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/core/utils/yaml_loader.py +0 -0
  65. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/__init__.py +0 -0
  66. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/moderation.py +0 -0
  67. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/provider.py +0 -0
  68. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/rerank.py +0 -0
  69. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/speech2text.py +0 -0
  70. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/text_embedding.py +0 -0
  71. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/model/tts.py +0 -0
  72. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/entities/workflow_node.py +0 -0
  73. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/errors/__init__.py +0 -0
  74. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/errors/model.py +0 -0
  75. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/errors/tool.py +0 -0
  76. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/file/__init__.py +0 -0
  77. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/file/constants.py +0 -0
  78. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/file/entities.py +0 -0
  79. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/file/file.py +0 -0
  80. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/__init__.py +0 -0
  81. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/endpoint/__init__.py +0 -0
  82. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/__init__.py +0 -0
  83. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/audio.mp3 +0 -0
  84. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/moderation_model.py +0 -0
  85. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/__init__.py +0 -0
  86. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/common.py +0 -0
  87. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/provider.py +0 -0
  88. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/rerank.py +0 -0
  89. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/speech2text.py +1 -1
  90. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/text_embedding.py +1 -1
  91. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/openai_compatible/tts.py +1 -1
  92. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/rerank_model.py +0 -0
  93. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/speech2text_model.py +0 -0
  94. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/text_embedding_model.py +0 -0
  95. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/interfaces/model/tts_model.py +0 -0
  96. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/__init__.py +0 -0
  97. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/app/chat.py +0 -0
  98. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/app/completion.py +0 -0
  99. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/app/workflow.py +0 -0
  100. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/file.py +0 -0
  101. {dify_plugin-0.1.0/dify_plugin/invocations/app → dify_plugin-0.2.0/dify_plugin/invocations/model}/__init__.py +0 -0
  102. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/moderation.py +0 -0
  103. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/rerank.py +0 -0
  104. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/speech2text.py +0 -0
  105. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/model/tts.py +0 -0
  106. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/tool.py +0 -0
  107. {dify_plugin-0.1.0/dify_plugin/invocations/model → dify_plugin-0.2.0/dify_plugin/invocations/workflow_node}/__init__.py +0 -0
  108. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/workflow_node/parameter_extractor.py +0 -0
  109. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/invocations/workflow_node/question_classifier.py +0 -0
  110. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/plugin.py +1 -1
  111. {dify_plugin-0.1.0/dify_plugin/invocations/workflow_node → dify_plugin-0.2.0/dify_plugin/tool}/__init__.py +0 -0
  112. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/dify_plugin/tool/entities.py +0 -0
  113. {dify_plugin-0.1.0/dify_plugin/tool → dify_plugin-0.2.0/tests}/__init__.py +0 -0
  114. {dify_plugin-0.1.0 → dify_plugin-0.2.0}/tests/test_prompt_message.py +0 -0
@@ -199,4 +199,4 @@
199
199
  distributed under the License is distributed on an "AS IS" BASIS,
200
200
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
201
  See the License for the specific language governing permissions and
202
- limitations under the License.
202
+ limitations under the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dify_plugin
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Dify Plugin SDK
5
5
  Keywords: dify,plugin,sdk
6
6
  Author-Email: langgenius <hello@dify.ai>
@@ -43,6 +43,6 @@ When depending on this SDK, it's recommended to specify version constraints that
43
43
  Example in your project's dependency management:
44
44
 
45
45
  ```
46
- dify_plugin = ">=0.1.0,<0.2.0"
46
+ dify_plugin>=0.1.0,<0.2.0
47
47
  ```
48
48
 
@@ -19,6 +19,6 @@ When depending on this SDK, it's recommended to specify version constraints that
19
19
  Example in your project's dependency management:
20
20
 
21
21
  ```
22
- dify_plugin = ">=0.1.0,<0.2.0"
22
+ dify_plugin>=0.1.0,<0.2.0
23
23
  ```
24
24
 
@@ -15,6 +15,7 @@ class InvokeType(Enum):
15
15
  Storage = "storage"
16
16
  UploadFile = "upload_file"
17
17
  SYSTEM_SUMMARY = "system_summary"
18
+ FetchApp = "fetch_app"
18
19
 
19
20
  @classmethod
20
21
  def value_of(cls, value: str) -> "InvokeType":
@@ -75,6 +75,7 @@ class PluginConfiguration(BaseModel):
75
75
  version: str
76
76
  arch: list[PluginArch]
77
77
  runner: PluginRunner
78
+ minimum_dify_version: Optional[str] = Field(..., pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$")
78
79
 
79
80
  version: str = Field(..., pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$")
80
81
  type: PluginType
@@ -281,7 +281,7 @@ class PluginExecutor:
281
281
  except ValueError as e:
282
282
  response = Response(str(e), status=404)
283
283
  except Exception as e:
284
- response = Response(f"Internal Server Error: {str(e)}", status=500)
284
+ response = Response(f"Internal Server Error: {e!s}", status=500)
285
285
 
286
286
  # check if response is a generator
287
287
  if isinstance(response.response, Generator):
@@ -123,7 +123,7 @@ class PluginRegistration:
123
123
  self.agent_strategies_configuration.append(agent_provider_configuration)
124
124
 
125
125
  except Exception as e:
126
- raise ValueError(f"Error loading plugin configuration: {str(e)}") from e
126
+ raise ValueError(f"Error loading plugin configuration: {e!s}") from e
127
127
 
128
128
  def _resolve_tool_providers(self):
129
129
  """
@@ -349,4 +349,4 @@ class PluginRegistration:
349
349
  endpoint, values = adapter.match()
350
350
  return endpoint, values
351
351
  except werkzeug.exceptions.HTTPException as e:
352
- raise ValueError(f"Failed to dispatch endpoint request: {str(e)}") from e
352
+ raise ValueError(f"Failed to dispatch endpoint request: {e!s}") from e
@@ -1,13 +1,12 @@
1
- import json
2
1
  import uuid
3
2
  from abc import ABC
4
- from collections.abc import Generator
3
+ from collections.abc import Generator, Mapping
5
4
  from concurrent.futures import ThreadPoolExecutor
6
5
  from enum import Enum
7
- from typing import Generic, Optional, TypeVar, Union
6
+ from typing import Any, Generic, Optional, TypeVar, Union
8
7
 
9
8
  import httpx
10
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, TypeAdapter
11
10
  from yarl import URL
12
11
 
13
12
  from dify_plugin.config.config import InstallMethod
@@ -46,6 +45,7 @@ class ModelInvocations:
46
45
 
47
46
  class AppInvocations:
48
47
  def __init__(self, session: "Session"):
48
+ from dify_plugin.invocations.app import FetchAppInvocation
49
49
  from dify_plugin.invocations.app.chat import ChatAppInvocation
50
50
  from dify_plugin.invocations.app.completion import CompletionAppInvocation
51
51
  from dify_plugin.invocations.app.workflow import WorkflowAppInvocation
@@ -53,6 +53,10 @@ class AppInvocations:
53
53
  self.chat = ChatAppInvocation(session)
54
54
  self.completion = CompletionAppInvocation(session)
55
55
  self.workflow = WorkflowAppInvocation(session)
56
+ self.fetch_app_invocation = FetchAppInvocation(session)
57
+
58
+ def fetch_app(self, app_id: str) -> Mapping:
59
+ return self.fetch_app_invocation.get(app_id)
56
60
 
57
61
 
58
62
  class WorkflowNodeInvocations:
@@ -219,7 +223,7 @@ class BackwardsInvocation(Generic[T], ABC):
219
223
  try:
220
224
  yield data_type(**event.data)
221
225
  except Exception as e:
222
- raise Exception(f"Failed to parse response: {str(e)}") from e
226
+ raise Exception(f"Failed to parse response: {e!s}") from e
223
227
 
224
228
  def _http_backwards_invoke(
225
229
  self,
@@ -271,7 +275,7 @@ class BackwardsInvocation(Generic[T], ABC):
271
275
  if not line:
272
276
  continue
273
277
 
274
- data = json.loads(line)
278
+ data = TypeAdapter(dict[str, Any]).validate_json(line)
275
279
  yield PluginInStreamBase(
276
280
  session_id=data["session_id"],
277
281
  event=PluginInStreamEvent.value_of(data["event"]),
@@ -0,0 +1,114 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import Callable, Generator
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from dify_plugin.core.entities.plugin.io import PluginInStream
10
+
11
+ from dify_plugin.core.server.__base.filter_reader import (
12
+ FilterReader,
13
+ )
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class RequestReader(ABC):
19
+ def __init__(self):
20
+ # Convert class variables to instance variables to avoid global lock contention
21
+ self.lock = threading.Lock()
22
+ self.readers = []
23
+
24
+ @abstractmethod
25
+ def _read_stream(self) -> Generator["PluginInStream", None, None]:
26
+ """
27
+ Read stream from stdin
28
+ """
29
+ raise NotImplementedError
30
+
31
+ def event_loop(self):
32
+ # read line by line
33
+ while True:
34
+ try:
35
+ for line in self._read_stream():
36
+ self._process_line(line)
37
+ except Exception:
38
+ logger.exception("Error in event loop")
39
+ time.sleep(0.01) # Prevent high CPU usage
40
+
41
+ def _process_line(self, data: "PluginInStream"):
42
+ try:
43
+ session_id = data.session_id
44
+ readers_to_process = []
45
+
46
+ # Acquire lock to safely access readers list
47
+ self.lock.acquire()
48
+ try:
49
+ # Safely copy the reader list under lock protection
50
+ readers_to_process = self.readers.copy()
51
+ finally:
52
+ self.lock.release()
53
+
54
+ # Execute filter operations outside of lock
55
+ matched_readers = []
56
+ for reader in readers_to_process:
57
+ try:
58
+ result = reader.filter(data)
59
+ if result:
60
+ matched_readers.append(reader)
61
+ except Exception:
62
+ logger.exception("Error in filter")
63
+
64
+ # Process readers in batches to avoid blocking
65
+ for reader in matched_readers:
66
+ try:
67
+ reader.write(data)
68
+ except Exception:
69
+ logger.exception("Error writing to reader")
70
+
71
+ except Exception as e:
72
+ data.writer.error(
73
+ session_id=session_id,
74
+ data={"error": f"Failed to process request ({type(e).__name__}): {e!s}"},
75
+ )
76
+
77
+ def read(self, filter: Callable[["PluginInStream"], bool]) -> FilterReader: # noqa: A002
78
+ def close(reader: FilterReader):
79
+ self.lock.acquire()
80
+ try:
81
+ if reader in self.readers:
82
+ self.readers.remove(reader)
83
+ finally:
84
+ self.lock.release()
85
+
86
+ reader = FilterReader(filter, close_callback=lambda: close(reader))
87
+
88
+ self.lock.acquire()
89
+ try:
90
+ self.readers.append(reader)
91
+ finally:
92
+ self.lock.release()
93
+
94
+ return reader
95
+
96
+ def close(self):
97
+ """
98
+ close stdin processing
99
+ """
100
+ readers_to_close = []
101
+
102
+ self.lock.acquire()
103
+ try:
104
+ readers_to_close = self.readers.copy()
105
+ self.readers.clear()
106
+ finally:
107
+ self.lock.release()
108
+
109
+ # Close readers outside the lock
110
+ for reader in readers_to_close:
111
+ try:
112
+ reader.close()
113
+ except Exception:
114
+ logger.exception("Error closing reader")
@@ -1,7 +1,7 @@
1
1
  import threading
2
+ import time
2
3
  from collections.abc import Generator
3
4
  from queue import Empty, Queue
4
- import time
5
5
 
6
6
  from flask import Flask, request
7
7
 
@@ -27,6 +27,7 @@ class ServerlessRequestReader(RequestReader):
27
27
  """
28
28
  Initialize the ServerlessStream and wait for jobs
29
29
  """
30
+ super().__init__()
30
31
  self.app = Flask(__name__)
31
32
  self.host = host
32
33
  self.port = port
@@ -94,6 +95,7 @@ class ServerlessRequestReader(RequestReader):
94
95
  self.app.route("/health", methods=["GET"])(self.health)
95
96
 
96
97
  import socket
98
+
97
99
  import gevent.socket
98
100
 
99
101
  if socket.socket is gevent.socket.socket:
@@ -101,7 +103,7 @@ class ServerlessRequestReader(RequestReader):
101
103
 
102
104
  server = WSGIServer((self.host, self.port), self.app)
103
105
  print("* Serving Flask app 'dify_plugin.core.server.serverless.request_reader'")
104
- print("* Running on http://%s:%d (Press CTRL+C to quit)" % (self.host, self.port))
106
+ print(f"* Running on http://{self.host}:{self.port} (Press CTRL+C to quit)")
105
107
  print("* Server Worker: gevent.wsgi.WSGIServer", flush=True)
106
108
  server.serve_forever()
107
109
  else:
@@ -1,8 +1,9 @@
1
1
  import sys
2
2
  from collections.abc import Generator
3
- from json import loads
3
+ from typing import Any
4
4
 
5
5
  from gevent.os import tp_read
6
+ from pydantic import TypeAdapter
6
7
 
7
8
  from dify_plugin.core.entities.plugin.io import (
8
9
  PluginInStream,
@@ -13,6 +14,9 @@ from dify_plugin.core.server.stdio.response_writer import StdioResponseWriter
13
14
 
14
15
 
15
16
  class StdioRequestReader(RequestReader):
17
+ def __init__(self):
18
+ super().__init__()
19
+
16
20
  def _read_stream(self) -> Generator[PluginInStream, None, None]:
17
21
  buffer = b""
18
22
  while True:
@@ -34,9 +38,13 @@ class StdioRequestReader(RequestReader):
34
38
  lines = lines[:-1]
35
39
  for line in lines:
36
40
  try:
37
- data = loads(line)
41
+ data = TypeAdapter(dict[str, Any]).validate_json(line)
38
42
  yield PluginInStream(
39
43
  session_id=data["session_id"],
44
+ conversation_id=data.get("conversation_id"),
45
+ message_id=data.get("message_id"),
46
+ app_id=data.get("app_id"),
47
+ endpoint_id=data.get("endpoint_id"),
40
48
  event=PluginInStreamEvent.value_of(data["event"]),
41
49
  data=data["data"],
42
50
  reader=self,
@@ -2,18 +2,18 @@ import errno
2
2
  import logging
3
3
  import os
4
4
  import signal
5
- from threading import Lock
6
- import time
7
5
  import socket as native_socket
8
- from collections.abc import Generator
9
- from json import loads
10
- from typing import Callable, Optional
6
+ import time
7
+ from collections.abc import Callable, Generator
8
+ from threading import Lock
9
+ from typing import Any, Optional
11
10
 
11
+ from gevent import sleep
12
+ from gevent import socket as gevent_socket
12
13
  from gevent.select import select
13
- from gevent import socket as gevent_socket, sleep
14
+ from pydantic import TypeAdapter
14
15
 
15
16
  from dify_plugin.core.entities.message import InitializeMessage
16
-
17
17
  from dify_plugin.core.entities.plugin.io import (
18
18
  PluginInStream,
19
19
  PluginInStreamEvent,
@@ -37,6 +37,8 @@ class TCPReaderWriter(RequestReader, ResponseWriter):
37
37
  """
38
38
  Initialize the TCPStream and connect to the target, raise exception if connection failed
39
39
  """
40
+ super().__init__()
41
+
40
42
  self.host = host
41
43
  self.port = port
42
44
  self.key = key
@@ -188,9 +190,13 @@ class TCPReaderWriter(RequestReader, ResponseWriter):
188
190
  lines = lines[:-1]
189
191
  for line in lines:
190
192
  try:
191
- data = loads(line)
193
+ data = TypeAdapter(dict[str, Any]).validate_json(line)
192
194
  chunk = PluginInStream(
193
195
  session_id=data["session_id"],
196
+ conversation_id=data.get("conversation_id"),
197
+ message_id=data.get("message_id"),
198
+ app_id=data.get("app_id"),
199
+ endpoint_id=data.get("endpoint_id"),
194
200
  event=PluginInStreamEvent.value_of(data["event"]),
195
201
  data=data["data"],
196
202
  reader=self,
@@ -45,6 +45,7 @@ class AgentStrategyParameter(BaseModel):
45
45
 
46
46
  name: str = Field(..., description="The name of the parameter")
47
47
  label: I18nObject = Field(..., description="The label presented to the user")
48
+ help: Optional[I18nObject] = None
48
49
  type: ToolParameterType = Field(..., description="The type of the parameter")
49
50
  auto_generate: Optional[ParameterAutoGenerate] = Field(
50
51
  default=None, description="The auto generate of the parameter"
@@ -115,7 +116,7 @@ class AgentStrategyProviderConfiguration(BaseModel):
115
116
  )
116
117
  )
117
118
  except Exception as e:
118
- raise ValueError(f"Error loading agent strategy configuration: {str(e)}") from e
119
+ raise ValueError(f"Error loading agent strategy configuration: {e!s}") from e
119
120
 
120
121
  return strategies
121
122
 
@@ -38,6 +38,6 @@ class EndpointProviderConfiguration(BaseModel):
38
38
  file = load_yaml_file(endpoint)
39
39
  endpoints.append(EndpointConfiguration(**file))
40
40
  except Exception as e:
41
- raise ValueError(f"Error loading endpoint configuration: {str(e)}") from e
41
+ raise ValueError(f"Error loading endpoint configuration: {e!s}") from e
42
42
 
43
43
  return endpoints
@@ -199,6 +199,7 @@ class ModelFeature(Enum):
199
199
  DOCUMENT = "document"
200
200
  VIDEO = "video"
201
201
  AUDIO = "audio"
202
+ STRUCTURED_OUTPUT = "structured-output"
202
203
 
203
204
 
204
205
  class ParameterType(Enum):
@@ -2,7 +2,7 @@ from decimal import Decimal
2
2
  from enum import Enum
3
3
  from typing import Optional
4
4
 
5
- from pydantic import BaseModel, ConfigDict, Field
5
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
6
6
 
7
7
  from dify_plugin.entities.model import BaseModelConfig, ModelType, ModelUsage, PriceInfo
8
8
  from dify_plugin.entities.model.message import (
@@ -86,10 +86,25 @@ class LLMResultChunk(BaseModel):
86
86
  """
87
87
 
88
88
  model: str
89
- prompt_messages: list[PromptMessage]
89
+ prompt_messages: list[PromptMessage] = Field(default_factory=list)
90
90
  system_fingerprint: Optional[str] = None
91
91
  delta: LLMResultChunkDelta
92
92
 
93
+ @field_validator("prompt_messages", mode="before")
94
+ @classmethod
95
+ def transform_prompt_messages(cls, value):
96
+ """
97
+ ISSUE:
98
+ - https://github.com/langgenius/dify/issues/17799
99
+ - https://github.com/langgenius/dify-official-plugins/issues/648
100
+
101
+ The `prompt_messages` field is deprecated, but to keep backward compatibility
102
+ we need to always set it to an empty list.
103
+
104
+ NOTE: just do not use it anymore, it will be removed in the future.
105
+ """
106
+ return []
107
+
93
108
 
94
109
  class LLMResult(BaseModel):
95
110
  """
@@ -97,15 +112,29 @@ class LLMResult(BaseModel):
97
112
  """
98
113
 
99
114
  model: str
100
- prompt_messages: list[PromptMessage]
115
+ prompt_messages: list[PromptMessage] = Field(default_factory=list)
101
116
  message: AssistantPromptMessage
102
117
  usage: LLMUsage
103
118
  system_fingerprint: Optional[str] = None
104
119
 
120
+ @field_validator("prompt_messages", mode="before")
121
+ @classmethod
122
+ def transform_prompt_messages(cls, value):
123
+ """
124
+ ISSUE:
125
+ - https://github.com/langgenius/dify/issues/17799
126
+ - https://github.com/langgenius/dify-official-plugins/issues/648
127
+
128
+ The `prompt_messages` field is deprecated, but to keep backward compatibility
129
+ we need to always set it to an empty list.
130
+
131
+ NOTE: just do not use it anymore, it will be removed in the future.
132
+ """
133
+ return []
134
+
105
135
  def to_llm_result_chunk(self) -> "LLMResultChunk":
106
136
  return LLMResultChunk(
107
137
  model=self.model,
108
- prompt_messages=self.prompt_messages,
109
138
  system_fingerprint=self.system_fingerprint,
110
139
  delta=LLMResultChunkDelta(
111
140
  index=0,
@@ -2,7 +2,7 @@ from collections.abc import Sequence
2
2
  from enum import Enum, StrEnum
3
3
  from typing import Annotated, Literal, Optional, Union
4
4
 
5
- from pydantic import BaseModel, Field, field_validator, BeforeValidator
5
+ from pydantic import BaseModel, BeforeValidator, Field, field_validator
6
6
 
7
7
 
8
8
  class PromptMessageRole(Enum):
@@ -188,6 +188,7 @@ def _ensure_field_empty_str(value: Optional[str]) -> str:
188
188
  return ""
189
189
  return value
190
190
 
191
+
191
192
  class AssistantPromptMessage(PromptMessage):
192
193
  """
193
194
  Model class for assistant prompt message.
@@ -81,7 +81,7 @@ class ToolInvokeMessage(BaseModel):
81
81
  return {"text": self.text}
82
82
 
83
83
  class JsonMessage(BaseModel):
84
- json_object: dict
84
+ json_object: Mapping
85
85
 
86
86
  def to_dict(self):
87
87
  return {"json_object": self.json_object}
@@ -384,7 +384,7 @@ class ToolProviderConfiguration(BaseModel):
384
384
  )
385
385
  )
386
386
  except Exception as e:
387
- raise ValueError(f"Error loading tool configuration: {str(e)}") from e
387
+ raise ValueError(f"Error loading tool configuration: {e!s}") from e
388
388
 
389
389
  return tools
390
390
 
@@ -28,7 +28,7 @@ class AgentToolIdentity(ToolIdentity):
28
28
 
29
29
  class AgentModelConfig(LLMModelConfig):
30
30
  entity: AIModelEntity | None = None
31
- history_prompt_messages: list[PromptMessage] = []
31
+ history_prompt_messages: list[PromptMessage] = Field(default_factory=list)
32
32
 
33
33
  @field_validator("history_prompt_messages", mode="before")
34
34
  @classmethod
@@ -277,6 +277,11 @@ class AgentStrategy(ToolLike[AgentInvokeMessage]):
277
277
  ToolParameter.ToolParameterType.FILES,
278
278
  }:
279
279
  continue
280
+ if parameter.type in {
281
+ ToolParameter.ToolParameterType.SELECT,
282
+ ToolParameter.ToolParameterType.SECRET_INPUT,
283
+ }:
284
+ parameter_type = ToolParameter.ToolParameterType.STRING
280
285
  enum = []
281
286
  if parameter.type == ToolParameter.ToolParameterType.SELECT:
282
287
  enum = [option.value for option in parameter.options] if parameter.options else []
@@ -311,6 +316,11 @@ class AgentStrategy(ToolLike[AgentInvokeMessage]):
311
316
  ToolParameter.ToolParameterType.FILES,
312
317
  }:
313
318
  continue
319
+ if parameter.type in {
320
+ ToolParameter.ToolParameterType.SELECT,
321
+ ToolParameter.ToolParameterType.SECRET_INPUT,
322
+ }:
323
+ parameter_type = ToolParameter.ToolParameterType.STRING
314
324
  enum = []
315
325
  if parameter.type == ToolParameter.ToolParameterType.SELECT:
316
326
  enum = [option.value for option in parameter.options] if parameter.options else []
@@ -1,4 +1,5 @@
1
1
  import decimal
2
+ import socket
2
3
  from abc import ABC, abstractmethod
3
4
  from collections.abc import Mapping
4
5
  from typing import Optional
@@ -18,8 +19,6 @@ from dify_plugin.entities.model import (
18
19
  )
19
20
  from dify_plugin.errors.model import InvokeAuthorizationError, InvokeError
20
21
 
21
- import socket
22
-
23
22
  if socket.socket is gevent.socket.socket:
24
23
  import gevent.threadpool
25
24
 
@@ -84,9 +83,9 @@ class AIModel(ABC):
84
83
  "please check and try again. "
85
84
  )
86
85
 
87
- return invoke_error(description=f"[{provider_name}] {invoke_error.description}, {str(error)}")
86
+ return invoke_error(description=f"[{provider_name}] {invoke_error.description}, {error!s}")
88
87
 
89
- return InvokeError(description=f"[{provider_name}] Error: {str(error)}")
88
+ return InvokeError(description=f"[{provider_name}] Error: {error!s}")
90
89
 
91
90
  def get_price(self, model: str, credentials: dict, price_type: PriceType, tokens: int) -> PriceInfo:
92
91
  """
@@ -271,11 +270,11 @@ class AIModel(ABC):
271
270
  if len(text) >= 100000:
272
271
  return len(text)
273
272
 
274
- import tiktoken
275
-
276
273
  # check if gevent is patched to main thread
277
274
  import socket
278
275
 
276
+ import tiktoken
277
+
279
278
  if socket.socket is gevent.socket.socket:
280
279
  # using gevent real thread to avoid blocking main thread
281
280
  result = threadpool.spawn(lambda: len(tiktoken.encoding_for_model("gpt2").encode(text)))
@@ -440,7 +440,6 @@ if you are not sure about the structure.
440
440
  if new_piece:
441
441
  yield LLMResultChunk(
442
442
  model=model,
443
- prompt_messages=prompt_messages,
444
443
  delta=LLMResultChunkDelta(
445
444
  index=0,
446
445
  message=AssistantPromptMessage(content=new_piece, tool_calls=[]),
@@ -515,7 +514,6 @@ if you are not sure about the structure.
515
514
  # Only yield content collected within the code block
516
515
  yield LLMResultChunk(
517
516
  model=model,
518
- prompt_messages=prompt_messages,
519
517
  delta=LLMResultChunkDelta(
520
518
  index=0,
521
519
  message=AssistantPromptMessage(content=new_piece, tool_calls=[]),