verdant 0.1.0__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.
verdant-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mavericks (Raheem, Emafido, Ismail)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
verdant-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: verdant
3
+ Version: 0.1.0
4
+ Summary: VERDANT SDK for inspectable AI governance
5
+ Author: Raheem, Emafido, Ismail
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/lgndcraft2/Verdant
8
+ Project-URL: Repository, https://github.com/lgndcraft2/Verdant
9
+ Project-URL: Documentation, https://github.com/lgndcraft2/Verdant/tree/main/docs
10
+ Project-URL: Issues, https://github.com/lgndcraft2/Verdant/issues
11
+ Keywords: ai-governance,ai-safety,audit,bias-detection,ndpr,trust-score
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: anthropic<1,>=0.34
27
+ Requires-Dist: google-generativeai<1,>=0.7
28
+ Requires-Dist: httpx<1,>=0.27
29
+ Requires-Dist: pydantic<3,>=2.7
30
+ Requires-Dist: redis<6,>=5
31
+ Requires-Dist: supabase<3,>=2
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
35
+ Requires-Dist: pytest-mock>=3.14; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # VERDANT SDK
39
+
40
+ VERDANT is a lightweight SDK for making AI outputs inspectable. It wraps an AI call and returns the clean model output together with a structured audit payload, bias flags, a plain-language explanation, and a 0-100 trust score.
41
+
42
+ The SDK is built for Nigerian and African teams shipping AI into higher-stakes contexts such as hiring, lending, content moderation, and healthcare.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install verdant
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ```python
53
+ from verdant import VerdantClient
54
+
55
+ client = VerdantClient(api_key="vd_live_...")
56
+
57
+ result = await client.wrap(
58
+ fn=openai.chat.completions.create,
59
+ model="gpt-4o",
60
+ messages=[{"role": "user", "content": user_input}],
61
+ )
62
+
63
+ print(result.output)
64
+ print(result.trust_score)
65
+ print(result.flags)
66
+ print(result.explanation)
67
+ ```
68
+
69
+ ## Project
70
+
71
+ Source code, documentation, and issue tracking live at:
72
+
73
+ https://github.com/lgndcraft2/Verdant
@@ -0,0 +1,36 @@
1
+ # VERDANT SDK
2
+
3
+ VERDANT is a lightweight SDK for making AI outputs inspectable. It wraps an AI call and returns the clean model output together with a structured audit payload, bias flags, a plain-language explanation, and a 0-100 trust score.
4
+
5
+ The SDK is built for Nigerian and African teams shipping AI into higher-stakes contexts such as hiring, lending, content moderation, and healthcare.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install verdant
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from verdant import VerdantClient
17
+
18
+ client = VerdantClient(api_key="vd_live_...")
19
+
20
+ result = await client.wrap(
21
+ fn=openai.chat.completions.create,
22
+ model="gpt-4o",
23
+ messages=[{"role": "user", "content": user_input}],
24
+ )
25
+
26
+ print(result.output)
27
+ print(result.trust_score)
28
+ print(result.flags)
29
+ print(result.explanation)
30
+ ```
31
+
32
+ ## Project
33
+
34
+ Source code, documentation, and issue tracking live at:
35
+
36
+ https://github.com/lgndcraft2/Verdant
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "verdant"
7
+ version = "0.1.0"
8
+ description = "VERDANT SDK for inspectable AI governance"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Raheem" },
14
+ { name = "Emafido" },
15
+ { name = "Ismail" },
16
+ ]
17
+ keywords = [
18
+ "ai-governance",
19
+ "ai-safety",
20
+ "audit",
21
+ "bias-detection",
22
+ "ndpr",
23
+ "trust-score",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3 :: Only",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
35
+ "Topic :: Software Development :: Libraries :: Python Modules",
36
+ "Typing :: Typed",
37
+ ]
38
+ dependencies = [
39
+ "anthropic>=0.34,<1",
40
+ "google-generativeai>=0.7,<1",
41
+ "httpx>=0.27,<1",
42
+ "pydantic>=2.7,<3",
43
+ "redis>=5,<6",
44
+ "supabase>=2,<3",
45
+ ]
46
+
47
+ [tool.setuptools]
48
+ package-dir = {"" = "."}
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["."]
52
+ include = ["verdant*"]
53
+
54
+ [tool.setuptools.package-data]
55
+ "verdant.services" = ["prompts/*.txt"]
56
+
57
+ [project.optional-dependencies]
58
+ dev = [
59
+ "pytest>=8.0",
60
+ "pytest-asyncio>=0.23",
61
+ "pytest-mock>=3.14",
62
+ ]
63
+
64
+ [project.urls]
65
+ Homepage = "https://github.com/lgndcraft2/Verdant"
66
+ Repository = "https://github.com/lgndcraft2/Verdant"
67
+ Documentation = "https://github.com/lgndcraft2/Verdant/tree/main/docs"
68
+ Issues = "https://github.com/lgndcraft2/Verdant/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ from .config import Settings, get_settings
2
+ from .client import VerdantAPIError, VerdantClient
3
+ from .models import (
4
+ AuditPayload,
5
+ BaselineStageOutput,
6
+ BiasSeverity,
7
+ BiasStageOutput,
8
+ ContextType,
9
+ ExplainStageOutput,
10
+ IntentStageOutput,
11
+ PipelineRunRequest,
12
+ PipelineStageOutputs,
13
+ RiskLevel,
14
+ TrustStageOutput,
15
+ WrapResult,
16
+ )
17
+
18
+ __all__ = [
19
+ "AuditPayload",
20
+ "BaselineStageOutput",
21
+ "BiasSeverity",
22
+ "BiasStageOutput",
23
+ "ContextType",
24
+ "ExplainStageOutput",
25
+ "IntentStageOutput",
26
+ "PipelineRunRequest",
27
+ "PipelineStageOutputs",
28
+ "RiskLevel",
29
+ "Settings",
30
+ "TrustStageOutput",
31
+ "VerdantAPIError",
32
+ "VerdantClient",
33
+ "WrapResult",
34
+ "get_settings",
35
+ ]
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable
4
+
5
+ import httpx
6
+
7
+ from .config import Settings, get_settings
8
+ from .models import ContextType, WrapResult
9
+ from .pipeline import VerdantPipeline
10
+
11
+
12
+ class VerdantClient:
13
+ def __init__(
14
+ self,
15
+ api_key: str | None = None,
16
+ *,
17
+ base_url: str | None = None,
18
+ settings: Settings | None = None,
19
+ pipeline: VerdantPipeline | None = None,
20
+ ) -> None:
21
+ self.settings = settings or get_settings()
22
+ updates: dict[str, Any] = {}
23
+ if api_key:
24
+ updates["verdant_api_key"] = api_key
25
+ if base_url:
26
+ updates["verdant_api_url"] = base_url
27
+ if updates:
28
+ self.settings = self.settings.model_copy(update=updates)
29
+ self.pipeline = pipeline or VerdantPipeline(self.settings)
30
+
31
+ @property
32
+ def _is_hosted(self) -> bool:
33
+ """Use the hosted API when both a base URL and a VERDANT key are configured."""
34
+ return bool(self.settings.verdant_api_url and self.settings.verdant_api_key)
35
+
36
+ async def wrap(
37
+ self,
38
+ fn: Callable[..., Any],
39
+ *,
40
+ context_type: str | ContextType | None = None,
41
+ input_text: str | None = None,
42
+ metadata: dict[str, Any] | None = None,
43
+ **fn_kwargs: Any,
44
+ ) -> WrapResult:
45
+ """Wrap a local model call and run the reasoning pipeline in-process."""
46
+ return await self.pipeline.run(
47
+ fn=fn,
48
+ context_type=context_type,
49
+ input_text=input_text,
50
+ fn_kwargs=fn_kwargs,
51
+ metadata=metadata,
52
+ )
53
+
54
+ async def run(
55
+ self,
56
+ *,
57
+ context_type: str | ContextType,
58
+ input_text: str,
59
+ metadata: dict[str, Any] | None = None,
60
+ ) -> WrapResult:
61
+ """Run the reasoning pipeline for the given input.
62
+
63
+ In hosted mode (a ``verdant_api_url`` and ``verdant_api_key`` are set) this calls
64
+ the VERDANT API over HTTP, so provider (Claude/Gemini) keys stay server-side. Otherwise
65
+ it runs the pipeline in-process.
66
+ """
67
+ if self._is_hosted:
68
+ return await self._run_hosted(context_type=context_type, input_text=input_text, metadata=metadata)
69
+ return await self.pipeline.run(
70
+ context_type=context_type,
71
+ input_text=input_text,
72
+ metadata=metadata,
73
+ )
74
+
75
+ async def _run_hosted(
76
+ self,
77
+ *,
78
+ context_type: str | ContextType,
79
+ input_text: str,
80
+ metadata: dict[str, Any] | None,
81
+ ) -> WrapResult:
82
+ url = self.settings.verdant_api_url.rstrip("/") + "/pipeline/run"
83
+ context_value = context_type.value if isinstance(context_type, ContextType) else str(context_type)
84
+ payload = {
85
+ "context_type": context_value,
86
+ "input_text": input_text,
87
+ "metadata": metadata or {},
88
+ }
89
+ headers = {"Authorization": f"Bearer {self.settings.verdant_api_key}"}
90
+
91
+ async with httpx.AsyncClient(timeout=self.settings.request_timeout_seconds) as client:
92
+ response = await client.post(url, json=payload, headers=headers)
93
+
94
+ try:
95
+ body = response.json()
96
+ except ValueError:
97
+ body = None
98
+
99
+ if response.status_code >= 400:
100
+ message = None
101
+ if isinstance(body, dict) and body.get("error"):
102
+ message = body["error"].get("message")
103
+ raise VerdantAPIError(message or f"VERDANT API returned {response.status_code}", status_code=response.status_code)
104
+
105
+ if not isinstance(body, dict) or body.get("data") is None:
106
+ raise VerdantAPIError("VERDANT API returned an unexpected response.", status_code=response.status_code)
107
+
108
+ return WrapResult.model_validate(body["data"])
109
+
110
+
111
+ class VerdantAPIError(RuntimeError):
112
+ def __init__(self, message: str, *, status_code: int | None = None) -> None:
113
+ super().__init__(message)
114
+ self.status_code = status_code
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from functools import lru_cache
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ def _split_csv(value: str | None, default: list[str]) -> list[str]:
10
+ if not value:
11
+ return default
12
+ return [item.strip() for item in value.split(",") if item.strip()]
13
+
14
+
15
+ class Settings(BaseModel):
16
+ model_config = ConfigDict(extra="ignore")
17
+
18
+ verdant_api_key: str = ""
19
+ verdant_api_url: str = ""
20
+ anthropic_api_key: str = ""
21
+ gemini_api_key: str = ""
22
+ supabase_url: str = ""
23
+ supabase_service_role_key: str = ""
24
+ redis_url: str = "redis://localhost:6379/0"
25
+ webhook_secret: str = ""
26
+ environment: str = "development"
27
+ log_level: str = "info"
28
+ trust_score_alert_threshold: int = Field(default=40, ge=0, le=100)
29
+ cors_origins: list[str] = Field(default_factory=lambda: ["*"])
30
+ claude_model: str = "claude-sonnet-4-6"
31
+ gemini_model: str = "gemini-2.5-flash"
32
+ request_timeout_seconds: float = 30.0
33
+ webhook_timeout_seconds: float = 10.0
34
+
35
+ @classmethod
36
+ def from_env(cls) -> "Settings":
37
+ return cls(
38
+ verdant_api_key=os.getenv("VERDANT_API_KEY", ""),
39
+ verdant_api_url=os.getenv("VERDANT_API_URL", ""),
40
+ anthropic_api_key=os.getenv("ANTHROPIC_API_KEY", ""),
41
+ gemini_api_key=os.getenv("GEMINI_API_KEY", ""),
42
+ supabase_url=os.getenv("SUPABASE_URL", ""),
43
+ supabase_service_role_key=os.getenv("SUPABASE_SERVICE_ROLE_KEY", ""),
44
+ redis_url=os.getenv("REDIS_URL", "redis://localhost:6379/0"),
45
+ webhook_secret=os.getenv("WEBHOOK_SECRET", ""),
46
+ environment=os.getenv("ENVIRONMENT", "development"),
47
+ log_level=os.getenv("LOG_LEVEL", "info"),
48
+ trust_score_alert_threshold=int(os.getenv("TRUST_SCORE_ALERT_THRESHOLD", "40")),
49
+ cors_origins=_split_csv(os.getenv("CORS_ORIGINS"), ["*"]),
50
+ claude_model=os.getenv("CLAUDE_MODEL", "claude-sonnet-4-6"),
51
+ gemini_model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"),
52
+ request_timeout_seconds=float(os.getenv("REQUEST_TIMEOUT_SECONDS", "30")),
53
+ webhook_timeout_seconds=float(os.getenv("WEBHOOK_TIMEOUT_SECONDS", "10")),
54
+ )
55
+
56
+
57
+ @lru_cache(maxsize=1)
58
+ def get_settings() -> Settings:
59
+ return Settings.from_env()
@@ -0,0 +1,152 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from enum import Enum
5
+ from typing import Any
6
+ from uuid import UUID, uuid4
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
9
+
10
+
11
+ class VerdantBaseModel(BaseModel):
12
+ model_config = ConfigDict(extra="forbid", populate_by_name=True, protected_namespaces=())
13
+
14
+
15
+ class ContextType(str, Enum):
16
+ hiring = "hiring"
17
+ lending = "lending"
18
+ content = "content"
19
+ healthcare = "healthcare"
20
+
21
+ @classmethod
22
+ def normalize(cls, value: str | ContextType | None) -> ContextType:
23
+ if isinstance(value, cls):
24
+ return value
25
+ if value is None:
26
+ return cls.content
27
+
28
+ normalized = str(value).strip().lower().replace(" ", "_").replace("-", "_")
29
+ aliases = {
30
+ "content_moderation": cls.content,
31
+ "content_moderation_review": cls.content,
32
+ "moderation": cls.content,
33
+ }
34
+ if normalized in aliases:
35
+ return aliases[normalized]
36
+
37
+ for member in cls:
38
+ if member.value == normalized:
39
+ return member
40
+ raise ValueError(f"Unsupported context type: {value}")
41
+
42
+
43
+ class BiasSeverity(str, Enum):
44
+ low = "low"
45
+ medium = "medium"
46
+ high = "high"
47
+ critical = "critical"
48
+
49
+
50
+ class RiskLevel(str, Enum):
51
+ low = "low"
52
+ medium = "medium"
53
+ high = "high"
54
+ critical = "critical"
55
+
56
+
57
+ class IntentStageOutput(VerdantBaseModel):
58
+ detected_intent: str
59
+ context_type: ContextType
60
+ user_intent_summary: str
61
+ entities: list[str] = Field(default_factory=list)
62
+ signals: list[str] = Field(default_factory=list)
63
+ confidence: float = Field(ge=0.0, le=1.0)
64
+ needs_review: bool = False
65
+
66
+
67
+ class BaselineStageOutput(VerdantBaseModel):
68
+ context_type: ContextType
69
+ baseline_name: str
70
+ baseline_version: str
71
+ demographic_focus: list[str] = Field(default_factory=list)
72
+ baseline_summary: str
73
+ policy_notes: list[str] = Field(default_factory=list)
74
+ baseline_data: dict[str, Any] = Field(default_factory=dict)
75
+ source: str = "supabase"
76
+ confidence: float = Field(ge=0.0, le=1.0)
77
+
78
+
79
+ class BiasStageOutput(VerdantBaseModel):
80
+ flags: list[str] = Field(default_factory=list)
81
+ matched_patterns: list[str] = Field(default_factory=list)
82
+ severity: BiasSeverity = BiasSeverity.low
83
+ bias_score: int = Field(ge=0, le=100)
84
+ summary: str
85
+ confidence: float = Field(ge=0.0, le=1.0)
86
+
87
+ @field_validator("bias_score", mode="before")
88
+ @classmethod
89
+ def _clamp_bias_score(cls, value: Any) -> int:
90
+ return max(0, min(100, int(value)))
91
+
92
+
93
+ class ExplainStageOutput(VerdantBaseModel):
94
+ plain_language_explanation: str
95
+ reasoning_summary: list[str] = Field(default_factory=list)
96
+ caveats: list[str] = Field(default_factory=list)
97
+ confidence: float = Field(ge=0.0, le=1.0)
98
+
99
+
100
+ class TrustStageOutput(VerdantBaseModel):
101
+ trust_score: int = Field(ge=0, le=100)
102
+ risk_level: RiskLevel
103
+ score_breakdown: dict[str, float] = Field(default_factory=dict)
104
+ reasons: list[str] = Field(default_factory=list)
105
+ alerts: list[str] = Field(default_factory=list)
106
+ confidence: float = Field(ge=0.0, le=1.0)
107
+
108
+ @field_validator("trust_score", mode="before")
109
+ @classmethod
110
+ def _clamp_trust_score(cls, value: Any) -> int:
111
+ return max(0, min(100, int(value)))
112
+
113
+
114
+ class PipelineStageOutputs(VerdantBaseModel):
115
+ intent: IntentStageOutput
116
+ baseline: BaselineStageOutput
117
+ bias: BiasStageOutput
118
+ explanation: ExplainStageOutput
119
+ trust: TrustStageOutput
120
+
121
+
122
+ class AuditPayload(VerdantBaseModel):
123
+ audit_id: UUID | None = None
124
+ request_id: UUID = Field(default_factory=uuid4)
125
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
126
+ context_type: ContextType
127
+ input_text: str
128
+ output_text: str
129
+ raw_output: Any = None
130
+ clean_output: Any = None
131
+ stages: PipelineStageOutputs
132
+ trust_score: int = Field(ge=0, le=100)
133
+ flags: list[str] = Field(default_factory=list)
134
+ explanation: str
135
+ metadata: dict[str, Any] = Field(default_factory=dict)
136
+ model_name: str = "claude-sonnet-4-6"
137
+ duration_ms: int = 0
138
+ error: str | None = None
139
+
140
+
141
+ class WrapResult(VerdantBaseModel):
142
+ output: Any
143
+ audit: AuditPayload
144
+ trust_score: int = Field(ge=0, le=100)
145
+ flags: list[str] = Field(default_factory=list)
146
+ explanation: str
147
+
148
+
149
+ class PipelineRunRequest(VerdantBaseModel):
150
+ input_text: str
151
+ context_type: ContextType
152
+ metadata: dict[str, Any] = Field(default_factory=dict)