feat(web): expose exa/perplexity search tuning as config
The Exa and Perplexity providers hard-coded request parameters that deployments should control while defaults are still unsettled. Exa gains searchType, numResults, and highlightsPerResult; Perplexity gains maxTokens (it previously sent none) and an optional searchRecency. Each follows the deepseek provider's shape: a defaulted Config field, a DEFAULT_* constant, and a positive-integer status() check for numeric limits. The call-level maxResults still flows through WebSearchRequest and wins over the configured default, keeping the seam layering intact. Addresses tianyicui's "make everything configurable" review comment.
This commit is contained in:
@@ -10,17 +10,18 @@
|
||||
import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import type {} from '@deepseek-ai/dsh-web'
|
||||
import { PerplexitySearchProvider, PERPLEXITY_DEFAULT_BASE_URL, PERPLEXITY_DEFAULT_MODEL } from './provider.ts'
|
||||
import { PerplexitySearchProvider, PERPLEXITY_DEFAULT_BASE_URL, PERPLEXITY_DEFAULT_MAX_TOKENS, PERPLEXITY_DEFAULT_MODEL } from './provider.ts'
|
||||
|
||||
export {
|
||||
PERPLEXITY_DEFAULT_BASE_URL,
|
||||
PERPLEXITY_DEFAULT_MAX_TOKENS,
|
||||
PERPLEXITY_DEFAULT_MODEL,
|
||||
PERPLEXITY_PROVIDER_ID,
|
||||
PerplexitySearchProvider,
|
||||
mapPerplexityResponse,
|
||||
mapPerplexityResult,
|
||||
} from './provider.ts'
|
||||
export type { PerplexitySearchProviderOptions } from './provider.ts'
|
||||
export type { PerplexityRecency, PerplexitySearchProviderOptions } from './provider.ts'
|
||||
|
||||
/** Cordis plugin name used by loader diagnostics. */
|
||||
export const name = 'web-search-perplexity'
|
||||
@@ -35,18 +36,27 @@ export interface Config {
|
||||
baseURL?: string
|
||||
/** Search model name. Defaults to `sonar`. */
|
||||
model?: string
|
||||
/** Upper bound on generated answer tokens. Defaults to 1024. */
|
||||
maxTokens?: number
|
||||
/** Recency window sent as `search_recency_filter`. Omitted = no filter. */
|
||||
searchRecency?: 'day' | 'week' | 'month' | 'year'
|
||||
}
|
||||
|
||||
export const Config: z<Config> = z.object({
|
||||
apiKey: z.string(),
|
||||
baseURL: z.string(),
|
||||
model: z.string(),
|
||||
maxTokens: z.number().step(1).min(1),
|
||||
searchRecency: z.union(['day', 'week', 'month', 'year'] as const),
|
||||
})
|
||||
|
||||
/** Register the Perplexity search provider with `ctx.web`. */
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
const apiKey = config.apiKey ?? process.env.PERPLEXITY_API_KEY ?? ''
|
||||
const baseURL = config.baseURL ?? PERPLEXITY_DEFAULT_BASE_URL
|
||||
const model = config.model ?? PERPLEXITY_DEFAULT_MODEL
|
||||
ctx.web.registerSearchProvider(new PerplexitySearchProvider({ apiKey, baseURL, model }))
|
||||
ctx.web.registerSearchProvider(new PerplexitySearchProvider({
|
||||
apiKey: config.apiKey ?? process.env.PERPLEXITY_API_KEY ?? '',
|
||||
baseURL: config.baseURL ?? PERPLEXITY_DEFAULT_BASE_URL,
|
||||
model: config.model ?? PERPLEXITY_DEFAULT_MODEL,
|
||||
maxTokens: config.maxTokens ?? PERPLEXITY_DEFAULT_MAX_TOKENS,
|
||||
...config.searchRecency !== undefined ? { searchRecency: config.searchRecency } : {},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ export const PERPLEXITY_DEFAULT_BASE_URL = 'https://api.perplexity.ai'
|
||||
/** Default search model. */
|
||||
export const PERPLEXITY_DEFAULT_MODEL = 'sonar'
|
||||
|
||||
/** Default upper bound on generated answer tokens. */
|
||||
export const PERPLEXITY_DEFAULT_MAX_TOKENS = 1024
|
||||
|
||||
/** Recency filter values Perplexity accepts for `search_recency_filter`. */
|
||||
export type PerplexityRecency = 'day' | 'week' | 'month' | 'year'
|
||||
|
||||
/** Attribution header sent on every request. Bump with the package version. */
|
||||
const USER_AGENT = 'deepseek-harness/0.0.1'
|
||||
|
||||
@@ -42,6 +48,10 @@ export interface PerplexitySearchProviderOptions {
|
||||
baseURL: string
|
||||
/** Search model name. */
|
||||
model: string
|
||||
/** Upper bound on generated answer tokens (`max_tokens`). */
|
||||
maxTokens: number
|
||||
/** Optional recency window sent as `search_recency_filter`; omitted = no filter. */
|
||||
searchRecency?: PerplexityRecency
|
||||
}
|
||||
|
||||
/** Map one structured Perplexity search result to a normalized source. */
|
||||
@@ -82,6 +92,7 @@ export class PerplexitySearchProvider implements WebSearchProvider {
|
||||
status(): WebProviderStatus {
|
||||
if (this.options.apiKey.length === 0) return { available: false, reason: 'missing-credential' }
|
||||
if (!URL.canParse(this.options.baseURL)) return { available: false, reason: 'misconfigured' }
|
||||
if (!isPositiveInteger(this.options.maxTokens)) return { available: false, reason: 'misconfigured' }
|
||||
return { available: true }
|
||||
}
|
||||
|
||||
@@ -98,7 +109,9 @@ export class PerplexitySearchProvider implements WebSearchProvider {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.options.model,
|
||||
max_tokens: this.options.maxTokens,
|
||||
messages: [{ role: 'user', content: request.query }],
|
||||
...this.options.searchRecency !== undefined ? { search_recency_filter: this.options.searchRecency } : {},
|
||||
}),
|
||||
...exec?.signal ? { signal: exec.signal } : {},
|
||||
})
|
||||
@@ -140,3 +153,8 @@ export class PerplexitySearchProvider implements WebSearchProvider {
|
||||
function isAbortError(error: unknown): boolean {
|
||||
return error instanceof DOMException && error.name === 'AbortError'
|
||||
}
|
||||
|
||||
/** True for a request limit that can be sent to Perplexity (a positive whole number). */
|
||||
function isPositiveInteger(value: number): boolean {
|
||||
return Number.isInteger(value) && value > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user