Skip to main content

attestq

Answer security questionnaires and compliance attestations from your own evidence.

attestq is a small, model-agnostic RAG kernel for a problem every security and GRC team has: you have a questionnaire (a vendor security review, a SIG/CAIQ response, an audit-evidence request, a due-diligence form, the security section of an RFP) and you have a pile of evidence (SOC 2 reports, policies, standards, prior questionnaires). attestq retrieves the relevant evidence for each question and drafts a grounded, cited answer — and, just as importantly, tells you plainly when the evidence isn't there.

It is not a classic ML / training problem. It's retrieval + an LLM you bring yourself. attestq owns the orchestration; you own the model, the embedder, and the store.

Why it exists

The same pattern keeps getting rebuilt one-off inside every program: chunk the docs, embed them, retrieve per question, prompt an LLM, paste into a form. Done naively it hallucinates (answers with no supporting evidence) and it silently drops the one focused document that actually had the answer. attestq bakes in the two hard-won fixes:

  • Confidence gate. When the best retrieved evidence scores below a threshold, the question is answered "insufficient evidence" without calling the LLM. Absence of evidence is a first-class, valid result — not an invitation to guess.
  • Wide rerank window. The kernel keeps a generous number of chunks after reranking so a single relevant document isn't dropped on a small corpus.

Install

pip install attestq                 # dependency-free core
pip install "attestq[chroma]"       # + persistent Chroma vector store
pip install "attestq[openai]"       # + OpenAI-compatible chat adapter
pip install "attestq[ollama]"       # + local Ollama embeddings
pip install "attestq[rerank]"       # + cross-encoder reranker
pip install "attestq[loaders]"      # + pdf / docx / xlsx loaders
pip install "attestq[all]"          # everything

The core has zero third-party dependencies. Adapters are opt-in extras.

Quick start

You inject any chat model and any embedder as plain callables:

from attestq import Engine, Question

# Bring your own model + embedder (one-liners around any provider).
def my_chat(prompt: str) -> str:
    ...   # call OpenAI, Anthropic, a local model, your corporate gateway, ...

def my_embed(texts):
    ...   # return one vector per text

engine = Engine(chat=my_chat, embed=my_embed)   # in-memory store by default

# Ingest a vendor's evidence into its own namespace.
engine.ingest(
    [
        ("All customer data at rest is encrypted with AES-256...", {"source": "DataProtection.pdf"}),
        ("MFA is enforced for all privileged access...", {"source": "AccessControl.docx"}),
    ],
    namespace="helios",
)

# Answer one control.
ans = engine.evaluate(
    Question(
        id="ENC-1",
        prompt="Is customer data encrypted at rest?",
        choices=["Met", "Not Met", "Not Applicable"],
    ),
    namespace="helios",
)

print(ans.determination)          # "Met"
print(ans.confidence)             # 0.0 - 1.0 retrieval confidence
print(ans.insufficient_evidence)  # False
for c in ans.citations:
    print(c.source, "->", c.snippet)

Run a whole questionnaire:

from attestq import Questionnaire

qn = Questionnaire(
    id="vendor-sec-review",
    title="Vendor Security Review",
    questions=[
        Question(id="ENC-1", prompt="Is data encrypted at rest?", choices=["Met", "Not Met", "Not Applicable"]),
        Question(id="IAM-1", prompt="Is MFA enforced for privileged access?", choices=["Met", "Not Met", "Not Applicable"]),
    ],
)

answers = engine.evaluate_all(qn, namespace="helios", on_answer=lambda a: print(a.question_id, a.determination))

Command line

Installing attestq gives you an attestq command. Run the bundled sample, or point it at your own questionnaire and evidence:

# Run the bundled fictional sample assessment end-to-end
attestq demo -o report.md

# Evaluate your questionnaire against a folder of evidence
attestq run -q questionnaire.yaml -e ./vendor-evidence -n acme -o report.docx

# Pipe JSON to another tool
attestq run -q q.json -e ./evidence --format json | jq .summary

Providers are resolved from flags or environment, so the same command works against OpenAI-compatible endpoints or a local Ollama:

export OPENAI_API_KEY=sk-...                 # uses OpenAI by default
attestq demo

attestq demo --provider ollama               # local, no key, nothing leaves the host
attestq run -q q.yaml -e ./ev --provider openai --base-url https://my-gateway/v1

Try it instantly (no setup)

The built-in HashEmbedder needs no model and no service, so you can watch the retrieval pipeline and the confidence gate work the moment you install:

pip install attestq
python examples/quickstart.py

See examples/ for a full provider-wired demo (helios_demo.py) and a minimal web UI (examples/web/) that runs the sample assessment in your browser.

How it works

evidence docs ──▶ chunk ──▶ embed ──▶ vector store (namespaced per corpus)

question ──▶ embed ──▶ retrieve(k) ──▶ rerank(top_k) ──▶ confidence gate
                                                              │
                              below threshold ───────────────┤──▶ "insufficient evidence" (no LLM call)
                                                              │
                              above threshold ──▶ prompt LLM ─┴──▶ parse ──▶ Answer(determination, summary, citations, confidence)

Verifying the draft

Drafting is half the job; the other half is not trusting the draft. Turn on verify=True and every answer carries two reports:

engine = Engine(chat=my_llm, embed=my_embedder, verify=True)
answer = engine.evaluate(question, namespace="vendor-x")

if answer.needs_review:
    print(answer.grounding.unverified)   # ['2019-03-01'] — asserted, not in evidence
    print(answer.quality.detail())       # "only 20% of the answer is carried by ..."

answer.grounding extracts every concrete value the draft asserted — dates, versions, percentages, standards, durations — and confirms each one actually occurs in the evidence. This catches the highest-liability failure: "certified to ISO 27001 since 2019-03-01" when neither appears in a single retrieved document.

answer.quality catches the quieter failure — an answer that restates the question and cites evidence it never drew on:

Q: Are encryption keys securely managed through defined key-management processes? A: Yes. Encryption keys are securely managed through defined key-management processes.

That asserts nothing the question didn't already say. Per sentence, it measures support (closeness to the retrieved evidence) against echo (closeness to the question), each judged against the question's own similarity to that evidence — so there is no magic cosine constant to recalibrate when you swap embedders.

Both checks are deterministic: no second LLM grading the first, which would only share the drafter's blind spots. Both flag and never rewrite — a flagged answer may still be the right answer, so the disposition stays with the reviewer. Verification is off by default; it costs one extra embedding call per answer.

One judgment measurement genuinely can't make is whether a question wanted documentary evidence at all — "What is your registered legal entity name?" has a correct answer no policy document will ever back. Inject a small model to decide, and it may only ever clear a flag, never raise one:

from attestq import make_claim_classifier

engine = Engine(chat=big_model, embed=embedder, verify=True,
                claim_classifier=make_claim_classifier(small_fast_model))

Every failure path in that classifier — endpoint down, unparseable reply — leaves the flag standing.

Everything is swappable:

Piece Default Swap for
Chat model you inject it any LLM / gateway
Embedder you inject it Ollama, sentence-transformers, OpenAI
Vector store InMemoryVectorStore attestq[chroma]
Reranker none attestq[rerank] cross-encoder
Prompt / parser evidence-only default your own prompt_builder / response_parser

Design principles

  • Bring your own model. No provider is hard-wired. A lambda is enough.
  • Grounded or silent. Answers cite their evidence; thin evidence yields an explicit "insufficient" result, never a confident guess.
  • The model proposes, deterministic code disposes. Verification is plain string and vector math, so the verifier cannot hallucinate the way an LLM-grading-an-LLM judge can. It flags for a human; it never rewrites.
  • Per-corpus isolation. One store, many namespaces — keep each vendor's evidence separate without standing up a new index each time.
  • Light core. The kernel imports nothing third-party; heavy deps stay in extras you opt into.

Status

Usable today: the core kernel, the verification layer, in-memory + Chroma stores, OpenAI/Ollama adapters, a cross-encoder reranker, document loaders, JSON/Markdown/Word export, a CLI, and a web demo. Contributions and issues welcome.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

attestq-0.3.0.tar.gz (63.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

attestq-0.3.0-py3-none-any.whl (52.6 kB view details)

Uploaded Python 3

File details

Details for the file attestq-0.3.0.tar.gz.

File metadata

  • Download URL: attestq-0.3.0.tar.gz
  • Upload date:
  • Size: 63.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for attestq-0.3.0.tar.gz
Algorithm Hash digest
SHA256 e91c943789619c503e3a44070f8b4942e172dea12f4d28517ff571596a766ed9
MD5 43af7bbe0e906ed1ec10a591e48097d7
BLAKE2b-256 736afa8ab8e036e354a26557768b6bfc7570e788c619bd3ffbc40c5ea0f8c0a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for attestq-0.3.0.tar.gz:

Publisher: publish.yml on vinayvobbili/attestq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file attestq-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: attestq-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 52.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for attestq-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ff2c917642460540d21a029e16d6ef82e10a85cc46bc0b473411d6b5bd14bf0
MD5 abb50697c25ee58e4babc86d3614c47f
BLAKE2b-256 5f54dbc427e39137e6f821249bb71750c3c5b615365f556c8ce93560b57778de

See more details on using hashes here.

Provenance

The following attestation bundles were made for attestq-0.3.0-py3-none-any.whl:

Publisher: publish.yml on vinayvobbili/attestq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page