Add a knowledge indexing generator for an LLM-friendly docs index - #3453
Add a knowledge indexing generator for an LLM-friendly docs index#3453deruyter92 wants to merge 21 commits into
Conversation
Adds tools/knowledge_indexing, which reads the repository and writes a directory of small YAML files describing the codebase and user docs, one node per file, so an agent can load them lazily by id. Everything comes from the checkout, not from built or deployed docs: griffe reads the source statically for API signatures and docstrings, markdown-it-py parses the Markdown, and _toc.yml defines which pages are published along with their part/parent/child hierarchy. On the current checkout that is 270 nodes: 207 modules with 685 documented symbols, and 63 pages with 656 sections. Docs pages carry sections rather than concepts. A section is a retrievable part of one document, with an anchor, an absolute docs_url and the first paragraph of prose under the heading; anchors come from docutils.nodes.make_id, so they match the ids Sphinx emits. Concepts are reusable entities discussed across documents, which slugged headings are not, so none are generated. symbols.yaml maps every symbol id to the module node documenting it, for lookups that start from a bare name. index.yaml records the schema version, git revision and the two site base URLs the
C-Achard
left a comment
There was a problem hiding this comment.
Great work so far!
A few questions and some hopefully useful suggestions below, but nothing major besides the revision granularity
There was a problem hiding this comment.
A general comment is that library version versus docs could use a little more granularity and separation to ensure the source is perfectly clear always. E.g. which API revision is used vs which user docs revision
There was a problem hiding this comment.
Should we consider file content hashes for Nodes ? For correctness checks and maybe more optimized building (mainly the former)
There was a problem hiding this comment.
Good suggestion, added hashes now.
There was a problem hiding this comment.
A general comment is that library version versus docs could use a little more granularity and separation to ensure the source is perfectly clear always. E.g. which API revision is used vs which user docs revision
Dev-docs api knowledge index can now be build separately from the main docs index using --version-label, to build separate granular knowledge indices. For the latest main docs and the api-index tagged "main": these are build together, with a shared revision timestamp. This matches how the CI currently really works: deploy latest (all) or add/remove a specific version (dev-docs only).
There was a problem hiding this comment.
hooking it up in CI now. Please let me know what you think when I'm done (if it's granular enough in practice).
There was a problem hiding this comment.
Curiosity, does Amadeus know about the current local commit and is it compared to the knowledge system's commit? How should it handle mismatches?
(This is mostly a concern for testing with local DLC versions, and likely only affects us, but I wonder)
There was a problem hiding this comment.
Good question. I don't have a definite answer yet.
Per your suggestion I've added content hashes and the revision field allows to compare the specific source. When it mismatches, that means that no specific docs are available for this version (as would be the case when a human would go and look at our versioned documentation, not finding the specific version they were looking for).
The user can either change their DLC version to one of the documented versions, or Amadeus could assume the latest available version and reason from there.
There was a problem hiding this comment.
Maybe a warning and a fallback is nicer indeed?
| - **The user docs are not versioned** upstream, so `docs-pages/` is identical | ||
| across index versions and only stamped with the revision it came from. |
There was a problem hiding this comment.
Could we make use of the frontmatter metadata for this?
There was a problem hiding this comment.
the problem is: we could perfectly make a versioned index for user docs (we know the commit when the index is generated), but the ACTUAL docs on gh-pages are always just the latest.
We could choose to index historical references (the doc source files on git) but I think it would make more sense to point to the rendered live docs (which can also be opened by the user). These live docs are unfortunately not versioned, but I don't think it is a huge problem: the main user guidance doesn't change that much. The API docs are versioned anyway.
There was a problem hiding this comment.
BTW, the status, last_verified and other metadata fields are still very useful. They are included in the indexing description so downstream LLMs can make use of it. Super helpful product of the docs audit.
Meanwhile I've restructured the output a bit (see 83c8587) such that dev API-docs are listed per version, and the main docs are explicitly marked as unversioned (only latest: "main")
- replace the per-node YAML with knowledge/<version>/{api,docs}.jsonl
plus a manifest.json per version and a top-level knowledge/manifest.json
- Add llms.txt
- restructure schemas: separate build-requirement schemas (docs_index.py, api_index.py) and output schemas (schemas.py)
…`version_label`)
…ge indexing tool
- add new deploy-knowledge-index workflow for publishing _build/knowledge-index via peaceiris gh-pages - hook new workflow in deploy docs and deploy dev-docs-mike
…rence to the knowledge-index
C-Achard
left a comment
There was a problem hiding this comment.
A few CI-related comments, really liking the new version!
I also started on the write.py side a bit.
Were you thinking of adding tests as for the other tools as well?
|
|
||
| output: Path = args.output or repo / DEFAULT_OUTPUT_ROOT | ||
| knowledge_dir = output / KNOWLEDGE_DIR | ||
| api_base_url = API_BASE_URL.format(version=args.version_label) |
There was a problem hiding this comment.
Should the label be checked a bit more thoroughly if it's going to end up in the URL?
| def _git_revision(repo: Path) -> str: | ||
| """Full commit hash of `repo`, or "" if it is not a git checkout.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["git", "-C", str(repo), "rev-parse", "HEAD"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=10, | ||
| check=True, | ||
| ) | ||
| except (OSError, subprocess.SubprocessError): | ||
| return "" | ||
| return result.stdout.strip() |
| - name: Remove deleted version | ||
| if: inputs.action == 'delete' | ||
| shell: bash | ||
| run: rm -rf "_build/knowledge-index/knowledge/${{ inputs.version_label }}" |
There was a problem hiding this comment.
It seems that since the deploy runs with keep_files this may not reliably remove the published version
There was a problem hiding this comment.
Also deletion deltes both api and docs JSONs, which affects both at once, whereas building seems to be independent. Maybe worth separating the two if possible
| --deploy-prefix "${{ inputs.deploy-prefix }}" \ | ||
| "${{ inputs.version_label }}" | ||
|
|
||
| knowledge-index: |
There was a problem hiding this comment.
This may need a concurrency guard shared with other workflows operating on gh-pages, as having several workflows modifying concurrently may be a problem
| deploy-prefix: dev | ||
| secrets: inherit | ||
|
|
||
| deploy-user-docs-knowledge-index: |
There was a problem hiding this comment.
This may need a concurrency guard shared with other workflows operating on gh-pages, as having several workflows modifying concurrently may be a problem
There was a problem hiding this comment.
This may need a concurrency guard shared with other workflows operating on gh-pages, as having several workflows modifying concurrently may be a problem
|
|
||
| - name: Check out existing knowledge index from gh-pages | ||
| uses: actions/checkout@v6 | ||
| continue-on-error: true |
There was a problem hiding this comment.
Should this be more error-specific? Since checkout failing != does not exist
| """Read a JSON object, or None if `path` doesn't exist or isn't valid JSON.""" | ||
| try: | ||
| return json.loads(path.read_text(encoding="utf-8")) | ||
| except (OSError, json.JSONDecodeError): |
There was a problem hiding this comment.
This might be a tad permissive if there is a malformed output, since None means "keep that side of the index", being unable to read an existing file makes it as if it should be ignored safely
| api_provenance = ApiProvenance.from_dict(existing["api"]) if existing and existing.get("api") else None | ||
| docs_provenance = DocsProvenance.from_dict(existing["docs"]) if existing and existing.get("docs") else None |
There was a problem hiding this comment.
Is there a check that the files exist rather than only being listed by the manifest?
unsafe free-text inputs are now read from the environment (as text).
the knowledge-index deletion logic did not work correctly with peaceiris when `keep_files` is True. Also: the pre-existing workflow allowed the deletion of "main" which always contains the most recent documentaion and is reset after every push to main. The current commit fixes this and prevents deleting "main" in the first place. Only published releases can be deleted, and "main" is always tracking the most recent documentation version. For the knowledge-index, only the API docs index can be removed, as the main user docs are always kept up to date with the latest "main" version.
For API-docs deployment, a checkout from a specific tag is used, but this was done AFTER the latest deeplabcut was already cloned. Since checkout only WRITES the new state of files, stale paths (non-existent in the new checkout) would still remain on disk, potentially causing mixed API to be reported.
…g checkout For API-docs deployment, a checkout from a specific tag is used, but this was done AFTER the latest deeplabcut was already cloned. Since checkout only WRITES the new state of files, stale paths (non-existent in the new checkout) would still remain on disk, potentially causing mixed API to be reported.
Motivation
Recently the DeepLabCut documentation got an overhaul. This includes the recent launch of the api-docs. However for LLM agents, searching our documentation can still be a difficult task that easily fills the context window with irrelevant information.
The current PR adds a module
tools/knowledge_indexingfor generating a LLM-friendly index of the DeepLabCut API and user docs, and deploys it alongside the existing docs. This helps to point out AI agents where to retrieve relevant information.Implementation
llms.txtis generated at the site root following the the llmstxt.org convention and points at the structured index atknowledge/<version>/...knowledge/<version>/{api,docs}.jsonl. It contains one JSON object per record (with acontent_hash)manifest.jsonand a top-levelknowledge/manifest.jsonenumerates every versionapi.jsonlis versioned per dev-docs release, mirroring mike. The main user docs knowledge index always indexes the currently deployed user docs indocs.jsonl(the user docs have no versioning).knowledge-indexextra (docutils, griffelib, markdown-it-py) is added to make the CI workflow lean (no other installs needed)How to use
More information is added in tools/knowledge_indexing/README.md.
To locally build the full knowledge-index and
llms.txt, run:The main knowledge-index is always deployed to
gh-pagestogether with the user docs and dev-docs, triggered by updates on themainbranch. To deploy a specific version label, just run the pre-existing manual dispatch workflow for versioned dev-docs API docs, this now automatically adds / removes the corresponding llm-index as well.Status