Metadata-Version: 2.5
Name: operonx-kb
Version: 0.2.3
Summary: Knowledge base and document intelligence on OperonX: versioned documents with span-level provenance
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: httpx>=0.24
Requires-Dist: numpy>=1.24
Requires-Dist: operonx>=1.16
Requires-Dist: pydantic>=2.0
Provides-Extra: admin
Requires-Dist: operonx[serve]; extra == 'admin'
Requires-Dist: pillow>=10; extra == 'admin'
Requires-Dist: pypdfium2>=4.30; extra == 'admin'
Provides-Extra: all
Requires-Dist: docling-parse>=7.22; extra == 'all'
Requires-Dist: operonx[faiss]; extra == 'all'
Requires-Dist: operonx[openai]; extra == 'all'
Requires-Dist: operonx[serve]; extra == 'all'
Requires-Dist: pillow>=10; extra == 'all'
Requires-Dist: psycopg-pool>=3.2; extra == 'all'
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
Requires-Dist: pypdfium2>=4.30; extra == 'all'
Provides-Extra: answer
Requires-Dist: operonx[openai]; extra == 'answer'
Provides-Extra: bench
Requires-Dist: operonx[faiss]; extra == 'bench'
Requires-Dist: operonx[huggingface]; extra == 'bench'
Requires-Dist: torch>=2.2.2; extra == 'bench'
Provides-Extra: drive
Requires-Dist: google-api-python-client>=2.100; extra == 'drive'
Requires-Dist: google-auth>=2.20; extra == 'drive'
Provides-Extra: faiss
Requires-Dist: operonx[faiss]; extra == 'faiss'
Provides-Extra: layout
Requires-Dist: docling-ibm-models[opencv-python-headless]>=4.0; extra == 'layout'
Requires-Dist: docling-parse>=7.22; extra == 'layout'
Requires-Dist: pypdfium2>=4.30; extra == 'layout'
Requires-Dist: torch>=2.2.2; extra == 'layout'
Requires-Dist: torchvision; extra == 'layout'
Provides-Extra: mcp
Requires-Dist: mcp>=2.0; extra == 'mcp'
Provides-Extra: ocr
Requires-Dist: docling-parse>=7.22; extra == 'ocr'
Requires-Dist: pillow>=10; extra == 'ocr'
Requires-Dist: pypdfium2>=4.30; extra == 'ocr'
Provides-Extra: pdf
Requires-Dist: docling-parse>=7.22; extra == 'pdf'
Provides-Extra: pgvector
Requires-Dist: operonx[pgvector]; extra == 'pgvector'
Provides-Extra: postgres
Requires-Dist: psycopg-pool>=3.2; extra == 'postgres'
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
Provides-Extra: qdrant
Requires-Dist: operonx[qdrant]; extra == 'qdrant'
Provides-Extra: s3
Requires-Dist: boto3>=1.28; extra == 's3'
Description-Content-Type: text/markdown

# operonx-kb

Knowledge base and document intelligence on [operonx](https://github.com/batman1m2001-cyber/Operonx): files become versioned documents
whose every character has an address (version, span → page, bbox), indexed for retrieval, and
answers cite spans that were checked against the text they quote.

```bash
uv sync --extra pdf --extra faiss     # core + PDF text layer + FAISS index
PYTHONPATH=../operonx-wt/feat-kb-upstream uv run pytest -q   # until operonx PR #74 merges
```

```python
import operonx
from operonx_kb import CollectionSpec, DenseIndexSpec, KnowledgeBase
from operonx_kb.model.collection import LexicalIndexSpec

operonx.bootstrap(resources="resources.yaml")  # kb_catalog:, kb_blob:, kb_lexical:, embedding:, vector_store:, llm:
kb = KnowledgeBase()
kb.create_collection("handbook", CollectionSpec(
    dense=DenseIndexSpec(embedder="e5", store="vector_store:kb",
                         passage_template="passage: {text}", query_template="query: {text}"),
    lexical=LexicalIndexSpec(),  # SQLite FTS5 or Postgres FTS
    filterable={"dept": "keyword"},
))
await kb.add("handbook", "policy.pdf", key="policy.pdf", tags=["hr"], acl=["team:hr"],
             metadata={"dept": "hr"})
found = await kb.search("handbook", "how many days of leave", mode="hybrid",
                        filter={"acl_any": ["team:hr"]}, reranker="ce")
answer = await kb.ask("handbook", "how many days of leave", "assistant")
answer["citations"]  # each: quote, canonical span, pages, bboxes; unverified ones are in answer["dropped"]
```

```bash
operonx-kb query handbook "how many days of leave" --mode hybrid --tag hr
operonx-kb query wiki "who directed the film that won in 1999"   # created with --graph: mode auto sends relation questions to the graph (docs/bench/router.md)
operonx-kb query handbook "how many days of leave" --answer assistant
operonx-kb eval handbook datasets/handbook.jsonl --mode hybrid
```

## Use it inside your flows

A search is an operonx graph, so it is one step of your own flow — traced with it, no service in
between (`tests/graphs/test_in_a_flow.py`):

```python
from operonx.kb.graphs import search   # module level: the collection's settings are read per run

@graph
def answer_flow(question, catalog):
    found = search(query=question, collection="handbook", filter=None, k=5, mode="hybrid",
                   catalog=catalog)
    reply = cite(hits=found["hits"])          # your own op
    START >> found >> reply >> END

engine = Operon(answer_flow, params={"question": None, "catalog": None})
out = await engine.run(inputs={"question": "...", "catalog": "kb_catalog:main"})
```

For an agent (operonx-agents installed beside it), `kb_tools` gives a read-only `kb_search` /
`kb_read` toolset. The scope comes from your code or the run's context — the model only sends a
query — and `kb_read` checks it again (`tests/graphs/test_agent_tools.py`):

```python
from operonx.kb.tools import kb_tools

tools = kb_tools(kb, "handbook", scope=lambda ctx: {"acl_any": ctx.deps.principals})
agent = Agent(name="hr", model=Model("assistant"), tools=[tools])
```

A client outside your code (Claude Desktop, an IDE) gets the same two tools over MCP (extra
`mcp`). The scope is the server's, fixed when it starts — one server per audience:

```bash
operonx-kb mcp handbook --acl team:hr
```

Scanned PDFs: a page with no text layer is read by OCR when the collection asks for it (extra
`ocr`, plus the `tesseract` binary with its language data) — `CollectionSpec(ocr=OcrSpec(languages="vie+eng"))`.

Files from S3 or Google Drive (extras `s3`, `drive`): `S3Source("bucket", prefix="hr/")` and
`DriveSource(folder_id)` are job sources, like a folder, so an ingest job reads them and resumes.

Studio's Knowledge tab reads a knowledge base through its admin API (extra `admin`), served as
one of the project's services:

```python
import operonx
from operonx.app import Service, asgi
from operonx_kb.admin import kb_admin_app

operonx.bootstrap()  # operonx serve loads no resources for an asgi service
Service("kb_admin", asgi("/kb", port=8021), app=kb_admin_app(llm="assistant"))
```

Outstanding work (K5 visual retrieval needs a GPU): [BACKLOG.md](BACKLOG.md).
Design and phase gates: [PLAN.md](PLAN.md); measured gates: [docs/bench](docs/bench).
Contributor and agent rules: [AGENTS.md](AGENTS.md).
