diff --git a/libs/partners/ollama/pyproject.toml b/libs/partners/ollama/pyproject.toml index 63306aa0421..1766105cfbd 100644 --- a/libs/partners/ollama/pyproject.toml +++ b/libs/partners/ollama/pyproject.toml @@ -8,10 +8,10 @@ license = { text = "MIT" } requires-python = ">=3.10.0,<4.0.0" dependencies = [ "ollama>=0.6.0,<1.0.0", - "langchain-core>=1.0.0a7,<2.0.0", + "langchain-core>=1.0.0,<2.0.0", ] name = "langchain-ollama" -version = "1.0.0a1" +version = "1.0.0" description = "An integration package connecting Ollama and LangChain" readme = "README.md" diff --git a/libs/partners/ollama/tests/integration_tests/chat_models/test_chat_models_reasoning.py b/libs/partners/ollama/tests/integration_tests/chat_models/test_chat_models_reasoning.py index 6ea05a626fa..33d1fe14338 100644 --- a/libs/partners/ollama/tests/integration_tests/chat_models/test_chat_models_reasoning.py +++ b/libs/partners/ollama/tests/integration_tests/chat_models/test_chat_models_reasoning.py @@ -71,11 +71,8 @@ async def test_stream_reasoning_none(model: str, use_async: bool) -> None: result += chunk assert isinstance(result, AIMessageChunk) assert result.content - assert "" in result.content - assert "" in result.content + # reasoning_content is only captured when reasoning=True assert "reasoning_content" not in result.additional_kwargs - assert "" not in result.additional_kwargs.get("reasoning_content", "") - assert "" not in result.additional_kwargs.get("reasoning_content", "") @pytest.mark.parametrize("model", [REASONING_MODEL_NAME]) @@ -149,11 +146,8 @@ async def test_invoke_reasoning_none(model: str, use_async: bool) -> None: else: result = llm.invoke([message]) assert result.content + # reasoning_content is only captured when reasoning=True assert "reasoning_content" not in result.additional_kwargs - assert "" in result.content - assert "" in result.content - assert "" not in result.additional_kwargs.get("reasoning_content", "") - assert "" not in result.additional_kwargs.get("reasoning_content", "") @pytest.mark.parametrize("model", [REASONING_MODEL_NAME]) @@ -184,40 +178,49 @@ async def test_reasoning_invoke(model: str, use_async: bool) -> None: @pytest.mark.parametrize("model", [REASONING_MODEL_NAME]) -def test_think_tag_stripping_necessity(model: str) -> None: - """Test that demonstrates why `_strip_think_tags` is necessary. +def test_reasoning_modes_behavior(model: str) -> None: + """Test the behavior differences between reasoning modes. - DeepSeek R1 models include reasoning/thinking as their default behavior. - When `reasoning=False` is set, the user explicitly wants no reasoning content, - but Ollama cannot disable thinking at the API level for these models. - Therefore, post-processing is required to strip the `` tags. + This test documents how the Ollama API and LangChain handle reasoning content + for DeepSeek R1 models across different reasoning settings. - This test documents the specific behavior that necessitates the - `_strip_think_tags` function in the chat_models.py implementation. + Current Ollama API behavior: + - Ollama automatically separates reasoning content into a 'thinking' field + - No tags are present in responses + - `think=False` prevents the 'thinking' field from being included + - `think=None` includes the 'thinking' field (model default) + - `think=True` explicitly requests the 'thinking' field + + LangChain behavior: + - `reasoning=False`: Does not capture reasoning content + - `reasoning=None`: Does not capture reasoning content (model default behavior) + - `reasoning=True`: Captures reasoning in `additional_kwargs['reasoning_content']` """ - # Test with reasoning=None (default behavior - should include think tags) - llm_default = ChatOllama(model=model, reasoning=None, num_ctx=2**12) message = HumanMessage(content=SAMPLE) + # Test with reasoning=None (model default - no reasoning captured) + llm_default = ChatOllama(model=model, reasoning=None, num_ctx=2**12) result_default = llm_default.invoke([message]) - - # With reasoning=None, the model's default behavior includes tags - # This demonstrates why we need the stripping logic - assert "" in result_default.content - assert "" in result_default.content + assert result_default.content + assert "" not in result_default.content + assert "" not in result_default.content assert "reasoning_content" not in result_default.additional_kwargs - # Test with reasoning=False (explicit disable - should NOT include think tags) + # Test with reasoning=False (explicit disable - no reasoning captured) llm_disabled = ChatOllama(model=model, reasoning=False, num_ctx=2**12) - result_disabled = llm_disabled.invoke([message]) - - # With reasoning=False, think tags should be stripped from content - # This verifies that _strip_think_tags is working correctly + assert result_disabled.content assert "" not in result_disabled.content assert "" not in result_disabled.content assert "reasoning_content" not in result_disabled.additional_kwargs - # Verify the difference: same model, different reasoning settings - # Default includes tags, disabled strips them - assert result_default.content != result_disabled.content + # Test with reasoning=True (reasoning captured separately) + llm_enabled = ChatOllama(model=model, reasoning=True, num_ctx=2**12) + result_enabled = llm_enabled.invoke([message]) + assert result_enabled.content + assert "" not in result_enabled.content + assert "" not in result_enabled.content + assert "reasoning_content" in result_enabled.additional_kwargs + assert len(result_enabled.additional_kwargs["reasoning_content"]) > 0 + assert "" not in result_enabled.additional_kwargs["reasoning_content"] + assert "" not in result_enabled.additional_kwargs["reasoning_content"] diff --git a/libs/partners/ollama/uv.lock b/libs/partners/ollama/uv.lock index 40e41454de8..06ad39d6905 100644 --- a/libs/partners/ollama/uv.lock +++ b/libs/partners/ollama/uv.lock @@ -288,7 +288,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.0.0a8" +version = "1.0.0" source = { editable = "../../core" } dependencies = [ { name = "jsonpatch" }, @@ -346,7 +346,7 @@ typing = [ [[package]] name = "langchain-ollama" -version = "1.0.0a1" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "langchain-core" }, @@ -400,7 +400,7 @@ typing = [ [[package]] name = "langchain-tests" -version = "1.0.0a2" +version = "1.0.0" source = { editable = "../../standard-tests" } dependencies = [ { name = "httpx" },