mirror of
https://github.com/langchain-ai/docs.git
synced 2026-08-27 10:52:15 -04:00
Fix fabricated API URLs and an upscaled image found in self-review
Three defects in the preceding two commits.
llms.txt generated 47 API URLs for pages that do not exist, from two
wrong assumptions about how Mintlify slugs OpenAPI operations:
- Tags were slugged with underscores collapsed to hyphens. Mintlify uses
the tag verbatim apart from case and whitespace, and the LangSmith spec
carries both `annotation-queues` and `annotation_queues` as distinct
tags rendering to different directories. Collapsing them produced URLs
for pages that were never generated.
- Operations marked `x-hidden` were emitted. Mintlify renders no page for
them, which is why every fleet-* endpoint 404'd.
Two smaller slug mismatches: apostrophes were turned into separators
rather than dropped ("user's" gave user-s, not users), and operations
sharing a summary were skipped rather than given the numeric suffix
Mintlify assigns.
Derived API URLs now match the production sitemap exactly: 599 derived,
599 present, 0 fabricated, down from 47 wrong. Re-sampled 45 at random
against production, all 200, and separately confirmed 0 malformed
entries, 0 empty titles, and 0 duplicate URLs across all 2,038 lines.
Separately, vertex-ai-image-example.jpg had been upscaled from its native
602x800 to 770x1024, because `sips -Z` enlarges images smaller than the
target as well as shrinking larger ones. Regenerated at native size: no
invented detail, and smaller on disk (97,695 vs 123,886 bytes).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1129,8 +1129,26 @@ class DocumentationBuilder:
|
||||
|
||||
@staticmethod
|
||||
def _slugify(value: str) -> str:
|
||||
"""Lowercase, hyphenate, and strip a string for use in a URL path."""
|
||||
return re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", value.lower())).strip("-")
|
||||
"""Lowercase, hyphenate, and strip a string for use in a URL path.
|
||||
|
||||
Apostrophes are dropped rather than turned into separators, matching
|
||||
Mintlify: "Get the authenticated user's provider user ID" slugs to
|
||||
``...-users-provider-user-id``, not ``...-user-s-...``.
|
||||
"""
|
||||
cleaned = re.sub(r"['’]", "", value.lower())
|
||||
return re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", cleaned)).strip("-")
|
||||
|
||||
@staticmethod
|
||||
def _tag_slug(value: str) -> str:
|
||||
"""Slug an OpenAPI tag the way Mintlify does, preserving underscores.
|
||||
|
||||
Mintlify lowercases the tag and replaces whitespace, but otherwise uses
|
||||
it verbatim. Underscores must survive: the LangSmith spec carries both
|
||||
``annotation-queues`` and ``annotation_queues`` as distinct tags that
|
||||
render to different directories, so normalising them together would
|
||||
point at pages that do not exist.
|
||||
"""
|
||||
return re.sub(r"[^a-z0-9_-]+", "-", value.lower()).strip("-")
|
||||
|
||||
def _read_frontmatter(self, path: Path) -> dict:
|
||||
"""Return the YAML frontmatter of an MDX file, or an empty dict."""
|
||||
@@ -1214,17 +1232,25 @@ class DocumentationBuilder:
|
||||
for operation in item.values():
|
||||
if not isinstance(operation, dict) or "responses" not in operation:
|
||||
continue
|
||||
# Mintlify renders no page for hidden operations.
|
||||
if operation.get("x-hidden"):
|
||||
continue
|
||||
summary = operation.get("summary") or operation.get("operationId")
|
||||
if not summary:
|
||||
continue
|
||||
tags = operation.get("tags") or ["default"]
|
||||
slug = (
|
||||
f"{self._slugify(str(tags[0]))}/{self._slugify(str(summary))}"
|
||||
f"{self._tag_slug(str(tags[0]))}/{self._slugify(str(summary))}"
|
||||
)
|
||||
if slug in seen:
|
||||
continue
|
||||
seen.add(slug)
|
||||
entries.append((label, f"{base}/{slug}", str(summary)))
|
||||
# Two operations can share a summary. Mintlify keeps both
|
||||
# and disambiguates with a numeric suffix, so mirror that
|
||||
# rather than dropping the second page.
|
||||
unique, duplicate_index = slug, 0
|
||||
while unique in seen:
|
||||
duplicate_index += 1
|
||||
unique = f"{slug}-{duplicate_index}"
|
||||
seen.add(unique)
|
||||
entries.append((label, f"{base}/{unique}", str(summary)))
|
||||
return entries
|
||||
|
||||
def _generate_llms_txt(self) -> None:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 95 KiB |
@@ -5,6 +5,7 @@ covering all methods and edge cases including file extension handling,
|
||||
directory structure preservation, and error conditions.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -704,3 +705,72 @@ def test_build_all_writes_llms_txt() -> None:
|
||||
# noindex pages and snippets are not real pages, so they stay out.
|
||||
assert "hidden.md" not in llms_txt
|
||||
assert "snippets/shared.md" not in llms_txt
|
||||
|
||||
|
||||
def test_tag_slug_preserves_underscores() -> None:
|
||||
"""Test that OpenAPI tag slugs keep underscores but normalize case and spaces.
|
||||
|
||||
The LangSmith spec carries both `annotation-queues` and `annotation_queues`
|
||||
as distinct tags that Mintlify renders to different directories, so
|
||||
collapsing them together would generate URLs for pages that do not exist.
|
||||
"""
|
||||
slug = DocumentationBuilder._tag_slug
|
||||
assert slug("annotation_queues") == "annotation_queues"
|
||||
assert slug("annotation-queues") == "annotation-queues"
|
||||
assert slug("SCIM Tokens") == "scim-tokens"
|
||||
assert slug("A2A") == "a2a"
|
||||
|
||||
|
||||
def test_slugify_drops_apostrophes() -> None:
|
||||
"""Test that apostrophes are removed rather than turned into separators."""
|
||||
slug = DocumentationBuilder._slugify
|
||||
assert slug("Get the authenticated user's provider user ID") == (
|
||||
"get-the-authenticated-users-provider-user-id"
|
||||
)
|
||||
assert slug("Get company info") == "get-company-info"
|
||||
|
||||
|
||||
def test_openapi_entries_skip_hidden_and_number_duplicates() -> None:
|
||||
"""Test that hidden operations are omitted and duplicate slugs get suffixes.
|
||||
|
||||
Mintlify renders no page for `x-hidden` operations, and disambiguates two
|
||||
operations sharing a summary with a numeric suffix instead of dropping one.
|
||||
"""
|
||||
spec = {
|
||||
"paths": {
|
||||
"/a": {"get": {"tags": ["orgs"], "summary": "Get info", "responses": {}}},
|
||||
"/b": {"get": {"tags": ["orgs"], "summary": "Get info", "responses": {}}},
|
||||
"/c": {
|
||||
"get": {
|
||||
"tags": ["fleet orgs"],
|
||||
"summary": "Hidden op",
|
||||
"responses": {},
|
||||
"x-hidden": True,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
docs_json = {
|
||||
"navigation": {
|
||||
"pages": [
|
||||
{
|
||||
"group": "REST API",
|
||||
"openapi": {
|
||||
"source": "langsmith/spec.json",
|
||||
"directory": "langsmith/api",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
files: list[File] = [
|
||||
{"path": "docs.json", "content": json.dumps(docs_json)},
|
||||
{"path": "langsmith/spec.json", "content": json.dumps(spec)},
|
||||
{"path": "langsmith/page.mdx", "content": "---\ntitle: Page\n---\n\nBody.\n"},
|
||||
]
|
||||
with file_system(files) as fs:
|
||||
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
|
||||
builder.build_all()
|
||||
slugs = [slug for _, slug, _ in builder._openapi_entries()]
|
||||
|
||||
assert slugs == ["langsmith/api/orgs/get-info", "langsmith/api/orgs/get-info-1"]
|
||||
|
||||
Reference in New Issue
Block a user