import { promises as fsPromises } from 'fs'; import { join } from 'path'; import { themes } from 'prism-react-renderer'; import type * as Preset from '@docusaurus/preset-classic'; import type { Config, Plugin } from '@docusaurus/types'; const lightCodeTheme = themes.github; const darkCodeTheme = themes.duotoneDark; function webpackProgressCompatibilityPlugin(): Plugin { return { name: 'webpack-progress-compatibility-plugin', configureWebpack(config) { config.plugins = config.plugins?.filter( (plugin) => plugin?.constructor?.name !== 'WebpackBarPlugin', ); return {}; }, }; } const config: Config = { title: 'Promptfoo', tagline: 'Ship secure AI agents and LLM applications', favicon: '/favicon.ico', // Set the production url of your site here url: 'https://www.promptfoo.dev', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' baseUrl: '/', trailingSlash: true, // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. organizationName: 'promptfoo', // Usually your GitHub org/user name. projectName: 'promptfoo', // Usually your repo name. onBrokenLinks: 'throw', onBrokenAnchors: 'throw', // Even if you don't use internationalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { defaultLocale: 'en', locales: ['en'], }, headTags: [], scripts: [ { src: '/js/consent.js', async: true, }, ], presets: [ [ 'classic', { docs: { sidebarPath: require.resolve('./sidebars.js'), editUrl: undefined, sidebarCollapsed: false, showLastUpdateAuthor: true, showLastUpdateTime: true, exclude: [ '**/CLAUDE.md', // Exclude Claude Code context files '**/AGENTS.md', // Exclude AI agent instruction files ], }, blog: { blogDescription: 'Learn how to test and secure AI applications with practical guides on LLM red teaming, evaluations, real-world vulnerabilities, and updates from Promptfoo.', showReadingTime: false, blogSidebarCount: 0, postsPerPage: 20, // Please change this to your repo. // Remove this to remove the "edit this page" links. //editUrl: // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, theme: { customCss: require.resolve('./src/css/custom.css'), }, // gtag loaded conditionally via consent.js (GDPR) } satisfies Preset.Options, ], ], themeConfig: { announcementBar: { id: 'joined-openai', content: 'Promptfoo is now part of OpenAI. Read the update →', backgroundColor: '#dc2626', textColor: '#ffffff', isCloseable: false, }, image: 'img/thumbnail.png', colorMode: { defaultMode: 'light', disableSwitch: false, respectPrefersColorScheme: true, }, navbar: { title: 'promptfoo', logo: { alt: 'promptfoo logo', src: 'img/logo-panda.svg', }, items: [ { type: 'custom-navMenuCard', label: 'Products', position: 'left', items: [ { to: '/red-teaming/', label: 'Red Teaming', description: 'Proactively identify and fix vulnerabilities in your AI applications', }, { to: '/guardrails/', label: 'Guardrails', description: 'Real-time protection against jailbreaks and adversarial attacks', }, { to: '/model-security/', label: 'Model Security', description: 'Comprehensive security testing and monitoring for AI models', }, { to: '/mcp/', label: 'MCP Proxy', description: 'Secure proxy for Model Context Protocol communications', }, { to: '/code-scanning/', label: 'Code Scanning', description: 'Find LLM vulnerabilities in your IDE and CI/CD', }, { to: '/docs/getting-started/', label: 'Evaluations', description: 'Test and evaluate your prompts, models, and RAG pipelines', }, ], }, { type: 'custom-navMenuCard', label: 'Solutions', position: 'left', items: [ { type: 'section-header', label: 'By Industry', }, { to: '/solutions/finance/', label: 'Financial Services', description: 'FINRA-aligned security testing', }, { to: '/solutions/insurance/', label: 'Insurance', description: 'Policyholder data & coverage accuracy', }, { to: '/solutions/telecom/', label: 'Telecommunications', description: 'Voice & text AI agent security', }, { to: '/solutions/real-estate/', label: 'Real Estate', description: 'Fair housing compliance testing', }, ], }, { type: 'custom-navMenuCard', label: 'Company', position: 'left', items: [ { href: '/about/', label: 'About', description: 'Learn about our mission and team', }, { href: '/press/', label: 'Press', description: 'Media coverage and press releases', }, { href: '/events/', label: 'Events', description: 'Meet the team at conferences and events', }, { to: '/store/', label: 'Swag', description: 'Official Promptfoo merch and swag', }, ], }, { to: '/docs/intro/', label: 'Docs', position: 'left' }, { to: '/blog/', label: 'Blog', position: 'left' }, { to: '/pricing/', label: 'Pricing', position: 'left' }, { to: '/contact/', position: 'right', 'aria-label': 'Book a Demo', label: 'Book a Demo', className: 'header-book-demo-link', }, { to: 'https://promptfoo.app', position: 'right', 'aria-label': 'Promptfoo App', label: 'Log in', }, { type: 'custom-githubStars', position: 'right', }, { href: 'https://discord.gg/promptfoo', position: 'right', className: 'header-discord-link', 'aria-label': 'Discord community', }, ], }, footer: { style: 'dark', links: [ { title: 'Product', items: [ { label: 'Red Teaming', to: '/red-teaming/', }, { label: 'Guardrails', to: '/guardrails/', }, { label: 'Model Security', to: '/model-security/', }, { label: 'Evaluations', to: '/docs/getting-started/', }, { label: 'Enterprise', href: '/pricing/', }, { label: 'MCP Proxy', to: '/mcp/', }, { label: 'Status', href: 'https://status.promptfoo.app/', }, ], }, { title: 'Solutions', items: [ { label: 'Financial Services', to: '/solutions/finance/', }, { label: 'Insurance', to: '/solutions/insurance/', }, { label: 'Telecommunications', to: '/solutions/telecom/', }, { label: 'Real Estate', to: '/solutions/real-estate/', }, ], }, { title: 'Resources', items: [ { label: 'API Reference', to: '/docs/api-reference/', }, { label: 'LLM Red Teaming', to: '/docs/red-team/', }, { label: 'Foundation Model Reports', to: 'https://www.promptfoo.dev/models/', }, { label: 'Language Model Security DB', to: 'https://www.promptfoo.dev/lm-security-db/', }, { label: 'Running Benchmarks', to: '/docs/guides/censored-vs-uncensored-ollama/', }, { label: 'Evaluating Factuality', to: '/docs/guides/factuality-eval/', }, { label: 'Evaluating RAGs', to: '/docs/guides/evaluate-rag/', }, { label: 'Minimizing Hallucinations', to: '/docs/guides/prevent-llm-hallucinations/', }, { label: 'Config Validator', to: '/validator/', }, ], }, { title: 'Company', items: [ { label: 'About', to: '/about/', }, { label: 'Blog', to: '/blog/', }, { label: 'Release Notes', to: '/docs/releases/', }, { label: 'Press', to: '/press/', }, { label: 'Events', to: '/events/', }, { label: 'Contact', to: '/contact/', }, { label: 'Swag', to: '/store/', }, { label: 'Log in', to: 'https://promptfoo.app', }, ], }, { title: 'Legal & Social', items: [ { label: 'GitHub', href: 'https://github.com/promptfoo/promptfoo#readme', }, { label: 'Discord', href: 'https://discord.gg/promptfoo', }, { label: 'LinkedIn', href: 'https://www.linkedin.com/company/promptfoo/', }, { label: 'Privacy Policy', to: '/privacy/', }, { label: 'Terms of Service', to: '/terms-of-service/', }, { label: 'Trust Center', href: 'https://trust.promptfoo.dev', }, { html: 'Cookie Settings', }, { html: `
SOC2 Certified ISO 27001 Certified
`, }, ], }, ], copyright: `© ${new Date().getFullYear()} Promptfoo, Inc.`, }, prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, additionalLanguages: [ 'bash', 'javascript', 'typescript', 'python', 'json', 'yaml', 'markup-templating', 'liquid', ], }, zoom: { selector: '.markdown :not(em) > img:not(.no-zoom)', }, algolia: { // The application ID provided by Algolia appId: 'VPUDC1V4TA', // Public API key: it is safe to commit it apiKey: '0b4fcfd05976eb0aaf4b7c51ec4fcd23', indexName: 'promptfoo', }, } satisfies Preset.ThemeConfig, plugins: [ webpackProgressCompatibilityPlugin, require.resolve('docusaurus-plugin-image-zoom'), require.resolve('./src/plugins/docusaurus-plugin-og-image'), // GA/analytics loaded conditionally via consent.js (GDPR) [ '@docusaurus/plugin-client-redirects', { redirects: [ { from: '/docs/category/troubleshooting', to: '/docs/usage/troubleshooting/', }, { from: '/docs/providers/palm', to: '/docs/providers/google/', }, { from: '/docs', to: '/docs/intro/', }, { from: '/vegas-contact', to: 'https://triangular-manchego-867.notion.site/2395ae153a138028a8bef35f6889f6e6?pvs=105', }, { from: '/docs/guides/prevent-llm-hallucations', to: '/docs/guides/prevent-llm-hallucinations/', }, { from: '/docs/category/guides', to: '/docs/guides/', }, { from: '/docs/guides/gpt-5-vs-gpt-5-mini-mmlu', to: '/docs/guides/gpt-mmlu-comparison/', }, { from: '/docs/guides/gpt-5.2-vs-o3', to: '/docs/guides/gpt-vs-reasoning-model/', }, { from: '/solutions/healthcare', to: '/docs/red-team/plugins/medical/', }, { from: '/docs/guides/llama2-uncensored-benchmark-ollama', to: '/docs/guides/censored-vs-uncensored-ollama/', }, { from: '/docs/red-team/guardrails', to: '/docs/enterprise/guardrails/', }, // Deleted guides consolidated into gpt-vs-claude-vs-gemini { from: '/docs/guides/claude-vs-gpt', to: '/docs/guides/gpt-vs-claude-vs-gemini/', }, { from: '/docs/guides/gemini-vs-gpt', to: '/docs/guides/gpt-vs-claude-vs-gemini/', }, // Deleted guides consolidated into compare-open-source-models { from: '/docs/guides/compare-llama2-vs-gpt', to: '/docs/guides/compare-open-source-models/', }, { from: '/docs/guides/gemma-vs-llama', to: '/docs/guides/compare-open-source-models/', }, { from: '/docs/guides/gemma-vs-mistral', to: '/docs/guides/compare-open-source-models/', }, { from: '/docs/guides/mistral-vs-llama', to: '/docs/guides/compare-open-source-models/', }, { from: '/docs/guides/phi-vs-llama', to: '/docs/guides/compare-open-source-models/', }, // Deleted guides consolidated into choosing-best-gpt-model { from: '/docs/guides/gpt-3.5-vs-gpt-4', to: '/docs/guides/choosing-best-gpt-model/', }, { from: '/docs/guides/gpt-4-vs-gpt-4o', to: '/docs/guides/choosing-best-gpt-model/', }, // Deleted guides consolidated into gpt-vs-reasoning-model { from: '/docs/guides/gpt-vs-o1', to: '/docs/guides/gpt-vs-reasoning-model/', }, // Deleted guides consolidated into gpt-mmlu-comparison { from: '/docs/guides/gpt-4.1-vs-gpt-4o-mmlu', to: '/docs/guides/gpt-mmlu-comparison/', }, // Deleted guides redirected to guides index { from: '/docs/guides/cohere-command-r-benchmark', to: '/docs/guides/', }, { from: '/docs/guides/dbrx-benchmark', to: '/docs/guides/', }, { from: '/docs/guides/evaluate-replicate-lifeboat', to: '/docs/guides/', }, { from: '/docs/guides/building-trust-in-ai-with-portkey-and-promptfoo', to: '/docs/guides/', }, { from: '/docs/guides/mistral-magistral-aime2024', to: '/docs/guides/', }, // Renamed guide: agent-eval -> evaluate-coding-agents { from: '/docs/guides/agent-eval', to: '/docs/guides/evaluate-coding-agents/', }, ], }, ], // Define the llms.txt plugin inline similar to the Prisma example async function llmsTxtPlugin(context) { return { name: 'llms-txt-plugin', loadContent: async () => { const { siteDir } = context; const docsDir = join(siteDir, 'docs'); const allMdx: string[] = []; // Recursive function to get all mdx/md files const getMdFiles = async (dir: string): Promise => { const entries = await fsPromises.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { await getMdFiles(fullPath); } else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) { const content = await fsPromises.readFile(fullPath, 'utf8'); allMdx.push(content); } } }; await getMdFiles(docsDir); return { allMdx }; }, postBuild: async ({ content, routesPaths, outDir }) => { // Type assertion to handle TypeScript type checking const pluginContent = content as { allMdx: string[] }; const { allMdx } = pluginContent; // Write concatenated MDX content const concatenatedPath = join(outDir, 'llms-full.txt'); await fsPromises.writeFile(concatenatedPath, allMdx.join('\n\n---\n\n')); // Process routes - use routesPaths which is a string[] of all routes const docsRoutes: string[] = []; // Filter for docs paths and generate entries for (const routePath of routesPaths) { if (routePath.startsWith('/docs/')) { // Extract a title from the route path as fallback const pathParts = routePath.split('/').filter(Boolean); const lastPart = pathParts[pathParts.length - 1]; const title = lastPart .split('-') .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(' '); docsRoutes.push(`- [${title}](${routePath})`); } } // Build up llms.txt file const llmsTxt = `# ${context.siteConfig.title}\n\n## Docs\n\n${docsRoutes.join('\n')}`; // Write llms.txt file const llmsTxtPath = join(outDir, 'llms.txt'); try { await fsPromises.writeFile(llmsTxtPath, llmsTxt); console.log('Successfully created llms.txt and llms-full.txt files.'); } catch (err) { console.error('Error writing llms.txt file:', err); throw err; } }, }; }, [ '@docusaurus/plugin-content-blog', { id: 'releases', routeBasePath: 'releases', path: './releases', authorsMapPath: '../blog/authors.yml', blogTitle: 'Release Notes', blogDescription: 'promptfoo release notes and updates', blogSidebarCount: 0, blogSidebarTitle: 'Recent Releases', postsPerPage: 10, showReadingTime: false, feedOptions: { type: 'all', title: 'promptfoo Release Notes', description: 'Stay updated with the latest promptfoo releases', copyright: `© ${new Date().getFullYear()} promptfoo`, language: 'en', }, editUrl: 'https://github.com/promptfoo/promptfoo/tree/main/site', }, ], ], // Mermaid diagram support markdown: { mermaid: true, hooks: { onBrokenMarkdownLinks: 'throw', }, }, themes: ['@docusaurus/theme-mermaid'], }; export default config;