fix(perplexity-web): make the built-in-search hint opt-in

Extracted from #8634: the two other fixes there (mode "search" -> "copilot"
downgrade, pplx-opus -> claude50opus) are already on this release tip
(open-sse/executors/perplexity-web/protocol.ts already uses "copilot" and
maps pplx-opus to claude50opus). Only the search-hint opt-in remained
uncovered.

The built-in-search hint appended to every system message is now opt-in via
OMNIROUTE_PPLX_SEARCH_HINT (off by default). Perplexity's answer engine
searches anyway, and for coding clients the sentence leaks into replies as
meta-commentary ("I need to search before responding per my instructions").

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
dansc
2026-08-20 19:33:47 -03:00
committed by Markus Hartung
parent e968d11b1c
commit 390c71540c
5 changed files with 57 additions and 4 deletions

View File

@@ -1415,6 +1415,14 @@ CURSOR_USER_AGENT="Cursor/3.4"
# OMNIROUTE_PPLX_TLS_TIMEOUT_MS=30000
# OMNIROUTE_PPLX_TLS_GRACE_MS=10000
# ── Perplexity web: built-in-search hint ──
# Used by: open-sse/executors/perplexity-web/protocol.ts — appends "You have
# built-in web search. Answer questions directly using search results." to the
# caller's system message. Off by default: Perplexity's answer engine searches
# anyway, and for coding clients the sentence leaks into replies as
# meta-commentary. Set to 1/true/yes/on to restore the old behavior.
# OMNIROUTE_PPLX_SEARCH_HINT=0
# ── Grok web TLS sidecar (Chrome-fingerprinted client) ──
# Used by: open-sse/services/grokTlsClient.ts — wire-level timeout for the
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on

View File

@@ -0,0 +1 @@
- **fix(perplexity-web):** make the built-in-search hint appended to every system message opt-in via `OMNIROUTE_PPLX_SEARCH_HINT` (off by default) — Perplexity's answer engine searches anyway, and the hint leaked into replies as meta-commentary for coding clients ([#10902](https://github.com/diegosouzapw/OmniRoute/pull/10902), extracted from [#8634](https://github.com/diegosouzapw/OmniRoute/pull/8634)) — thanks @danscMax

View File

@@ -755,6 +755,7 @@ REQUEST_TIMEOUT_MS (global override)
| `OMNIROUTE_CLAUDE_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
| `OMNIROUTE_PPLX_TLS_TIMEOUT_MS` | `30000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`perplexityTlsClient.ts`). |
| `OMNIROUTE_PPLX_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
| `OMNIROUTE_PPLX_SEARCH_HINT` | `0` (off) | Appends "You have built-in web search. Answer questions directly using search results." to the caller's system message (`perplexity-web/protocol.ts`). Off by default — Perplexity searches anyway, and the sentence leaks into replies as meta-commentary for coding clients. Set `1`/`true`/`yes`/`on` to restore. |
| `OMNIROUTE_GROK_TLS_TIMEOUT_MS` | `60000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`grokTlsClient.ts`). |
| `OMNIROUTE_GROK_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
| `OMNIROUTE_NOTION_TLS_TIMEOUT_MS` | `30000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`notionTlsClient.ts`); the `notion-web` executor raises it per-request to `180000` for long generations. |

View File

@@ -370,15 +370,29 @@ export function buildPplxRequestBody(
};
}
const SEARCH_HINT = "You have built-in web search. Answer questions directly using search results.";
/**
* Whether to append {@link SEARCH_HINT} to the caller's system message.
*
* It used to be unconditional. Perplexity's answer engine is search-first anyway, and
* for coding clients the sentence leaks into replies as meta-commentary ("I need to
* search before responding per my instructions"), so it is now opt-in via
* `OMNIROUTE_PPLX_SEARCH_HINT`. Read per call rather than at module load so the flag
* can be flipped without restarting the server (and so tests can toggle it).
*/
function searchHintEnabled(): boolean {
return /^(1|true|yes|on)$/i.test(process.env.OMNIROUTE_PPLX_SEARCH_HINT ?? "");
}
export function buildQuery(parsed: ParsedMessages, followUpUuid: string | null): string {
if (followUpUuid) return parsed.currentMsg;
const obj: Record<string, unknown> = {};
if (parsed.systemMsg.trim()) {
obj.instructions = [
parsed.systemMsg.trim(),
"You have built-in web search. Answer questions directly using search results.",
];
obj.instructions = searchHintEnabled()
? [parsed.systemMsg.trim(), SEARCH_HINT]
: [parsed.systemMsg.trim()];
}
if (parsed.history.length > 0) {
obj.history = parsed.history;

View File

@@ -912,6 +912,35 @@ test("Model mapping: thinking mode uses thinking variant", async () => {
}
});
// ─── The search hint is opt-in ──────────────────────────────────────────────
// It used to be appended to every system message and leaked into answers as
// meta-commentary, which is noise for coding clients.
test("buildQuery: search hint is off by default and opt-in via env", async () => {
const { buildQuery } = await import("../../open-sse/executors/perplexity-web/protocol.ts");
const parsed = { systemMsg: "You are terse.", history: [], currentMsg: "hi" };
const HINT = "built-in web search";
const prev = process.env.OMNIROUTE_PPLX_SEARCH_HINT;
try {
delete process.env.OMNIROUTE_PPLX_SEARCH_HINT;
const off = JSON.parse(buildQuery(parsed, null));
assert.deepEqual(off.instructions, ["You are terse."]);
assert.equal(off.query, "hi");
process.env.OMNIROUTE_PPLX_SEARCH_HINT = "1";
const on = JSON.parse(buildQuery(parsed, null));
assert.equal(on.instructions.length, 2);
assert.ok(on.instructions[1].includes(HINT));
process.env.OMNIROUTE_PPLX_SEARCH_HINT = "0";
assert.equal(JSON.parse(buildQuery(parsed, null)).instructions.length, 1);
} finally {
if (prev === undefined) delete process.env.OMNIROUTE_PPLX_SEARCH_HINT;
else process.env.OMNIROUTE_PPLX_SEARCH_HINT = prev;
}
});
// ─── Test: Live multi-step stream (no COMPLETED; text_completed + diffs) ────
test("Live multi-step: reconstructs answer without status COMPLETED", async () => {