Sentence Similarity
sentence-transformers
English
ros2
ros
robotics
rag
retrieval
faiss
documentation
qwen
offline
Instructions to use eoinedge/ros2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use eoinedge/ros2 with sentence-transformers:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("eoinedge/ros2") sentences = [ "That is a happy person", "That is a happy dog", "That is a very happy person", "Today is a sunny day" ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [4, 4] - Notebooks
- Google Colab
- Kaggle
ROS 2 docs RAG index and tooling
Browse files- scripts/fetch_docs.py +165 -0
scripts/fetch_docs.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fetch the documentation source for the configured project.
|
| 2 |
+
|
| 3 |
+
Reads the RST rather than crawling the rendered site. Rendered pages carry
|
| 4 |
+
navigation, version switchers and generated API listings, all of which land in
|
| 5 |
+
chunks and compete with prose during retrieval; the RST is the authoritative
|
| 6 |
+
text and is versioned and diffable.
|
| 7 |
+
|
| 8 |
+
ROS 2 documents each distribution on its own branch, so --ref selects the
|
| 9 |
+
distribution: rolling (default), jazzy, humble.
|
| 10 |
+
|
| 11 |
+
python scripts/fetch_docs.py
|
| 12 |
+
python scripts/fetch_docs.py --ref jazzy
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import shutil
|
| 19 |
+
import subprocess
|
| 20 |
+
import sys
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
# The default Windows console is cp1252 and raises UnicodeEncodeError on any
|
| 24 |
+
# non-Latin-1 character. These scripts print document titles and paths straight
|
| 25 |
+
# from the Zephyr tree, which is full of them.
|
| 26 |
+
if hasattr(sys.stdout, "reconfigure"):
|
| 27 |
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
| 28 |
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
| 29 |
+
|
| 30 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
| 31 |
+
import source_config as cfg # noqa: E402
|
| 32 |
+
|
| 33 |
+
REPO = cfg.REPO
|
| 34 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 35 |
+
DEFAULT_CLONE = ROOT / "data" / "doc-src"
|
| 36 |
+
DEFAULT_OUT = ROOT / "data" / "raw_docs"
|
| 37 |
+
|
| 38 |
+
SKIP_DIRS = cfg.SKIP_DIRS
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def run(cmd: list[str], cwd: Path | None = None, check: bool = True) -> int:
|
| 42 |
+
result = subprocess.run(cmd, cwd=cwd, text=True)
|
| 43 |
+
if check and result.returncode != 0:
|
| 44 |
+
raise SystemExit(f"command failed ({result.returncode}): {' '.join(cmd)}")
|
| 45 |
+
return result.returncode
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def sparse_clone(clone_dir: Path, ref: str) -> None:
|
| 49 |
+
"""Shallow + blobless + sparse: only doc/, only one commit."""
|
| 50 |
+
# An empty leftover directory is not a checkout. Testing `exists()` sent the
|
| 51 |
+
# reuse path at a directory with no .git in it, which then failed on fetch
|
| 52 |
+
# and again on removal, reporting a file lock that was never the problem.
|
| 53 |
+
if (clone_dir / ".git").exists():
|
| 54 |
+
# A --branch clone sets a narrow fetch refspec, so a bare `git fetch
|
| 55 |
+
# origin main` fails with "couldn't find remote ref". Ask for the ref
|
| 56 |
+
# explicitly, and treat the cache as disposable if anything goes wrong:
|
| 57 |
+
# it is a checkout of someone else's repository, not state worth
|
| 58 |
+
# rescuing.
|
| 59 |
+
print(f"Reusing existing checkout at {clone_dir}")
|
| 60 |
+
refspec = f"+refs/heads/{ref}:refs/remotes/origin/{ref}"
|
| 61 |
+
ok = run(["git", "fetch", "--depth", "1", "origin", refspec], cwd=clone_dir, check=False)
|
| 62 |
+
if ok == 0:
|
| 63 |
+
ok = run(["git", "checkout", "-f", "FETCH_HEAD"], cwd=clone_dir, check=False)
|
| 64 |
+
if ok == 0:
|
| 65 |
+
return
|
| 66 |
+
print(" cached checkout is unusable - re-cloning")
|
| 67 |
+
shutil.rmtree(clone_dir, ignore_errors=True)
|
| 68 |
+
if clone_dir.exists():
|
| 69 |
+
raise SystemExit(
|
| 70 |
+
f"could not remove {clone_dir} (a file may be locked). Delete it and retry."
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
clone_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 74 |
+
print(f"Cloning {REPO} ({ref}, {cfg.DOC_ROOT}/ only) -> {clone_dir}")
|
| 75 |
+
run(
|
| 76 |
+
[
|
| 77 |
+
"git",
|
| 78 |
+
"clone",
|
| 79 |
+
"--depth",
|
| 80 |
+
"1",
|
| 81 |
+
"--filter=blob:none",
|
| 82 |
+
"--sparse",
|
| 83 |
+
"--branch",
|
| 84 |
+
ref,
|
| 85 |
+
REPO,
|
| 86 |
+
str(clone_dir),
|
| 87 |
+
]
|
| 88 |
+
)
|
| 89 |
+
run(["git", "sparse-checkout", "set", cfg.DOC_ROOT], cwd=clone_dir)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def collect(clone_dir: Path, out_dir: Path) -> tuple[int, int]:
|
| 93 |
+
"""Copy documentation sources out of the checkout, flattened by path."""
|
| 94 |
+
doc_root = clone_dir / cfg.DOC_ROOT
|
| 95 |
+
if not doc_root.is_dir():
|
| 96 |
+
raise SystemExit(f"no {cfg.DOC_ROOT}/ directory in {clone_dir}")
|
| 97 |
+
|
| 98 |
+
if out_dir.exists():
|
| 99 |
+
shutil.rmtree(out_dir)
|
| 100 |
+
out_dir.mkdir(parents=True)
|
| 101 |
+
|
| 102 |
+
copied = 0
|
| 103 |
+
total_bytes = 0
|
| 104 |
+
for path in sorted(doc_root.rglob("*")):
|
| 105 |
+
if not path.is_file() or path.suffix.lower() not in {".rst", ".md", ".txt"}:
|
| 106 |
+
continue
|
| 107 |
+
relative = path.relative_to(doc_root)
|
| 108 |
+
if any(part in SKIP_DIRS for part in relative.parts):
|
| 109 |
+
continue
|
| 110 |
+
|
| 111 |
+
# Flatten so the source path survives as the filename. Retrieval cites
|
| 112 |
+
# the file, and "kernel/services/threads.rst" is a far more useful
|
| 113 |
+
# citation than "threads.rst" repeated across a dozen subsystems.
|
| 114 |
+
flat = str(relative).replace("\\", "/").replace("/", "__")
|
| 115 |
+
destination = out_dir / flat
|
| 116 |
+
destination.write_bytes(path.read_bytes())
|
| 117 |
+
copied += 1
|
| 118 |
+
total_bytes += destination.stat().st_size
|
| 119 |
+
|
| 120 |
+
return copied, total_bytes
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def main() -> int:
|
| 124 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 125 |
+
parser.add_argument(
|
| 126 |
+
"--ref",
|
| 127 |
+
default=cfg.DEFAULT_REF,
|
| 128 |
+
help=f"branch or tag to fetch (default: {cfg.DEFAULT_REF})",
|
| 129 |
+
)
|
| 130 |
+
parser.add_argument("--clone", type=Path, default=DEFAULT_CLONE)
|
| 131 |
+
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
|
| 132 |
+
parser.add_argument(
|
| 133 |
+
"--keep-clone",
|
| 134 |
+
action="store_true",
|
| 135 |
+
help="keep the sparse checkout so a later run can update instead of re-cloning",
|
| 136 |
+
)
|
| 137 |
+
args = parser.parse_args()
|
| 138 |
+
|
| 139 |
+
sparse_clone(args.clone, args.ref)
|
| 140 |
+
copied, total_bytes = collect(args.clone, args.out)
|
| 141 |
+
|
| 142 |
+
revision = subprocess.run(
|
| 143 |
+
["git", "rev-parse", "--short", "HEAD"],
|
| 144 |
+
cwd=args.clone,
|
| 145 |
+
text=True,
|
| 146 |
+
capture_output=True,
|
| 147 |
+
).stdout.strip()
|
| 148 |
+
|
| 149 |
+
# Provenance travels with the corpus. An index built from an unknown commit
|
| 150 |
+
# cannot be reproduced or explained later.
|
| 151 |
+
(args.out / "_SOURCE.txt").write_text(
|
| 152 |
+
f"repository: {REPO}\nref: {args.ref}\ncommit: {revision}\nfiles: {copied}\n",
|
| 153 |
+
encoding="utf-8",
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
if not args.keep_clone:
|
| 157 |
+
shutil.rmtree(args.clone, ignore_errors=True)
|
| 158 |
+
|
| 159 |
+
print(f"\n{copied} documents ({total_bytes / 1_048_576:.1f} MB) -> {args.out}")
|
| 160 |
+
print(f"{cfg.PROJECT} {args.ref} @ {revision}")
|
| 161 |
+
return 0
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
sys.exit(main())
|