diff --git a/.github/workflows/prompts.yml b/.github/workflows/prompts.yml new file mode 100644 index 000000000..0546046df --- /dev/null +++ b/.github/workflows/prompts.yml @@ -0,0 +1,43 @@ +name: Check documentation prompts + +on: + pull_request: + paths: + - 'qdrant-landing/content/**/*.md' + # Every shortcode, not just the prompt ones: the Markdown variants are + # what keep prompt bodies out of the agent-facing output, and naming them + # individually is how prompt-index.markdown.md went unguarded. + - 'qdrant-landing/layouts/shortcodes/**' + - 'qdrant-landing/themes/qdrant-2024/layouts/shortcodes/**' + - 'automation/prompts/**' + - '.github/workflows/prompts.yml' + # A skill link breaks when a skill is renamed or unpublished in qdrant/skills, + # which does not touch this repository and so never triggers the pull request + # run. The weekly run is what catches that. + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +jobs: + check-prompts: + if: github.event_name != 'schedule' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Source checks only: no Hugo, no Dart Sass, no npm install. The rules + # this guards are all visible in the source, so a full site build would + # cost minutes on every docs PR to verify something that takes seconds. + - name: Check prompts + run: automation/prompts/check-prompts.sh + + check-skill-links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Kept separate from check-prompts because this one reaches the network. + # An unreachable skills.qdrant.tech skips rather than fails, so the job + # only goes red on a skill that is genuinely not published. + - name: Check agent skill links + run: automation/prompts/check-skill-links.sh diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2e8d99b35 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,114 @@ +# AGENTS.md + +Guidance for AI coding agents working in this repository. This is the Hugo site +behind [qdrant.tech](https://qdrant.tech): the marketing pages, the blog, and +the documentation. + +This file is about editing the repository. It is not the agent-facing entry +point for the published site, which is +[`qdrant.tech/llms.txt`](https://qdrant.tech/llms.txt), generated from +`themes/qdrant-2024/layouts/index.llms.txt`. + +## Build and serve + +```bash +./run.sh # npm install, then hugo serve on :1313 +./install-and-build.sh # what Netlify runs: fetches Dart Sass, builds to public/ +``` + +Three things about the build cause more lost time than everything else combined: + +- **Hugo 0.160.1 exactly.** `install-and-build.sh` refuses any other version and + `run.sh` warns. A different version produces failures that look like content + bugs. +- **Dart Sass is required, and the npm package named `sass` is not it.** It is a + different implementation. Without Dart Sass on `PATH`, Hugo still reports a + successful build and serves stale CSS out of `qdrant-landing/resources/_gen`. + A panel that renders unstyled after an SCSS change almost always means this + rather than a mistake in the SCSS. +- **`hugo --quiet` hides build errors.** It has reported exit 0 through a pipe + while the site failed to render. Grep unpiped output for `ERROR` before + trusting a green build. + +## Layout of the repository + +| Path | What it holds | +|---|---| +| `qdrant-landing/content/` | All content. `documentation/` is the docs tree | +| `qdrant-landing/content/headless/`, `.../documentation/headless/` | Page bundles that are never published as URLs: code snippets, prompts | +| `qdrant-landing/themes/qdrant-2024/` | The theme: layouts, shortcodes, SCSS, JS | +| `qdrant-landing/layouts/` | Project layouts, which override the theme's | +| `automation/` | Checks and generators, each with its own README | +| `.github/workflows/` | CI | + +`qdrant-landing/public/`, `qdrant-landing/resources/_gen/`, and `node_modules/` +are generated and git-ignored. Never edit them, and never cite a path under +`public/` as the source of anything. + +Project layouts win over theme layouts, which is why `layouts/shortcodes/` +holds the per-output-format variants while the theme holds the HTML ones. + +## Every page has two outputs + +A content page renders as HTML and as Markdown at `/index.md`. The Markdown +output is what AI agents consume, and `qdrant.tech/llms.txt` indexes it. It is +not a format conversion of the HTML: `layouts/_default/single.markdown.md` +prepends pointers to the skills catalog and rewrites internal links to their +`index.md` form. + +So a change to a page changes two published artifacts. When a shortcode should +behave differently for agents, give it a `.markdown.md` variant in +`layouts/shortcodes/` rather than trying to detect the format inside the HTML +one. `code-snippet`, `include`, `prompt`, and `prompt-index` all do this. + +## Content conventions + +- **`weight` in front matter orders the docs sidebar.** Adding a page without + one puts it in an unpredictable position. +- **Moving or renaming a page needs `aliases`** in the destination's front + matter. Old URLs are linked from blog posts, from articles, and from the + pointers prepended to every documentation `index.md`. +- **Code samples belong in `documentation/headless/snippets/`**, not written + inline. They are compilable sources that CI type-checks against the real + client libraries, and `automation/snippets/generate-md.py` derives the + Markdown. See `automation/snippets/README.md`. +- **Prompts belong in `documentation/headless/prompts/`**, one file per prompt, + included with `{{< prompt "id" >}}`. A prompt body written inline on a page + reaches the agent-facing `index.md`, where an instruction addressed to an + agent can displace the question that agent was actually asked. See + `automation/prompts/README.md`. +- **Agent skill links are `https://skills.qdrant.tech//SKILL.md`.** The + bare path without the suffix returns 404, meta skills live under `meta/`, and + a path that exists in the `qdrant/skills` repository is not necessarily + published. `https://skills.qdrant.tech/llms.txt` lists what is actually + served. + +## Writing style + +American English, Oxford commas, straight quotes. No em dashes. No directional +language: an element is not "below" or "above", because the Markdown output, +the mobile layout, and a screen reader all order things differently. Name the +thing instead. + +## Before opening a pull request + +```bash +automation/prompts/check-prompts.sh # prompt rules, source only, seconds +automation/prompts/check-skill-links.sh # skill links resolve, needs network +automation/snippets/generate-md.py # if you touched snippets, then commit the result +``` + +When you are not at the repository root, stage with `git add -A` or +`git add :/`. Plain `git add .` inside `qdrant-landing/` silently misses +changes to `automation/` and `.github/`. + +## Two traps in the templates + +- **`.Section` returns the top-level section, not the nested one.** For + `/documentation/agentic-tools/skills/` it is `documentation`. Gating an asset + on it loads that asset on every documentation page, several hundred of them. + Check the path instead. +- **`[data-theme='light'] &` does not work inside a compound selector.** Nested + under `&__a &__b` it compiles to `.a [data-theme=light] .b`, which puts the + attribute in descendant position where it can never match, because the theme + attribute sits on ``. Write those rules out longhand. diff --git a/automation/prompts/README.md b/automation/prompts/README.md new file mode 100644 index 000000000..d0f097c42 --- /dev/null +++ b/automation/prompts/README.md @@ -0,0 +1,132 @@ +# Documentation prompts + +A prompt is a short, ready-to-paste instruction that gets a coding agent moving +on the task a documentation page is about. Prompts live in one folder, +`qdrant-landing/content/documentation/headless/prompts/`, one file each, and +pages include them by id. + +## Adding one + +1. Create `content/documentation/headless/prompts/.md`: + + ```yaml + --- + title: "Set up monitoring" + page: /documentation/ops-monitoring/monitoring/ + skills: + - qdrant-monitoring/setup + --- + Help me set up monitoring for my Qdrant deployment. Read + https://skills.qdrant.tech/qdrant-monitoring/setup/SKILL.md first, then ask + me whether I am self-hosted, on Qdrant Cloud, or on Hybrid Cloud. + ``` + + - `title` is a plain task name: "Set up monitoring", not "Set up monitoring + with an AI agent". The collapsed element already carries a `Prompt` marker + placed by the shortcode, so working the word into the title says the same + thing twice. Write it as the task, starting with a verb. + - `skills` is **always a YAML sequence**, even for a single skill, so the + four templates that render it have one shape to handle rather than two. + The old scalar `skill:` key is rejected by CI rather than ignored. Each + entry is a path under `skills.qdrant.tech` without the `/SKILL.md` suffix. + List more than one when the prompt genuinely spans them, as + `design-a-collection` does across tenancy and sizing; the index renders + every entry. Omit the key entirely when no skill applies, rather than + reaching for the nearest one, because this value is what labels the prompt + in the index. Published paths are listed at + `https://skills.qdrant.tech/llms.txt`, and a path in the `qdrant/skills` + repo is not automatically published, so the repo is not a reliable source + for this value. Meta skills sit under `meta/`, as in `meta/qdrant-advisor`. + `check-skill-links.sh` verifies all of this, so there is no need to curl + by hand. + - `page` is where the prompt is included. CI checks that page really + includes it. + - `open: true` renders the element expanded. **In-page prompts are always + collapsed**, so leave this unset. It exists for a page whose whole purpose + is the prompt, which today means only the Agentic Tools hub. A prompt + feeling important is not a reason to set it: every prompt feels important + to its author, and the collapsed state is what keeps a docs page readable. + +2. Add `{{< prompt "" >}}` to that page, near the top of the section it + relates to rather than at the bottom. + +3. Run both checks: + + ```bash + automation/prompts/check-prompts.sh # source rules, no build, seconds + automation/prompts/check-skill-links.sh # skill links resolve, needs network + ``` + + `check-prompts.sh` also accepts a built site directory, to verify the real + output as well: `automation/prompts/check-prompts.sh qdrant-landing/public`. + +## Writing one + +**A few sentences.** Enough to get someone moving, not a specification. + +**Name the skill inside the body,** with its full `SKILL.md` URL, so the agent +loads it rather than guessing. Prompts exist partly to surface skills. + +**No variables.** Never ask the reader to fill something in before pasting. If +the task needs their configuration, tell the agent to ask for it. + +**Do not restate the page.** The reader is already on it. A prompt that +re-explains the feature earns nothing. + +**Ask for what the docs cannot give.** The useful instructions are the ones +that make an agent commit to a judgment call: which setup applies to the +reader's deployment, what to check first, what to ignore. + +**Title it as the task.** The collapsed line reads `PROMPT | Set up +monitoring`: the marker says what kind of thing it is, the title says what it +does. That split is why titles stay short, and why the type signal is +consistent across every prompt rather than depending on each author phrasing it +the same way. + +## Formatting a prompt body + +**Do not hard-wrap prose.** Write each paragraph as one long line and let the +page wrap it. The element renders the body with `white-space: pre-wrap`, so any +line breaks you type are preserved, and a body wrapped at 80 columns keeps those +breaks in a content column that is narrower than that. `start-building` is the +exception: its numbered steps and indented commands are structure, so its line +breaks are content. + +**A body can contain anything, including fenced blocks.** The shortcode passes +it to Chroma as a value rather than re-parsing it as markdown, so a prompt that +includes a YAML or JSON snippet renders correctly. + +## Two rules that break silently + +**Prompt bodies must never reach a built `index.md`.** Those files are consumed +by agents. A prompt is an instruction written in the imperative first person, +so an agent reading it inside a page it was told to fetch can follow the prompt +instead of answering the question it was actually asked. The shortcode's +Markdown variant drops the body and keeps the skill pointer. This is also why a +prompt is never written as a plain fenced block on the page. + +**The `page:` declaration is what the index trusts.** Prompt files cannot know +who includes them, so a stale declaration would put a wrong link in the index, +and a prompt that is never included would still be listed. + +`check-prompts.sh` guards both, plus a third rule that keeps them equivalent: +the Markdown variant of the shortcode must never render the body. Rule one is +checked in the source rather than in the built output, because pasting a body +inline is a source-level mistake and catching it there means CI needs no site +build. + +## The third rule, which breaks without a commit + +**A skill link rots on its own.** A skill renamed or unpublished in +`qdrant/skills` breaks every reference to it here without anything in this +repository changing, so no pull request run would ever notice. +`check-skill-links.sh` covers that. It checks both places a skill path appears, +the `skill:` front matter and the `SKILL.md` URLs written inside prompt bodies +and documentation prose, against the catalog at +`https://skills.qdrant.tech/llms.txt`, and it also runs weekly on a schedule. + +It reaches the network, which is why it is a separate script and a separate CI +job. An unreachable catalog skips the run rather than failing it, so a docs pull +request never goes red because skills.qdrant.tech was down. A path missing from +the catalog is confirmed with a request before it is reported, since the catalog +can lag a freshly published skill. diff --git a/automation/prompts/check-prompts.sh b/automation/prompts/check-prompts.sh new file mode 100755 index 000000000..9d4539981 --- /dev/null +++ b/automation/prompts/check-prompts.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +# Guards the rules that keep documentation prompts working. All of them fail +# silently otherwise, which is why they are checked rather than reviewed. +# +# 1. No prompt body is written inline on a page. Prompt bodies must not reach +# a built index.md, because those files are consumed by agents and a prompt +# is an instruction addressed to an agent, so it can displace the question +# the agent was actually asked. The shortcode's Markdown variant drops the +# body, so the only way a body reaches index.md is an author pasting it as +# a fenced block instead of using the shortcode. That is a source-level +# mistake, so it is caught in the source. +# 2. No Markdown variant renders the body. Rule 1 is only equivalent to "no +# body in index.md" while this holds, and it has to cover every template +# that can reach a prompt's content, not just the element's. The index +# ranges the same pages, so a body added there lands in the built +# index.md exactly the same way. +# 3. Every prompt's declared `page:` really includes it. The prompt files live +# in one folder and cannot know who includes them, so the declaration is +# what the index trusts. This also catches a prompt that is written but +# never used, which would otherwise appear in the index anyway. +# +# These are all source checks, so no site build is needed and the run takes +# seconds. Pass a built-site directory to additionally verify the real output: +# +# automation/prompts/check-prompts.sh # source checks only +# automation/prompts/check-prompts.sh qdrant-landing/public # and the build + +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +public_dir="${1:-}" +prompts_dir="$repo_root/qdrant-landing/content/documentation/headless/prompts" +content_dir="$repo_root/qdrant-landing/content" +# Every Markdown-output template that can reach a prompt page. Add to this list +# rather than writing a new check if another one appears. +md_shortcodes=( + "$repo_root/qdrant-landing/layouts/shortcodes/prompt.markdown.md" + "$repo_root/qdrant-landing/layouts/shortcodes/prompt-index.markdown.md" +) + +failures=0 + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + failures=$((failures + 1)) +} + +if [[ ! -d "$prompts_dir" ]]; then + echo "No prompts directory at $prompts_dir; nothing to check." + exit 0 +fi + +# --- rule 2: no Markdown variant may render the prompt body --- +for md_shortcode in "${md_shortcodes[@]}"; do + rel="${md_shortcode#"$repo_root"/}" + if [[ ! -f "$md_shortcode" ]]; then + fail "missing $rel: without it, prompt bodies reach the agent-facing Markdown" + elif grep -qE '\.(RawContent|Content|Inner)\b' "$md_shortcode"; then + fail "$rel renders the prompt body; it must emit only titles, pages, and skill pointers" + fi +done + +shopt -s nullglob +checked=0 + +for file in "$prompts_dir"/*.md; do + id="$(basename "$file" .md)" + [[ "$id" == "_index" ]] && continue + checked=$((checked + 1)) + + # First non-empty body line, used as the probe. Front matter is delimited by + # the first two '---' lines. + probe="$(awk ' + /^---[[:space:]]*$/ { d++; next } + d >= 2 && NF { print; exit } + ' "$file")" + + if [[ -z "$probe" ]]; then + fail "$id: prompt file has no body" + continue + fi + + # --- rule 1: the body must not appear inline anywhere in the content tree --- + if hits="$(grep -rlF "$probe" --include='*.md' "$content_dir" 2>/dev/null \ + | grep -v "^$prompts_dir/" || true)" && [[ -n "$hits" ]]; then + fail "$id: prompt body is written inline instead of using the shortcode:" + printf ' %s\n' $hits >&2 + printf ' Replace it with {{< prompt "%s" >}}\n' "$id" >&2 + fi + + # --- rule 3: the declared page must include the shortcode --- + page="$(awk -F': *' ' + /^---[[:space:]]*$/ { d++; if (d >= 2) exit; next } + d == 1 && $1 == "page" { print $2; exit } + ' "$file" | tr -d '"'"'"'')" + + if [[ -z "$page" ]]; then + fail "$id: no page: declared in front matter" + continue + fi + + rel="${page#/}" + rel="${rel%/}" + src="" + for candidate in "$content_dir/$rel.md" "$content_dir/$rel/_index.md"; do + [[ -f "$candidate" ]] && { src="$candidate"; break; } + done + + if [[ -z "$src" ]]; then + fail "$id: declared page $page has no source file under content/" + continue + fi + + if ! grep -qF "prompt \"$id\"" "$src" && ! grep -qF "prompt '$id'" "$src"; then + fail "$id: declared page $page does not include it" + printf ' Expected {{< prompt "%s" >}} in %s\n' "$id" "${src#"$repo_root"/}" >&2 + fi + + # --- optional: verify the real built output when a build is available --- + if [[ -n "$public_dir" ]]; then + if [[ ! -d "$public_dir" ]]; then + fail "built site not found at $public_dir" + public_dir="" + elif built="$(grep -rlF "$probe" --include='index.md' "$public_dir" 2>/dev/null || true)" \ + && [[ -n "$built" ]]; then + fail "$id: prompt body reached the agent-facing Markdown output:" + printf ' %s\n' $built >&2 + fi + fi +done + +if (( failures > 0 )); then + printf '\n%d prompt check failure(s) across %d prompt(s).\n' "$failures" "$checked" >&2 + exit 1 +fi + +if [[ -n "$public_dir" ]]; then + printf 'Prompt checks passed (source and build): %d prompt(s).\n' "$checked" +else + printf 'Prompt checks passed: %d prompt(s).\n' "$checked" +fi diff --git a/automation/prompts/check-skill-links.sh b/automation/prompts/check-skill-links.sh new file mode 100755 index 000000000..7cb8667f8 --- /dev/null +++ b/automation/prompts/check-skill-links.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Verifies that every agent skill this repository points at is actually +# published on skills.qdrant.tech. +# +# Why this needs its own check. A skill reference breaks without anything in +# this repository changing: a skill gets renamed or unpublished in qdrant/skills +# and the link here rots in place. It is also easy to get wrong when writing, +# for two reasons that have each already produced a 404: +# +# - A path that exists in the qdrant/skills repository is not automatically +# served. The repository is not the source of truth; llms.txt is. +# - Meta skills live under meta/, as in meta/qdrant-advisor, not at the root +# alongside the domain skills. +# +# Two kinds of reference are checked, because both exist and they can disagree: +# +# 1. `skill:` in the front matter of a prompt, which the shortcode turns into +# a link and which the prompt index renders as a column. +# 2. Any https://skills.qdrant.tech//SKILL.md URL written in the content +# tree, which includes the URLs inside prompt bodies that tell the agent +# what to read, and ordinary prose links in the documentation. +# +# This check reaches the network, unlike check-prompts.sh, which is why it is a +# separate script and a separate CI job: skills.qdrant.tech being unreachable +# must not turn a documentation pull request red. An unreachable catalog skips +# the run; only a catalog that answers and does not list a path fails it. +# +# automation/prompts/check-skill-links.sh + +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +content_dir="$repo_root/qdrant-landing/content" +prompts_dir="$content_dir/documentation/headless/prompts" +catalog_url="${SKILLS_CATALOG_URL:-https://skills.qdrant.tech/llms.txt}" +base_url="https://skills.qdrant.tech" + +failures=0 +fail() { + printf 'FAIL: %s\n' "$1" >&2 + failures=$((failures + 1)) +} + +# --- collect references, as "\t" --- + +refs="$( + # 1. skills: values in prompt front matter. Always a YAML sequence, even for + # a single skill, so there is one shape here and in all four templates. + # A file still using the old scalar `skill:` key emits a LEGACY marker + # rather than being silently skipped, which would leave it unchecked. + if [[ -d "$prompts_dir" ]]; then + for file in "$prompts_dir"/*.md; do + [[ -f "$file" ]] || continue + awk -v src="${file#"$repo_root"/}" ' + /^---[[:space:]]*$/ { d++; if (d >= 2) exit; next } + d != 1 { next } + /^skill:[[:space:]]/ { print "LEGACY-SKILL-KEY\t" src; next } + /^skills:[[:space:]]*$/ { inlist = 1; next } + inlist && /^[[:space:]]*-[[:space:]]*/ { + v = $0 + sub(/^[[:space:]]*-[[:space:]]*/, "", v) + gsub(/^["\x27[:space:]]+|["\x27[:space:]]+$/, "", v) + if (v != "") print v "\t" src " (skills: front matter)" + next + } + /^[^[:space:]#]/ { inlist = 0 } + ' "$file" + done + fi + + # 2. SKILL.md URLs written anywhere in the content tree. The path is + # everything between the host and the /SKILL.md suffix. + grep -roE "https://skills\.qdrant\.tech/[A-Za-z0-9._/-]+/SKILL\.md" \ + --include='*.md' "$content_dir" 2>/dev/null \ + | sed -E "s|^${repo_root}/||" \ + | awk -F':' '{ src = $1; url = $0; sub(/^[^:]*:/, "", url) + sub(/^https:\/\/skills\.qdrant\.tech\//, "", url); sub(/\/SKILL\.md$/, "", url) + print url "\t" src " (SKILL.md link)" }' || true +)" + +# A prompt still on the old scalar `skill:` key would contribute no references +# at all, so this check would pass while that prompt's link went unverified. +if legacy="$(grep -F 'LEGACY-SKILL-KEY' <<<"$refs" || true)" && [[ -n "$legacy" ]]; then + while IFS=$'\t' read -r _ src; do + [[ -n "$src" ]] && fail "$src uses the old scalar 'skill:' key; it must be a 'skills:' sequence" + done <<<"$legacy" + printf '\n%d prompt(s) on the legacy key.\n' "$failures" >&2 + exit 1 +fi + +if [[ -z "$refs" ]]; then + echo "No agent skill references found; nothing to check." + exit 0 +fi + +# --- fetch the catalog of what is actually served --- + +catalog="" +if ! catalog="$(curl -fsS --max-time 30 "$catalog_url" 2>/dev/null)" || [[ -z "$catalog" ]]; then + echo "Skipping: could not reach $catalog_url." + echo "This check is advisory and does not fail on an unreachable catalog." + exit 0 +fi + +# --- check each distinct path --- + +checked=0 +while IFS=$'\t' read -r path sources; do + [[ -n "$path" ]] || continue + checked=$((checked + 1)) + url="$base_url/$path/SKILL.md" + + if grep -qF "$url" <<<"$catalog"; then + continue + fi + + # The catalog can lag a freshly published skill, so a miss is confirmed with + # a request before it is reported. Only a path that is absent from both the + # catalog and the live site is a failure. + status="$(curl -fsS -o /dev/null -w '%{http_code}' --max-time 30 "$url" 2>/dev/null || true)" + if [[ "$status" == "200" ]]; then + printf 'note: %s is served but missing from the catalog at %s\n' "$path" "$catalog_url" + continue + fi + + fail "$path is not a published skill (${url} returned ${status:-no response})" + printf ' Referenced by: %s\n' "$sources" >&2 + printf ' Published skills are listed at %s\n' "$catalog_url" >&2 + printf ' Meta skills are under meta/, as in meta/qdrant-advisor.\n' >&2 +done < <( + # Distinct paths, with their sources joined so one bad path reports once. + sort -u <<<"$refs" | awk -F'\t' ' + { if ($1 in seen) seen[$1] = seen[$1] ", " $2; else { seen[$1] = $2; order[++n] = $1 } } + END { for (i = 1; i <= n; i++) print order[i] "\t" seen[order[i]] } + ' +) + +if (( failures > 0 )); then + printf '\n%d broken skill link(s) across %d referenced skill(s).\n' "$failures" "$checked" >&2 + exit 1 +fi + +printf 'Skill link checks passed: %d referenced skill(s).\n' "$checked" diff --git a/qdrant-landing/content/articles/bulk-uploads-in-qdrant.md b/qdrant-landing/content/articles/bulk-uploads-in-qdrant.md index a3300ef70..b35cdb7f7 100644 --- a/qdrant-landing/content/articles/bulk-uploads-in-qdrant.md +++ b/qdrant-landing/content/articles/bulk-uploads-in-qdrant.md @@ -241,7 +241,7 @@ client.create_collection( ![Decision tree for choosing the right bulk upload strategy: dense, sparse, or hybrid vectors, with memory, quantization, and sharding options](/articles_data/bulk-uploads-in-qdrant/choosing-the-right-mix.png) -Still deciding exactly what to configure for your workload? [Qdrant's Agent Skills](https://qdrant.tech/documentation/skills/) provide hands-on, scenario-based guidance that walks you through the specific settings for your situation. +Still deciding exactly what to configure for your workload? [Qdrant's Agent Skills](https://qdrant.tech/documentation/agentic-tools/skills/) provide hands-on, scenario-based guidance that walks you through the specific settings for your situation. ## It's Not One-Size-Fits-All diff --git a/qdrant-landing/content/blog/qdrant-1.19.x.md b/qdrant-landing/content/blog/qdrant-1.19.x.md index f0940bc8b..6747bffc9 100644 --- a/qdrant-landing/content/blog/qdrant-1.19.x.md +++ b/qdrant-landing/content/blog/qdrant-1.19.x.md @@ -132,7 +132,7 @@ We recommend upgrading versions one by one. Qdrant Cloud does this automatically > If you still use the legacy `/search`, `/recommend`, or `/discover` endpoints, migrate to the [`/query` API](/documentation/search/search/#query-api) before upgrading to 1.19. -Need help with your upgrade? The [Qdrant Advisor agent skill](https://qdrant.tech/documentation/skills/) can help you navigate upgrades, troubleshoot configurations, and answer questions about your Qdrant setup, whether you're on Qdrant Cloud or self-hosting. +Need help with your upgrade? The [Qdrant Advisor agent skill](https://qdrant.tech/documentation/agentic-tools/skills/) can help you navigate upgrades, troubleshoot configurations, and answer questions about your Qdrant setup, whether you're on Qdrant Cloud or self-hosting. ## Engage diff --git a/qdrant-landing/content/documentation/agentic-tools/_index.md b/qdrant-landing/content/documentation/agentic-tools/_index.md new file mode 100644 index 000000000..1916a36d9 --- /dev/null +++ b/qdrant-landing/content/documentation/agentic-tools/_index.md @@ -0,0 +1,36 @@ +--- +title: "Agentic Tools" +short_description: "Tools that help AI coding assistants work with Qdrant: a prompt to get started, agent skills that carry solutions architect knowledge, and prompts embedded across the documentation." +description: "Qdrant agentic tools for AI coding assistants. Start from a single prompt that runs Qdrant locally and installs the Qdrant Advisor, then use agent skills to diagnose and tune search, memory, scaling, and relevance in a running deployment." +weight: 303 +partition: develop +--- + +# Agentic Tools + +Qdrant provides tools and resources that help AI agents work with Qdrant more effectively. +Use them to give your agent task-specific guidance, troubleshoot existing deployments, design and evaluate search systems, and apply Qdrant best practices to your specific use case. + +## Start Building + +If you're new to Qdrant, the Start building on Qdrant prompt takes your coding assistant from an empty project to a working search query. +It stands up a local instance, installs the client with local embeddings, adds the [Qdrant Advisor](/documentation/agentic-tools/skills/#the-qdrant-advisor) skill, and then builds against your own use case. + +{{< prompt "start-building" >}} + +Qdrant runs in Docker and [FastEmbed](/documentation/fastembed/fastembed-quickstart/) generates embeddings on your machine, so you can go from nothing to a working search query without signing up for anything. + +Collection configuration is important. A mismatched distance metric or a missing payload index can result in slower or lower quality search. +The prompt is written to make your assistant ask what you are building, then commit to those choices out loud before it writes any code. + +## Agent Skills + +[Agent skills](/documentation/agentic-tools/skills/) encode solutions architect knowledge as structured files your agent loads on demand. +They're organized around symptoms, so they answer "why is this happening, and what should I check first?" +Reach for skills when something is already running and behaving unexpectedly: slow search, growing memory, a stuck optimizer, poor relevance. + +## Prompts in the Docs + +Short, ready-to-use prompts appear directly on the documentation pages where they are relevant, so you can use the prompts when you need them instead of looking for them elsewhere. +Each prompt points to the agent skill your assistant should load. The prompt gets the task started; the skill provides the deeper Qdrant-specific guidance. +The [prompt index](/documentation/agentic-tools/prompts/) lists all available prompts. diff --git a/qdrant-landing/content/documentation/agentic-tools/prompts.md b/qdrant-landing/content/documentation/agentic-tools/prompts.md new file mode 100644 index 000000000..858b9d6bb --- /dev/null +++ b/qdrant-landing/content/documentation/agentic-tools/prompts.md @@ -0,0 +1,17 @@ +--- +title: "Prompts in the Docs" +short_description: "Every prompt in the Qdrant documentation, with the page it appears on and the agent skill it uses." +description: "An index of the ready-to-paste prompts embedded in the Qdrant documentation. Each one sits on the page it relates to, links the relevant agent skill, and is written to be handed straight to a coding assistant." +weight: 20 +partition: develop +--- + +# Prompts in the Docs + +Qdrant documentation includes short, ready-to-use prompts on the pages where they are relevant: setting up monitoring on the monitoring page, diagnosing memory issues on the optimization page, and so on. + +Each prompt is collapsed by default, requires no additional input, and tells your assistant which [agent skill](/documentation/agentic-tools/skills/) to load. The prompt defines the task; the skill provides the Qdrant-specific guidance needed to complete it. + +This page lists all available prompts. Follow a link to jump to the prompt on the page that provides its context. + +{{< prompt-index >}} diff --git a/qdrant-landing/content/documentation/skills.md b/qdrant-landing/content/documentation/agentic-tools/skills.md similarity index 99% rename from qdrant-landing/content/documentation/skills.md rename to qdrant-landing/content/documentation/agentic-tools/skills.md index 2006f31b4..86aec2f99 100644 --- a/qdrant-landing/content/documentation/skills.md +++ b/qdrant-landing/content/documentation/agentic-tools/skills.md @@ -2,7 +2,9 @@ title: "Agent Skills" short_description: Qdrant agent skills encode solutions architect knowledge for AI coding assistants, helping them diagnose vector search problems, recommend the right configuration, and navigate to the exact documentation without installation. description: Qdrant agent skills help AI coding assistants diagnose and tune vector search in production. Pass a skill URL from skills.qdrant.tech to your agent to get targeted guidance on scaling, search quality, performance, monitoring, and more. -weight: 303 +weight: 10 +aliases: + - /documentation/skills/ partition: develop --- diff --git a/qdrant-landing/content/documentation/headless/prompts/_index.md b/qdrant-landing/content/documentation/headless/prompts/_index.md new file mode 100644 index 000000000..9fcf4e65a --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/_index.md @@ -0,0 +1,16 @@ +--- +title: "Prompts" +# The parent headless bundle cascades `list: never`, which makes this section +# unreachable via site.GetPage and leaves the prompt index empty. `list: local` +# keeps these pages out of site-wide collections while still allowing the +# section's .RegularPages to be ranged over. They are never published as URLs. +build: + list: local + render: never + publishResources: false +cascade: +- build: + list: local + render: never + publishResources: false +--- diff --git a/qdrant-landing/content/documentation/headless/prompts/design-a-collection.md b/qdrant-landing/content/documentation/headless/prompts/design-a-collection.md new file mode 100644 index 000000000..b071d79cc --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/design-a-collection.md @@ -0,0 +1,11 @@ +--- +title: "Design a collection" +page: /documentation/manage-data/collections/ +# Two skills because the prompt genuinely spans both. Naming only one would +# label the whole prompt as that one thing, which is why this prompt had no +# skill at all before the body grew to cover sizing. +skills: + - qdrant-multitenancy + - qdrant-sizing +--- +Help me design a Qdrant collection for my data. Ask me what I am searching over, roughly how many vectors I expect and how fast that will grow, which embedding model I am using, what I will filter on, and what query rate I need, before you write any code. If the use case has to support multiple users, customers, or organizations, decide on a multitenancy strategy by reading https://skills.qdrant.tech/qdrant-multitenancy/SKILL.md. Then derive the vector size from the embedding model rather than hardcoding it, name the distance metric the model was trained for and say why you picked it, and add a payload index for every field I will filter on. Size it before I create anything, reading https://skills.qdrant.tech/qdrant-sizing/SKILL.md: tell me how much memory the vectors and the index will need, how many shards to create and why, whether I need replication, and whether the vectors belong in memory or on disk. If quantization would change that answer, say which kind, what it saves, and what it costs me in recall. Finish by telling me which of these I cannot change later without recreating the collection. diff --git a/qdrant-landing/content/documentation/headless/prompts/diagnose-memory-usage.md b/qdrant-landing/content/documentation/headless/prompts/diagnose-memory-usage.md new file mode 100644 index 000000000..09968b896 --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/diagnose-memory-usage.md @@ -0,0 +1,7 @@ +--- +title: "Diagnose high memory usage" +skills: + - qdrant-performance-optimization/memory-usage-optimization +page: /documentation/ops-optimization/optimize/ +--- +My Qdrant memory usage is higher than I expected, or a node was killed for running out of memory. Read https://skills.qdrant.tech/qdrant-performance-optimization/memory-usage-optimization/SKILL.md first, then ask me for my collection configuration before proposing anything. Tell me which component is actually consuming the memory, whether that is the vectors, the HNSW index, or the payload indexes, and give me the fixes in order of impact. Say which ones need a reindex, and what recall I would lose if I quantize. diff --git a/qdrant-landing/content/documentation/headless/prompts/review-before-launch.md b/qdrant-landing/content/documentation/headless/prompts/review-before-launch.md new file mode 100644 index 000000000..70870855b --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/review-before-launch.md @@ -0,0 +1,7 @@ +--- +title: "Review my deployment" +skills: + - qdrant-sizing +page: /documentation/production-checklist/ +--- +Review my Qdrant deployment before I put production traffic on it. Read https://skills.qdrant.tech/qdrant-sizing/SKILL.md, then ask me for my collection configuration, my expected vector count and query rate, and the hardware I have provisioned. Tell me which of my settings will not survive real load, ordered by how much it would hurt, and separate what I can change later from what needs the collection recreated. Where my numbers do not support a conclusion, say what to measure rather than guessing. diff --git a/qdrant-landing/content/documentation/headless/prompts/set-up-monitoring.md b/qdrant-landing/content/documentation/headless/prompts/set-up-monitoring.md new file mode 100644 index 000000000..fd900ab3c --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/set-up-monitoring.md @@ -0,0 +1,7 @@ +--- +title: "Set up monitoring" +skills: + - qdrant-monitoring/setup +page: /documentation/ops-monitoring/monitoring/ +--- +Help me set up monitoring for my Qdrant deployment. Read https://skills.qdrant.tech/qdrant-monitoring/setup/SKILL.md first, then ask me whether I am self-hosted, on Qdrant Cloud, or on Hybrid Cloud, since the scrape setup differs. Give me the Prometheus scrape config, the health probes worth wiring up, and the three or four alerts that would actually have caught a real incident. Tell me which metrics to ignore. diff --git a/qdrant-landing/content/documentation/headless/prompts/start-building.md b/qdrant-landing/content/documentation/headless/prompts/start-building.md new file mode 100644 index 000000000..6faf2c195 --- /dev/null +++ b/qdrant-landing/content/documentation/headless/prompts/start-building.md @@ -0,0 +1,35 @@ +--- +title: "Start building on Qdrant" +page: /documentation/agentic-tools/ +# Expanded because this prompt is the point of the hub page. Do not copy +# this to an in-page prompt; those are always collapsed. +# +# This is the one prompt whose line breaks are structure rather than wrapping, +# so it keeps them. Two rules follow from that, both about pre-wrap wrapping a +# second time on a narrow screen: +# - Commands go on one line, however long. A backslash continuation wraps +# again and lands the continuation under the wrong column. +# - Prose inside a step is one line per step or bullet, never hard-wrapped. +# A 65-column source wrap colliding with a 37-column screen wrap leaves +# orphan fragments on their own lines. +open: true +skills: + - meta/qdrant-advisor +--- +Help me get started building on Qdrant. + +1. Start Qdrant locally with Docker. No signup and no API key needed: + docker run -d -p 6333:6333 -p 6334:6334 -v "$(pwd)/qdrant_storage:/qdrant/storage:z" qdrant/qdrant + Confirm it's running at http://localhost:6333/dashboard + +2. Ask me which language I want to build in, then install the Qdrant client for it. If I have no preference, use Python with local embeddings so I don't need an embedding provider key either: + pip install "qdrant-client[fastembed]" + +3. Install the Qdrant Advisor agent skill: + npx skills add qdrant/skills/meta/qdrant-advisor + +4. Then ask me what I'm building and what I'll search over before you write any code. Once you know, build the integration against my local instance, and get the collection right the first time: + - Derive the vector size from the embedding model itself. Never hardcode a dimension. + - Choose the distance metric the model was trained for, and say which one you picked and why. + - Create a payload index for every field I'll filter on, before I load data rather than after. + Then load a small sample, run a real query, and show me the results. diff --git a/qdrant-landing/content/documentation/manage-data/collections.md b/qdrant-landing/content/documentation/manage-data/collections.md index 9fbee724a..9bfd9664c 100644 --- a/qdrant-landing/content/documentation/manage-data/collections.md +++ b/qdrant-landing/content/documentation/manage-data/collections.md @@ -29,6 +29,10 @@ Qdrant supports these most popular types of metrics: In addition to metrics and vector size, each collection uses its own set of parameters that controls collection optimization, index construction, and vacuum. These settings can be changed at any time by a corresponding request. +To hand this to an agent instead, the Design a collection prompt makes it ask about your data and query shape first, then commit to a vector configuration and the payload indexes to create. + +{{< prompt "design-a-collection" >}} + ## Setting Up Multitenancy **How many collections should you create?** In most cases, you should only use a single collection with payload-based partitioning. This approach is called [multitenancy](https://en.wikipedia.org/wiki/Multitenancy). It is efficient for most of users, but it requires additional configuration. [Learn how to set it up](/documentation/manage-data/multitenancy/). diff --git a/qdrant-landing/content/documentation/ops-monitoring/monitoring.md b/qdrant-landing/content/documentation/ops-monitoring/monitoring.md index 314e77c9c..9289d5cf8 100644 --- a/qdrant-landing/content/documentation/ops-monitoring/monitoring.md +++ b/qdrant-landing/content/documentation/ops-monitoring/monitoring.md @@ -21,6 +21,10 @@ The integration with Qdrant is easy to [configure](https://prometheus.io/docs/prometheus/latest/getting_started/#configure-prometheus-to-monitor-the-sample-targets) with Prometheus and Grafana. +If you would rather have an agent handle this, the Set up monitoring prompt asks it to select the right configuration for your deployment type and recommend the alerts worth wiring up. + +{{< prompt "set-up-monitoring" >}} + ## Metrics Qdrant exposes various metrics in Prometheus/OpenMetrics format, commonly used together with Grafana for monitoring. diff --git a/qdrant-landing/content/documentation/ops-optimization/optimize.md b/qdrant-landing/content/documentation/ops-optimization/optimize.md index 903940c7f..81e9a02ad 100644 --- a/qdrant-landing/content/documentation/ops-optimization/optimize.md +++ b/qdrant-landing/content/documentation/ops-optimization/optimize.md @@ -25,6 +25,10 @@ This guide will walk you three main optimization strategies: This page covers the memory parameter introduced in Qdrant v1.19. If you're using an older version, see the Legacy Settings section for how to map the new parameter to the old ones. +An agent can work through this with you. The Diagnose high memory usage prompt makes it find which component is actually using the memory before it suggests any changes. + +{{< prompt "diagnose-memory-usage" >}} + ## 1. High-Speed Search with Low Memory Usage To achieve high search speed with minimal memory usage, you can store vectors on disk while minimizing the number of disk reads. Vector quantization is a technique that compresses vectors, allowing more of them to be stored in memory, thus reducing the need to read from disk. diff --git a/qdrant-landing/content/documentation/production-checklist.md b/qdrant-landing/content/documentation/production-checklist.md index 58f7f9127..3a8676982 100644 --- a/qdrant-landing/content/documentation/production-checklist.md +++ b/qdrant-landing/content/documentation/production-checklist.md @@ -10,6 +10,10 @@ weight: 147 A practical checklist to ensure Qdrant is optimized, stable, and ready to handle real-world load. +An agent can run the checklist with you. The Review my deployment prompt asks it for a prioritized list of what would break first under real load. + +{{< prompt "review-before-launch" >}} + --- ## 1. Distributed Deployment & Sharding diff --git a/qdrant-landing/layouts/shortcodes/prompt-index.markdown.md b/qdrant-landing/layouts/shortcodes/prompt-index.markdown.md new file mode 100644 index 000000000..f27f38ab9 --- /dev/null +++ b/qdrant-landing/layouts/shortcodes/prompt-index.markdown.md @@ -0,0 +1,23 @@ +{{- /* + Markdown variant of the prompt index. + + Titles, pages, and skill pointers only. Prompt bodies never appear here, for + the same reason the `prompt` shortcode drops them: an index.md is consumed by + agents, and a prompt is an instruction addressed to an agent. + + Keep in step with prompt-index.html, which emits table markup instead of + pipes because shortcode output is not re-parsed as markdown. + + Page links are absolute and index.md-suffixed to match every other link in + this output, which single.markdown.md rewrites that way. A bare site path + would be the one column an agent reading this table could not resolve. +*/ -}} +{{- $section := site.GetPage "documentation/headless/prompts" -}} +{{- if not $section -}} + {{- errorf "prompt-index: cannot resolve documentation/headless/prompts (see prompt-index.html for the cause)." -}} +{{- end -}} +| Prompt | Page | Agent skills | +|---|---|---| +{{ range $section.RegularPages.ByTitle -}} +| {{ .Title }} | {{ with .Params.page }}{{ with site.GetPage . }}{{ .Permalink }}index.md{{ else }}{{ . }}{{ end }}{{ else }}None{{ end }} | {{ with .Params.skills }}{{ range $i, $s := . }}{{ if $i }}, {{ end }}https://skills.qdrant.tech/{{ $s }}/SKILL.md{{ end }}{{ else }}None{{ end }} | +{{ end -}} diff --git a/qdrant-landing/layouts/shortcodes/prompt.markdown.md b/qdrant-landing/layouts/shortcodes/prompt.markdown.md new file mode 100644 index 000000000..e219ad326 --- /dev/null +++ b/qdrant-landing/layouts/shortcodes/prompt.markdown.md @@ -0,0 +1,22 @@ +{{- /* + Markdown output variant of the `prompt` shortcode. + + The prompt body is deliberately omitted. An index.md file is consumed by + agents, and a prompt is written in the imperative first person, so including + it risks an agent following the prompt instead of answering the question it + was actually asked. The skill pointer survives, because that is the part with + genuine value to an agent. + + The title is always emitted, even when there is no skill. The prose around a + prompt refers to it by name, so a prompt that left no trace here would turn + that sentence into a reference to nothing, which is the failure this whole + variant exists to avoid creating. +*/ -}} +{{- $id := .Get 0 -}} +{{- $p := site.GetPage (printf "documentation/headless/prompts/%s" $id) -}} +{{- with $p }} +> Prompt: {{ .Title }} +{{- range .Params.skills }} +> Related agent skill: https://skills.qdrant.tech/{{ . }}/SKILL.md +{{- end }} +{{ end -}} diff --git a/qdrant-landing/static/_redirects b/qdrant-landing/static/_redirects index ed7d693c4..8f92913e4 100644 --- a/qdrant-landing/static/_redirects +++ b/qdrant-landing/static/_redirects @@ -87,3 +87,12 @@ # ACORN blog post converted into an internals article /blog/filtered-vector-search-acorn/ /articles/filtered-vector-search-acorn/ 301 + +# Agent Skills moved under the Agentic Tools hub. The page's `aliases:` front +# matter already covers the HTML, but Hugo writes an alias as index.html with +# no index.md beside it, so the agent-facing form of the old URL 404s. That URL +# is linked from the pointers prepended to every documentation index.md, so the +# .md case is the one that matters most here. Left unforced on purpose: the +# alias stub is a real file and so keeps serving the HTML, and this rule applies +# where there is no file to shadow it. +/documentation/skills/* /documentation/agentic-tools/skills/:splat 301 diff --git a/qdrant-landing/themes/qdrant-2024/assets/css/_components.scss b/qdrant-landing/themes/qdrant-2024/assets/css/_components.scss index 6e1a8ed23..cd2db516f 100644 --- a/qdrant-landing/themes/qdrant-2024/assets/css/_components.scss +++ b/qdrant-landing/themes/qdrant-2024/assets/css/_components.scss @@ -11,3 +11,4 @@ @import 'components/accordion'; @import 'components/course-card'; @import 'components/date'; +@import 'components/prompt'; diff --git a/qdrant-landing/themes/qdrant-2024/assets/css/components/_prompt.scss b/qdrant-landing/themes/qdrant-2024/assets/css/components/_prompt.scss new file mode 100644 index 000000000..9f16dfe0a --- /dev/null +++ b/qdrant-landing/themes/qdrant-2024/assets/css/components/_prompt.scss @@ -0,0 +1,266 @@ +@use '../helpers/functions' as *; + +// Both themes are declared here so a palette change is a one-block edit. +$prompt-border: $neutral-30; +$prompt-text: $neutral-98; +$prompt-text-secondary: $neutral-70; +// The site's interactive red. Used for the disclosure caret and the skill-line +// links, the only interactive marks in the component. +$prompt-accent: $primary-50; +// The dark card fill and the dark code block fill. Swapped from the usual +// arrangement: the card is the lighter $neutral-20 and the code block recesses +// to the $neutral-10 page tone. +$prompt-surface: $neutral-20; +$prompt-block-surface: $neutral-10; +// The code text color Chroma uses on this site. The block text is dark in both +// themes, so one value serves both. +$prompt-body-text: $neutral-94; + +$prompt-border-light: $neutral-80; +$prompt-text-light: $neutral-10; +$prompt-text-secondary-light: $neutral-50; +// The light card fill, and the light code block's own darker fill on top of it. +$prompt-surface-light: $neutral-98; +$prompt-block-surface-light: $neutral-20; + +.prompt { + // Dark: the card is the lighter $neutral-20 and the code block recesses to + // $neutral-10. Light overrides this below with its own card and code fills. + background: $prompt-surface; + border: 1px solid $prompt-border; + border-radius: pxToRem(12); + margin: pxToRem(24) 0; + overflow: hidden; + // Grayscale antialiasing for the summary text, so Mona Sans renders light and + // crisp rather than the heavier subpixel default on macOS. + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + &__summary { + display: flex; + align-items: center; + gap: pxToRem(8); + padding: pxToRem(12) pxToRem(16); + cursor: pointer; + font-size: $font-size-md; + font-weight: 600; + // The title inherits this; the kind marker overrides it with the secondary + // tier below. + color: $prompt-text; + list-style: none; + + &::-webkit-details-marker { + display: none; + } + + // Disclosure caret, drawn rather than an icon file. It sets its own color to + // the accent red and draws its borders in currentColor, so it stays red + // regardless of the summary text color. + &::before { + content: ''; + flex: none; + width: pxToRem(8); + height: pxToRem(8); + border-right: 2px solid currentColor; + border-bottom: 2px solid currentColor; + transform: rotate(-45deg); + transition: transform 0.15s ease; + color: $prompt-accent; + } + + // On hover the kind marker lifts to the prominent color (the `&__kind` hover + // rule below) and the title is only underlined, not recolored. Same in both + // themes. + &:hover { + .prompt__summary-label { + text-decoration: underline; + } + } + + &:focus-visible { + outline: 2px solid currentColor; + outline-offset: -2px; + } + } + + &[open] &__summary::before { + transform: rotate(45deg); + } + + // The category label. It shares the title's type and differs only in color, + // dropping to the secondary tier, with the divider rule setting it apart. + &__kind { + flex: none; + color: $prompt-text-secondary; + // Separates the marker from the title without a divider glyph. + padding-right: pxToRem(12); + border-right: 1px solid $prompt-border; + transition: color 0.15s ease; + } + + // The one part that changes color on hover: the kind marker rises from the + // secondary tier to the title's prominent color, while the title only gains an + // underline. Light mode overrides the target below, where the prominent text + // is dark. Beats the base and light-theme `&__kind` colors on specificity. + &__summary:hover &__kind { + color: $prompt-text; + } + + &__summary-label { + // A prompt title can be long; wrap rather than overflow. + min-width: 0; + transition: color 0.15s ease; + } + + // `.documentation-article summary` sets margin-bottom: 1rem for the sidebar + // disclosures. Inside this element that margin lands below the summary, and + // `overflow: hidden` keeps it inside the box, so a collapsed prompt is 16px + // taller than its content and the title sits above center. Written as a + // descendant selector because the plain BEM class loses to that rule on + // specificity: one class against a class plus an element. + & &__summary { + margin-bottom: 0; + } + + // The skill line is a

