Metadata-Version: 2.4
Name: context-drift-detector-py
Version: 0.1.0
Summary: Detect topic drift between user intent, retrieved context, and AI answers. Python port of @mukundakatta/context-drift-detector.
Project-URL: Homepage, https://github.com/MukundaKatta/context-drift-detector-py
Project-URL: Issues, https://github.com/MukundaKatta/context-drift-detector-py/issues
Project-URL: Source, https://github.com/MukundaKatta/context-drift-detector-py
Project-URL: JS sibling, https://github.com/MukundaKatta/context-drift-detector
Author-email: Mukunda Rao Katta <mukunda.vjcs6@gmail.com>
License: MIT
License-File: LICENSE
Keywords: agents,ai,drift,evals,groundedness,guardrails,hallucination,llm,rag,retrieval
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# context-drift-detector-py

[![PyPI](https://img.shields.io/pypi/v/context-drift-detector-py.svg)](https://pypi.org/project/context-drift-detector-py/)
[![Python](https://img.shields.io/pypi/pyversions/context-drift-detector-py.svg)](https://pypi.org/project/context-drift-detector-py/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

**Detect topic drift between user intent, retrieved context, and AI answers.** A fast, lexical Jaccard-overlap heuristic for "did the model wander off?" -- useful as a cheap first-pass guardrail before reaching for an embedding-based check. Zero runtime dependencies.

Python port of [@mukundakatta/context-drift-detector](https://github.com/MukundaKatta/context-drift-detector).

## Install

```bash
pip install context-drift-detector-py
```

## Usage

```python
from context_drift_detector import detect

intent  = "What is the capital of France?"
context = ["Paris is the capital of France. It sits on the Seine."]
answer  = "Paris is the capital of France."

report = detect(intent, context, answer)
report.drift              # False
report.drift_score        # 0.0 - 1.0 (higher = more drift)
report.signals            # dict of jaccard overlaps
report.signals["answer_to_context"]  # 0.0 - 1.0
```

When drift is real:

```python
report = detect(
    intent="What is the capital of France?",
    context_chunks=["Paris is the capital of France."],
    answer="Cats love tuna and naps.",
)
report.drift           # True
report.drift_score     # high (e.g. > 0.65)
```

## API

```python
detect(
    intent: str,
    context_chunks: str | Sequence[str],
    answer: str,
    *,
    threshold: float = 0.65,
    min_term_len: int = 3,
) -> DriftReport
```

`DriftReport` fields:

| Field | Meaning |
|---|---|
| `drift` | `True` iff `drift_score > threshold`. |
| `drift_score` | `0.0`-`1.0`; weighted blend of answer-to-context (60%) and answer-to-intent (40%) overlap, inverted. |
| `signals.intent_to_context` | Jaccard overlap between intent and retrieved context. |
| `signals.answer_to_context` | Jaccard overlap between answer and retrieved context. |
| `signals.answer_to_intent` | Jaccard overlap between answer and intent. |
| `intent_terms` / `context_terms` / `answer_terms` | Frozensets of the terms used. |

`detect_context_drift(...)` is exported as a JS-aligned alias.

## How it works

Tokenizes each input into the lowercase set of `min_term_len`-char alphanumeric runs, then computes pairwise Jaccard overlaps. Empty inputs short-circuit to drift-free; this is intentional so a totally absent retrieval doesn't get flagged as drift on its own.

This is a **cheap heuristic** -- it doesn't catch paraphrases, synonyms, or semantically grounded contradictions. Use it as a fast first filter, then invest in an embedding/LLM-as-judge check for borderline cases.

See the JS sibling's [README](https://github.com/MukundaKatta/context-drift-detector) for the full design notes.
