community: fix perplexity response parameters not being included in model response (#30440)

This pull request includes enhancements to the `perplexity.py` file in
the `chat_models` module, focusing on improving the handling of
additional keyword arguments (`additional_kwargs`) in message processing
methods. Additionally, new unit tests have been added to ensure the
correct inclusion of citations, images, and related questions in the
`additional_kwargs`.

Issue: resolves https://github.com/langchain-ai/langchain/issues/30439

Enhancements to `perplexity.py`:

*
[`libs/community/langchain_community/chat_models/perplexity.py`](diffhunk://#diff-d3e4d7b277608683913b53dcfdbd006f0f4a94d110d8b9ac7acf855f1f22207fL208-L212):
Modified the `_convert_delta_to_message_chunk`, `_stream`, and
`_generate` methods to handle `additional_kwargs`, which include
citations, images, and related questions.
[[1]](diffhunk://#diff-d3e4d7b277608683913b53dcfdbd006f0f4a94d110d8b9ac7acf855f1f22207fL208-L212)
[[2]](diffhunk://#diff-d3e4d7b277608683913b53dcfdbd006f0f4a94d110d8b9ac7acf855f1f22207fL277-L286)
[[3]](diffhunk://#diff-d3e4d7b277608683913b53dcfdbd006f0f4a94d110d8b9ac7acf855f1f22207fR324-R331)

New unit tests:

*
[`libs/community/tests/unit_tests/chat_models/test_perplexity.py`](diffhunk://#diff-dab956d79bd7d17a0f5dea3f38ceab0d583b43b63eb1b29138ee9b6b271ba1d9R119-R275):
Added new tests `test_perplexity_stream_includes_citations_and_images`
and `test_perplexity_stream_includes_citations_and_related_questions` to
verify that the `stream` method correctly includes citations, images,
and related questions in the `additional_kwargs`.
This commit is contained in:
David Sánchez Sánchez 2025-03-27 03:28:08 +01:00 committed by GitHub
parent 7664874a0d
commit 75823d580b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 178 additions and 5 deletions

View File

@ -345,16 +345,25 @@ class ChatPerplexity(BaseChatModel):
if len(chunk["choices"]) == 0:
continue
choice = chunk["choices"][0]
citations = chunk.get("citations", [])
additional_kwargs = {}
if first_chunk:
additional_kwargs["citations"] = chunk.get("citations", [])
for attr in ["images", "related_questions"]:
if attr in chunk:
additional_kwargs[attr] = chunk[attr]
chunk = self._convert_delta_to_message_chunk(
choice["delta"], default_chunk_class
)
if isinstance(chunk, AIMessageChunk) and usage_metadata:
chunk.usage_metadata = usage_metadata
if first_chunk:
chunk.additional_kwargs |= {"citations": citations}
chunk.additional_kwargs |= additional_kwargs
first_chunk = False
finish_reason = choice.get("finish_reason")
generation_info = (
dict(finish_reason=finish_reason) if finish_reason is not None else None
@ -386,9 +395,14 @@ class ChatPerplexity(BaseChatModel):
else:
usage_metadata = None
additional_kwargs = {"citations": response.citations}
for attr in ["images", "related_questions"]:
if hasattr(response, attr):
additional_kwargs[attr] = getattr(response, attr)
message = AIMessage(
content=response.choices[0].message.content,
additional_kwargs={"citations": response.citations},
additional_kwargs=additional_kwargs,
usage_metadata=usage_metadata,
)
return ChatResult(generations=[ChatGeneration(message=message)])

View File

@ -116,3 +116,160 @@ def test_perplexity_stream_includes_citations(mocker: MockerFixture) -> None:
assert full.additional_kwargs == {"citations": ["example.com", "example2.com"]}
patcher.assert_called_once()
@pytest.mark.requires("openai")
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()
@pytest.mark.requires("openai")
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()

View File

@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.9, <4.0"
resolution-markers = [
"python_full_version >= '3.12.4' and platform_python_implementation == 'PyPy'",
@ -1531,6 +1532,7 @@ requires-dist = [
{ name = "requests", specifier = ">=2,<3" },
{ name = "sqlalchemy", specifier = ">=1.4,<3" },
]
provides-extras = ["community", "anthropic", "openai", "azure-ai", "cohere", "google-vertexai", "google-genai", "fireworks", "ollama", "together", "mistralai", "huggingface", "groq", "aws", "deepseek", "xai"]
[package.metadata.requires-dev]
codespell = [{ name = "codespell", specifier = ">=2.2.0,<3.0.0" }]
@ -1745,7 +1747,7 @@ typing = [
[[package]]
name = "langchain-core"
version = "0.3.45"
version = "0.3.47"
source = { editable = "../core" }
dependencies = [
{ name = "jsonpatch" },
@ -1803,7 +1805,7 @@ typing = [
[[package]]
name = "langchain-tests"
version = "0.3.14"
version = "0.3.15"
source = { editable = "../standard-tests" }
dependencies = [
{ name = "httpx" },