From b299cf86e822f8726e2fe1dbe3e6507220d21c1b Mon Sep 17 00:00:00 2001 From: "zhu.junling" Date: Mon, 22 Dec 2025 09:38:04 +0800 Subject: [PATCH 1/4] Adapt to the Ollama reasoning platform and use the "reasoning" JSON key to return the reasoning content --- python/dify_plugin/interfaces/model/large_language_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/dify_plugin/interfaces/model/large_language_model.py b/python/dify_plugin/interfaces/model/large_language_model.py index 145bab46..49348948 100644 --- a/python/dify_plugin/interfaces/model/large_language_model.py +++ b/python/dify_plugin/interfaces/model/large_language_model.py @@ -535,7 +535,7 @@ def _wrap_thinking_by_reasoning_content(self, delta: dict, is_reasoning: bool) - """ content = delta.get("content") or "" - reasoning_content = delta.get("reasoning_content") + reasoning_content = delta.get("reasoning_content") or delta.get("reasoning") if reasoning_content: if not is_reasoning: From e84c4de811051a76f64a9d44b99d10290cba2644 Mon Sep 17 00:00:00 2001 From: "zhu.junling" Date: Mon, 22 Dec 2025 09:44:42 +0800 Subject: [PATCH 2/4] update method document. --- python/dify_plugin/interfaces/model/large_language_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/dify_plugin/interfaces/model/large_language_model.py b/python/dify_plugin/interfaces/model/large_language_model.py index 49348948..06f9d0a2 100644 --- a/python/dify_plugin/interfaces/model/large_language_model.py +++ b/python/dify_plugin/interfaces/model/large_language_model.py @@ -526,8 +526,8 @@ def _code_block_mode_stream_processor_with_backtick( def _wrap_thinking_by_reasoning_content(self, delta: dict, is_reasoning: bool) -> tuple[str, bool]: """ - If the reasoning response is from delta.get("reasoning_content"), we wrap - it with HTML think tag. + If the reasoning response is from delta.get("reasoning_content") or delta.get("reasoning"), + we wrap it with HTML think tag. :param delta: delta dictionary from LLM streaming response :param is_reasoning: is reasoning From 3f7b316c0dd2378dee92e8635766cc72b3243f72 Mon Sep 17 00:00:00 2001 From: "zhu.junling" Date: Thu, 25 Dec 2025 14:27:38 +0800 Subject: [PATCH 3/4] add tests --- .../model/test_llm_ollama_adapter.py | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 python/tests/interfaces/model/test_llm_ollama_adapter.py diff --git a/python/tests/interfaces/model/test_llm_ollama_adapter.py b/python/tests/interfaces/model/test_llm_ollama_adapter.py new file mode 100644 index 00000000..de244c54 --- /dev/null +++ b/python/tests/interfaces/model/test_llm_ollama_adapter.py @@ -0,0 +1,146 @@ +import unittest + +from dify_plugin.entities.model import AIModelEntity, ModelPropertyKey, ModelType +from dify_plugin.entities.model.llm import LLMMode, LLMResult +from dify_plugin.interfaces.model.large_language_model import LargeLanguageModel + + +class MockLLM(LargeLanguageModel): + """ + Concrete Mock class for testing non-abstract methods of LargeLanguageModel. + """ + + def _invoke( + self, + model: str, + credentials: dict, + prompt_messages: list, + model_parameters: dict, + tools: list, + stop: list, + stream: bool, + user: str, + ) -> LLMResult: + pass + + def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list, tools: list) -> int: + return 0 + + def validate_credentials(self, model: str, credentials: dict) -> None: + pass + + @property + def _invoke_error_mapping(self) -> dict: + return {} + + +class TestOllamaAdapter(unittest.TestCase): + def setUp(self): + # Create a dummy model schema to satisfy AIModel.__init__ + dummy_schema = AIModelEntity( + model="mock_model", + label={"en_US": "Mock Model"}, + model_type=ModelType.LLM, + features=[], + model_properties={ModelPropertyKey.MODE: LLMMode.CHAT.value, ModelPropertyKey.CONTEXT_SIZE: 4096}, + parameter_rules=[], + pricing=None, + deprecated=False, + ) + self.llm = MockLLM(model_schemas=[dummy_schema]) + + + def test_with_reasoning_content(self): + """ + The test includes reasoning_content, + and the output should contain the tag. + """ + + # Simulate simulated streaming data: + # 1. Has reasoning_content + + chunks = [ + # Chunk 1: Thinking started + {"reasoning_content": "Thinking started.", "content": ""}, + # Chunk 2: Still thinking + {"reasoning_content": " Still thinking.", "content": ""}, + ] + + # Assume we are testing the logic function itself: + is_reasoning = False + full_output = "" + + for chunk in chunks: + # Directly call the implementation in SDK to verify real code logic + output, is_reasoning = self.llm._wrap_thinking_by_reasoning_content(chunk, is_reasoning) + full_output += output + + # Verify results + print(f"DEBUG Output: {full_output!r}") + + assert "" in full_output + assert "Thinking started. Still thinking." in full_output + + + def test_with_reasoning(self): + """ + The test includes reasoning, + and the output should contain the tag. + """ + + # Simulate simulated streaming data: + # 1. Has reasoning + + chunks = [ + # Chunk 1: Thinking started + {"reasoning": "Thinking started.", "content": ""}, + # Chunk 2: Still thinking + {"reasoning": " Still thinking.", "content": ""}, + ] + + # Assume we are testing the logic function itself: + is_reasoning = False + full_output = "" + + for chunk in chunks: + # Directly call the implementation in SDK to verify real code logic + output, is_reasoning = self.llm._wrap_thinking_by_reasoning_content(chunk, is_reasoning) + full_output += output + + # Verify results + print(f"DEBUG Output: {full_output!r}") + + assert "" in full_output + assert "Thinking started. Still thinking." in full_output + + + def test_without_reasoning(self): + """ + The test does not include reasoning_content or reasoning. + The output should not contain the tag. + """ + + # Simulate simulated streaming data: + # 1. Has no reasoning_content and reasoning + + chunks = [ + # Chunk 1: No Thinking + {"content": "Content started."}, + # Chunk 2: Still No thinking + {"content": " Still content."}, + ] + + # Assume we are testing the logic function itself: + is_reasoning = False + full_output = "" + + for chunk in chunks: + # Directly call the implementation in SDK to verify real code logic + output, is_reasoning = self.llm._wrap_thinking_by_reasoning_content(chunk, is_reasoning) + full_output += output + + # Verify results + print(f"DEBUG Output: {full_output!r}") + + assert "" not in full_output + assert "Content started. Still content." in full_output From 3a676fc900d01d9dd6b29ed45a5cff34b00adb56 Mon Sep 17 00:00:00 2001 From: "zhu.junling" Date: Thu, 25 Dec 2025 14:39:25 +0800 Subject: [PATCH 4/4] add content chunk --- python/tests/interfaces/model/test_llm_ollama_adapter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/tests/interfaces/model/test_llm_ollama_adapter.py b/python/tests/interfaces/model/test_llm_ollama_adapter.py index de244c54..d85c7147 100644 --- a/python/tests/interfaces/model/test_llm_ollama_adapter.py +++ b/python/tests/interfaces/model/test_llm_ollama_adapter.py @@ -64,6 +64,7 @@ def test_with_reasoning_content(self): {"reasoning_content": "Thinking started.", "content": ""}, # Chunk 2: Still thinking {"reasoning_content": " Still thinking.", "content": ""}, + {"content": "Content started."}, ] # Assume we are testing the logic function itself: @@ -96,6 +97,7 @@ def test_with_reasoning(self): {"reasoning": "Thinking started.", "content": ""}, # Chunk 2: Still thinking {"reasoning": " Still thinking.", "content": ""}, + {"content": "Content started."}, ] # Assume we are testing the logic function itself: