Answer security questionnaires and compliance attestations from your evidence — a model-agnostic RAG kernel.
Project description
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)
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.
- 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, 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file attestq-0.1.0.tar.gz.
File metadata
- Download URL: attestq-0.1.0.tar.gz
- Upload date:
- Size: 36.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78a5dadb13e91d65413472f2cf537f0b9b1560a9197300f9864f621f6d27c632
|
|
| MD5 |
5d66fa874b7df8a0da9d8b7947e788b1
|
|
| BLAKE2b-256 |
e398b16612b09210d96fe50e5f8f5e7d2fda82dbf23088929c2e234c9c0fe503
|
Provenance
The following attestation bundles were made for attestq-0.1.0.tar.gz:
Publisher:
publish.yml on vinayvobbili/attestq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
attestq-0.1.0.tar.gz -
Subject digest:
78a5dadb13e91d65413472f2cf537f0b9b1560a9197300f9864f621f6d27c632 - Sigstore transparency entry: 1971228680
- Sigstore integration time:
-
Permalink:
vinayvobbili/attestq@0b3fc7c5063594aa7a9f2c4677081714370850ca -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vinayvobbili
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b3fc7c5063594aa7a9f2c4677081714370850ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file attestq-0.1.0-py3-none-any.whl.
File metadata
- Download URL: attestq-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30d9eff8d7c5edb9b365e53d6775926566d1654ba5d4f524021adb287a72a64b
|
|
| MD5 |
1106cea2a6e0026093b8376d01a07ce9
|
|
| BLAKE2b-256 |
1bb1f28117d40dbcb7e3562991ef9ee3bc642aac3f493d47866181fd5fb2cece
|
Provenance
The following attestation bundles were made for attestq-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on vinayvobbili/attestq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
attestq-0.1.0-py3-none-any.whl -
Subject digest:
30d9eff8d7c5edb9b365e53d6775926566d1654ba5d4f524021adb287a72a64b - Sigstore transparency entry: 1971228714
- Sigstore integration time:
-
Permalink:
vinayvobbili/attestq@0b3fc7c5063594aa7a9f2c4677081714370850ca -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vinayvobbili
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0b3fc7c5063594aa7a9f2c4677081714370850ca -
Trigger Event:
push
-
Statement type: