Skip to content

Repository files navigation

pais-sdk-cli

Contract-first Python SDK + CLI for VMware Private AI Service (PAIS), with a bundled mock server for offline development. Build and test against PAIS APIs without a live host; switch to a real instance via config.

Install

Not on PyPI yet — install straight from GitHub. The pais command is the console script.

# pip — latest main
pip install "git+https://github.com/dshahnaz/pais-sdk-cli.git"

# pip — pinned to a tag/commit (recommended for reproducibility)
pip install "git+https://github.com/dshahnaz/pais-sdk-cli.git@v0.7.0"

# pip — include dev extras (adds the HuggingFace tokenizers dep required by the test-suite splitters)
pip install "git+https://github.com/dshahnaz/pais-sdk-cli.git#egg=pais-sdk-cli[dev]"

# pip — with optional contextual-retrieval extra (Anthropic SDK for --with-context-llm)
pip install "git+https://github.com/dshahnaz/pais-sdk-cli.git#egg=pais-sdk-cli[dev,contextual]"

# uv — into an isolated tool environment (recommended for CLI users)
uv tool install "git+https://github.com/dshahnaz/pais-sdk-cli.git"
uv tool install --with "pais-sdk-cli[dev]" "git+https://github.com/dshahnaz/pais-sdk-cli.git"

# pipx — same idea
pipx install "git+https://github.com/dshahnaz/pais-sdk-cli.git"

Verify:

pais --help

For local development (clone + editable install) see CONTRIBUTING.md.

Quickstart

# run the mock server
python -m pais_mock &

# use the CLI against the mock
export PAIS_MODE=http
export PAIS_BASE_URL=http://localhost:8080/api/v1
export PAIS_AUTH=none
pais kb list

Interactive mode

Run pais (no args) in a terminal and it opens with a smart landing screen:

$ pais
profile=lab  ·  mode=http (real PAIS)
3 KBs  ·  5 indexes  ·  2 agents  · 0 drift

recommended: 💬  Chat with an agent

> 💬  Chat with an agent
  🤖  Set up a chat agent over my docs
  📦  Provision KB + index (no agent)
  🔧  Apply pending TOML config
  📥  Ingest data into an index
  🔎  Search an index (no LLM)
  🗑  Cleanup (delete KB / index / agent)
  📋  all commands…

The recommended workflow is chosen from your env state (no agents → "Set up an agent"; drift detected → "Apply pending TOML"). Each workflow walks you through the steps with pick-or-create lists (showing existing items + + create new), single-screen reviews of all defaults (with one-line hints — e.g. chunk_size 512 ↑ tokens, not chars), and a "what next?" menu after success.

Set up an agent is the headline flow:

🤖 Set up a chat agent over my docs.
1. Pick or create a KB
2. (optional) save it as alias `prod_docs` in pais.toml
3. Pick or create an index under it
4. (optional) save the index alias too
5. Create the agent (index_id + index_top_n, doc-aligned)
6. → Ingest data into this index now (recommended — index is empty)
   💬 Chat with it
   📊 View `pais status`
   ✅ Done

Destructive ops use type-to-confirm (you type the resource name to proceed). --quick-confirm / -Q falls back to y/N for power users.

pais shell opens the menu explicitly. To opt out of the bare-pais trigger, pass --no-interactive or set PAIS_NONINTERACTIVE=1. Non-TTY callers (pipes, scripts, CI) always print the help banner, never the menu. Pick 📋 all commands… from the landing screen for the full v0.5-style flat command list.

Navigation shortcuts:

  • Ctrl-C / Esc → back to the previous step (or exit shell at the landing screen). Every prompt shows the hint inline.
  • ← back row in every picker — same effect as Ctrl-C, just clickable.
  • Verbosity tiers (applies to both the shell and any pais <cmd> subcommand):
    • no flag → WARNING — only warnings/errors (TLS-verify-off, purge fallback, retries). Silent happy path.
    • -vINFO — high-signal events (ingest start/done, index recreated, etc.).
    • -vvDEBUG — per-request HTTP traces with latency + status. The full log always rotates to ~/.pais/logs/pais.log regardless of the tier you pick for stderr.

Persistent config

Tired of export PAIS_* on every shell? Drop a TOML config file with named profiles:

pais config init                  # writes ~/.pais/config.toml with comments
pais config init --project        # writes ./pais.toml in the current dir
pais config show --profile lab    # print effective settings (secrets redacted)
pais config path                  # which file + profile resolve right now

Example file:

# ~/.pais/config.toml  (or ./pais.toml — project wins over global)
default_profile = "lab"

[profiles.lab]
mode = "http"
base_url = "https://pais.internal/api/v1"
auth = "none"
verify_ssl = false

[profiles.prod]
mode = "http"
base_url = "https://pais.example.com/api/v1"
auth = "oidc_password"
oidc_issuer = "https://pais.example.com"
client_id = "pais-cli"
username = "alice"
# password / client_secret / bearer_token are REJECTED here — env vars only.

Then every command picks it up:

pais --profile lab kb list
pais --profile prod agent chat agent_xx "hello"
pais --profile prod agent chat agent_xx --file prompt.md    # long prompts from a file
# or set PAIS_PROFILE=lab once for the whole shell

Inside the interactive chat (pais chat), type /file <path> at the you: prompt to send a file's contents as your message instead of typing it in.

Precedence (highest first): CLI flag → PAIS_* env var → config file → defaults. Discovery order for the file: --config <path>PAIS_CONFIG./pais.toml~/.pais/config.toml.

Three runbooks

1. Mock mode (no real host)

export PAIS_MODE=mock
uv run pais kb create --name demo
uv run pais kb list

Tests use the in-process fake transport; no server needed.

2. Real PAIS — internal network, no auth

export PAIS_MODE=http
export PAIS_BASE_URL=https://pais.internal/api/v1
export PAIS_AUTH=none
export PAIS_VERIFY_SSL=false
uv run pais kb list

3. Real PAIS — OIDC

export PAIS_MODE=http
export PAIS_BASE_URL=https://pais.example.com/api/v1
export PAIS_AUTH=oidc_password
export PAIS_OIDC_ISSUER=https://pais.example.com
export PAIS_CLIENT_ID=... PAIS_USERNAME=... PAIS_PASSWORD=...
uv run pais kb list

Declarative config & pais kb ensure

Declare KBs + indexes + their splitters in TOML, then operate on short aliases instead of UUIDs.

# ~/.pais/config.toml  (or ./pais.toml — project wins)
[profiles.lab]
mode = "http"
base_url = "https://10.160.11.45/api/v1"
auth = "none"
verify_ssl = false

[profiles.lab.knowledge_bases.test_suites]
name = "mops-permanent-test-suites"
data_origin_type = "LOCAL_FILES"

  [[profiles.lab.knowledge_bases.test_suites.indexes]]
  alias = "main"
  name = "ts-idx"
  embeddings_model_endpoint = "BAAI/bge-small-en-v1.5"
  chunk_size = 512
  chunk_overlap = 64

    [profiles.lab.knowledge_bases.test_suites.indexes.splitter]
    kind = "test_suite_bge"   # per-test-case chunks + breadcrumb; 400-tok budget
    max_case_tokens = 400

  [[profiles.lab.knowledge_bases.test_suites.indexes]]
  alias = "arctic"
  name = "ts-arctic"
  embeddings_model_endpoint = "Snowflake/snowflake-arctic-embed-m-v2.0"
  chunk_size = 2048
  chunk_overlap = 256

    [profiles.lab.knowledge_bases.test_suites.indexes.splitter]
    kind = "test_suite_arctic"  # wider 1500-tok budget for Arctic's 8 K window

Then:

pais --profile lab kb ensure   # create anything missing on the server (idempotent)
pais kb list --with-counts     # see KBs with index + document totals
pais kb show test_suites       # full KB detail with per-index breakdown

pais kb ensure is idempotent. Re-run after editing the TOML — it adds new KBs/indexes and warns about server-side mismatches PAIS doesn't expose updates for. --dry-run previews; --prune --yes deletes server-side resources not in the TOML (per-item confirmation).

Ingest data

Generic pais ingest runs the splitter declared on the target index over any file or directory.

# 0. ensure the KB + index exist (one-time setup from config)
pais --profile lab kb ensure

# 1. ingest a directory of suite markdown files (uses test_suite_bge splitter from config)
pais ingest test_suites:main ./suites/

# 2. same files into an index backed by a different embedding model
pais ingest test_suites:arctic ./suites/

# 3. re-ingest only changed suites; other suites in the index stay
pais ingest test_suites:main ./changed/ --replace

# 4. preview without uploading
pais ingest test_suites:main ./suites/ --dry-run

# 5. one-off override of the splitter (pick the variant matching the index's embeddings model)
pais ingest test_suites:main ./suites/ --splitter test_suite_arctic

# 6. wait for indexing
pais index wait test_suites:main

UUIDs work everywhere aliases do — pais ingest <kb_uuid>:<idx_uuid> ./files/ is fine for ad-hoc use.

Built-in splitters

kind summary target embeddings model chunk_size chunk_overlap
test_suite_bge Per-test-case chunks with breadcrumb; tuned for bge-small-en-v1.5 BAAI/bge-small-en-v1.5 512 64
test_suite_arctic Per-test-case chunks with breadcrumb; tuned for arctic-embed-m-v2.0 Snowflake/snowflake-arctic-embed-m-v2.0 2048 256

Both emit 1 overview chunk + 1 chunk per test case, each with a compact 2-line breadcrumb prepended so retrieval can't lose the suite/case anchor. Pick the variant matching the index's embeddings_model_endpoint. See docs/ingestion.md for the full story (two-layer chunking, why the breadcrumb lives inside the body, optional Anthropic contextual retrieval).

Inspect + preview from the CLI:

pais splitters list                                            # compact: kind + summary
pais splitters show test_suite_bge                             # full meta + suggested index config
pais splitters preview test_suite_bge ~/suites/foo.md          # stats + sample chunk
pais splitters preview test_suite_bge ~/suites/foo.md --dump /tmp/preview   # writes every chunk to disk
pais splitters preview test_suite_bge ~/suites/foo.md --show-all            # prints each chunk inline
pais splitters new my_custom                                   # scaffold a new splitter

The preview --dump <dir>/ flag is the fastest way to answer "will each of my 300 test suites chunk correctly?" — dump the first handful, open the files, verify the breadcrumbs + sizes, then bulk-ingest with confidence.

Content hygiene: bodies are uploaded as-is. Scrub internal hostnames / IPs / credentials from input files before ingesting into a shared PAIS deployment — the structured logger redacts secret-looking keys but cannot sanitize arbitrary prose.

Cleanup & cancel

Destructive ops require either a TTY confirmation prompt or --yes / -y. They refuse to run in scripts (non-TTY) without --yes.

All commands accept either an alias (from your config) or a UUID.

# delete a whole KB (cascades indexes + documents)
pais kb delete test_suites --yes

# keep the KB, drop every document under every index in it
pais kb purge test_suites --yes

# keep the index, drop its documents
pais index purge test_suites main --yes

# delete one index entirely
pais index delete test_suites main --yes

# cancel a running indexing job
pais index cancel test_suites main --yes

Each cleanup/cancel command takes --strategy {auto,api,recreate}:

  • api — try the obvious REST verb (DELETE /documents/{id} for purge, DELETE /active-indexing for cancel). Fails fast if the PAIS deployment doesn't expose it.
  • recreate — delete the index entirely and recreate it with the same config. Always works but the new index gets a different id — you'll need to re-link any agents pointing at the old one. The CLI prints a warning when this happens.
  • auto (default) — try api first, fall back to recreate on 404/405.

Re-ingest cleanly

pais ingest --replace deletes only the documents whose origin_name matches the splitter's group_key for each input file; everything else stays:

pais ingest test_suites:main ./changed-suites/ --replace

Logging & troubleshooting

  • Logs: ~/.pais/logs/pais.log (rotating, 5MB × 3).
  • Verbosity: PAIS_LOG_LEVEL=DEBUG.
  • Secrets (authorization, password, access_token, refresh_token, ...) are redacted. Safe to share log files as-is.
  • Every request carries a request_id that round-trips to the server as X-Request-ID for correlation.

Architecture

CLI (typer) → SDK (resources) → Transport (httpx | fake) → PAIS host | mock server

Models in src/pais/models/ are imported by both the SDK and the mock server — the mock serves the exact contract the SDK validates.

License

MIT

About

Contract-first Python SDK + CLI for VMware Private AI Service (PAIS), with mock server for offline development

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages