search.facets
Classify search results by section type, industry, audience, and tags, returning counts per value. Use for search result refinement UI and filter selection.
Instructions
[DEPRECATED: Use search.unified with include_facets: true instead] ファセット検索(絞り込みカウント表示)。検索結果をsectionType・industry・audience・tagsで分類し、各値の件数を返却します。検索結果の絞り込みUIやフィルタ選択に使用します。代替: search.unified({ query: '...', include_facets: true, facet_fields: ['sectionType'], enable_reranking: false, limit: 50 }) / Faceted search with filter counts. Classifies search results by sectionType, industry, audience, and tags, returning counts per value. Used for search result refinement UI and filter selection.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | 検索クエリ(自然言語、1-500文字) / Search query (natural language, 1-500 chars) | |
| facet_fields | No | ファセットフィールド(デフォルト: 全フィールド) / Facet fields (default: all). sectionType: セクション/パーツタイプ, industry: 業種, audience: ターゲット, tags: タグ | |
| limit | No | ファセット算出のベース結果数(1-50、デフォルト: 50) / Base result limit for facet computation (1-50, default: 50) | |
| webPageId | No | WebページIDでフィルター / Filter by web page ID | |
| industry | No | 業種フィルター / Industry filter (e.g., 'SaaS', 'E-commerce') | |
| audience | No | ターゲットオーディエンスフィルター / Target audience filter (e.g., 'Developer') | |
| tags | No | タグフィルター / Tags filter |
Implementation Reference
- The main handler function for the search.facets tool. Validates input, calls searchUnifiedHandler, computes facet counts via computeFacetsFromResults, logs search activity, and returns facet counts with a deprecation warning.
export async function searchFacetsHandler(input: unknown): Promise<SearchFacetsOutput> { const startTime = Date.now(); // 1. 入力バリデーション / Input validation let validated: SearchFacetsInput; try { validated = searchFacetsInputSchema.parse(input); } catch (error) { logger.warn("[search.facets] Validation error", { error: (error as Error).message }); return { success: false, error: { code: SEARCH_FACETS_ERROR_CODES.VALIDATION_ERROR, message: "Validation error", }, }; } const facetFields: FacetField[] = validated.facet_fields ?? ([...SUPPORTED_FACET_FIELDS] as FacetField[]); // 2. クエリタイプ分類 / Classify query type const queryType = classifyQueryType(validated.query); try { // 3. search.unified 実行 / Execute search.unified const searchResult = (await searchUnifiedHandler({ query: validated.query, limit: validated.limit, webPageId: validated.webPageId, industry: validated.industry, audience: validated.audience, tags: validated.tags, enable_reranking: false, // ファセットカウントにはリランキング不要 })) as SearchUnifiedOutput; if (!searchResult.success) { return { success: false, error: { code: SEARCH_FACETS_ERROR_CODES.SEARCH_FAILED, message: "Unified search failed", }, }; } // 4. ファセットカウント算出 / Compute facet counts const facets = computeFacetsFromResults(searchResult.data.results, facetFields); const searchTimeMs = Date.now() - startTime; // 5. 検索ログ記録(fire-and-forget) / Log search (fire-and-forget) logSearch({ query: validated.query, queryType, services: ["facets"], resultCount: searchResult.data.total, topResultId: searchResult.data.results[0]?.id, filters: { facet_fields: facetFields, industry: validated.industry, audience: validated.audience, tags: validated.tags, }, latencyMs: searchTimeMs, cacheHit: false, }).catch(() => { // fire-and-forget: エラーは logSearch 内部で処理済み }); return { success: true, data: { facets, query_type: queryType, total_results: searchResult.data.total, searchTimeMs, _deprecation: { message: "search.facets is deprecated. Use search.unified with include_facets: true (and optionally facet_fields) instead. " + "Example: search.unified({ query: '...', include_facets: true, facet_fields: ['sectionType'], enable_reranking: false, limit: 50 })", removal_version: "v0.4.0", alternative: "search.unified with include_facets: true", }, }, }; } catch (error) { const errorInstance = error instanceof Error ? error : new Error(String(error)); logger.warn("[search.facets] Failed", { error: sanitizeErrorMessage(errorInstance), }); return { success: false, error: { code: SEARCH_FACETS_ERROR_CODES.INTERNAL_ERROR, message: sanitizeErrorMessage(errorInstance), }, }; } } - Zod input schema for search.facets (searchFacetsInputSchema) defining query, facet_fields, limit, webPageId, industry, audience, tags.
const facetFieldSchema = z.enum(["sectionType", "industry", "audience", "tags"]); /** * search.facets 入力スキーマ / search.facets input schema */ export const searchFacetsInputSchema = z.object({ /** 検索クエリ(自然言語、1-500文字) / Search query (natural language, 1-500 chars) */ query: z.string().min(1).max(500), /** ファセットフィールド(デフォルト: 全フィールド) / Facet fields (default: all) */ facet_fields: z.array(facetFieldSchema).min(1).optional(), /** 取得件数(1-50、デフォルト: 50) / Result limit for facet computation (1-50, default: 50) */ limit: z.number().int().min(1).max(50).default(50), /** WebページIDでフィルター / Filter by web page ID */ webPageId: z.string().uuid().optional(), /** 業種フィルター / Industry filter */ industry: z.string().max(100).optional(), /** ターゲットオーディエンスフィルター / Target audience filter */ audience: z.string().max(100).optional(), /** タグフィルター / Tags filter */ tags: z.array(z.string()).max(10).optional(), }); export type SearchFacetsInput = z.infer<typeof searchFacetsInputSchema>; - TypeScript output type (SearchFacetsOutput) with success/error discriminated union, including FacetCounts, query_type, total_results, searchTimeMs, and _deprecation.
export type SearchFacetsOutput = | { success: true; data: { /** ファセットカウント / Facet counts */ facets: FacetCounts; /** クエリタイプ / Query type */ query_type: string; /** 検索結果総数(ファセット算出のベース) / Total results (base for facet computation) */ total_results: number; /** 検索時間(ms) / Search time (ms) */ searchTimeMs: number; /** 非推奨警告 / Deprecation warning */ _deprecation: { message: string; removal_version: string; alternative: string; }; }; } | { success: false; error: { code: string; message: string; }; }; - apps/mcp-server/src/tools/search/facets.tool.ts:228-297 (registration)Tool definition object (searchFacetsToolDefinition) with name 'search.facets', description, deprecation annotation, and JSON Schema inputSchema.
export const searchFacetsToolDefinition = { name: "search.facets", description: "[DEPRECATED: Use search.unified with include_facets: true instead] " + "ファセット検索(絞り込みカウント表示)。検索結果をsectionType・industry・audience・tagsで" + "分類し、各値の件数を返却します。検索結果の絞り込みUIやフィルタ選択に使用します。" + "代替: search.unified({ query: '...', include_facets: true, facet_fields: ['sectionType'], enable_reranking: false, limit: 50 })" + " / Faceted search with filter counts. Classifies search results by sectionType, industry, " + "audience, and tags, returning counts per value. Used for search result refinement UI and filter selection.", annotations: { title: "Faceted Search", readOnlyHint: true, idempotentHint: true, openWorldHint: false, deprecated: true, }, inputSchema: { type: "object" as const, properties: { query: { type: "string", description: "検索クエリ(自然言語、1-500文字) / Search query (natural language, 1-500 chars)", minLength: 1, maxLength: 500, }, facet_fields: { type: "array", items: { type: "string", enum: ["sectionType", "industry", "audience", "tags"], }, description: "ファセットフィールド(デフォルト: 全フィールド) / Facet fields (default: all). " + "sectionType: セクション/パーツタイプ, industry: 業種, audience: ターゲット, tags: タグ", }, limit: { type: "number", description: "ファセット算出のベース結果数(1-50、デフォルト: 50) / Base result limit for facet computation (1-50, default: 50)", minimum: 1, maximum: 50, default: 50, }, webPageId: { type: "string", format: "uuid", description: "WebページIDでフィルター / Filter by web page ID", }, industry: { type: "string", maxLength: 100, description: "業種フィルター / Industry filter (e.g., 'SaaS', 'E-commerce')", }, audience: { type: "string", maxLength: 100, description: "ターゲットオーディエンスフィルター / Target audience filter (e.g., 'Developer')", }, tags: { type: "array", items: { type: "string" }, maxItems: 10, description: "タグフィルター / Tags filter", }, }, required: ["query"], }, }; - The computeFacetsFromResults function that iterates over search results, extracts facet values from metadata (sectionType, industry, audience, tags), counts them, and returns sorted FacetCounts.
export function computeFacetsFromResults( results: UnifiedSearchResultItem[], fields: FacetField[] ): FacetCounts { const facets: FacetCounts = {}; for (const field of fields) { const counterMap = new Map<string, number>(); const metadataKeys = FACET_METADATA_KEYS[field]; for (const result of results) { if (!result.metadata) continue; for (const key of metadataKeys) { const value = result.metadata[key]; if (value === undefined || value === null) continue; if (field === "tags" && Array.isArray(value)) { // tags: 配列を展開して個別にカウント // tags: flatten arrays and count individually for (const tag of value) { if (typeof tag === "string" && tag.length > 0) { counterMap.set(tag, (counterMap.get(tag) ?? 0) + 1); } } } else if (typeof value === "string" && value.length > 0) { counterMap.set(value, (counterMap.get(value) ?? 0) + 1); } } } // カウント降順でソート / Sort by count descending const sorted: FacetCountItem[] = Array.from(counterMap.entries()) .map(([value, count]) => ({ value, count })) .sort((a, b) => b.count - a.count); facets[field] = sorted; } return facets; }