diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts index 30915b87529..7ebf6d7ae4b 100644 --- a/apps/docs/app/api/search/route.ts +++ b/apps/docs/app/api/search/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { truncateAtCodePoint } from '@sim/utils/string' import { sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { db, docsEmbeddings } from '@/lib/db' @@ -11,6 +12,8 @@ export const revalidate = 0 const DEFAULT_SEARCH_LIMIT = 10 const MAX_SEARCH_LIMIT = 20 +/** Bounds the paid embedding call per request; real searches are a few words. */ +const MAX_QUERY_LENGTH = 256 /** PostgreSQL text-search configuration for the docs' English content. */ const TS_CONFIG = 'english' @@ -28,7 +31,11 @@ function getSearchLimit(value: unknown): number { function getSearchParams(request: NextRequest) { const searchParams = request.nextUrl.searchParams return { - query: searchParams.get('query') || searchParams.get('q') || '', + query: truncateAtCodePoint( + searchParams.get('query') || searchParams.get('q') || '', + MAX_QUERY_LENGTH, + '' + ), limit: getSearchLimit(searchParams.get('limit')), } } @@ -38,7 +45,7 @@ export async function GET(request: NextRequest) { try { const { query, limit } = getSearchParams(request) - if (!query || query.trim().length === 0) { + if (query.trim().length === 0) { return NextResponse.json([]) } diff --git a/apps/docs/lib/embeddings.ts b/apps/docs/lib/embeddings.ts index c41a3f19899..67570bb7801 100644 --- a/apps/docs/lib/embeddings.ts +++ b/apps/docs/lib/embeddings.ts @@ -1,3 +1,5 @@ +const EMBEDDING_TIMEOUT_MS = 10_000 + /** * Generate embeddings for search queries using OpenAI API */ @@ -19,6 +21,7 @@ export async function generateSearchEmbedding(query: string): Promise model: 'text-embedding-3-small', encoding_format: 'float', }), + signal: AbortSignal.timeout(EMBEDDING_TIMEOUT_MS), }) if (!response.ok) {