Perplexity:AIネイティブ検索デザイン
PerplexityがAI検索で信頼を構築した方法:引用優先デザイン、プログレッシブ・ディスクロージャー、ストリーミングレスポンス、クエリ改善。TypeScriptとCSSの実装パターン付き。
Perplexity
「人々は情報をできるだけ速く得たいし、その情報を信頼したいと思っている。そうであれば、これは実にシンプルなデザイン問題なのだ。」
哲学
Perplexityは新しいパラダイムを体現している:AIネイティブ検索だ。従来の検索がリンクのリストを返し、チャットボットが会話形式の応答を返すのに対し、Perplexityは複数のソースからの情報を統合し、引用付きの単一の回答として提示する。このデザインは徹底的な透明性を具現化している—すべての主張がその出典まで追跡可能なのだ。
チームの洞察:検索は会話ではない。情報を求めるミッションなのだ。 UIはこれを反映し、チャット履歴やペルソナではなく、ソースと回答を提示します。
重要なポイント
- 引用はAIの信頼性において譲れない要素 - すべての事実に基づく主張はソースにリンクされ、ソースパネルは常に表示されるため、ユーザーは回答から離れることなく検証できます
- 馴染みのあるインターフェースが障壁を下げる - 検索ボックスはチャットプロンプトではなくGoogleのように見え、ユーザーは会話パターンを学ぶことなくキーワードや完全な質問を入力できます
- フォローアップの質問を予測する - ほとんどのユーザーは次に何を聞けばよいかわかりません。文脈に関連したフォローアップを提案することで、エンゲージメントを維持します
- 結果だけでなくプロセスを見せる - 「検索中 → 読み取り中 → 作成中」のフェーズは信頼を構築し、透明性を通じて体感待ち時間を短縮します
- 弱い結果は失敗状態として扱う - クエリが不十分な結果を生成する場合、中途半端な回答を返すのではなく、明確化を求めます
パターンライブラリ
引用優先デザイン
PerplexityはAI回答におけるインライン引用を普及させ、ユーザーが生成コンテンツを信頼する方法を根本的に変えました。 すべての事実に基づく主張は、その情報源にリンクされています。
interface Citation {
index: number;
url: string;
title: string;
favicon: string;
snippet: string;
domain: string;
}
interface AnswerBlock {
text: string;
citations: number[]; // 引用配列へのインデックス
}
function CitedAnswer({ blocks, citations }: {
blocks: AnswerBlock[];
citations: Citation[];
}) {
return (
<article className="answer">
{blocks.map((block, i) => (
<p key={i}>
{block.text}
{block.citations.map(citationIndex => (
<CitationMarker
key={citationIndex}
citation={citations[citationIndex]}
index={citationIndex + 1}
/>
))}
</p>
))}
{/* ソースパネルは常に表示 */}
<aside className="sources-panel">
<h3>ソース</h3>
{citations.map((citation, i) => (
<SourceCard key={i} citation={citation} index={i + 1} />
))}
</aside>
</article>
);
}
function CitationMarker({ citation, index }: {
citation: Citation;
index: number;
}) {
const [expanded, setExpanded] = useState(false);
return (
<span className="citation-wrapper">
<sup
className="citation-marker"
onMouseEnter={() => setExpanded(true)}
onMouseLeave={() => setExpanded(false)}
>
[{index}]
</sup>
{/* 展開可能なスニペットプレビュー */}
{expanded && (
<div className="citation-preview">
<img src={citation.favicon} alt="" className="citation-favicon" />
<span className="citation-domain">{citation.domain}</span>
<p className="citation-snippet">{citation.snippet}</p>
</div>
)}
</span>
);
}
デザインの洞察:引用は任意ではありません。すべてのインタラクションに組み込まれています。ソースパネルは常に表示されたままなので、ユーザーは回答ビューを離れることなく主張を検証できます。
親しみやすい検索インターフェース
Perplexityの入力欄は、チャットプロンプトではなく従来の検索ボックスのように見えます。この馴染みのあるデザインにより、会話型AIに慣れていないユーザーでも使いやすくなっています。
function SearchInput({ onSubmit }: { onSubmit: (query: string) => void }) {
const [query, setQuery] = useState('');
return (
<div className="search-container">
{/* 意図的にGoogle/従来の検索と同じ見た目にしている */}
<div className="search-box">
<SearchIcon className="search-icon" />
<input
type="text"
className="search-input"
placeholder="何でも聞いてください..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onSubmit(query)}
/>
{query && (
<button
className="clear-button"
onClick={() => setQuery('')}
>
<XIcon />
</button>
)}
<button
className="submit-button"
onClick={() => onSubmit(query)}
>
<ArrowRightIcon />
</button>
</div>
{/* オプションのコンテキストチップ */}
<div className="focus-chips">
<Chip icon={<GlobeIcon />}>すべて</Chip>
<Chip icon={<AcademicIcon />}>学術</Chip>
<Chip icon={<CodeIcon />}>コード</Chip>
<Chip icon={<VideoIcon />}>動画</Chip>
</div>
</div>
);
}
デザインの洞察:数個のキーワードでも十分に機能します。ユーザーは手の込んだプロンプトを作成する必要はありません。インターフェースはシンプルなクエリと複雑な質問の両方を受け付けます。
フォローアップによる段階的な情報開示
ユーザーに優れたフォローアップの質問を期待する代わりに、Perplexityはそれらを予測して提案します。予測的なフォローアップは、ほとんどのユーザーが次に何を質問すべきかわからないという現実に対応しています。
interface FollowUpSuggestion {
question: string;
reasoning: string; // これが関連する可能性がある理由
}
function FollowUpSuggestions({
suggestions,
onSelect
}: {
suggestions: FollowUpSuggestion[];
onSelect: (question: string) => void;
}) {
return (
<div className="follow-ups">
<h4>関連</h4>
{/* 一度に1つずつ表示 - 段階的開示 */}
{suggestions.slice(0, 4).map((suggestion, i) => (
<button
key={i}
className="follow-up-chip"
onClick={() => onSelect(suggestion.question)}
>
<span className="follow-up-text">{suggestion.question}</span>
<ArrowRightIcon className="follow-up-arrow" />
</button>
))}
</div>
);
}
// クエリのコンテキストに基づいてフォローアップを予測
function generateFollowUps(query: string, answer: string): FollowUpSuggestion[] {
// AIが文脈に関連した次の質問を生成
return [
{ question: "他の選択肢と比較するとどうですか?", reasoning: "比較" },
{ question: "制限事項は何ですか?", reasoning: "批判的分析" },
{ question: "具体的な例を挙げていただけますか?", reasoning: "具体的な詳細" },
{ question: "歴史的な背景は何ですか?", reasoning: "背景" },
];
}
デザインの洞察:基調講演の最後に質問する人が少ないように、ほとんどのユーザーはフォローアップの質問が得意ではありません。ユーザーが知りたいと思うことを予測しましょう。
ストリーミングレスポンスとプログレッシブレンダリング
PerplexityはServer-Sent Events(SSE)を使用してレスポンスをストリーミングしますが、UIは自然に感じられる方法でコンテンツを段階的に表示します。
function StreamingAnswer({ query }: { query: string }) {
const [sources, setSources] = useState<Citation[]>([]);
const [answer, setAnswer] = useState<string>('');
const [phase, setPhase] = useState<'searching' | 'reading' | 'writing'>('searching');
useEffect(() => {
const eventSource = new EventSource(`/api/search?q=${encodeURIComponent(query)}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'sources':
setPhase('reading');
setSources(data.sources);
break;
case 'chunk':
setPhase('writing');
setAnswer(prev => prev + data.text);
break;
case 'done':
eventSource.close();
break;
}
};
return () => eventSource.close();
}, [query]);
return (
<div className="streaming-answer">
{/* フェーズインジケーター */}
<PhaseIndicator phase={phase} />
{/* ソースを最初に表示 */}
{sources.length > 0 && (
<SourceCards sources={sources} />
)}
{/* 回答がストリーミングされる */}
<div className="answer-content">
<TypewriterText text={answer} />
{phase === 'writing' && <BlinkingCursor />}
</div>
</div>
);
}
function PhaseIndicator({ phase }: { phase: 'searching' | 'reading' | 'writing' }) {
const phases = {
searching: { icon: <SearchIcon />, text: 'ウェブを検索中...' },
reading: { icon: <BookIcon />, text: 'ソースを読み込み中...' },
writing: { icon: <PenIcon />, text: '回答を作成中...' },
};
return (
<div className="phase-indicator">
{phases[phase].icon}
<span>{phases[phase].text}</span>
<LoadingDots />
</div>
);
}
デザインの知見:結果だけでなく、プロセスを見せる。ユーザーはまずソースが表示されるのを見て(信頼を構築)、次に回答が書かれていく様子を見守る。透明性は待ち時間中の不安を軽減する。
絞り込みによるエラー防止
クエリが広すぎる場合、Perplexityは質の低い結果を返す代わりに明確化を求める。システムは不十分な結果を失敗状態として扱う。
interface ClarificationRequest {
type: 'ambiguous' | 'too_broad' | 'missing_context';
suggestions: string[];
originalQuery: string;
}
function QueryRefinement({ request, onRefine }: {
request: ClarificationRequest;
onRefine: (refinedQuery: string) => void;
}) {
const messages = {
ambiguous: "複数の意味が見つかりました。どの意味でお探しですか?",
too_broad: "このトピックはかなり広範です。もう少し具体的にしていただけますか?",
missing_context: "有用な回答をするには、もう少しコンテキストが必要です。",
};
return (
<div className="refinement-prompt">
<p className="refinement-message">{messages[request.type]}</p>
<div className="refinement-suggestions">
{request.suggestions.map((suggestion, i) => (
<button
key={i}
className="refinement-option"
onClick={() => onRefine(suggestion)}
>
{suggestion}
</button>
))}
</div>
<div className="refinement-custom">
<input
type="text"
placeholder="または独自の調整を入力..."
onKeyDown={(e) => {
if (e.key === 'Enter') {
onRefine((e.target as HTMLInputElement).value);
}
}}
/>
</div>
</div>
);
}
デザインの洞察:他の検索プロバイダーは弱い結果を返し、ユーザーが自分で絞り込むことを期待します。Perplexityはユーザーをより良いクエリへと積極的に導きます。
コレクションとスペース
構造化されたリサーチのために、Perplexityはスペースを提供しています。これはユーザーがクエリを整理し、結果をピン留めし、参考資料をアップロードできる専用のコレクションです。
interface Space {
id: string;
name: string;
threads: Thread[];
documents: Document[];
createdAt: Date;
}
interface Thread {
id: string;
query: string;
answer: Answer;
citations: Citation[];
pinned: boolean;
}
function SpacesSidebar({ spaces, activeSpace, onSelect }: {
spaces: Space[];
activeSpace: string;
onSelect: (id: string) => void;
}) {
return (
<aside className="spaces-sidebar">
<header>
<h2>スペース</h2>
<button className="new-space">
<PlusIcon /> 新規スペース
</button>
</header>
<nav className="space-list">
{spaces.map(space => (
<button
key={space.id}
className={`space-item ${space.id === activeSpace ? 'active' : ''}`}
onClick={() => onSelect(space.id)}
>
<FolderIcon />
<span className="space-name">{space.name}</span>
<span className="thread-count">{space.threads.length}</span>
</button>
))}
</nav>
</aside>
);
}
function SpaceView({ space }: { space: Space }) {
return (
<div className="space-view">
<header className="space-header">
<h1>{space.name}</h1>
<div className="space-actions">
<button><ShareIcon /> 共有</button>
<button><UploadIcon /> ファイルを追加</button>
</div>
</header>
{/* ピン留めされたスレッド */}
<section className="pinned-threads">
<h3>ピン留め</h3>
{space.threads.filter(t => t.pinned).map(thread => (
<ThreadCard key={thread.id} thread={thread} />
))}
</section>
{/* すべてのスレッド */}
<section className="all-threads">
<h3>すべてのスレッド</h3>
{space.threads.map(thread => (
<ThreadCard key={thread.id} thread={thread} />
))}
</section>
{/* 埋め込みドキュメント */}
{space.documents.length > 0 && (
<section className="space-documents">
<h3>参考資料</h3>
{space.documents.map(doc => (
<DocumentCard key={doc.id} document={doc} />
))}
</section>
)}
</div>
);
}
デザインの洞察:コレクションは、学者やジャーナリストが手動で管理してきたタブ付きリサーチスタックを反映しており、それが製品に直接組み込まれています。
ビジュアルデザインシステム
タイポグラフィ:個性より明瞭さ
Perplexityは中立的で非常に読みやすいタイプシステムを使用しています。主役はインターフェースではなく、コンテンツです。
:root {
/* 最速読み込みのためのシステムフォントスタック */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
/* タイプスケール - 読みやすさに最適化 */
--text-sm: 13px;
--text-base: 15px;
--text-lg: 17px;
--text-xl: 20px;
--text-2xl: 24px;
/* 行の高さ - 読みやすさのためにゆとりを持たせる */
--leading-tight: 1.3;
--leading-normal: 1.6;
--leading-relaxed: 1.8;
}
/* 回答テキスト - 長文読み込み用に最適化 */
.answer-content {
font-size: var(--text-base);
line-height: var(--leading-relaxed);
max-width: 680px; /* 最適な読み幅 */
}
/* 引用マーカー - 小さく、控えめに */
.citation-marker {
font-size: var(--text-sm);
color: var(--accent-primary);
cursor: pointer;
vertical-align: super;
}
/* ソースカード - スキャンしやすいメタデータ */
.source-card {
font-size: var(--text-sm);
line-height: var(--leading-tight);
}
.source-domain {
font-family: var(--font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
カラーシステム:抑制による信頼
色は控えめに使用され、主に引用、ステータス、フォーカス状態に用いられます。
:root {
/* ニュートラルな基盤 */
--bg-primary: #ffffff;
--bg-secondary: #f7f7f8;
--bg-tertiary: #ededef;
/* テキスト階層 */
--text-primary: #1a1a1b;
--text-secondary: #57575a;
--text-tertiary: #8e8e93;
/* 引用アクセント - 信頼感のあるブルー */
--accent-citation: #0066cc;
--accent-citation-hover: #0052a3;
/* フォーカス状態 */
--focus-ring: 0 0 0 2px var(--accent-citation);
/* フェーズインジケーター */
--phase-searching: #f59e0b;
--phase-reading: #3b82f6;
--phase-writing: #10b981;
}
/* クリーンで広告のないインターフェース */
.search-results {
background: var(--bg-primary);
padding: 24px;
border-radius: 12px;
}
/* 引用のホバー状態 */
.citation-marker:hover {
background: var(--accent-citation);
color: white;
border-radius: 2px;
}
/* ソースカード - 控えめな境界線 */
.source-card {
border: 1px solid var(--bg-tertiary);
border-radius: 8px;
padding: 12px;
transition: border-color 0.15s ease;
}
.source-card:hover {
border-color: var(--accent-citation);
}
アニメーションパターン
フェーズ遷移
検索 → 読み取り → 書き込みのフェーズ間のスムーズな遷移により、ユーザーに状況を伝えます。
/* フェーズインジケーター */
.phase-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border-radius: 20px;
font-size: var(--text-sm);
color: var(--text-secondary);
}
/* ローディングドットアニメーション */
.loading-dots {
display: flex;
gap: 4px;
}
.loading-dots span {
width: 4px;
height: 4px;
background: var(--text-tertiary);
border-radius: 50%;
animation: dot-pulse 1.4s infinite ease-in-out;
}
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes dot-pulse {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
/* ソースは見つかり次第フェードイン表示 */
.source-card {
animation: fade-in-up 0.3s ease-out;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ソースカードの時間差表示 */
.source-card:nth-child(1) { animation-delay: 0ms; }
.source-card:nth-child(2) { animation-delay: 100ms; }
.source-card:nth-child(3) { animation-delay: 200ms; }
.source-card:nth-child(4) { animation-delay: 300ms; }
ストリーミングテキスト
回答は1文字ずつ表示されますが、読みやすさのためにまとまったチャンク単位で表示されます。
function TypewriterText({ text }: { text: string }) {
const [displayedText, setDisplayedText] = useState('');
useEffect(() => {
// テキストはSSEから既に到着済み - そのまま表示する
setDisplayedText(text);
}, [text]);
return (
<div className="typewriter">
{displayedText}
</div>
);
}
function BlinkingCursor() {
return (
<span className="cursor" aria-hidden="true">|</span>
);
}
.cursor {
animation: blink 1s step-end infinite;
color: var(--text-secondary);
}
@keyframes blink {
50% { opacity: 0; }
}
学んだ教訓
1. 引用はAIの信頼性において譲れない要素
AIインターフェースでは、すべての事実に基づく主張が追跡可能であるべきです。Perplexityは引用の忠実性をすべてのインタラクションに組み込んでいます。オプションでも隠されてもいません。
2. 検索ボックス > チャットプロンプト
馴染みのある検索インターフェースは障壁を下げます。ユーザーはキーワードでも完全な質問でも入力でき、どちらも機能します。会話パターンを強制しないことが重要です。
3. フォローアップ質問を予測する
ほとんどのユーザーは次に何を聞けばいいかわかりません。ユーザーに会話を主導させるのではなく、文脈に関連したフォローアップを提案しましょう。
4. プロセスを見せる
ストリーミングは単なる速度ではなく、透明性を提供します。「検索中 → 読み込み中 → 執筆中」のフェーズを表示することで信頼を築き、体感待ち時間を短縮します。
5. 弱い結果は失敗として扱う
クエリが不十分な結果を生成する場合、弱い回答を返すのではなく、改善を求めましょう。ユーザーをより良いクエリへと導きます。
6. クリーンなインターフェース = 信頼のシグナル
広告のないミニマルなインターフェースは、製品がマネタイズよりも情報の質を優先していることを示しています。
よくある質問
Perplexityの引用システムはどのように機能しますか?
Perplexityの回答に含まれるすべての事実に基づく主張には、ソースURLにリンクするインライン番号付き引用が含まれています。ソースパネルは回答と並んで表示され続け、ファビコン、ドメイン、スニペットプレビューを表示します。 引用番号にカーソルを合わせると、その情報源の具体的なコンテキストが展開表示されます。これにより、ユーザーが自分で探し回る必要がなく、即座に検証が可能になります。
なぜPerplexityはチャットボットではなく検索エンジンのような外観なのか?
チームは、検索は会話ではなく情報探索のミッションであることを発見しました。馴染みのある検索ボックスインターフェースは、シンプルなキーワードから複雑な質問まで受け付け、ユーザーが会話的なプロンプトパターンを学ぶ必要がありません。これにより、AIチャットインターフェースに不慣れなユーザーにとっての障壁が下がります。
Perplexityのストリーミングレスポンスへのアプローチとは?
Perplexityは、Server-Sent Events(SSE)を使用して、コンテンツを3つのフェーズで段階的に表示します:「検索中」(情報源の発見)、「読み取り中」(情報源の分析)、「作成中」(回答の合成)。回答のストリーミングが始まる前に情報源が最初に表示され、信頼を構築します。この透明性により、待ち時間中の不安が軽減されます。
Perplexityは曖昧または広範なクエリをどのように処理するのか?
クエリが弱い結果を生成する可能性がある場合、Perplexityは質の低い回答を返す代わりに明確化を求めます。システムは曖昧な用語、過度に広範なトピック、または不足しているコンテキストを特定し、具体的な絞り込みを提案します。 ユーザーは提案をクリックするか、独自の明確化を入力できます。
Perplexity Spacesとは何か、どのように使用されるのか?
Spacesは構造化されたリサーチのための専用コレクションで、ユーザーは関連するクエリを整理し、重要な結果をピン留めし、参考文献をアップロードできます。学術研究者やジャーナリストが手動で管理してきたタブ付きリサーチスタックを反映しており、継続的な研究プロジェクトのために製品に直接組み込まれています。