release: v1.0.0 (#32567)

Co-authored-by: Mohammad Mohtashim <45242107+keenborder786@users.noreply.github.com>
Co-authored-by: Caspar Broekhuizen <caspar@langchain.dev>
Co-authored-by: ccurme <chester.curme@gmail.com>
Co-authored-by: Christophe Bornet <cbornet@hotmail.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Sadra Barikbin <sadraqazvin1@yahoo.com>
Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
This commit is contained in:
Mason Daugherty
2025-10-02 10:49:42 -04:00
committed by GitHub
parent d7cce2f469
commit eaa6dcce9e
188 changed files with 23644 additions and 17479 deletions

View File

@@ -7,9 +7,9 @@ from unittest.mock import MagicMock, patch
import pytest
from httpx import ConnectError
from langchain_core.messages.ai import AIMessageChunk
from langchain_core.messages.ai import AIMessage, AIMessageChunk
from langchain_core.messages.human import HumanMessage
from langchain_core.messages.tool import ToolCallChunk
from langchain_core.messages.tool import ToolCallChunk, ToolMessage
from langchain_core.tools import tool
from ollama import ResponseError
from pydantic import BaseModel, Field, ValidationError
@@ -18,6 +18,7 @@ from typing_extensions import TypedDict
from langchain_ollama import ChatOllama
DEFAULT_MODEL_NAME = "llama3.1"
REASONING_MODEL_NAME = "gpt-oss:20b"
@tool
@@ -236,3 +237,59 @@ async def test_tool_astreaming(model: str) -> None:
if chunk.get("id")
)
assert final_tool_call["id"] == tool_call_id
@pytest.mark.parametrize(
("model", "output_version"),
[(REASONING_MODEL_NAME, None), (REASONING_MODEL_NAME, "v1")],
)
def test_agent_loop(model: str, output_version: Optional[str]) -> None:
"""Test agent loop with tool calling and message passing."""
@tool
def get_weather(location: str) -> str:
"""Get the weather for a location."""
return "It's sunny and 75 degrees."
llm = ChatOllama(model=model, output_version=output_version, reasoning="low")
llm_with_tools = llm.bind_tools([get_weather])
input_message = HumanMessage("What is the weather in San Francisco, CA?")
tool_call_message = llm_with_tools.invoke([input_message])
assert isinstance(tool_call_message, AIMessage)
tool_calls = tool_call_message.tool_calls
assert len(tool_calls) == 1
tool_call = tool_calls[0]
assert tool_call["name"] == "get_weather"
assert "location" in tool_call["args"]
tool_message = get_weather.invoke(tool_call)
assert isinstance(tool_message, ToolMessage)
assert tool_message.content
assert isinstance(tool_message.content, str)
assert "sunny" in tool_message.content.lower()
resp_message = llm_with_tools.invoke(
[
input_message,
tool_call_message,
tool_message,
]
)
follow_up = HumanMessage("Explain why that might be using a reasoning step.")
assert isinstance(resp_message, AIMessage)
assert len(resp_message.content) > 0
response = llm_with_tools.invoke(
[input_message, tool_call_message, tool_message, resp_message, follow_up]
)
assert isinstance(resp_message, AIMessage)
assert len(resp_message.content) > 0
if output_version == "v1":
content_blocks = response.content_blocks
assert content_blocks is not None
assert len(content_blocks) > 0
assert any(block["type"] == "text" for block in content_blocks)
assert any(block["type"] == "reasoning" for block in content_blocks)

View File

@@ -7,8 +7,10 @@ from langchain_ollama import ChatOllama
SAMPLE = "What is 3^3?"
REASONING_MODEL_NAME = "deepseek-r1:1.5b"
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_stream_no_reasoning(model: str, use_async: bool) -> None:
"""Test streaming with ``reasoning=False``."""
@@ -41,7 +43,7 @@ async def test_stream_no_reasoning(model: str, use_async: bool) -> None:
assert "reasoning_content" not in result.additional_kwargs
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_stream_reasoning_none(model: str, use_async: bool) -> None:
"""Test streaming with ``reasoning=None``."""
@@ -76,7 +78,7 @@ async def test_stream_reasoning_none(model: str, use_async: bool) -> None:
assert "</think>" not in result.additional_kwargs.get("reasoning_content", "")
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_reasoning_stream(model: str, use_async: bool) -> None:
"""Test streaming with ``reasoning=True``."""
@@ -111,8 +113,16 @@ async def test_reasoning_stream(model: str, use_async: bool) -> None:
assert "<think>" not in result.additional_kwargs["reasoning_content"]
assert "</think>" not in result.additional_kwargs["reasoning_content"]
content_blocks = result.content_blocks
assert content_blocks is not None
assert len(content_blocks) > 0
reasoning_blocks = [
block for block in content_blocks if block.get("type") == "reasoning"
]
assert len(reasoning_blocks) > 0
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_invoke_no_reasoning(model: str, use_async: bool) -> None:
"""Test invoke with ``reasoning=False``."""
@@ -128,7 +138,7 @@ async def test_invoke_no_reasoning(model: str, use_async: bool) -> None:
assert "</think>" not in result.content
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_invoke_reasoning_none(model: str, use_async: bool) -> None:
"""Test invoke with ``reasoning=None``."""
@@ -146,7 +156,7 @@ async def test_invoke_reasoning_none(model: str, use_async: bool) -> None:
assert "</think>" not in result.additional_kwargs.get("reasoning_content", "")
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@pytest.mark.parametrize("model", [REASONING_MODEL_NAME])
@pytest.mark.parametrize("use_async", [False, True])
async def test_reasoning_invoke(model: str, use_async: bool) -> None:
"""Test invoke with ``reasoning=True``."""
@@ -164,8 +174,16 @@ async def test_reasoning_invoke(model: str, use_async: bool) -> None:
assert "<think>" not in result.additional_kwargs["reasoning_content"]
assert "</think>" not in result.additional_kwargs["reasoning_content"]
content_blocks = result.content_blocks
assert content_blocks is not None
assert len(content_blocks) > 0
reasoning_blocks = [
block for block in content_blocks if block.get("type") == "reasoning"
]
assert len(reasoning_blocks) > 0
@pytest.mark.parametrize("model", ["deepseek-r1:1.5b"])
@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.