AI 與 LLM
在 Fumadocs 中整合 AI 功能。
供 LLM 使用的文件
以 Markdown 格式將您的文件提供給 LLM 與 AI 代理:
npx @fumadocs/cli feature llms它會為您的 docs 集合啟用處理後的 Markdown、在 lib/source.ts 中加入 docsLlms,並提供下方列出的檔案。若您偏好手動設定,可參考此處列出的產生檔案。
docsLlms
llms() 會將您的文件算繪為供 LLM 使用的格式。renderPage 會將單一頁面轉換為 Markdown,且依據的是處理後的文件,而非原始檔案內容:
import { llms } from 'fumadocs-core/source';
export const docsLlms = llms(source, {
renderPage: async (page) => `# ${page.data.title} (${page.url})
${await page.data.getText('processed')}`,
});
| 方法 | 輸出 |
|---|---|
index(lang?) | llms.txt 索引,依據頁面樹建立 |
page(page) | 單一頁面,以 renderPage 算繪 |
full(lang?) | 所有頁面以 renderPage 算繪後合併的內容 |
files() | 供 AI 代理使用的檔案的路徑 |
serve(path) | 指定路徑檔案的回應 |
renderPage 為必要選項
只有在傳入 renderPage 時,才能使用 page()、full()、files() 與 serve(),因為
Fumadocs 無從得知您的內容來源如何提供 Markdown。
renderPage 會讀取頁面處理後的 Markdown,因此請啟用 includeProcessedMarkdown:
import { defineConfig } from 'astro/config';
import fumadocs from '@fumadocs/astro';
export default defineConfig({
integrations: [
fumadocs({
includeProcessedMarkdown: true,
}),
],
});除非 MDX 元件已定義其 Markdown 形式,否則會以 JSX 語法呈現。
@fumadocs/local-md 這類執行階段內容來源會隨需解析。只要傳入其 getSource,CLI 就會產生相同的檔案:
export const docsLlms = llms(getSource, {
renderPage: (page) => `# ${page.data.title} (${page.url})
${page.data.content}`,
});使用 getSource 時,index() 與 indexNode() 會回傳 Promise,而非字串。
檔案
CLI 會為 AI 代理提供以下檔案:
/llms.txt:文件的索引,附有每個頁面 Markdown 版本的連結。/llms-full.txt:所有頁面的內容。<page>.md:單一頁面的 Markdown,例如/docs/ui.md。位於/的頁面則對應/index.md。<section>/llms-full.txt:某個區段的內容,例如/docs/ui/llms-full.txt。
若要將特定頁面 (例如草稿) 排除在這些檔案之外,請傳入 filter。llms.txt 仍會列出這些頁面,但會連結至其 HTML 頁面:
export const docsLlms = llms(source, {
filter: (page) => page.slugs[0] !== 'drafts',
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});在這些檔案中,renderUrl 會接收連結所指向檔案的路徑,例如頁面的 .md 檔案。
讀取頁面 HTML 的 AI 代理,會透過 <head> 中的連結找到該頁面的 Markdown:
<link rel="alternate" type="text/markdown" href="/docs/ui.md" />區段
區段是頁面樹中擁有獨立 llms-full.txt 的資料夾,讓 AI 代理能分段閱讀大型文件。預設情況下,每個根資料夾都是一個區段。在 llms.txt 中,資料夾對應的那一行會連結到其所屬區段的 llms-full.txt。
若要自訂區段,請傳入 section。它會接收一個資料夾,並回傳該資料夾是否為區段:
export const docsLlms = llms(source, {
// 所有含 index 頁面的資料夾
section: (folder) => folder.index !== undefined,
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});在大型網站上,您可以收合區段,讓 llms.txt 保持精簡。收合的區段在 llms.txt 中只會列為一個連結,指向該區段專屬的 <section>/llms.txt,再由該檔案列出區段內的頁面:
export const docsLlms = llms(source, {
section: (folder) => (folder.root ? { collapse: true } : false),
renderPage: async (page) => `# ${page.data.title}\n\n${await page.data.getText('processed')}`,
});區段的檔案會以其資料夾 index 頁面的 URL 提供。若資料夾沒有 index 頁面,則改用其中頁面所在的 URL 目錄。當兩個資料夾共用同一個 URL 時 (例如資料夾群組) ,請傳入 asPath,將其中一個改放在其他路徑下提供,例如 { asPath: '/docs/guides' }。
路由
files() 會列出檔案的路徑,而 serve(path) 會回應指定路徑上的檔案,因此每個路由只需將請求路徑傳給 serve() 即可。
由單一端點在各檔案的路徑上預先算繪所有檔案:
import type { APIRoute, GetStaticPaths } from 'astro';
import { docsLlms } from '@/lib/source';
export const prerender = true;
export const getStaticPaths = (async () => {
const paths: { params: { path: string } }[] = [];
for (const path of await docsLlms.files()) paths.push({ params: { path: decodeURI(path.slice(1)) } });
return paths;
}) satisfies GetStaticPaths;
export const GET: APIRoute = async ({ params }) => (await docsLlms.serve(`/${params.path}`))!;
若路徑沒有對應的檔案,serve() 會回傳 undefined,讓中介軟體能將請求繼續往下傳遞。使用 hideLocale: 'always' 時,URL 不會包含語系:請將語言作為 serve() 的第二個引數及 files() 的第一個引數傳入。
靜態輸出
無論是否使用轉接器,端點都會預先算繪。
Accept
文件頁面無法依據 Accept 標頭提供 Markdown。Astro 中介軟體只會在請求時才算繪的頁面上執行,而文件頁面是預先算繪的。
頁面動作
頁面動作可讓讀者複製頁面的 Markdown,或在 AI 應用程式中開啟該頁面。這些動作會讀取頁面的 .md 檔案。

