mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-08 06:23:20 +00:00
partners: Add Perplexity Chat Integration (#30618)
Perplexity's importance in the space has been growing, so we think it's time to add an official integration! Note: following the release of `langchain-perplexity` to `pypi`, we should be able to add `perplexity` as an extra in `libs/langchain/pyproject.toml`, but we're blocked by a circular import for now. --------- Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: Chester Curme <chester.curme@gmail.com>
This commit is contained in:
0
libs/partners/perplexity/tests/__init__.py
Normal file
0
libs/partners/perplexity/tests/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Standard LangChain interface tests."""
|
||||
|
||||
from typing import Type
|
||||
|
||||
import pytest
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_tests.integration_tests import ChatModelIntegrationTests
|
||||
|
||||
from langchain_perplexity import ChatPerplexity
|
||||
|
||||
|
||||
class TestPerplexityStandard(ChatModelIntegrationTests):
|
||||
@property
|
||||
def chat_model_class(self) -> Type[BaseChatModel]:
|
||||
return ChatPerplexity
|
||||
|
||||
@property
|
||||
def chat_model_params(self) -> dict:
|
||||
return {"model": "sonar"}
|
||||
|
||||
@pytest.mark.xfail(reason="TODO: handle in integration.")
|
||||
def test_double_messages_conversation(self, model: BaseChatModel) -> None:
|
||||
super().test_double_messages_conversation(model)
|
||||
|
||||
@pytest.mark.xfail(reason="Raises 400: Custom stop words not supported.")
|
||||
def test_stop_sequence(self, model: BaseChatModel) -> None:
|
||||
super().test_stop_sequence(model)
|
@@ -0,0 +1,7 @@
|
||||
import pytest # type: ignore[import-not-found]
|
||||
|
||||
|
||||
@pytest.mark.compile
|
||||
def test_placeholder() -> None:
|
||||
"""Used for compiling integration tests without running any real tests."""
|
||||
pass
|
3
libs/partners/perplexity/tests/unit_tests/__init__.py
Normal file
3
libs/partners/perplexity/tests/unit_tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import os
|
||||
|
||||
os.environ["PPLX_API_KEY"] = "test"
|
195
libs/partners/perplexity/tests/unit_tests/test_chat_models.py
Normal file
195
libs/partners/perplexity/tests/unit_tests/test_chat_models.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from langchain_core.messages import AIMessageChunk, BaseMessageChunk
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from langchain_perplexity import ChatPerplexity
|
||||
|
||||
|
||||
def test_perplexity_model_name_param() -> None:
|
||||
llm = ChatPerplexity(model="foo")
|
||||
assert llm.model == "foo"
|
||||
|
||||
|
||||
def test_perplexity_model_kwargs() -> None:
|
||||
llm = ChatPerplexity(model="test", model_kwargs={"foo": "bar"})
|
||||
assert llm.model_kwargs == {"foo": "bar"}
|
||||
|
||||
|
||||
def test_perplexity_initialization() -> None:
|
||||
"""Test perplexity initialization."""
|
||||
# Verify that chat perplexity can be initialized using a secret key provided
|
||||
# as a parameter rather than an environment variable.
|
||||
for model in [
|
||||
ChatPerplexity(
|
||||
model="test", timeout=1, api_key="test", temperature=0.7, verbose=True
|
||||
),
|
||||
ChatPerplexity(
|
||||
model="test",
|
||||
request_timeout=1,
|
||||
pplx_api_key="test",
|
||||
temperature=0.7,
|
||||
verbose=True,
|
||||
),
|
||||
]:
|
||||
assert model.request_timeout == 1
|
||||
assert (
|
||||
model.pplx_api_key is not None
|
||||
and model.pplx_api_key.get_secret_value() == "test"
|
||||
)
|
||||
|
||||
|
||||
def test_perplexity_stream_includes_citations(mocker: MockerFixture) -> None:
|
||||
"""Test that the stream method includes citations in the additional_kwargs."""
|
||||
llm = ChatPerplexity(model="test", timeout=30, verbose=True)
|
||||
mock_chunk_0 = {
|
||||
"choices": [{"delta": {"content": "Hello "}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
}
|
||||
mock_chunk_1 = {
|
||||
"choices": [{"delta": {"content": "Perplexity"}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
}
|
||||
mock_chunks: List[Dict[str, Any]] = [mock_chunk_0, mock_chunk_1]
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.__iter__.return_value = mock_chunks
|
||||
patcher = mocker.patch.object(
|
||||
llm.client.chat.completions, "create", return_value=mock_stream
|
||||
)
|
||||
stream = llm.stream("Hello langchain")
|
||||
full: Optional[BaseMessageChunk] = None
|
||||
for i, chunk in enumerate(stream):
|
||||
full = chunk if full is None else full + chunk
|
||||
assert chunk.content == mock_chunks[i]["choices"][0]["delta"]["content"]
|
||||
if i == 0:
|
||||
assert chunk.additional_kwargs["citations"] == [
|
||||
"example.com",
|
||||
"example2.com",
|
||||
]
|
||||
else:
|
||||
assert "citations" not in chunk.additional_kwargs
|
||||
assert isinstance(full, AIMessageChunk)
|
||||
assert full.content == "Hello Perplexity"
|
||||
assert full.additional_kwargs == {"citations": ["example.com", "example2.com"]}
|
||||
|
||||
patcher.assert_called_once()
|
||||
|
||||
|
||||
def test_perplexity_stream_includes_citations_and_images(mocker: MockerFixture) -> None:
|
||||
"""Test that the stream method includes citations in the additional_kwargs."""
|
||||
llm = ChatPerplexity(model="test", timeout=30, verbose=True)
|
||||
mock_chunk_0 = {
|
||||
"choices": [{"delta": {"content": "Hello "}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"images": [
|
||||
{
|
||||
"image_url": "mock_image_url",
|
||||
"origin_url": "mock_origin_url",
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
}
|
||||
],
|
||||
}
|
||||
mock_chunk_1 = {
|
||||
"choices": [{"delta": {"content": "Perplexity"}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"images": [
|
||||
{
|
||||
"image_url": "mock_image_url",
|
||||
"origin_url": "mock_origin_url",
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
}
|
||||
],
|
||||
}
|
||||
mock_chunks: List[Dict[str, Any]] = [mock_chunk_0, mock_chunk_1]
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.__iter__.return_value = mock_chunks
|
||||
patcher = mocker.patch.object(
|
||||
llm.client.chat.completions, "create", return_value=mock_stream
|
||||
)
|
||||
stream = llm.stream("Hello langchain")
|
||||
full: Optional[BaseMessageChunk] = None
|
||||
for i, chunk in enumerate(stream):
|
||||
full = chunk if full is None else full + chunk
|
||||
assert chunk.content == mock_chunks[i]["choices"][0]["delta"]["content"]
|
||||
if i == 0:
|
||||
assert chunk.additional_kwargs["citations"] == [
|
||||
"example.com",
|
||||
"example2.com",
|
||||
]
|
||||
assert chunk.additional_kwargs["images"] == [
|
||||
{
|
||||
"image_url": "mock_image_url",
|
||||
"origin_url": "mock_origin_url",
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
}
|
||||
]
|
||||
else:
|
||||
assert "citations" not in chunk.additional_kwargs
|
||||
assert "images" not in chunk.additional_kwargs
|
||||
assert isinstance(full, AIMessageChunk)
|
||||
assert full.content == "Hello Perplexity"
|
||||
assert full.additional_kwargs == {
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"images": [
|
||||
{
|
||||
"image_url": "mock_image_url",
|
||||
"origin_url": "mock_origin_url",
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
patcher.assert_called_once()
|
||||
|
||||
|
||||
def test_perplexity_stream_includes_citations_and_related_questions(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""Test that the stream method includes citations in the additional_kwargs."""
|
||||
llm = ChatPerplexity(model="test", timeout=30, verbose=True)
|
||||
mock_chunk_0 = {
|
||||
"choices": [{"delta": {"content": "Hello "}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"related_questions": ["example_question_1", "example_question_2"],
|
||||
}
|
||||
mock_chunk_1 = {
|
||||
"choices": [{"delta": {"content": "Perplexity"}, "finish_reason": None}],
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"related_questions": ["example_question_1", "example_question_2"],
|
||||
}
|
||||
mock_chunks: List[Dict[str, Any]] = [mock_chunk_0, mock_chunk_1]
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.__iter__.return_value = mock_chunks
|
||||
patcher = mocker.patch.object(
|
||||
llm.client.chat.completions, "create", return_value=mock_stream
|
||||
)
|
||||
stream = llm.stream("Hello langchain")
|
||||
full: Optional[BaseMessageChunk] = None
|
||||
for i, chunk in enumerate(stream):
|
||||
full = chunk if full is None else full + chunk
|
||||
assert chunk.content == mock_chunks[i]["choices"][0]["delta"]["content"]
|
||||
if i == 0:
|
||||
assert chunk.additional_kwargs["citations"] == [
|
||||
"example.com",
|
||||
"example2.com",
|
||||
]
|
||||
assert chunk.additional_kwargs["related_questions"] == [
|
||||
"example_question_1",
|
||||
"example_question_2",
|
||||
]
|
||||
else:
|
||||
assert "citations" not in chunk.additional_kwargs
|
||||
assert "related_questions" not in chunk.additional_kwargs
|
||||
assert isinstance(full, AIMessageChunk)
|
||||
assert full.content == "Hello Perplexity"
|
||||
assert full.additional_kwargs == {
|
||||
"citations": ["example.com", "example2.com"],
|
||||
"related_questions": ["example_question_1", "example_question_2"],
|
||||
}
|
||||
|
||||
patcher.assert_called_once()
|
@@ -0,0 +1,18 @@
|
||||
"""Test Perplexity Chat API wrapper."""
|
||||
|
||||
from typing import Tuple, Type
|
||||
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_tests.unit_tests import ChatModelUnitTests
|
||||
|
||||
from langchain_perplexity import ChatPerplexity
|
||||
|
||||
|
||||
class TestPerplexityStandard(ChatModelUnitTests):
|
||||
@property
|
||||
def chat_model_class(self) -> Type[BaseChatModel]:
|
||||
return ChatPerplexity
|
||||
|
||||
@property
|
||||
def init_from_env_params(self) -> Tuple[dict, dict, dict]:
|
||||
return ({"PPLX_API_KEY": "api_key"}, {}, {"pplx_api_key": "api_key"})
|
@@ -0,0 +1,7 @@
|
||||
from langchain_perplexity import __all__
|
||||
|
||||
EXPECTED_ALL = ["ChatPerplexity"]
|
||||
|
||||
|
||||
def test_all_imports() -> None:
|
||||
assert sorted(EXPECTED_ALL) == sorted(__all__)
|
@@ -0,0 +1,8 @@
|
||||
from langchain_perplexity import ChatPerplexity
|
||||
|
||||
|
||||
def test_chat_perplexity_secrets() -> None:
|
||||
model = ChatPerplexity(
|
||||
model="llama-3.1-sonar-small-128k-online", pplx_api_key="foo"
|
||||
)
|
||||
assert "foo" not in str(model)
|
Reference in New Issue
Block a user