@@ -203,26 +203,27 @@ def _generate_llms_txt(app, exception):
203203
204204 The file is resolved in this order:
205205
206- 1. **Explicit option** — ``llm_custom_file`` theme option pointing to a file
206+ 1. **Explicit disable** — ``llm_disabled = "true"`` skips generation entirely.
207+ 2. **Custom file** — ``llm_custom_file`` theme option pointing to a file
207208 relative to the Sphinx source directory.
208- 2 . **Convention** — A file named ``llms.txt`` in the Sphinx source root.
209- 3 . **Auto-generation** — A simple page listing following the Hugging Face
210- style, with URLs resolved as:
209+ 3 . **Convention** — A file named ``llms.txt`` in the Sphinx source root.
210+ 4 . **Auto-generation** — A simple page listing following the llms.txt spec,
211+ with URLs resolved as:
211212 a. ``llm_domain`` + ``llm_base_path`` theme options → fully constructed URLs
212213 b. Sphinx ``html_baseurl`` config → baseurl + relative path
213214 c. Relative URLs as a last resort
214215
215- Opt-in: set ``llm_disabled = false `` in html_theme_options to enable .
216+ Enabled by default. Set ``llm_disabled = "true" `` to disable .
216217 """
217218 if exception is not None :
218219 return # Don't generate if build failed
219220
220221 if app .builder .name != "html" :
221222 return
222223
223- # Disabled by default; opt-in with llm_disabled = false
224+ # Enabled by default; opt-out with llm_disabled = "true"
224225 theme_options = app .config .html_theme_options or {}
225- if str (theme_options .get ("llm_disabled" , "true " )).lower () == "true" :
226+ if str (theme_options .get ("llm_disabled" , "false " )).lower () == "true" :
226227 return
227228
228229 dest_path = Path (app .outdir ) / "llms.txt"
@@ -286,17 +287,60 @@ def make_url(relative_path):
286287
287288 # Build the URL
288289 url = make_url (docname + ".html" )
289- docs .append ({"title" : str (title ), "url" : url })
290+ docs .append ({"title" : str (title ), "url" : url , "docname" : docname })
290291
291292 except Exception as e :
292293 print (f"Warning: Could not discover pages for llms.txt: { e } " )
293294
295+ # Deduplicate titles if enabled
296+ # This adds a disambiguating suffix to duplicate titles based on their URL path
297+ deduplicate = (
298+ str (theme_options .get ("llm_deduplicate_titles" , "false" )).lower () == "true"
299+ )
300+ if deduplicate :
301+ # Count title occurrences
302+ title_counts = {}
303+ for doc in docs :
304+ title_counts [doc ["title" ]] = title_counts .get (doc ["title" ], 0 ) + 1
305+
306+ # Find duplicates and add disambiguation
307+ for doc in docs :
308+ if title_counts [doc ["title" ]] > 1 :
309+ # Extract module/path info from docname for disambiguation
310+ # e.g., "generated/torch.nn.GRU" -> "torch.nn.GRU"
311+ docname = doc ["docname" ]
312+
313+ # Try to get a meaningful suffix from the docname
314+ if "/" in docname :
315+ suffix = docname .split ("/" )[- 1 ]
316+ else :
317+ suffix = docname
318+
319+ # Remove "generated/" prefix if present (Sphinx autodoc convention)
320+ if suffix .startswith ("generated/" ):
321+ suffix = suffix [10 :]
322+
323+ # Only add suffix if it's different from the title
324+ if suffix .lower () != doc ["title" ].lower ():
325+ doc ["title" ] = f"{ doc ['title' ]} ({ suffix } )"
326+
294327 # Build the llms.txt content in Hugging Face style
295328 lines = []
296329
297330 # Header
298331 lines .append (f"# { project } " )
299332 lines .append ("" )
333+
334+ # Quote block with project description (for spec compliance)
335+ # If llm_description is set, use it. Otherwise, generate a generic one from project name.
336+ llm_description = theme_options .get ("llm_description" , "" ).strip ()
337+ if not llm_description :
338+ # Generic fallback using Sphinx project name
339+ llm_description = f"{ project } documentation."
340+
341+ lines .append (f"> { llm_description } " )
342+ lines .append ("" )
343+
300344 lines .append ("## Docs" )
301345 lines .append ("" )
302346
0 commit comments