將 Markdown URL 從文件頁面傳給對應的 React island,再於 island 中算繪這些動作:
<Docs
client:load
markdownUrl={`${page.url}.md`}
githubUrl={`https://github.com/${owner}/${repo}/blob/main/content/docs/${encodeURI(page.path)}`}
>
<Content />
</Docs>MCP 伺服器
透過 MCP 伺服器將您的文件開放給 AI 代理使用,並提供列出、搜尋及讀取頁面的工具。
npx @fumadocs/cli feature mcp這會在上述 LLM 路由之外,再建立一個使用 Streamable HTTP 傳輸層的 /api/mcp 路由。
這些工具來自 fumadocs-core/mcp。
import type { APIRoute } from 'astro';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';
import { createFromSource } from 'fumadocs-core/search/server';
import { docsLlms, source } from '@/lib/source';
const handler = createMcpHandler(() => {
const mcp = new McpServer(
{
name: 'docs',
version: '1.0.0',
},
// the tools never change, a stream to watch them would hold a serverless function open
{ capabilities: { tools: { listChanged: false } } },
);
registerSourceTools(mcp, source, docsLlms);
registerSearchTool(mcp, createFromSource(source));
return mcp;
});
export const prerender = false;
export const GET: APIRoute = async ({ request }) => handler.fetch(request);
export const POST: APIRoute = async ({ request }) => handler.fetch(request);
export const DELETE: APIRoute = async ({ request }) => handler.fetch(request);
此路由會在收到請求時才算繪,因此您的專案需要設定轉接器。
您可以透過以下方式將 AI 代理連接到此路由:
{
"mcpServers": {
"docs": { "url": "https://your-site.com/api/mcp" }
}
}WebMCP
實驗性功能
WebMCP 是一項仍處於早期階段的 Web 標準,目前可在
Chrome 149+ 中透過 #enable-webmcp-testing 旗標及來源試用 (origin trial) 使用。此 API 日後仍可能
變動。
上述 MCP 伺服器服務的是連線至您網站的 AI 代理;WebMCP 則是在讀者正在瀏覽的頁面上,將工具提供給瀏覽器內建的 AI 代理使用。
npx @fumadocs/cli feature webmcp它會以 LLM 路由為基礎,建立一個 WebMCP 元件。請在 <RootProvider /> 內算繪此元件。
search_docs會查詢搜尋 API 路由。您可以將fetchClient替換為其他用戶端。read_page會從/docs/page.md取得頁面的 Markdown 內容。
詢問 AI

您可以使用 Fumadocs CLI 安裝 AI 聊天對話方塊。
npx @fumadocs/cli add ai/openrouter它會透過 Vercel AI SDK 自動完成 OpenRouter 的設定,並為 AI 提供 /search 工具。
您可以修改 /api/chat 路由來改用其他模型。
Fumadocs 不提供 AI 模型,需由您自行決定。
您的 AI 模型可以使用上方產生的 llms-full.txt 檔案;若搭配第三方解決方案,也能使用更多元的資訊來源。
文件版面配置會依據 aiChat 選項算繪聊天介面。此選項需要用戶端狀態,因此請為您的版面配置建立用戶端元件,並用它取代原本的版面配置:
'use client';
// 也可改用 Notebook、Glass 或 Spacious 版面配置
import { DocsLayout as Layout, type DocsLayoutProps } from 'fumadocs-ui/layouts/docs';
// 已安裝的元件
import { AIChat, AIChatPanel, useAIChat } from './search';
export function DocsLayout(props: DocsLayoutProps) {
return (
<AIChat>
<ChatLayout {...props} />
</AIChat>
);
}
function ChatLayout(props: DocsLayoutProps) {
const { open, setOpen } = useAIChat();
return (
<Layout
{...props}
aiChat={{ open, onOpenChange: setOpen, panel: <AIChatPanel />, trigger: true }}
/>
);
}How is this guide?
最後更新於
