svitch 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ *.egg
10
+
11
+ # Node
12
+ node_modules/
13
+ sdk/node/dist/
14
+
15
+ # Environment
16
+ .env
17
+ .env.local
18
+ .env.*.local
19
+
20
+ # IDE
21
+ .vscode/
22
+ .idea/
23
+ *.swp
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Logs
30
+ *.log
svitch-0.1.5/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.5
2
+ Name: svitch
3
+ Version: 0.1.5
4
+ Summary: Privacy-first AI infrastructure — PII detection, redaction, agent audit trails, and compliance reports for teams building AI on sensitive data.
5
+ Project-URL: Homepage, https://svitch.ai
6
+ Project-URL: Documentation, https://svitch.ai/dpdp
7
+ Project-URL: Repository, https://github.com/koushiknarendra/svitch
8
+ Project-URL: Issues, https://github.com/koushiknarendra/svitch/issues
9
+ License: Apache-2.0
10
+ Keywords: aadhaar,agent,audit,compliance,dpdp,gdpr,hipaa,llm,pan,pii,privacy,security
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Security
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.10
18
+ Provides-Extra: all
19
+ Requires-Dist: anthropic>=0.25.0; extra == 'all'
20
+ Requires-Dist: langchain-core>=0.2.0; extra == 'all'
21
+ Requires-Dist: openai>=1.0.0; extra == 'all'
22
+ Requires-Dist: opentelemetry-api>=1.0.0; extra == 'all'
23
+ Provides-Extra: anthropic
24
+ Requires-Dist: anthropic>=0.25.0; extra == 'anthropic'
25
+ Provides-Extra: langchain
26
+ Requires-Dist: langchain-core>=0.2.0; extra == 'langchain'
27
+ Provides-Extra: openai
28
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
29
+ Provides-Extra: otel
30
+ Requires-Dist: opentelemetry-api>=1.0.0; extra == 'otel'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # svitch
34
+
35
+ DPDP-compliant AI data security for Indian enterprises.
36
+ Zero dependencies. Runs locally. Works with any LLM provider.
37
+
38
+ ```bash
39
+ pip install svitch
40
+ ```
41
+
42
+ ---
43
+
44
+ ## PII Detection and Redaction
45
+
46
+ Detects and redacts Indian PII (Aadhaar, PAN, UPI, IFSC, mobile, GST, bank accounts) and global PII (email, IP) — entirely locally, no network calls.
47
+
48
+ ```python
49
+ import svitch
50
+
51
+ # Detect
52
+ entities = svitch.detect("Customer Aadhaar: 2345 6789 0123, PAN: ABCDE1234F")
53
+ # [Entity(type='AADHAAR', value='2345 6789 0123', ...), Entity(type='PAN', ...)]
54
+
55
+ # Redact (token replacement)
56
+ result = svitch.redact("Call me on 9876543210, UPI: rahul@okicici")
57
+ result.text # "Call me on [MOBILE_IN], UPI: [UPI_ID]"
58
+ result.count # 2
59
+ result.clean # False
60
+
61
+ # Redact (partial mask)
62
+ result = svitch.redact("Aadhaar: 2345 6789 0123", replacement="mask")
63
+ result.text # "Aadhaar: XXXX XXXX 0123"
64
+ ```
65
+
66
+ ### Wrap any LLM client
67
+
68
+ PII is redacted from every prompt before it leaves your network, and from every response before it's stored or displayed.
69
+
70
+ ```python
71
+ import svitch, openai
72
+
73
+ client = svitch.wrap(openai.OpenAI())
74
+ # Use exactly like openai.OpenAI() — PII is handled automatically
75
+
76
+ response = client.chat.completions.create(
77
+ model="gpt-4o",
78
+ messages=[{
79
+ "role": "user",
80
+ "content": "Assess loan for Aadhaar 9876 5432 1098, PAN ABCDE1234F"
81
+ # ↑ redacted to [AADHAAR_IN], [PAN_IN] before reaching OpenAI
82
+ }]
83
+ )
84
+ ```
85
+
86
+ Anthropic:
87
+ ```python
88
+ import svitch, anthropic
89
+ client = svitch.wrap(anthropic.Anthropic())
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Agent Audit Trail
95
+
96
+ Records every agent decision in an immutable, hash-chained audit log.
97
+ Required by DPDP §8 and the RBI FREE AI Framework.
98
+
99
+ ```python
100
+ from svitch_tracer import SvitchTracer
101
+
102
+ tracer = SvitchTracer(agent_id="loan-processor-v2")
103
+
104
+ with tracer.run() as run:
105
+ run.data_access(
106
+ source="crm",
107
+ fields_accessed=["name", "income", "aadhaar"],
108
+ purpose="loan_processing",
109
+ data_principal_id="CUST-5821",
110
+ )
111
+
112
+ run.llm_call(
113
+ provider="openai",
114
+ model="gpt-4o",
115
+ prompt="Assess eligibility for [AADHAAR_IN] applicant", # already redacted
116
+ response="Eligible. Score: 72/100.",
117
+ redact_pii=True,
118
+ )
119
+
120
+ run.decision(
121
+ reason="Score above threshold (70)",
122
+ outcome="approve",
123
+ confidence=0.87,
124
+ )
125
+
126
+ # Human-in-the-loop checkpoint — required for high-risk decisions
127
+ run.human_checkpoint(
128
+ question="Approve ₹5L loan for this applicant?",
129
+ approved=True,
130
+ reviewer_id="anand.k",
131
+ )
132
+
133
+ # Verify the hash chain is intact
134
+ valid, err = run.verify()
135
+ assert valid, f"Audit chain broken: {err}"
136
+ ```
137
+
138
+ ### Configuration
139
+
140
+ ```bash
141
+ export SVITCH_TRACER_URL=https://agent-tracer.vercel.app # default (hosted)
142
+ # or point to your self-hosted Agent Tracer
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Supported PII types
148
+
149
+ | Type | Pattern |
150
+ |------|---------|
151
+ | `AADHAAR` | 12-digit, masked and unmasked |
152
+ | `PAN` | `ABCDE1234F` format |
153
+ | `UPI_ID` | `handle@provider` |
154
+ | `IFSC` | 11-character bank code |
155
+ | `MOBILE_IN` | 6xxx–9xxx Indian mobile |
156
+ | `BANK_ACCOUNT` | 9–18 digit account numbers |
157
+ | `GST` | `22AAAAA0000A1Z5` |
158
+ | `EMAIL` | RFC 5322 |
159
+ | `IPV4` | IPv4 addresses |
160
+
161
+ ---
162
+
163
+ ## License
164
+
165
+ Apache 2.0 — [svitch.ai](https://svitch.ai) · [DPDP Guide](https://svitch.ai/dpdp) · [GitHub](https://github.com/koushiknarendra/svitch)
svitch-0.1.5/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # svitch
2
+
3
+ DPDP-compliant AI data security for Indian enterprises.
4
+ Zero dependencies. Runs locally. Works with any LLM provider.
5
+
6
+ ```bash
7
+ pip install svitch
8
+ ```
9
+
10
+ ---
11
+
12
+ ## PII Detection and Redaction
13
+
14
+ Detects and redacts Indian PII (Aadhaar, PAN, UPI, IFSC, mobile, GST, bank accounts) and global PII (email, IP) — entirely locally, no network calls.
15
+
16
+ ```python
17
+ import svitch
18
+
19
+ # Detect
20
+ entities = svitch.detect("Customer Aadhaar: 2345 6789 0123, PAN: ABCDE1234F")
21
+ # [Entity(type='AADHAAR', value='2345 6789 0123', ...), Entity(type='PAN', ...)]
22
+
23
+ # Redact (token replacement)
24
+ result = svitch.redact("Call me on 9876543210, UPI: rahul@okicici")
25
+ result.text # "Call me on [MOBILE_IN], UPI: [UPI_ID]"
26
+ result.count # 2
27
+ result.clean # False
28
+
29
+ # Redact (partial mask)
30
+ result = svitch.redact("Aadhaar: 2345 6789 0123", replacement="mask")
31
+ result.text # "Aadhaar: XXXX XXXX 0123"
32
+ ```
33
+
34
+ ### Wrap any LLM client
35
+
36
+ PII is redacted from every prompt before it leaves your network, and from every response before it's stored or displayed.
37
+
38
+ ```python
39
+ import svitch, openai
40
+
41
+ client = svitch.wrap(openai.OpenAI())
42
+ # Use exactly like openai.OpenAI() — PII is handled automatically
43
+
44
+ response = client.chat.completions.create(
45
+ model="gpt-4o",
46
+ messages=[{
47
+ "role": "user",
48
+ "content": "Assess loan for Aadhaar 9876 5432 1098, PAN ABCDE1234F"
49
+ # ↑ redacted to [AADHAAR_IN], [PAN_IN] before reaching OpenAI
50
+ }]
51
+ )
52
+ ```
53
+
54
+ Anthropic:
55
+ ```python
56
+ import svitch, anthropic
57
+ client = svitch.wrap(anthropic.Anthropic())
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Agent Audit Trail
63
+
64
+ Records every agent decision in an immutable, hash-chained audit log.
65
+ Required by DPDP §8 and the RBI FREE AI Framework.
66
+
67
+ ```python
68
+ from svitch_tracer import SvitchTracer
69
+
70
+ tracer = SvitchTracer(agent_id="loan-processor-v2")
71
+
72
+ with tracer.run() as run:
73
+ run.data_access(
74
+ source="crm",
75
+ fields_accessed=["name", "income", "aadhaar"],
76
+ purpose="loan_processing",
77
+ data_principal_id="CUST-5821",
78
+ )
79
+
80
+ run.llm_call(
81
+ provider="openai",
82
+ model="gpt-4o",
83
+ prompt="Assess eligibility for [AADHAAR_IN] applicant", # already redacted
84
+ response="Eligible. Score: 72/100.",
85
+ redact_pii=True,
86
+ )
87
+
88
+ run.decision(
89
+ reason="Score above threshold (70)",
90
+ outcome="approve",
91
+ confidence=0.87,
92
+ )
93
+
94
+ # Human-in-the-loop checkpoint — required for high-risk decisions
95
+ run.human_checkpoint(
96
+ question="Approve ₹5L loan for this applicant?",
97
+ approved=True,
98
+ reviewer_id="anand.k",
99
+ )
100
+
101
+ # Verify the hash chain is intact
102
+ valid, err = run.verify()
103
+ assert valid, f"Audit chain broken: {err}"
104
+ ```
105
+
106
+ ### Configuration
107
+
108
+ ```bash
109
+ export SVITCH_TRACER_URL=https://agent-tracer.vercel.app # default (hosted)
110
+ # or point to your self-hosted Agent Tracer
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Supported PII types
116
+
117
+ | Type | Pattern |
118
+ |------|---------|
119
+ | `AADHAAR` | 12-digit, masked and unmasked |
120
+ | `PAN` | `ABCDE1234F` format |
121
+ | `UPI_ID` | `handle@provider` |
122
+ | `IFSC` | 11-character bank code |
123
+ | `MOBILE_IN` | 6xxx–9xxx Indian mobile |
124
+ | `BANK_ACCOUNT` | 9–18 digit account numbers |
125
+ | `GST` | `22AAAAA0000A1Z5` |
126
+ | `EMAIL` | RFC 5322 |
127
+ | `IPV4` | IPv4 addresses |
128
+
129
+ ---
130
+
131
+ ## License
132
+
133
+ Apache 2.0 — [svitch.ai](https://svitch.ai) · [DPDP Guide](https://svitch.ai/dpdp) · [GitHub](https://github.com/koushiknarendra/svitch)
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "svitch"
7
+ version = "0.1.5"
8
+ description = "Privacy-first AI infrastructure — PII detection, redaction, agent audit trails, and compliance reports for teams building AI on sensitive data."
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.10"
12
+ keywords = ["pii", "privacy", "llm", "security", "compliance", "gdpr", "dpdp", "hipaa", "aadhaar", "pan", "agent", "audit"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: Apache Software License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Security",
19
+ "Topic :: Software Development :: Libraries",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ openai = ["openai>=1.0.0"]
25
+ anthropic = ["anthropic>=0.25.0"]
26
+ langchain = ["langchain-core>=0.2.0"]
27
+ otel = ["opentelemetry-api>=1.0.0"]
28
+ all = ["openai>=1.0.0", "anthropic>=0.25.0", "langchain-core>=0.2.0", "opentelemetry-api>=1.0.0"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://svitch.ai"
32
+ Documentation = "https://svitch.ai/dpdp"
33
+ Repository = "https://github.com/koushiknarendra/svitch"
34
+ Issues = "https://github.com/koushiknarendra/svitch/issues"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["svitch", "svitch_tracer"]
@@ -0,0 +1,14 @@
1
+ from .shield import detect, redact, SvitchResult, Entity
2
+ from .wrap import wrap
3
+ from .router import Router, RouteResult
4
+ from .cache import stabilize, StabilizeResult
5
+
6
+ __all__ = ["detect", "redact", "wrap", "Router", "RouteResult", "SvitchResult", "Entity", "stabilize", "StabilizeResult"]
7
+ __version__ = "0.1.5"
8
+
9
+
10
+ def __getattr__(name: str):
11
+ if name == "SvitchCallbackHandler":
12
+ from .langchain import SvitchCallbackHandler
13
+ return SvitchCallbackHandler
14
+ raise AttributeError(f"module 'svitch' has no attribute {name!r}")
@@ -0,0 +1,130 @@
1
+ """
2
+ Prefix Stabilizer — normalize system prompts for LLM provider KV cache hits.
3
+
4
+ Unstable system prompts (timestamps, session IDs, request tokens) cause a
5
+ cache miss on every single call, paying full input token cost each time.
6
+ Stabilizing the prefix lets the provider serve from its KV cache:
7
+
8
+ Anthropic — cache-read tokens cost ~10% of normal input token price.
9
+ OpenAI — prompts > 1024 tokens are auto-cached when the prefix is stable.
10
+
11
+ Latency impact: 30-50% lower TTFT on repeated agent calls with the same
12
+ system prompt structure.
13
+
14
+ This is also a compliance signal: timestamps and session IDs embedded in
15
+ system prompts are inadvertent per-request data leaks. `StabilizeResult.removed`
16
+ surfaces exactly what was replaced so the audit trail knows what changed.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import re
22
+ from dataclasses import dataclass, field
23
+
24
+ # (label, compiled pattern, stable placeholder)
25
+ _UNSTABLE: list[tuple[str, re.Pattern, str]] = [
26
+ (
27
+ "timestamp_iso",
28
+ re.compile(
29
+ r"\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?",
30
+ ),
31
+ "[TIMESTAMP]",
32
+ ),
33
+ (
34
+ "timestamp_date_phrase",
35
+ re.compile(
36
+ r"(today is|current date[:\s]+|as of[:\s]+)\s*[\w,]+ \d{1,2}[,\s]+\d{4}",
37
+ re.IGNORECASE,
38
+ ),
39
+ "[DATE_PHRASE]",
40
+ ),
41
+ (
42
+ "unix_timestamp",
43
+ re.compile(r"\b1[6-9]\d{8}\b"),
44
+ "[UNIX_TS]",
45
+ ),
46
+ (
47
+ "uuid",
48
+ re.compile(
49
+ r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b",
50
+ re.IGNORECASE,
51
+ ),
52
+ "[UUID]",
53
+ ),
54
+ (
55
+ "session_id",
56
+ re.compile(r"\bsession[_-]?id[:\s]+[\w\-]{8,}\b", re.IGNORECASE),
57
+ "[SESSION_ID]",
58
+ ),
59
+ (
60
+ "request_id",
61
+ re.compile(r"\brequest[_-]?id[:\s]+[\w\-]{8,}\b", re.IGNORECASE),
62
+ "[REQUEST_ID]",
63
+ ),
64
+ (
65
+ "hex_id",
66
+ re.compile(r"\b[0-9a-f]{24,}\b"),
67
+ "[HEX_ID]",
68
+ ),
69
+ ]
70
+
71
+
72
+ @dataclass
73
+ class StabilizeResult:
74
+ text: str # normalized system prompt — safe to send to the provider
75
+ original: str # unmodified original — preserved for audit
76
+ hash: str # SHA-256 prefix of `text` — use for cache-hit rate tracking
77
+ removed: list[str] # pattern labels replaced (e.g. ["timestamp_iso", "uuid"])
78
+ changed: bool # True when at least one substitution occurred
79
+
80
+
81
+ def stabilize(system: str) -> StabilizeResult:
82
+ """
83
+ Normalize a system prompt to maximize LLM provider KV cache hits.
84
+
85
+ Replaces per-request variables (timestamps, UUIDs, session/request IDs)
86
+ with stable placeholders. The original is preserved in `StabilizeResult.original`
87
+ so callers can log it to the audit trail without losing information.
88
+
89
+ Args:
90
+ system: Raw system prompt string.
91
+
92
+ Returns:
93
+ StabilizeResult with normalized text, SHA-256 prefix hash, and a list
94
+ of pattern labels that were replaced.
95
+ """
96
+ if not system:
97
+ return StabilizeResult(text=system, original=system, hash="", removed=[], changed=False)
98
+
99
+ text = system
100
+ removed: list[str] = []
101
+
102
+ for label, pattern, placeholder in _UNSTABLE:
103
+ new_text, n = pattern.subn(placeholder, text)
104
+ if n:
105
+ text = new_text
106
+ removed.append(label)
107
+
108
+ digest = hashlib.sha256(text.encode()).hexdigest()[:16]
109
+ return StabilizeResult(
110
+ text=text,
111
+ original=system,
112
+ hash=digest,
113
+ removed=removed,
114
+ changed=bool(removed),
115
+ )
116
+
117
+
118
+ def anthropic_cache_block(text: str) -> list[dict]:
119
+ """
120
+ Wrap stabilized text in an Anthropic prompt-caching block.
121
+
122
+ Pass the returned list as the `system` parameter to `client.messages.create()`.
123
+ Anthropic caches tokens at this breakpoint; subsequent requests with the
124
+ same prefix pay ~10% of normal input token cost (minimum 1024 tokens).
125
+
126
+ Example:
127
+ system_blocks = anthropic_cache_block(stabilize(raw_system).text)
128
+ client.messages.create(model=..., system=system_blocks, messages=...)
129
+ """
130
+ return [{"type": "text", "text": text, "cache_control": {"type": "ephemeral"}}]
@@ -0,0 +1,65 @@
1
+ """
2
+ Prompt complexity classifier — decides "fast", "default", or "complex".
3
+ Used by the Router to pick the right model tier automatically.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ _COMPLEX_KEYWORDS = frozenset([
8
+ "analyze", "analyse", "explain", "compare", "evaluate", "assess",
9
+ "research", "investigate", "comprehensive", "detailed", "thorough",
10
+ "implement", "architecture", "strategy", "design",
11
+ "write a", "create a", "build a", "develop", "refactor", "debug",
12
+ "step by step", "in depth", "elaborate", "translate",
13
+ "legal", "medical", "financial", "compliance", "audit",
14
+ "summarize", "generate code", "write code", "essay",
15
+ ])
16
+
17
+ _SIMPLE_PREFIXES = ("what is ", "who is ", "when did ", "where is ",
18
+ "define ", "list ", "name ")
19
+
20
+
21
+ def _token_estimate(text: str) -> int:
22
+ return max(1, int(len(text.split()) * 1.3))
23
+
24
+
25
+ def classify(messages: list[dict]) -> str:
26
+ """
27
+ Returns "fast", "default", or "complex".
28
+
29
+ fast — simple lookup, extraction, short Q&A → cheapest model
30
+ default — standard reasoning, explanation → standard model
31
+ complex — deep analysis, long-form, code generation → strongest model
32
+ """
33
+ user_turns = [m for m in messages if m.get("role") == "user"]
34
+ user_text = " ".join(m.get("content", "") for m in user_turns).lower().strip()
35
+ total_tokens = sum(_token_estimate(m.get("content", "")) for m in messages)
36
+
37
+ score = 0
38
+
39
+ # Token volume is the strongest signal
40
+ if total_tokens > 600:
41
+ score += 3
42
+ elif total_tokens > 200:
43
+ score += 1
44
+
45
+ # Multi-turn context
46
+ if len(user_turns) > 2:
47
+ score += 1
48
+
49
+ # Complex keyword hit
50
+ for kw in _COMPLEX_KEYWORDS:
51
+ if kw in user_text:
52
+ score += 2
53
+ break
54
+
55
+ # Simple prefix — negative signal
56
+ for prefix in _SIMPLE_PREFIXES:
57
+ if user_text.startswith(prefix):
58
+ score -= 1
59
+ break
60
+
61
+ if score <= 1:
62
+ return "fast"
63
+ if score <= 4:
64
+ return "default"
65
+ return "complex"