From 98fb58763c1430c596108f05b20ba7e3d974d81f Mon Sep 17 00:00:00 2001 From: mayor Date: Thu, 12 Feb 2026 12:50:34 +0100 Subject: [PATCH 1/5] Add llms.txt for machine-readable documentation index Adds an llms.txt file following the llms.txt spec, providing AI tools with a structured index of all 35 documentation pages. Includes a Node generator script, test suite, docs page, CI integration, and sidebar link. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/docs.yml | 3 + docs/.vitepress/config.mts | 4 +- docs/llms_txt.md | 31 ++++++++ docs/public/llms.txt | 59 ++++++++++++++ package.json | 3 +- scripts/generate-llms-txt.mjs | 144 ++++++++++++++++++++++++++++++++++ scripts/test-llms-txt.mjs | 84 ++++++++++++++++++++ 7 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 docs/llms_txt.md create mode 100644 docs/public/llms.txt create mode 100644 scripts/generate-llms-txt.mjs create mode 100644 scripts/test-llms-txt.mjs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f529d3fb..293c71be 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -81,6 +81,9 @@ jobs: OPEN_ROUTER_API_KEY: OPEN_ROUTER_API_KEY run: bin/test + - name: Generate llms.txt + run: node scripts/generate-llms-txt.mjs + - name: Build with VitePress run: npm run docs:build diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 02262704..905a28e1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -61,7 +61,8 @@ export default defineConfig({ ['meta', { property: 'og:description', content: 'The AI framework for Rails with less code & more fun.' }], ['meta', { property: 'og:url', content: 'https://activeagents.ai' }], ['meta', { property: 'og:type', content: 'website' }], - ['script', { async: '', defer: '', src: 'https://buttons.github.io/buttons.js' }] + ['script', { async: '', defer: '', src: 'https://buttons.github.io/buttons.js' }], + ['link', { rel: 'help', type: 'text/markdown', href: '/llms.txt', title: 'LLM Documentation' }] ], cleanUrls: true, themeConfig: { @@ -145,6 +146,7 @@ export default defineConfig({ { text: 'Contributing', items: [ { text: 'Documentation', link: '/contributing/documentation' }, + { text: 'LLMs.txt', link: '/llms_txt' }, ] }, ], diff --git a/docs/llms_txt.md b/docs/llms_txt.md new file mode 100644 index 00000000..e700d7e1 --- /dev/null +++ b/docs/llms_txt.md @@ -0,0 +1,31 @@ +--- +title: LLMs.txt +description: Machine-readable documentation index for AI tools and large language models following the llms.txt specification. +--- +# {{ $frontmatter.title }} + +Active Agent publishes an [`llms.txt`](/llms.txt) file — a machine-readable index of all documentation pages, following the [llms.txt specification](https://llmstxt.org). + +## What is llms.txt? + +The llms.txt spec provides a standard way for websites to offer documentation in a format optimized for large language models. Instead of crawling HTML pages, AI tools can fetch a single markdown file with structured links and descriptions for every page. + +## Using llms.txt + +Point your AI tool at the file: + +``` +https://docs.activeagents.ai/llms.txt +``` + +Most AI-powered coding assistants and chat interfaces can ingest this URL directly to get full context on Active Agent. + +## Regenerating + +The file is regenerated on every docs deploy. To regenerate locally: + +```bash +npm run generate:llms-txt +``` + +This parses frontmatter from all documentation markdown files and writes `docs/public/llms.txt`. diff --git a/docs/public/llms.txt b/docs/public/llms.txt new file mode 100644 index 00000000..9a4ee7a3 --- /dev/null +++ b/docs/public/llms.txt @@ -0,0 +1,59 @@ +# Active Agent + +> ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns — controllers, actions, callbacks, and views. The AI framework for Rails with less code & more fun. + +## Getting Started + +- [Getting Started](https://docs.activeagents.ai/getting_started): Build AI agents with Rails in minutes. Learn how to install, configure, and create your first agent. + +## Framework + +- [Active Agent](https://docs.activeagents.ai/framework): ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns—controllers, actions, callbacks, and views. +- [Agents](https://docs.activeagents.ai/agents): Controllers for AI interactions with actions, callbacks, views, and concerns that generate AI responses instead of rendering HTML. +- [Providers](https://docs.activeagents.ai/providers): Connect your agents to AI services through a unified interface. Switch between OpenAI, Anthropic, local models, or testing mocks without changing agent code. +- [Configuration](https://docs.activeagents.ai/framework/configuration): Flexible configuration for framework-level settings and provider-specific options. Configure retry strategies, logging, and multiple AI providers with environment-specific settings. +- [Instrumentation and Logging](https://docs.activeagents.ai/framework/instrumentation): Monitor provider operations using ActiveSupport::Notifications. Track performance metrics, debug generation flows, and integrate with external monitoring services. +- [Retries](https://docs.activeagents.ai/framework/retries): Automatic retry mechanisms for handling rate limits, timeouts, and transient errors using provider-native SDK retry strategies with exponential backoff. +- [Rails Integration](https://docs.activeagents.ai/framework/rails): Install ActiveAgent in Rails applications with generators for agents, actions, and views. Configure providers and leverage familiar Rails conventions. +- [Testing ActiveAgent Applications](https://docs.activeagents.ai/framework/testing): Testing strategies for ActiveAgent applications with credential management, VCR integration, and test patterns. + +## Agents + +- [Actions](https://docs.activeagents.ai/actions): Public methods in your agent that define specific AI behaviors using prompt() for text generation or embed() for vector embeddings. +- [Generation](https://docs.activeagents.ai/agents/generation): Execute AI generations synchronously with prompt_now or asynchronously with prompt_later using ActiveAgent's generation methods. +- [Agent Instructions](https://docs.activeagents.ai/agents/instructions): System-level messages that guide agent behavior, personality, capabilities, and tool usage. The agent's operating manual for every interaction. +- [Streaming](https://docs.activeagents.ai/agents/streaming): Stream responses from AI providers in real-time using callbacks that execute at different points in the streaming lifecycle. +- [Callbacks](https://docs.activeagents.ai/agents/callbacks): Control agent lifecycle with generation, prompting, embedding, and streaming callbacks for setup, validation, cleanup, and real-time response handling. +- [Error Handling](https://docs.activeagents.ai/agents/error_handling): Build resilient agents with automatic retries for network failures and application-level rescue handlers for custom error recovery. + +## Actions + +- [Messages](https://docs.activeagents.ai/actions/messages): Build conversation context with messages containing roles (user, assistant, system, tool) and content (text, images, documents) in native or unified format. +- [Embeddings](https://docs.activeagents.ai/actions/embeddings): Generate vector embeddings from text to enable semantic search, clustering, and similarity comparison in your AI applications. +- [Tools](https://docs.activeagents.ai/actions/tools): Extend agents with callable functions that LLMs can trigger during generation. Unified interface across providers for function calling. +- [Model Context Protocols (MCP)](https://docs.activeagents.ai/actions/mcps): Connect agents to external services and APIs using the Model Context Protocol. Universal integration for tools and data sources. +- [Structured Output](https://docs.activeagents.ai/actions/structured_output): Control JSON responses from AI models with json_object for simple output or json_schema for validated structured data. +- [Usage Statistics](https://docs.activeagents.ai/actions/usage): Track token usage and performance metrics across all AI providers with normalized usage objects. + +## Providers + +- [Anthropic Provider](https://docs.activeagents.ai/providers/anthropic): Integration with Claude models including Sonnet 4.5, Haiku 4.5, and Opus 4.1. Advanced reasoning, extended context windows, thinking mode, and strong performance on complex tasks. +- [Ollama Provider](https://docs.activeagents.ai/providers/ollama): Local LLM inference using Ollama platform. Run Llama 3, Mistral, and Gemma locally without external APIs. Perfect for privacy-sensitive applications and development. +- [OpenAI Provider](https://docs.activeagents.ai/providers/open_ai): Integration with GPT models including GPT-5, GPT-4.1, GPT-4o, and o3. Responses API with built-in tools or traditional Chat Completions API for standard interactions. +- [OpenRouter Provider](https://docs.activeagents.ai/providers/open_router): Access 200+ AI models from multiple providers through unified API. Intelligent routing, automatic fallbacks, multimodal support, PDF processing, and cost optimization. +- [Mock Provider](https://docs.activeagents.ai/providers/mock): Testing provider for developing and testing agents without API calls or costs. Returns predictable pig latin responses and generates random embeddings. + +## Examples + +- [Browser Use Agent](https://docs.activeagents.ai/examples/browser-use-agent): Browser automation with AI-driven control. Navigate web pages, interact with elements, extract content, and take screenshots using Cuprite/Chrome. +- [Data Extraction](https://docs.activeagents.ai/examples/data_extraction_agent): Extract structured data from PDF resumes using AI-powered parsing. Demonstrates multimodal input and structured output with JSON schemas. +- [MCP Integration Agent](https://docs.activeagents.ai/examples/mcp-integration-agent): Connect ActiveAgent with external services through Model Context Protocol. Demonstrates standardized integration with cloud storage, APIs, and custom services. +- [Research Agent](https://docs.activeagents.ai/examples/research-agent): Combine multiple tools and data sources for comprehensive research tasks. Integrates web search, MCP servers, and image generation for powerful research workflows. +- [Support Agent](https://docs.activeagents.ai/examples/support-agent): Customer support chatbot demonstrating core ActiveAgent concepts including tool calling, message context, and multimodal responses. +- [Translation Agent](https://docs.activeagents.ai/examples/translation-agent): Create specialized agents for language translation tasks. Demonstrates how to build focused, single-purpose agents with clear responsibilities. +- [Web Search Agent](https://docs.activeagents.ai/examples/web-search-agent): Web search capabilities through OpenAI's search models and tools. Access real-time web information using Chat Completions API or Responses API. + +## Contributing + +- [Documentation](https://docs.activeagents.ai/contributing/documentation): Deterministic, always-accurate documentation where every code example comes from tested files. Learn how to maintain documentation that can't drift from code. +- [LLMs.txt](https://docs.activeagents.ai/llms_txt): Machine-readable documentation index for AI tools and large language models following the llms.txt specification. diff --git a/package.json b/package.json index 1cf2e586..7da0ab81 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "scripts": { "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" + "docs:preview": "vitepress preview docs", + "generate:llms-txt": "node scripts/generate-llms-txt.mjs" }, "dependencies": { "vitepress-plugin-group-icons": "^1.5.2" diff --git a/scripts/generate-llms-txt.mjs b/scripts/generate-llms-txt.mjs new file mode 100644 index 00000000..04975eed --- /dev/null +++ b/scripts/generate-llms-txt.mjs @@ -0,0 +1,144 @@ +#!/usr/bin/env node + +/** + * Generates docs/public/llms.txt from VitePress markdown files. + * Parses frontmatter (title, description) using regex — no dependencies. + */ + +import { readFileSync, writeFileSync, mkdirSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const DOCS_DIR = join(__dirname, '..', 'docs'); +const OUTPUT = join(DOCS_DIR, 'public', 'llms.txt'); +const BASE_URL = 'https://docs.activeagents.ai'; + +// Sidebar structure matching docs/.vitepress/config.mts +const sections = [ + { + title: 'Getting Started', + pages: [ + { path: 'getting_started' }, + ], + }, + { + title: 'Framework', + pages: [ + { path: 'framework' }, + { path: 'agents' }, + { path: 'providers' }, + { path: 'framework/configuration' }, + { path: 'framework/instrumentation' }, + { path: 'framework/retries' }, + { path: 'framework/rails' }, + { path: 'framework/testing' }, + ], + }, + { + title: 'Agents', + pages: [ + { path: 'actions' }, + { path: 'agents/generation' }, + { path: 'agents/instructions' }, + { path: 'agents/streaming' }, + { path: 'agents/callbacks' }, + { path: 'agents/error_handling' }, + ], + }, + { + title: 'Actions', + pages: [ + { path: 'actions/messages' }, + { path: 'actions/embeddings' }, + { path: 'actions/tools' }, + { path: 'actions/mcps' }, + { path: 'actions/structured_output' }, + { path: 'actions/usage' }, + ], + }, + { + title: 'Providers', + pages: [ + { path: 'providers/anthropic' }, + { path: 'providers/ollama' }, + { path: 'providers/open_ai' }, + { path: 'providers/open_router' }, + { path: 'providers/mock' }, + ], + }, + { + title: 'Examples', + pages: [ + { path: 'examples/browser-use-agent' }, + { path: 'examples/data_extraction_agent' }, + { path: 'examples/mcp-integration-agent' }, + { path: 'examples/research-agent' }, + { path: 'examples/support-agent' }, + { path: 'examples/translation-agent' }, + { path: 'examples/web-search-agent' }, + ], + }, + { + title: 'Contributing', + pages: [ + { path: 'contributing/documentation' }, + { path: 'llms_txt' }, + ], + }, +]; + +function parseFrontmatter(filePath) { + const content = readFileSync(filePath, 'utf-8'); + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) return {}; + + const fm = {}; + for (const line of match[1].split('\n')) { + const m = line.match(/^(\w+):\s*(.+)$/); + if (m) fm[m[1]] = m[2].replace(/^["']|["']$/g, ''); + } + return fm; +} + +function pageUrl(pagePath) { + return `${BASE_URL}/${pagePath}`; +} + +let count = 0; +const lines = []; + +lines.push('# Active Agent'); +lines.push(''); +lines.push('> ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns — controllers, actions, callbacks, and views. The AI framework for Rails with less code & more fun.'); +lines.push(''); + +for (const section of sections) { + lines.push(`## ${section.title}`); + lines.push(''); + + for (const page of section.pages) { + const filePath = join(DOCS_DIR, `${page.path}.md`); + let fm; + try { + fm = parseFrontmatter(filePath); + } catch { + console.warn(` skip: ${page.path}.md (not found)`); + continue; + } + + const title = fm.title || page.path; + const desc = fm.description || ''; + const url = pageUrl(page.path); + + lines.push(`- [${title}](${url}): ${desc}`); + count++; + } + + lines.push(''); +} + +mkdirSync(dirname(OUTPUT), { recursive: true }); +writeFileSync(OUTPUT, lines.join('\n')); + +console.log(`Generated ${OUTPUT} with ${count} entries`); diff --git a/scripts/test-llms-txt.mjs b/scripts/test-llms-txt.mjs new file mode 100644 index 00000000..249cc91e --- /dev/null +++ b/scripts/test-llms-txt.mjs @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +/** + * Tests that llms.txt is valid and complete. + * Run: node scripts/test-llms-txt.mjs + */ + +import { execSync } from 'child_process'; +import { readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUTPUT = join(__dirname, '..', 'docs', 'public', 'llms.txt'); + +let failures = 0; + +function assert(condition, msg) { + if (!condition) { + console.error(`FAIL: ${msg}`); + failures++; + } else { + console.log(` ok: ${msg}`); + } +} + +// Step 1: Run the generator +console.log('Running generator...'); +execSync('node scripts/generate-llms-txt.mjs', { + cwd: join(__dirname, '..'), + stdio: 'inherit', +}); + +// Step 2: Read output +const content = readFileSync(OUTPUT, 'utf-8'); +const lines = content.split('\n'); + +// Test: starts with H1 +assert(lines[0] === '# Active Agent', 'starts with "# Active Agent"'); + +// Test: has exactly one H1 +const h1s = lines.filter(l => /^# /.test(l)); +assert(h1s.length === 1, `exactly one H1 (got ${h1s.length})`); + +// Test: has blockquote description +const blockquotes = lines.filter(l => l.startsWith('> ')); +assert(blockquotes.length >= 1, 'has blockquote description'); + +// Test: has H2 sections +const h2s = lines.filter(l => /^## /.test(l)); +assert(h2s.length >= 5, `has at least 5 H2 sections (got ${h2s.length})`); + +// Test: all entries are markdown links with descriptions +const entries = lines.filter(l => /^- \[.+\]\(https:\/\//.test(l)); +assert(entries.length >= 30, `has at least 30 doc entries (got ${entries.length})`); + +// Test: every entry has a description after the link +for (const entry of entries) { + const hasDesc = /\): .+/.test(entry); + if (!hasDesc) { + assert(false, `entry missing description: ${entry.substring(0, 80)}`); + } +} + +// Test: expected sections present +const sectionNames = h2s.map(l => l.replace('## ', '')); +for (const expected of ['Getting Started', 'Framework', 'Agents', 'Actions', 'Providers', 'Examples', 'Contributing']) { + assert(sectionNames.includes(expected), `section "${expected}" present`); +} + +// Test: key pages are present +const entryText = entries.join('\n'); +for (const keyword of ['Getting Started', 'Configuration', 'Anthropic', 'OpenAI', 'Streaming', 'Tools', 'Callbacks']) { + assert(entryText.includes(keyword), `key page "${keyword}" present`); +} + +// Test: URLs point to docs.activeagents.ai +const urls = entries.map(l => l.match(/\((https:\/\/[^)]+)\)/)?.[1]).filter(Boolean); +for (const url of urls) { + assert(url.startsWith('https://docs.activeagents.ai/'), `valid URL: ${url}`); +} + +console.log(`\n${failures === 0 ? 'ALL TESTS PASSED' : `${failures} FAILURE(S)`}`); +process.exit(failures === 0 ? 0 : 1); From 855ed41ae2963ad8e78a4a79ff69f40dfc84ffbd Mon Sep 17 00:00:00 2001 From: mayor Date: Thu, 12 Feb 2026 13:22:52 +0100 Subject: [PATCH 2/5] Refactor llms.txt into VitePress build pipeline Move llms.txt generation from a standalone script into the VitePress buildEnd hook so docs:build produces everything in one command. - Extract generation logic to docs/.vitepress/llms-txt.ts - Delete scripts/generate-llms-txt.mjs and docs/public/llms.txt - Remove generate:llms-txt npm script and CI step - Update test to build docs and check dist/llms.txt - Update docs page with new regeneration instructions Co-Authored-By: Claude Opus 4.6 --- .github/workflows/docs.yml | 3 - docs/.vitepress/config.mts | 5 +- docs/.vitepress/llms-txt.ts | 128 ++++++++++++++++++++++++++++++ docs/llms_txt.md | 6 +- docs/public/llms.txt | 59 -------------- package.json | 3 +- scripts/generate-llms-txt.mjs | 144 ---------------------------------- scripts/test-llms-txt.mjs | 8 +- 8 files changed, 140 insertions(+), 216 deletions(-) create mode 100644 docs/.vitepress/llms-txt.ts delete mode 100644 docs/public/llms.txt delete mode 100644 scripts/generate-llms-txt.mjs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 293c71be..f529d3fb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -81,9 +81,6 @@ jobs: OPEN_ROUTER_API_KEY: OPEN_ROUTER_API_KEY run: bin/test - - name: Generate llms.txt - run: node scripts/generate-llms-txt.mjs - - name: Build with VitePress run: npm run docs:build diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 905a28e1..536b0067 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,6 +8,7 @@ import { } from "vitepress-plugin-group-icons" import versions from './versions.json' +import { generateLlmsTxt } from './llms-txt' // Build version dropdown items with absolute URLs for cross-version navigation // This forces full page reload instead of client-side routing @@ -163,5 +164,7 @@ export default defineConfig({ { icon: 'github', link: 'https://github.com/activeagents/activeagent' } ], }, - lastUpdated: true + lastUpdated: true, + + buildEnd: generateLlmsTxt }) diff --git a/docs/.vitepress/llms-txt.ts b/docs/.vitepress/llms-txt.ts new file mode 100644 index 00000000..4ab4d5d3 --- /dev/null +++ b/docs/.vitepress/llms-txt.ts @@ -0,0 +1,128 @@ +import { readFileSync, writeFileSync } from 'fs' +import { join } from 'path' +import type { SiteConfig } from 'vitepress' + +const BASE_URL = 'https://docs.activeagents.ai' + +const sections = [ + { + title: 'Getting Started', + pages: [{ path: 'getting_started' }], + }, + { + title: 'Framework', + pages: [ + { path: 'framework' }, + { path: 'agents' }, + { path: 'providers' }, + { path: 'framework/configuration' }, + { path: 'framework/instrumentation' }, + { path: 'framework/retries' }, + { path: 'framework/rails' }, + { path: 'framework/testing' }, + ], + }, + { + title: 'Agents', + pages: [ + { path: 'actions' }, + { path: 'agents/generation' }, + { path: 'agents/instructions' }, + { path: 'agents/streaming' }, + { path: 'agents/callbacks' }, + { path: 'agents/error_handling' }, + ], + }, + { + title: 'Actions', + pages: [ + { path: 'actions/messages' }, + { path: 'actions/embeddings' }, + { path: 'actions/tools' }, + { path: 'actions/mcps' }, + { path: 'actions/structured_output' }, + { path: 'actions/usage' }, + ], + }, + { + title: 'Providers', + pages: [ + { path: 'providers/anthropic' }, + { path: 'providers/ollama' }, + { path: 'providers/open_ai' }, + { path: 'providers/open_router' }, + { path: 'providers/mock' }, + ], + }, + { + title: 'Examples', + pages: [ + { path: 'examples/browser-use-agent' }, + { path: 'examples/data_extraction_agent' }, + { path: 'examples/mcp-integration-agent' }, + { path: 'examples/research-agent' }, + { path: 'examples/support-agent' }, + { path: 'examples/translation-agent' }, + { path: 'examples/web-search-agent' }, + ], + }, + { + title: 'Contributing', + pages: [ + { path: 'contributing/documentation' }, + { path: 'llms_txt' }, + ], + }, +] + +function parseFrontmatter(filePath: string): Record { + const content = readFileSync(filePath, 'utf-8') + const match = content.match(/^---\n([\s\S]*?)\n---/) + if (!match) return {} + + const fm: Record = {} + for (const line of match[1].split('\n')) { + const m = line.match(/^(\w+):\s*(.+)$/) + if (m) fm[m[1]] = m[2].replace(/^["']|["']$/g, '') + } + return fm +} + +export async function generateLlmsTxt(siteConfig: SiteConfig) { + let count = 0 + const lines: string[] = [] + + lines.push('# Active Agent') + lines.push('') + lines.push('> ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns — controllers, actions, callbacks, and views. The AI framework for Rails with less code & more fun.') + lines.push('') + + for (const section of sections) { + lines.push(`## ${section.title}`) + lines.push('') + + for (const page of section.pages) { + const filePath = join(siteConfig.srcDir, `${page.path}.md`) + let fm: Record + try { + fm = parseFrontmatter(filePath) + } catch { + console.warn(` skip: ${page.path}.md (not found)`) + continue + } + + const title = fm.title || page.path + const desc = fm.description || '' + const url = `${BASE_URL}/${page.path}` + + lines.push(`- [${title}](${url}): ${desc}`) + count++ + } + + lines.push('') + } + + const outPath = join(siteConfig.outDir, 'llms.txt') + writeFileSync(outPath, lines.join('\n')) + console.log(`Generated ${outPath} with ${count} entries`) +} diff --git a/docs/llms_txt.md b/docs/llms_txt.md index e700d7e1..a17d701e 100644 --- a/docs/llms_txt.md +++ b/docs/llms_txt.md @@ -22,10 +22,10 @@ Most AI-powered coding assistants and chat interfaces can ingest this URL direct ## Regenerating -The file is regenerated on every docs deploy. To regenerate locally: +The file is regenerated on every docs deploy as part of the VitePress build. To regenerate locally: ```bash -npm run generate:llms-txt +npm run docs:build ``` -This parses frontmatter from all documentation markdown files and writes `docs/public/llms.txt`. +This parses frontmatter from all documentation markdown files and writes `llms.txt` to the build output directory. diff --git a/docs/public/llms.txt b/docs/public/llms.txt deleted file mode 100644 index 9a4ee7a3..00000000 --- a/docs/public/llms.txt +++ /dev/null @@ -1,59 +0,0 @@ -# Active Agent - -> ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns — controllers, actions, callbacks, and views. The AI framework for Rails with less code & more fun. - -## Getting Started - -- [Getting Started](https://docs.activeagents.ai/getting_started): Build AI agents with Rails in minutes. Learn how to install, configure, and create your first agent. - -## Framework - -- [Active Agent](https://docs.activeagents.ai/framework): ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns—controllers, actions, callbacks, and views. -- [Agents](https://docs.activeagents.ai/agents): Controllers for AI interactions with actions, callbacks, views, and concerns that generate AI responses instead of rendering HTML. -- [Providers](https://docs.activeagents.ai/providers): Connect your agents to AI services through a unified interface. Switch between OpenAI, Anthropic, local models, or testing mocks without changing agent code. -- [Configuration](https://docs.activeagents.ai/framework/configuration): Flexible configuration for framework-level settings and provider-specific options. Configure retry strategies, logging, and multiple AI providers with environment-specific settings. -- [Instrumentation and Logging](https://docs.activeagents.ai/framework/instrumentation): Monitor provider operations using ActiveSupport::Notifications. Track performance metrics, debug generation flows, and integrate with external monitoring services. -- [Retries](https://docs.activeagents.ai/framework/retries): Automatic retry mechanisms for handling rate limits, timeouts, and transient errors using provider-native SDK retry strategies with exponential backoff. -- [Rails Integration](https://docs.activeagents.ai/framework/rails): Install ActiveAgent in Rails applications with generators for agents, actions, and views. Configure providers and leverage familiar Rails conventions. -- [Testing ActiveAgent Applications](https://docs.activeagents.ai/framework/testing): Testing strategies for ActiveAgent applications with credential management, VCR integration, and test patterns. - -## Agents - -- [Actions](https://docs.activeagents.ai/actions): Public methods in your agent that define specific AI behaviors using prompt() for text generation or embed() for vector embeddings. -- [Generation](https://docs.activeagents.ai/agents/generation): Execute AI generations synchronously with prompt_now or asynchronously with prompt_later using ActiveAgent's generation methods. -- [Agent Instructions](https://docs.activeagents.ai/agents/instructions): System-level messages that guide agent behavior, personality, capabilities, and tool usage. The agent's operating manual for every interaction. -- [Streaming](https://docs.activeagents.ai/agents/streaming): Stream responses from AI providers in real-time using callbacks that execute at different points in the streaming lifecycle. -- [Callbacks](https://docs.activeagents.ai/agents/callbacks): Control agent lifecycle with generation, prompting, embedding, and streaming callbacks for setup, validation, cleanup, and real-time response handling. -- [Error Handling](https://docs.activeagents.ai/agents/error_handling): Build resilient agents with automatic retries for network failures and application-level rescue handlers for custom error recovery. - -## Actions - -- [Messages](https://docs.activeagents.ai/actions/messages): Build conversation context with messages containing roles (user, assistant, system, tool) and content (text, images, documents) in native or unified format. -- [Embeddings](https://docs.activeagents.ai/actions/embeddings): Generate vector embeddings from text to enable semantic search, clustering, and similarity comparison in your AI applications. -- [Tools](https://docs.activeagents.ai/actions/tools): Extend agents with callable functions that LLMs can trigger during generation. Unified interface across providers for function calling. -- [Model Context Protocols (MCP)](https://docs.activeagents.ai/actions/mcps): Connect agents to external services and APIs using the Model Context Protocol. Universal integration for tools and data sources. -- [Structured Output](https://docs.activeagents.ai/actions/structured_output): Control JSON responses from AI models with json_object for simple output or json_schema for validated structured data. -- [Usage Statistics](https://docs.activeagents.ai/actions/usage): Track token usage and performance metrics across all AI providers with normalized usage objects. - -## Providers - -- [Anthropic Provider](https://docs.activeagents.ai/providers/anthropic): Integration with Claude models including Sonnet 4.5, Haiku 4.5, and Opus 4.1. Advanced reasoning, extended context windows, thinking mode, and strong performance on complex tasks. -- [Ollama Provider](https://docs.activeagents.ai/providers/ollama): Local LLM inference using Ollama platform. Run Llama 3, Mistral, and Gemma locally without external APIs. Perfect for privacy-sensitive applications and development. -- [OpenAI Provider](https://docs.activeagents.ai/providers/open_ai): Integration with GPT models including GPT-5, GPT-4.1, GPT-4o, and o3. Responses API with built-in tools or traditional Chat Completions API for standard interactions. -- [OpenRouter Provider](https://docs.activeagents.ai/providers/open_router): Access 200+ AI models from multiple providers through unified API. Intelligent routing, automatic fallbacks, multimodal support, PDF processing, and cost optimization. -- [Mock Provider](https://docs.activeagents.ai/providers/mock): Testing provider for developing and testing agents without API calls or costs. Returns predictable pig latin responses and generates random embeddings. - -## Examples - -- [Browser Use Agent](https://docs.activeagents.ai/examples/browser-use-agent): Browser automation with AI-driven control. Navigate web pages, interact with elements, extract content, and take screenshots using Cuprite/Chrome. -- [Data Extraction](https://docs.activeagents.ai/examples/data_extraction_agent): Extract structured data from PDF resumes using AI-powered parsing. Demonstrates multimodal input and structured output with JSON schemas. -- [MCP Integration Agent](https://docs.activeagents.ai/examples/mcp-integration-agent): Connect ActiveAgent with external services through Model Context Protocol. Demonstrates standardized integration with cloud storage, APIs, and custom services. -- [Research Agent](https://docs.activeagents.ai/examples/research-agent): Combine multiple tools and data sources for comprehensive research tasks. Integrates web search, MCP servers, and image generation for powerful research workflows. -- [Support Agent](https://docs.activeagents.ai/examples/support-agent): Customer support chatbot demonstrating core ActiveAgent concepts including tool calling, message context, and multimodal responses. -- [Translation Agent](https://docs.activeagents.ai/examples/translation-agent): Create specialized agents for language translation tasks. Demonstrates how to build focused, single-purpose agents with clear responsibilities. -- [Web Search Agent](https://docs.activeagents.ai/examples/web-search-agent): Web search capabilities through OpenAI's search models and tools. Access real-time web information using Chat Completions API or Responses API. - -## Contributing - -- [Documentation](https://docs.activeagents.ai/contributing/documentation): Deterministic, always-accurate documentation where every code example comes from tested files. Learn how to maintain documentation that can't drift from code. -- [LLMs.txt](https://docs.activeagents.ai/llms_txt): Machine-readable documentation index for AI tools and large language models following the llms.txt specification. diff --git a/package.json b/package.json index 7da0ab81..1cf2e586 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,7 @@ "scripts": { "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs", - "generate:llms-txt": "node scripts/generate-llms-txt.mjs" + "docs:preview": "vitepress preview docs" }, "dependencies": { "vitepress-plugin-group-icons": "^1.5.2" diff --git a/scripts/generate-llms-txt.mjs b/scripts/generate-llms-txt.mjs deleted file mode 100644 index 04975eed..00000000 --- a/scripts/generate-llms-txt.mjs +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env node - -/** - * Generates docs/public/llms.txt from VitePress markdown files. - * Parses frontmatter (title, description) using regex — no dependencies. - */ - -import { readFileSync, writeFileSync, mkdirSync } from 'fs'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const DOCS_DIR = join(__dirname, '..', 'docs'); -const OUTPUT = join(DOCS_DIR, 'public', 'llms.txt'); -const BASE_URL = 'https://docs.activeagents.ai'; - -// Sidebar structure matching docs/.vitepress/config.mts -const sections = [ - { - title: 'Getting Started', - pages: [ - { path: 'getting_started' }, - ], - }, - { - title: 'Framework', - pages: [ - { path: 'framework' }, - { path: 'agents' }, - { path: 'providers' }, - { path: 'framework/configuration' }, - { path: 'framework/instrumentation' }, - { path: 'framework/retries' }, - { path: 'framework/rails' }, - { path: 'framework/testing' }, - ], - }, - { - title: 'Agents', - pages: [ - { path: 'actions' }, - { path: 'agents/generation' }, - { path: 'agents/instructions' }, - { path: 'agents/streaming' }, - { path: 'agents/callbacks' }, - { path: 'agents/error_handling' }, - ], - }, - { - title: 'Actions', - pages: [ - { path: 'actions/messages' }, - { path: 'actions/embeddings' }, - { path: 'actions/tools' }, - { path: 'actions/mcps' }, - { path: 'actions/structured_output' }, - { path: 'actions/usage' }, - ], - }, - { - title: 'Providers', - pages: [ - { path: 'providers/anthropic' }, - { path: 'providers/ollama' }, - { path: 'providers/open_ai' }, - { path: 'providers/open_router' }, - { path: 'providers/mock' }, - ], - }, - { - title: 'Examples', - pages: [ - { path: 'examples/browser-use-agent' }, - { path: 'examples/data_extraction_agent' }, - { path: 'examples/mcp-integration-agent' }, - { path: 'examples/research-agent' }, - { path: 'examples/support-agent' }, - { path: 'examples/translation-agent' }, - { path: 'examples/web-search-agent' }, - ], - }, - { - title: 'Contributing', - pages: [ - { path: 'contributing/documentation' }, - { path: 'llms_txt' }, - ], - }, -]; - -function parseFrontmatter(filePath) { - const content = readFileSync(filePath, 'utf-8'); - const match = content.match(/^---\n([\s\S]*?)\n---/); - if (!match) return {}; - - const fm = {}; - for (const line of match[1].split('\n')) { - const m = line.match(/^(\w+):\s*(.+)$/); - if (m) fm[m[1]] = m[2].replace(/^["']|["']$/g, ''); - } - return fm; -} - -function pageUrl(pagePath) { - return `${BASE_URL}/${pagePath}`; -} - -let count = 0; -const lines = []; - -lines.push('# Active Agent'); -lines.push(''); -lines.push('> ActiveAgent extends Rails MVC to AI interactions. Build intelligent agents using familiar patterns — controllers, actions, callbacks, and views. The AI framework for Rails with less code & more fun.'); -lines.push(''); - -for (const section of sections) { - lines.push(`## ${section.title}`); - lines.push(''); - - for (const page of section.pages) { - const filePath = join(DOCS_DIR, `${page.path}.md`); - let fm; - try { - fm = parseFrontmatter(filePath); - } catch { - console.warn(` skip: ${page.path}.md (not found)`); - continue; - } - - const title = fm.title || page.path; - const desc = fm.description || ''; - const url = pageUrl(page.path); - - lines.push(`- [${title}](${url}): ${desc}`); - count++; - } - - lines.push(''); -} - -mkdirSync(dirname(OUTPUT), { recursive: true }); -writeFileSync(OUTPUT, lines.join('\n')); - -console.log(`Generated ${OUTPUT} with ${count} entries`); diff --git a/scripts/test-llms-txt.mjs b/scripts/test-llms-txt.mjs index 249cc91e..ba18036c 100644 --- a/scripts/test-llms-txt.mjs +++ b/scripts/test-llms-txt.mjs @@ -11,7 +11,7 @@ import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const OUTPUT = join(__dirname, '..', 'docs', 'public', 'llms.txt'); +const OUTPUT = join(__dirname, '..', 'docs', '.vitepress', 'dist', 'llms.txt'); let failures = 0; @@ -24,9 +24,9 @@ function assert(condition, msg) { } } -// Step 1: Run the generator -console.log('Running generator...'); -execSync('node scripts/generate-llms-txt.mjs', { +// Step 1: Build docs (which generates llms.txt via buildEnd hook) +console.log('Building docs...'); +execSync('npm run docs:build', { cwd: join(__dirname, '..'), stdio: 'inherit', }); From d3c54c0ac01a4e1f436223e1e3ba174f0bb58d85 Mon Sep 17 00:00:00 2001 From: mayor Date: Thu, 12 Feb 2026 13:28:15 +0100 Subject: [PATCH 3/5] Remove self-referential llms_txt entry from llms.txt output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The llms.txt page explains what llms.txt is — that's for humans, not LLMs consuming the file. Omit it to avoid the circular reference. Co-Authored-By: Claude Opus 4.6 --- docs/.vitepress/llms-txt.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/.vitepress/llms-txt.ts b/docs/.vitepress/llms-txt.ts index 4ab4d5d3..9c8a44e5 100644 --- a/docs/.vitepress/llms-txt.ts +++ b/docs/.vitepress/llms-txt.ts @@ -70,7 +70,6 @@ const sections = [ title: 'Contributing', pages: [ { path: 'contributing/documentation' }, - { path: 'llms_txt' }, ], }, ] From a62f4d8611f1c096db8730447222eaa96c8fb36b Mon Sep 17 00:00:00 2001 From: mayor Date: Thu, 12 Feb 2026 13:44:28 +0100 Subject: [PATCH 4/5] Replace standalone Node test with CI validation step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete scripts/test-llms-txt.mjs — a bespoke Node script with hand-rolled assertions outside the project's test conventions (Ruby Minitest via bin/test). Add inline validation in docs.yml after docs:build instead. This runs where it belongs: in the pipeline that produces the artifact, checking file existence, H1, entry count, and sections. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/docs.yml | 12 ++++++ scripts/test-llms-txt.mjs | 84 -------------------------------------- 2 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 scripts/test-llms-txt.mjs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f529d3fb..e3741231 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -84,6 +84,18 @@ jobs: - name: Build with VitePress run: npm run docs:build + - name: Validate llms.txt + run: | + FILE="docs/.vitepress/dist/llms.txt" + test -f "$FILE" || { echo "FAIL: $FILE not found"; exit 1; } + head -1 "$FILE" | grep -q "^# Active Agent" || { echo "FAIL: missing H1"; exit 1; } + ENTRIES=$(grep -c "^- \[" "$FILE") + test "$ENTRIES" -ge 30 || { echo "FAIL: only $ENTRIES entries (expected >=30)"; exit 1; } + for section in "Getting Started" "Framework" "Agents" "Actions" "Providers" "Examples" "Contributing"; do + grep -q "^## $section" "$FILE" || { echo "FAIL: missing section '$section'"; exit 1; } + done + echo "llms.txt valid: $ENTRIES entries, all sections present" + - name: Upload current docs artifact uses: actions/upload-artifact@v5 with: diff --git a/scripts/test-llms-txt.mjs b/scripts/test-llms-txt.mjs deleted file mode 100644 index ba18036c..00000000 --- a/scripts/test-llms-txt.mjs +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node - -/** - * Tests that llms.txt is valid and complete. - * Run: node scripts/test-llms-txt.mjs - */ - -import { execSync } from 'child_process'; -import { readFileSync } from 'fs'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const OUTPUT = join(__dirname, '..', 'docs', '.vitepress', 'dist', 'llms.txt'); - -let failures = 0; - -function assert(condition, msg) { - if (!condition) { - console.error(`FAIL: ${msg}`); - failures++; - } else { - console.log(` ok: ${msg}`); - } -} - -// Step 1: Build docs (which generates llms.txt via buildEnd hook) -console.log('Building docs...'); -execSync('npm run docs:build', { - cwd: join(__dirname, '..'), - stdio: 'inherit', -}); - -// Step 2: Read output -const content = readFileSync(OUTPUT, 'utf-8'); -const lines = content.split('\n'); - -// Test: starts with H1 -assert(lines[0] === '# Active Agent', 'starts with "# Active Agent"'); - -// Test: has exactly one H1 -const h1s = lines.filter(l => /^# /.test(l)); -assert(h1s.length === 1, `exactly one H1 (got ${h1s.length})`); - -// Test: has blockquote description -const blockquotes = lines.filter(l => l.startsWith('> ')); -assert(blockquotes.length >= 1, 'has blockquote description'); - -// Test: has H2 sections -const h2s = lines.filter(l => /^## /.test(l)); -assert(h2s.length >= 5, `has at least 5 H2 sections (got ${h2s.length})`); - -// Test: all entries are markdown links with descriptions -const entries = lines.filter(l => /^- \[.+\]\(https:\/\//.test(l)); -assert(entries.length >= 30, `has at least 30 doc entries (got ${entries.length})`); - -// Test: every entry has a description after the link -for (const entry of entries) { - const hasDesc = /\): .+/.test(entry); - if (!hasDesc) { - assert(false, `entry missing description: ${entry.substring(0, 80)}`); - } -} - -// Test: expected sections present -const sectionNames = h2s.map(l => l.replace('## ', '')); -for (const expected of ['Getting Started', 'Framework', 'Agents', 'Actions', 'Providers', 'Examples', 'Contributing']) { - assert(sectionNames.includes(expected), `section "${expected}" present`); -} - -// Test: key pages are present -const entryText = entries.join('\n'); -for (const keyword of ['Getting Started', 'Configuration', 'Anthropic', 'OpenAI', 'Streaming', 'Tools', 'Callbacks']) { - assert(entryText.includes(keyword), `key page "${keyword}" present`); -} - -// Test: URLs point to docs.activeagents.ai -const urls = entries.map(l => l.match(/\((https:\/\/[^)]+)\)/)?.[1]).filter(Boolean); -for (const url of urls) { - assert(url.startsWith('https://docs.activeagents.ai/'), `valid URL: ${url}`); -} - -console.log(`\n${failures === 0 ? 'ALL TESTS PASSED' : `${failures} FAILURE(S)`}`); -process.exit(failures === 0 ? 0 : 1); From be0c2441566993c0c677ca1e65f6edafcf9f2452 Mon Sep 17 00:00:00 2001 From: mayor Date: Sat, 14 Feb 2026 12:01:20 +0100 Subject: [PATCH 5/5] Address review feedback: fail on missing pages, trailing newline, accurate wording - Throw error instead of silently skipping when a listed doc page is missing - Add trailing newline to generated llms.txt for POSIX compliance - Change "all documentation pages" to "curated" for accuracy Co-Authored-By: Claude Opus 4.6 --- docs/.vitepress/llms-txt.ts | 7 +++---- docs/llms_txt.md | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/.vitepress/llms-txt.ts b/docs/.vitepress/llms-txt.ts index 9c8a44e5..8f78e87e 100644 --- a/docs/.vitepress/llms-txt.ts +++ b/docs/.vitepress/llms-txt.ts @@ -105,9 +105,8 @@ export async function generateLlmsTxt(siteConfig: SiteConfig) { let fm: Record try { fm = parseFrontmatter(filePath) - } catch { - console.warn(` skip: ${page.path}.md (not found)`) - continue + } catch (error) { + throw new Error(`Failed to read ${page.path}.md at ${filePath}: ${(error as Error).message}`) } const title = fm.title || page.path @@ -122,6 +121,6 @@ export async function generateLlmsTxt(siteConfig: SiteConfig) { } const outPath = join(siteConfig.outDir, 'llms.txt') - writeFileSync(outPath, lines.join('\n')) + writeFileSync(outPath, lines.join('\n') + '\n') console.log(`Generated ${outPath} with ${count} entries`) } diff --git a/docs/llms_txt.md b/docs/llms_txt.md index a17d701e..f28ae103 100644 --- a/docs/llms_txt.md +++ b/docs/llms_txt.md @@ -4,7 +4,7 @@ description: Machine-readable documentation index for AI tools and large languag --- # {{ $frontmatter.title }} -Active Agent publishes an [`llms.txt`](/llms.txt) file — a machine-readable index of all documentation pages, following the [llms.txt specification](https://llmstxt.org). +Active Agent publishes an [`llms.txt`](/llms.txt) file — a curated, machine-readable index of documentation pages, following the [llms.txt specification](https://llmstxt.org). ## What is llms.txt? @@ -28,4 +28,4 @@ The file is regenerated on every docs deploy as part of the VitePress build. To npm run docs:build ``` -This parses frontmatter from all documentation markdown files and writes `llms.txt` to the build output directory. +This parses frontmatter from the curated list of documentation markdown files and writes `llms.txt` to the build output directory.