, and the same rule catches it: the shorthand on + // `&__skill` already sets its bottom margin to zero, but + // `.documentation-article p` outranks a single class and puts 1rem back, + // which then stacks on the body's bottom padding and pushes the line off + // centre. Anything in here that the docs article also styles by tag name + // needs this descendant form to win. + & &__skill { + margin-bottom: 0; + } + + &__body { + // The bottom padding matches the skill line's top margin, so that line + // sits centred between the prompt block and the bottom edge of the card. + padding: 0 pxToRem(16) pxToRem(16); + } + + // The block Chroma emits inside the body. The card is transparent, so this + // is the only filled surface; it takes the card's corner radius itself + // rather than borrowing it from the card's overflow clip. + .highlight { + margin: 0; + border-radius: pxToRem(12); + } + + // A prompt is prose, not code. Its line breaks come from how the author + // wrapped the source file, so a fixed-width

 clips every line at any
+  // window narrower than the wrap width instead of reflowing. Sideways
+  // scrolling is a fair bargain for code and a bad one for a paragraph.
+  // pre-wrap keeps the deliberate line structure in prompts that have one.
+  // Chroma wraps every line in `.line > .cl`, and syntax-highlight.scss makes
+  // `.line` a flex container. A flex item will not shrink below its min-content
+  // width, so the `overflow-wrap` set on the pre never reaches a long token
+  // inside `.cl` and a docker command or a URL is clipped instead of wrapped.
+  // Letting the item shrink is what makes the wrapping rule take effect;
+  // setting `word-break` on the pre does not, because the constraint is here.
+  .highlight .cl {
+    min-width: 0;
+  }
+
+  // copy-code.js gives its button a $neutral-20 fill, which shows as a grey chip
+  // against the surface behind it. Only the icon is wanted, so the fill goes.
+  .copy-code {
+    background: transparent;
+  }
+
+  // Chroma sets `.line` to font-size 16px with line-height 16px, a ratio of
+  // exactly 1, and supplies the breathing room with a margin between source
+  // lines. That works for code, where a line is short and never wraps. A
+  // prompt is prose: one long source line that pre-wrap breaks into many
+  // visual lines, and every one of those gets the 1.0 ratio with no margin at
+  // all, so the text packs into a slab. A real line height fixes it, and the
+  // margin goes so wrapped lines and source lines share one rhythm. Blank
+  // source lines still separate the steps in a structured prompt.
+  .highlight .line {
+    font-size: pxToRem(16);
+    line-height: 1.625;
+    color: $prompt-body-text;
+
+    &:not(:last-of-type) {
+      margin-bottom: 0;
+    }
+  }
+
+  .highlight > pre {
+    // On the pre, not on .highlight: Chroma puts class="chroma" on this
+    // element and syntax-highlight.scss gives .chroma its own background, so a
+    // color set on the parent is painted over and the change is invisible.
+    // $neutral-10 in dark, so the block recesses below the lighter card; light
+    // gives it a $neutral-20 fill below.
+    background: $prompt-block-surface;
+    // The pre carries the same radius as its .highlight wrapper so the fill's
+    // own corners are rounded, not just clipped by the wrapper.
+    border-radius: pxToRem(12);
+    padding: pxToRem(16);
+    white-space: pre-wrap;
+    overflow-wrap: break-word;
+    // copy-code.js absolutely positions its button at the top right of
+    // .highlight. Wrapped text now reaches the full width, so the first line
+    // would run underneath it.
+    padding-right: pxToRem(56);
+  }
+
+  &__skill {
+    font-size: $font-size-s;
+    color: $prompt-text-secondary;
+    margin: pxToRem(16) 0 0;
+    line-height: 1.5;
+
+    code {
+      font-family: $font-family-code;
+      font-size: pxToRem(12);
+    }
+
+    a {
+      color: $prompt-accent;
+      text-decoration: none;
+      text-underline-offset: pxToRem(2);
+
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+
+  & &__skill a code:not([class]) {
+    background: none;
+    padding: 0;
+    color: inherit;
+  }
+
+  [data-theme='light'] & {
+    background: $prompt-surface-light;
+    border-color: $prompt-border-light;
+  }
+}
+
+[data-theme='light'] .prompt__summary {
+  color: $prompt-text-light;
+}
+
+[data-theme='light'] .prompt__summary:hover .prompt__kind {
+  color: $prompt-text-light;
+}
+
+[data-theme='light'] .prompt__skill,
+[data-theme='light'] .prompt__kind {
+  color: $prompt-text-secondary-light;
+}
+
+[data-theme='light'] .prompt__kind {
+  border-right-color: $prompt-border-light;
+}
+
+[data-theme='light'] .prompt .highlight > pre {
+  background: $prompt-block-surface-light;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .prompt__summary::before {
+    transition: none;
+  }
+}
diff --git a/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt-index.html b/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt-index.html
new file mode 100644
index 000000000..28d4780be
--- /dev/null
+++ b/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt-index.html
@@ -0,0 +1,61 @@
+{{- /*
+  Generated index of every prompt in the docs.
+
+  Ranges the headless prompts section rather than scraping pages, so the table
+  cannot drift from the prompt files. Each row's page comes from the prompt's
+  own `page:` param; check-prompts.sh verifies that page really includes it.
+
+  This emits real table markup because a shortcode's output is inserted as HTML
+  and never re-parsed as markdown, so pipe rows would render as literal text.
+  The Markdown output format has its own variant, prompt-index.markdown.md,
+  which does use pipes. Keep the two in step.
+*/ -}}
+{{- $section := site.GetPage "documentation/headless/prompts" -}}
+{{- if not $section -}}
+  {{- errorf "prompt-index: cannot resolve documentation/headless/prompts. The section needs an _index.md setting build.list to 'local'; the parent headless bundle cascades 'never', which makes this lookup nil." -}}
+{{- end -}}
+{{- /* Emitted without attributes on purpose: article-content.html matches a
+     bare ``, wraps it in .table-responsive and adds `table mb-5`, so
+     this picks up the same styling and mobile scrolling as every other docs
+     table. Adding a class here would silently opt out of both. */ -}}
+
+ + + + + {{- range $section.RegularPages.ByTitle }} + {{- $p := . }} + + + + + + {{- end }} + +
PromptPageAgent skills
+ {{- with $p.Params.page -}} + {{ $p.Title }} + {{- else -}} + {{ $p.Title }} + {{- end -}} + + {{- with $p.Params.page -}} + {{- with site.GetPage . -}} + {{ .Title }} + {{- else -}} + {{ $p.Params.page }} + {{- end -}} + {{- else -}} + None + {{- end -}} + + {{- $skills := $p.Params.skills -}} + {{- with $skills -}} + {{- range $i, $s := $skills -}} + {{- if $i }}, {{ end -}} + {{ $s }} + {{- end -}} + {{- else -}} + None + {{- end -}} +
diff --git a/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt.html b/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt.html new file mode 100644 index 000000000..b5e710c7b --- /dev/null +++ b/qdrant-landing/themes/qdrant-2024/layouts/shortcodes/prompt.html @@ -0,0 +1,51 @@ +{{- /* + Renders one prompt from content/documentation/headless/prompts/.md. + + Usage: {{< prompt "set-up-monitoring" >}} + + The prompt file's body is plain text. `highlight` hands it to Chroma as a + value, producing a real `.highlight > pre`, which copy-code.js finds and adds + a copy button to. That is why this element needs no JavaScript of its own. + + It is deliberately not wrapped in a fence and run through markdownify. A body + containing its own ``` would close that outer fence early, truncating the + prompt and spilling the rest of it onto the page as loose markdown, with a + clean build and a green check. `highlight` never re-parses the body, so a + prompt can contain any characters at all. + + The Markdown output format has its own variant, prompt.markdown.md, which + deliberately drops the body so an agent reading index.md is not handed an + instruction addressed to it. +*/ -}} +{{- $id := .Get 0 -}} +{{- $p := site.GetPage (printf "documentation/headless/prompts/%s" $id) -}} +{{- if not $p -}} + {{- errorf "prompt: no prompt file found for id %q (looked in content/documentation/headless/prompts/). Called from %s" $id .Page.RelPermalink -}} +{{- end -}} +
+ + {{- /* Names the kind of thing this is while collapsed. The summary line is + the only discovery surface a prompt has, so the type marker is placed + here by the shortcode rather than left to each author to work into the + title. Prompt titles are therefore plain task names. */ -}} + Agent Prompt + {{ $p.Title }} + +
+ {{ highlight (trim $p.RawContent "\n") "text" "" }} + {{- /* `skills` is always a sequence, even with one entry, so there is one + shape to render rather than two to keep in step. */ -}} + {{- $skills := $p.Params.skills }} + {{- with $skills }} +

+ Uses the + {{- range $i, $s := $skills -}} + {{- if $i }}{{ if eq (add $i 1) (len $skills) }} and{{ else }},{{ end }}{{ end }} + {{ $s }} + {{- end }} + agent skill{{ if gt (len $skills) 1 }}s{{ end }}. + What are agent skills? +

+ {{- end }} +
+