mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
chore(perplexity): bump perplexityai to 0.34.1 (#37710)
## Description Bumps `langchain-perplexity` to require the Perplexity SDK release with fixed Responses streaming and removes the temporary SSE shim workaround. ## Release Note `langchain-perplexity` now requires `perplexityai>=0.34.1` for Responses API streaming. ## Test Plan - [x] `NO_COLOR=1 uv run --group test pytest tests/unit_tests/test_chat_models_responses.py --disable-socket --allow-unix-socket` _Opened collaboratively by Mason Daugherty and open-swe._ --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com>
This commit is contained in:
@@ -329,97 +329,6 @@ def _convert_responses_to_chat_result(response: Any) -> ChatResult:
|
|||||||
return ChatResult(generations=[ChatGeneration(message=message)])
|
return ChatResult(generations=[ChatGeneration(message=message)])
|
||||||
|
|
||||||
|
|
||||||
def _normalize_perplexity_sse(sse: Any) -> dict[str, Any] | None:
|
|
||||||
"""Decode a Perplexity SSE frame to a typed-payload dict, or skip it.
|
|
||||||
|
|
||||||
Returns `None` for frames that should be skipped without breaking the
|
|
||||||
stream (empty data, non-dict JSON, decode errors). Uses the SSE
|
|
||||||
`event:` field as the authoritative event-type discriminator — payloads
|
|
||||||
that disagree with the SSE frame name are realigned, because the SSE
|
|
||||||
name is the only source the API guarantees.
|
|
||||||
"""
|
|
||||||
data = getattr(sse, "data", None)
|
|
||||||
if not data:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
payload = sse.json()
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
logger.warning(
|
|
||||||
"Discarding Perplexity SSE event with non-JSON data; event=%r data=%r",
|
|
||||||
getattr(sse, "event", None),
|
|
||||||
data[:200],
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
if not isinstance(payload, dict):
|
|
||||||
logger.debug(
|
|
||||||
"Discarding Perplexity SSE event with non-dict payload; event=%r type=%s",
|
|
||||||
getattr(sse, "event", None),
|
|
||||||
type(payload).__name__,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
sse_event = getattr(sse, "event", None)
|
|
||||||
if sse_event:
|
|
||||||
# The SSE frame name is authoritative — never let a mismatched
|
|
||||||
# `type` in the JSON body silently reclassify the event (e.g. a
|
|
||||||
# `response.failed` mis-tagged as `response.completed`).
|
|
||||||
payload["type"] = sse_event
|
|
||||||
return payload
|
|
||||||
|
|
||||||
|
|
||||||
def _iter_perplexity_sse_events(stream: Any) -> Iterator[Any]:
|
|
||||||
"""Yield Perplexity Responses streaming events.
|
|
||||||
|
|
||||||
Workaround for an upstream Perplexity Python SDK bug:
|
|
||||||
`Stream.__stream__` only yields events whose SSE `event:` field is
|
|
||||||
`None`, but the Agent API tags every event (e.g.
|
|
||||||
`event: response.completed`). The result is that
|
|
||||||
`list(client.responses.create(..., stream=True))` returns zero events.
|
|
||||||
Tracked upstream at:
|
|
||||||
|
|
||||||
https://github.com/perplexityai/perplexity-py/issues/53
|
|
||||||
|
|
||||||
Real `perplexity.Stream` instances always expose the lower-level
|
|
||||||
`_iter_events()` SSE iterator; we drop down to it and synthesize event
|
|
||||||
dicts (`type` taken from the SSE frame name) so they flow through
|
|
||||||
`_convert_responses_stream_event_to_chunk` — which already handles both
|
|
||||||
SDK objects and dicts via `_get_attr`. When `_iter_events` is missing
|
|
||||||
(test fakes that already yield decoded event objects), pass through.
|
|
||||||
"""
|
|
||||||
if not hasattr(stream, "_iter_events"):
|
|
||||||
yield from stream
|
|
||||||
return
|
|
||||||
for sse in stream._iter_events():
|
|
||||||
sse_data = getattr(sse, "data", None)
|
|
||||||
# Guard the `[DONE]` sentinel against frames with `data=None`
|
|
||||||
# (keepalive / comment SSE frames) — `None.startswith` would crash.
|
|
||||||
if sse_data and sse_data.startswith("[DONE]"):
|
|
||||||
break
|
|
||||||
payload = _normalize_perplexity_sse(sse)
|
|
||||||
if payload is None:
|
|
||||||
continue
|
|
||||||
yield payload
|
|
||||||
|
|
||||||
|
|
||||||
async def _aiter_perplexity_sse_events(stream: Any) -> AsyncIterator[Any]:
|
|
||||||
"""Async counterpart of `_iter_perplexity_sse_events`.
|
|
||||||
|
|
||||||
See the sync helper for rationale, removal criteria, and the upstream
|
|
||||||
bug tracking URL.
|
|
||||||
"""
|
|
||||||
if not hasattr(stream, "_iter_events"):
|
|
||||||
async for event in stream:
|
|
||||||
yield event
|
|
||||||
return
|
|
||||||
async for sse in stream._iter_events():
|
|
||||||
sse_data = getattr(sse, "data", None)
|
|
||||||
if sse_data and sse_data.startswith("[DONE]"):
|
|
||||||
break
|
|
||||||
payload = _normalize_perplexity_sse(sse)
|
|
||||||
if payload is None:
|
|
||||||
continue
|
|
||||||
yield payload
|
|
||||||
|
|
||||||
|
|
||||||
class PerplexityResponsesStreamError(RuntimeError):
|
class PerplexityResponsesStreamError(RuntimeError):
|
||||||
"""Raised when a Perplexity Responses (Agent) API stream fails mid-flight.
|
"""Raised when a Perplexity Responses (Agent) API stream fails mid-flight.
|
||||||
|
|
||||||
@@ -1062,7 +971,10 @@ class ChatPerplexity(BaseChatModel):
|
|||||||
)
|
)
|
||||||
responses_payload["stream"] = True
|
responses_payload["stream"] = True
|
||||||
stream_events = self.client.responses.create(**responses_payload)
|
stream_events = self.client.responses.create(**responses_payload)
|
||||||
for event in _iter_perplexity_sse_events(stream_events):
|
# Trusts SDK SSE decoding (perplexityai>=0.34.1, upstream issue
|
||||||
|
# perplexityai-python#53). `_convert_responses_stream_event_to_chunk`
|
||||||
|
# already handles both SDK objects and dicts via `_get_attr`.
|
||||||
|
for event in stream_events:
|
||||||
response_chunk = _convert_responses_stream_event_to_chunk(event)
|
response_chunk = _convert_responses_stream_event_to_chunk(event)
|
||||||
if response_chunk is None:
|
if response_chunk is None:
|
||||||
continue
|
continue
|
||||||
@@ -1178,7 +1090,8 @@ class ChatPerplexity(BaseChatModel):
|
|||||||
stream_events = await self.async_client.responses.create(
|
stream_events = await self.async_client.responses.create(
|
||||||
**responses_payload
|
**responses_payload
|
||||||
)
|
)
|
||||||
async for event in _aiter_perplexity_sse_events(stream_events):
|
# See sync `_stream` for SDK trust rationale (perplexityai>=0.34.1).
|
||||||
|
async for event in stream_events:
|
||||||
response_chunk = _convert_responses_stream_event_to_chunk(event)
|
response_chunk = _convert_responses_stream_event_to_chunk(event)
|
||||||
if response_chunk is None:
|
if response_chunk is None:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ version = "1.3.0"
|
|||||||
requires-python = ">=3.10.0,<4.0.0"
|
requires-python = ">=3.10.0,<4.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"langchain-core>=1.4.0,<2.0.0",
|
"langchain-core>=1.4.0,<2.0.0",
|
||||||
"perplexityai>=0.32.0,<1.0.0",
|
"perplexityai>=0.34.1,<1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
8
libs/partners/perplexity/uv.lock
generated
8
libs/partners/perplexity/uv.lock
generated
@@ -537,7 +537,7 @@ typing = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "langchain-core", editable = "../../core" },
|
{ name = "langchain-core", editable = "../../core" },
|
||||||
{ name = "perplexityai", specifier = ">=0.32.0,<1.0.0" },
|
{ name = "perplexityai", specifier = ">=0.34.1,<1.0.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -973,7 +973,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "perplexityai"
|
name = "perplexityai"
|
||||||
version = "0.32.1"
|
version = "0.34.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
@@ -983,9 +983,9 @@ dependencies = [
|
|||||||
{ name = "sniffio" },
|
{ name = "sniffio" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/09/02/73f460c85a5ec533a97fd1ff34fa729a009b4a217a4a87d8da946b6e1c52/perplexityai-0.32.1.tar.gz", hash = "sha256:b03503498591d06c4d50b666f7f7469875d3586f664c29416aae9012ae7a64d1", size = 135741, upload-time = "2026-04-21T04:35:40.345Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/4c/9e/253b60c5a51913a0a1e4eddccb02078e4cdb496cab994ad4bdd1e83a8f65/perplexityai-0.34.1.tar.gz", hash = "sha256:8e4b47d52c1d2c0d259eead6941dc60896045070bf0794bcbab8a96e428e06ef", size = 138767, upload-time = "2026-05-27T02:55:02.096Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/11/5c164f114311bc2e2350202393e7c5bd25bb156b5230a1edf5a2b2f4ba04/perplexityai-0.32.1-py3-none-any.whl", hash = "sha256:e5017d245fd8966cf79657edc03a93078d867708542b491b38152618f91e369b", size = 130223, upload-time = "2026-04-21T04:35:38.786Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/47/246e7e7463df53a9e37cc7427296c8ee5c18183c5217fb3df3a088dfb652/perplexityai-0.34.1-py3-none-any.whl", hash = "sha256:6dcf94e73ec22bc8c98724e6999aa8a371eb441cc7ddd7e44c13e326e06d37b8", size = 131948, upload-time = "2026-05-27T02:55:01.024Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user