Skip to content

fix(perplexity): decode embedding API responses#3344

Open
jliounis wants to merge 3 commits into
deepset-ai:mainfrom
jliounis:psi/fix-perplexity-embeddings-encoding
Open

fix(perplexity): decode embedding API responses#3344
jliounis wants to merge 3 commits into
deepset-ai:mainfrom
jliounis:psi/fix-perplexity-embeddings-encoding

Conversation

@jliounis
Copy link
Copy Markdown
Contributor

@jliounis jliounis commented May 22, 2026

Summary

Fixes the Perplexity embedders so they no longer send encoding_format="float" to POST /v1/embeddings. Perplexity's embeddings endpoint documents base64_int8 and base64_binary as the supported encoding_format values: https://docs.perplexity.ai/api-reference/embeddings-post

This PR adds Perplexity-specific response decoding for both sync and async embedding paths:

  • base64_int8 decodes to signed int8 values, casts to float32, and returns list[float] for Haystack Document.embedding / text embedding outputs.
  • base64_binary decodes packed bits with np.unpackbits(...) and returns a binary {0, 1} vector as list[float].
  • encoding_format="float" now fails at construction time with the requested explicit error.

Reproduction

Before this change, either embedder sent encoding_format="float" through the inherited OpenAI embedder request path and Perplexity returned HTTP 400:

from haystack import Document
from haystack_integrations.components.embedders.perplexity import (
    PerplexityDocumentEmbedder,
    PerplexityTextEmbedder,
)

d = PerplexityDocumentEmbedder()
d.run(documents=[Document(content="hello")])

q = PerplexityTextEmbedder()
q.run(text="hello")

Approach

Picked Approach A: keep the OpenAI embedder inheritance and override the minimum Perplexity-specific request/response handling.

Live spike status: skipped because PERPLEXITY_API_KEY is not set in this Buildkite runner. I did run the same SDK behavior check against an httpx.MockTransport; the OpenAI Python SDK passed encoding_format="base64_int8" through verbatim and exposed response.data[0].embedding as a raw str, which makes the minimal override viable. Live verification is explicitly marked skipped below rather than faked.

Acceptance Criteria

  • Default encoding_format is base64_int8 for PerplexityDocumentEmbedder and PerplexityTextEmbedder.
  • encoding_format="base64_binary" is supported.
  • encoding_format="float" raises ValueError with exactly: Perplexity's /v1/embeddings does not support encoding_format='float'; use 'base64_int8' or 'base64_binary'..
  • to_dict() / from_dict() round-trip encoding_format without renaming or removing existing fields.
  • X-Pplx-Integration attribution headers remain on outbound embedding requests.
  • Async embedding paths are fixed and covered by unit tests.
  • Unit tests cover known base64 payload decoding for both base64_int8 and base64_binary.
  • Unit tests cover rejecting float and serialization round-trip behavior.
  • Existing integration-test convention is used; live tests remain under @pytest.mark.integration and now sanity-check the default 1024-dim model response.
  • Docstring examples remain valid; no README embedder example was present to update.
  • Changelog entry added.
  • Live verification script was attempted but skipped because PERPLEXITY_API_KEY is not set in this runner.

Verification

# Preferred Hatch command attempted, but Hatch did not install pytest in this runner:
env -u VIRTUAL_ENV -u UV_PROJECT_ENVIRONMENT -u UV_RUN_RECURSION_DEPTH -u UV_CONFIG_FILE -u PYTHONPATH \
  /workspace/build/buildkite/bin/uvx hatch run test:unit
# /bin/sh: 1: pytest: not found

# Equivalent uv-created environment checks:
/tmp/perplexity-test-venv/bin/python -m ruff check --fix .
/tmp/perplexity-test-venv/bin/python -m ruff format .
/tmp/perplexity-test-venv/bin/python -m mypy \
  -p haystack_integrations.components.websearch.perplexity \
  -p haystack_integrations.components.generators.perplexity \
  -p haystack_integrations.components.embedders.perplexity
/tmp/perplexity-test-venv/bin/python -m pytest tests
# 50 passed, 8 skipped

# Requested live script:
# SKIPPED live verification: PERPLEXITY_API_KEY is not set

@github-actions github-actions Bot added integration:perplexity type:documentation Improvements or additions to documentation labels May 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

Coverage report (perplexity)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  integrations/perplexity/src/haystack_integrations/components/embedders/perplexity
  document_embedder.py
  embedding_encoding.py
  text_embedder.py
Project Total  

This report was generated by python-coverage-comment-action

@jliounis jliounis marked this pull request as ready for review May 22, 2026 15:12
@jliounis jliounis requested a review from a team as a code owner May 22, 2026 15:12
@jliounis jliounis requested review from bogdankostic and removed request for a team May 22, 2026 15:12
@anakin87
Copy link
Copy Markdown
Member

#3344 (comment)

Let's try to keep test coverage >= 90%.

@jliounis
Copy link
Copy Markdown
Contributor Author

Thanks for the review @anakin87 — pushed a19c375 to address this. Added unit coverage for the previously-uncovered Perplexity embedding branches: unsupported encoding_format validation + decoding paths in embedding_encoding.py; document usage accumulation across sync and async batches in document_embedder.py; async progress-bar wrapping; and sync/async APIError handling for both raise and skip behavior.
Local validation: pytest -m "not integration" tests → 58 passed, 8 deselected. Coverage for haystack_integrations.components.embedders.perplexity: 100% total (document_embedder.py, embedding_encoding.py, text_embedder.py all at 100%). ruff check, ruff format --check, and mypy -p haystack_integrations.components.embedders.perplexity all pass.

@jliounis
Copy link
Copy Markdown
Contributor Author

@anakin87 can we please merge this PR?

Copy link
Copy Markdown
Member

@anakin87 anakin87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR. I left some comments

Comment thread integrations/perplexity/CHANGELOG.md Outdated
@anakin87 anakin87 removed the request for review from bogdankostic May 25, 2026 13:19
jliounis added a commit to jliounis/haystack-cookbook that referenced this pull request May 25, 2026
Now that haystack-core-integrations PR #3344 fixes the embedders to
default to encoding_format=base64_int8 and decode responses to
list[float], drop the bespoke httpx + np.frombuffer helper and use
PerplexityDocumentEmbedder / PerplexityTextEmbedder directly for
seeding, querying, and ingest_url chunk embedding.

Refs: deepset-ai/haystack-core-integrations#3344
@jliounis
Copy link
Copy Markdown
Contributor Author

Thanks @anakin87 — pushed dd099db addressing all five comments:

  • CHANGELOG.md: reverted to main; the file is auto-generated so no manual edits remain.
  • document_embedder.py: removed the if TYPE_CHECKING: guard (the imports under it were safe to load at module level — no circular import) and dropped the now-unused TYPE_CHECKING import.
  • embedding_encoding.py: collapsed the redundant float check into the single membership check below it, while keeping the exact error message (Perplexity's /v1/embeddings does not support encoding_format='float'; use 'base64_int8' or 'base64_binary'.) that the unit tests assert.
  • embedding_encoding.py: renamed validate_encoding_format_validate_encoding_format and updated all call sites + imports.
  • embedding_encoding.py: renamed decode_embedding_decode_embedding and updated all call sites + imports.

Local validation on the integration's Hatch env: pytest tests -m "not integration" ✅, ruff check . ✅, ruff format --check . ✅, mypy -p haystack_integrations.components.embedders.perplexity ✅. Let me know if anything else needs tweaking.

Copy link
Copy Markdown
Member

@anakin87 anakin87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a final suggestion/question

Comment on lines +21 to +25
if encoding_format == "float":
msg = PERPLEXITY_FLOAT_ENCODING_FORMAT_ERROR
else:
supported_formats = "', '".join(sorted(SUPPORTED_ENCODING_FORMATS))
msg = f"Unsupported encoding_format='{encoding_format}'. Use '{supported_formats}'."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if encoding_format == "float":
msg = PERPLEXITY_FLOAT_ENCODING_FORMAT_ERROR
else:
supported_formats = "', '".join(sorted(SUPPORTED_ENCODING_FORMATS))
msg = f"Unsupported encoding_format='{encoding_format}'. Use '{supported_formats}'."
supported_formats = "', '".join(sorted(SUPPORTED_ENCODING_FORMATS))
msg = f"Unsupported encoding_format='{encoding_format}'. Use '{supported_formats}'."

I'd not distinguish between float and other unsupported formats, no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration:perplexity type:documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants