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 +21 -0
- verdant-0.1.0/PKG-INFO +73 -0
- verdant-0.1.0/README.md +36 -0
- verdant-0.1.0/pyproject.toml +68 -0
- verdant-0.1.0/setup.cfg +4 -0
- verdant-0.1.0/verdant/__init__.py +35 -0
- verdant-0.1.0/verdant/client.py +114 -0
- verdant-0.1.0/verdant/config.py +59 -0
- verdant-0.1.0/verdant/models.py +152 -0
- verdant-0.1.0/verdant/pipeline.py +274 -0
- verdant-0.1.0/verdant/services/__init__.py +1 -0
- verdant-0.1.0/verdant/services/cache_service.py +134 -0
- verdant-0.1.0/verdant/services/claude_service.py +158 -0
- verdant-0.1.0/verdant/services/db_service.py +427 -0
- verdant-0.1.0/verdant/services/gemini_service.py +142 -0
- verdant-0.1.0/verdant/services/prompts/explain.txt +14 -0
- verdant-0.1.0/verdant/services/prompts/generate.txt +9 -0
- verdant-0.1.0/verdant/services/prompts/intent.txt +16 -0
- verdant-0.1.0/verdant/stages/__init__.py +13 -0
- verdant-0.1.0/verdant/stages/baseline.py +177 -0
- verdant-0.1.0/verdant/stages/bias.py +82 -0
- verdant-0.1.0/verdant/stages/explain.py +87 -0
- verdant-0.1.0/verdant/stages/intent.py +120 -0
- verdant-0.1.0/verdant/stages/trust.py +114 -0
- verdant-0.1.0/verdant.egg-info/PKG-INFO +73 -0
- verdant-0.1.0/verdant.egg-info/SOURCES.txt +46 -0
- verdant-0.1.0/verdant.egg-info/dependency_links.txt +1 -0
- verdant-0.1.0/verdant.egg-info/requires.txt +11 -0
- verdant-0.1.0/verdant.egg-info/top_level.txt +1 -0
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
|
verdant-0.1.0/README.md
ADDED
|
@@ -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"
|
verdant-0.1.0/setup.cfg
ADDED
|
@@ -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)
|