mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-29 01:02:03 +03:00
fix(web_search): Fix invalid model name sent to Perplexity (#12795)
* fix(web_search): Fix invalid model name sent to Perplexity * chore: Only apply fix to direct Perplexity calls * fix(web_search): normalize direct Perplexity model IDs * fix: add changelog note for perplexity model normalization (#12795) (thanks @cdorsey) * fix: align tests and fetch type for gate stability (#12795) (thanks @cdorsey) * chore: keep #12795 scoped to web_search changes --------- Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
This commit is contained in:
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Auth: strip embedded line breaks from pasted API keys and tokens before storing/resolving credentials.
|
- Auth: strip embedded line breaks from pasted API keys and tokens before storing/resolving credentials.
|
||||||
- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.
|
- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.
|
||||||
- Tools/web_search: include provider-specific settings in the web search cache key, and pass `inlineCitations` for Grok. (#12419) Thanks @tmchow.
|
- Tools/web_search: include provider-specific settings in the web search cache key, and pass `inlineCitations` for Grok. (#12419) Thanks @tmchow.
|
||||||
|
- Tools/web_search: normalize direct Perplexity model IDs while keeping OpenRouter model IDs unchanged. (#12795) Thanks @cdorsey.
|
||||||
- Model failover: treat HTTP 400 errors as failover-eligible, enabling automatic model fallback. (#1879) Thanks @orenyomtov.
|
- Model failover: treat HTTP 400 errors as failover-eligible, enabling automatic model fallback. (#1879) Thanks @orenyomtov.
|
||||||
- Errors: prevent false positive context overflow detection when conversation mentions "context overflow" topic. (#2078) Thanks @sbking.
|
- Errors: prevent false positive context overflow detection when conversation mentions "context overflow" topic. (#2078) Thanks @sbking.
|
||||||
- Gateway: no more post-compaction amnesia; injected transcript writes now preserve Pi session `parentId` chain so agents can remember again. (#12283) Thanks @Takhoffman.
|
- Gateway: no more post-compaction amnesia; injected transcript writes now preserve Pi session `parentId` chain so agents can remember again. (#12283) Thanks @Takhoffman.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { __testing } from "./web-search.js";
|
|||||||
const {
|
const {
|
||||||
inferPerplexityBaseUrlFromApiKey,
|
inferPerplexityBaseUrlFromApiKey,
|
||||||
resolvePerplexityBaseUrl,
|
resolvePerplexityBaseUrl,
|
||||||
|
isDirectPerplexityBaseUrl,
|
||||||
|
resolvePerplexityRequestModel,
|
||||||
normalizeFreshness,
|
normalizeFreshness,
|
||||||
resolveGrokApiKey,
|
resolveGrokApiKey,
|
||||||
resolveGrokModel,
|
resolveGrokModel,
|
||||||
@@ -58,6 +60,32 @@ describe("web_search perplexity baseUrl defaults", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("web_search perplexity model normalization", () => {
|
||||||
|
it("detects direct Perplexity host", () => {
|
||||||
|
expect(isDirectPerplexityBaseUrl("https://api.perplexity.ai")).toBe(true);
|
||||||
|
expect(isDirectPerplexityBaseUrl("https://api.perplexity.ai/")).toBe(true);
|
||||||
|
expect(isDirectPerplexityBaseUrl("https://openrouter.ai/api/v1")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips provider prefix for direct Perplexity", () => {
|
||||||
|
expect(resolvePerplexityRequestModel("https://api.perplexity.ai", "perplexity/sonar-pro")).toBe(
|
||||||
|
"sonar-pro",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps prefixed model for OpenRouter", () => {
|
||||||
|
expect(
|
||||||
|
resolvePerplexityRequestModel("https://openrouter.ai/api/v1", "perplexity/sonar-pro"),
|
||||||
|
).toBe("perplexity/sonar-pro");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps model unchanged when URL is invalid", () => {
|
||||||
|
expect(resolvePerplexityRequestModel("not-a-url", "perplexity/sonar-pro")).toBe(
|
||||||
|
"perplexity/sonar-pro",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("web_search freshness normalization", () => {
|
describe("web_search freshness normalization", () => {
|
||||||
it("accepts Brave shortcut values", () => {
|
it("accepts Brave shortcut values", () => {
|
||||||
expect(normalizeFreshness("pd")).toBe("pd");
|
expect(normalizeFreshness("pd")).toBe("pd");
|
||||||
|
|||||||
@@ -280,6 +280,25 @@ function resolvePerplexityModel(perplexity?: PerplexityConfig): string {
|
|||||||
return fromConfig || DEFAULT_PERPLEXITY_MODEL;
|
return fromConfig || DEFAULT_PERPLEXITY_MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDirectPerplexityBaseUrl(baseUrl: string): boolean {
|
||||||
|
const trimmed = baseUrl.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new URL(trimmed).hostname.toLowerCase() === "api.perplexity.ai";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePerplexityRequestModel(baseUrl: string, model: string): string {
|
||||||
|
if (!isDirectPerplexityBaseUrl(baseUrl)) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
return model.startsWith("perplexity/") ? model.slice("perplexity/".length) : model;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveGrokConfig(search?: WebSearchConfig): GrokConfig {
|
function resolveGrokConfig(search?: WebSearchConfig): GrokConfig {
|
||||||
if (!search || typeof search !== "object") {
|
if (!search || typeof search !== "object") {
|
||||||
return {};
|
return {};
|
||||||
@@ -379,7 +398,9 @@ async function runPerplexitySearch(params: {
|
|||||||
model: string;
|
model: string;
|
||||||
timeoutSeconds: number;
|
timeoutSeconds: number;
|
||||||
}): Promise<{ content: string; citations: string[] }> {
|
}): Promise<{ content: string; citations: string[] }> {
|
||||||
const endpoint = `${params.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
const baseUrl = params.baseUrl.trim().replace(/\/$/, "");
|
||||||
|
const endpoint = `${baseUrl}/chat/completions`;
|
||||||
|
const model = resolvePerplexityRequestModel(baseUrl, params.model);
|
||||||
|
|
||||||
const res = await fetch(endpoint, {
|
const res = await fetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -390,7 +411,7 @@ async function runPerplexitySearch(params: {
|
|||||||
"X-Title": "OpenClaw Web Search",
|
"X-Title": "OpenClaw Web Search",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: params.model,
|
model,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -686,6 +707,8 @@ export function createWebSearchTool(options?: {
|
|||||||
export const __testing = {
|
export const __testing = {
|
||||||
inferPerplexityBaseUrlFromApiKey,
|
inferPerplexityBaseUrlFromApiKey,
|
||||||
resolvePerplexityBaseUrl,
|
resolvePerplexityBaseUrl,
|
||||||
|
isDirectPerplexityBaseUrl,
|
||||||
|
resolvePerplexityRequestModel,
|
||||||
normalizeFreshness,
|
normalizeFreshness,
|
||||||
resolveGrokApiKey,
|
resolveGrokApiKey,
|
||||||
resolveGrokModel,
|
resolveGrokModel,
|
||||||
|
|||||||
@@ -151,6 +151,12 @@ describe("web_search perplexity baseUrl defaults", () => {
|
|||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalled();
|
expect(mockFetch).toHaveBeenCalled();
|
||||||
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://api.perplexity.ai/chat/completions");
|
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://api.perplexity.ai/chat/completions");
|
||||||
|
const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined;
|
||||||
|
const requestBody = request?.body;
|
||||||
|
const body = JSON.parse(typeof requestBody === "string" ? requestBody : "{}") as {
|
||||||
|
model?: string;
|
||||||
|
};
|
||||||
|
expect(body.model).toBe("sonar-pro");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects freshness for Perplexity provider", async () => {
|
it("rejects freshness for Perplexity provider", async () => {
|
||||||
@@ -194,6 +200,12 @@ describe("web_search perplexity baseUrl defaults", () => {
|
|||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalled();
|
expect(mockFetch).toHaveBeenCalled();
|
||||||
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://openrouter.ai/api/v1/chat/completions");
|
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://openrouter.ai/api/v1/chat/completions");
|
||||||
|
const request = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined;
|
||||||
|
const requestBody = request?.body;
|
||||||
|
const body = JSON.parse(typeof requestBody === "string" ? requestBody : "{}") as {
|
||||||
|
model?: string;
|
||||||
|
};
|
||||||
|
expect(body.model).toBe("perplexity/sonar-pro");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefers PERPLEXITY_API_KEY when both env keys are set", async () => {
|
it("prefers PERPLEXITY_API_KEY when both env keys are set", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user