( owner: string, repo: string, query: string, limit: number = 5, vectorize: Vectorize, )
| 847 | * @returns Array of relevant document chunks with scores |
| 848 | */ |
| 849 | export async function searchDocumentation( |
| 850 | owner: string, |
| 851 | repo: string, |
| 852 | query: string, |
| 853 | limit: number = 5, |
| 854 | vectorize: Vectorize, |
| 855 | ): Promise<Array<{ chunk: string; score: number }>> { |
| 856 | try { |
| 857 | // Check if Vectorize is available |
| 858 | if (!vectorize) { |
| 859 | console.warn("Vectorize binding not available. Returning empty results."); |
| 860 | return []; |
| 861 | } |
| 862 | |
| 863 | // Generate namespace for this repository |
| 864 | const namespace = getRepoNamespace(owner, repo); |
| 865 | console.log(`Searching in namespace: ${namespace}`); |
| 866 | |
| 867 | const queryEmbedding = await getEmbeddings(query); |
| 868 | |
| 869 | // Query vectors using Cloudflare Vectorize with namespace |
| 870 | const results = await vectorize.query(queryEmbedding, { |
| 871 | topK: limit, |
| 872 | namespace: namespace, // Use namespace instead of filter |
| 873 | returnValues: false, // We don't need the vector values back |
| 874 | filter: { |
| 875 | timestamp: { $gt: Date.now() - VECTOR_TTL }, // Only keep recent vectors |
| 876 | }, |
| 877 | returnMetadata: true, // We need the metadata for chunks |
| 878 | }); |
| 879 | |
| 880 | console.log( |
| 881 | `Found ${results?.matches?.length || 0} results in namespace ${namespace}`, |
| 882 | ); |
| 883 | |
| 884 | if (!results || !results.matches || results.matches.length === 0) { |
| 885 | console.warn(`No results found in namespace ${namespace}`); |
| 886 | return []; |
| 887 | } |
| 888 | |
| 889 | // Enhanced ranking: combine vector similarity with keyword matching |
| 890 | const enhancedResults = results.matches.map((match) => { |
| 891 | const metadata = match.metadata as Record<string, any>; |
| 892 | const chunk = metadata?.chunk || ""; |
| 893 | |
| 894 | // Calculate keyword match score |
| 895 | const keywordScore = calculateKeywordMatchScore(chunk, query); |
| 896 | |
| 897 | // Combine scores (vector similarity + keyword matching) |
| 898 | // Normalize vector similarity from [-1,1] to [0,1] range if using cosine similarity |
| 899 | const normalizedVectorScore = (match.score + 1) / 2; |
| 900 | |
| 901 | // Combined score gives weight to both vector similarity and keyword matches |
| 902 | const combinedScore = normalizedVectorScore * 0.6 + keywordScore * 0.4; |
| 903 | |
| 904 | return { |
| 905 | chunk, |
| 906 | vectorScore: match.score, |
nothing calls this directly
no test coverage detected