gateforge-sdk 0.1.0__py3-none-any.whl
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.
- gateforge/__init__.py +58 -0
- gateforge/client.py +119 -0
- gateforge/config.py +38 -0
- gateforge/metrics.py +25 -0
- gateforge/pii.py +46 -0
- gateforge/providers/__init__.py +0 -0
- gateforge/providers/anthropic.py +59 -0
- gateforge/providers/gemini.py +70 -0
- gateforge/providers/openai.py +50 -0
- gateforge/response.py +14 -0
- gateforge_sdk-0.1.0.dist-info/METADATA +365 -0
- gateforge_sdk-0.1.0.dist-info/RECORD +14 -0
- gateforge_sdk-0.1.0.dist-info/WHEEL +4 -0
- gateforge_sdk-0.1.0.dist-info/licenses/LICENSE +37 -0
gateforge/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from gateforge.client import GatforgeClient
|
|
4
|
+
from gateforge.response import GatforgeResponse
|
|
5
|
+
|
|
6
|
+
_client: GatforgeClient | None = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def init(
|
|
10
|
+
api_key: str,
|
|
11
|
+
openai_key: str | None = None,
|
|
12
|
+
anthropic_key: str | None = None,
|
|
13
|
+
gemini_key: str | None = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialize Gateforge. Downloads user config from api.gateforge.dev.
|
|
17
|
+
Call once at application startup.
|
|
18
|
+
"""
|
|
19
|
+
global _client
|
|
20
|
+
_client = GatforgeClient(
|
|
21
|
+
api_key=api_key,
|
|
22
|
+
openai_key=openai_key,
|
|
23
|
+
anthropic_key=anthropic_key,
|
|
24
|
+
gemini_key=gemini_key,
|
|
25
|
+
)
|
|
26
|
+
_client.load_config()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def chat(
|
|
30
|
+
model: str,
|
|
31
|
+
messages: list[dict],
|
|
32
|
+
provider_key: str | None = None,
|
|
33
|
+
pii_domain: str | None = None,
|
|
34
|
+
prompt_id: str | None = None,
|
|
35
|
+
**kwargs,
|
|
36
|
+
) -> GatforgeResponse:
|
|
37
|
+
"""LLM call with automatic PII masking. All processing is local."""
|
|
38
|
+
if _client is None:
|
|
39
|
+
raise RuntimeError("Call gateforge.init() first")
|
|
40
|
+
return _client.chat(
|
|
41
|
+
model=model,
|
|
42
|
+
messages=messages,
|
|
43
|
+
provider_key=provider_key,
|
|
44
|
+
pii_domain=pii_domain,
|
|
45
|
+
prompt_id=prompt_id,
|
|
46
|
+
**kwargs,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def chat_stream(
|
|
51
|
+
model: str,
|
|
52
|
+
messages: list[dict],
|
|
53
|
+
**kwargs,
|
|
54
|
+
):
|
|
55
|
+
"""Streaming LLM call with automatic PII masking."""
|
|
56
|
+
if _client is None:
|
|
57
|
+
raise RuntimeError("Call gateforge.init() first")
|
|
58
|
+
return _client.chat_stream(model=model, messages=messages, **kwargs)
|
gateforge/client.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from gateforge.config import fetch_config, GatforgeConfig
|
|
6
|
+
from gateforge.pii import anonymize_messages, rehydrate_text
|
|
7
|
+
from gateforge.metrics import send_metrics_async
|
|
8
|
+
from gateforge.response import GatforgeResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _model_to_provider(model: str) -> str:
|
|
12
|
+
if model.startswith(("gpt-", "o1", "o3")):
|
|
13
|
+
return "openai"
|
|
14
|
+
if model.startswith("claude"):
|
|
15
|
+
return "anthropic"
|
|
16
|
+
if model.startswith("gemini"):
|
|
17
|
+
return "gemini"
|
|
18
|
+
raise ValueError(
|
|
19
|
+
f"Cannot infer provider from model name '{model}'. "
|
|
20
|
+
"Pass provider_key explicitly or use a model name starting with gpt-, claude, or gemini."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GatforgeClient:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: str,
|
|
28
|
+
openai_key: str | None = None,
|
|
29
|
+
anthropic_key: str | None = None,
|
|
30
|
+
gemini_key: str | None = None,
|
|
31
|
+
):
|
|
32
|
+
self.api_key = api_key
|
|
33
|
+
self.provider_keys = {
|
|
34
|
+
"openai": openai_key,
|
|
35
|
+
"anthropic": anthropic_key,
|
|
36
|
+
"gemini": gemini_key,
|
|
37
|
+
}
|
|
38
|
+
self.config: GatforgeConfig = GatforgeConfig()
|
|
39
|
+
|
|
40
|
+
def load_config(self) -> None:
|
|
41
|
+
self.config = fetch_config(self.api_key)
|
|
42
|
+
|
|
43
|
+
def chat(
|
|
44
|
+
self,
|
|
45
|
+
model: str,
|
|
46
|
+
messages: list[dict],
|
|
47
|
+
provider_key: str | None = None,
|
|
48
|
+
pii_domain: str | None = None,
|
|
49
|
+
prompt_id: str | None = None,
|
|
50
|
+
**kwargs,
|
|
51
|
+
) -> GatforgeResponse:
|
|
52
|
+
provider = _model_to_provider(model)
|
|
53
|
+
key = provider_key or self.provider_keys.get(provider)
|
|
54
|
+
if not key:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"No {provider} key provided. Pass it to init() or as provider_key in chat()."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
domain = pii_domain or self.config.pii_domain
|
|
60
|
+
backend = self.config.pii_backend
|
|
61
|
+
|
|
62
|
+
context = {
|
|
63
|
+
"tenant_id": self.api_key,
|
|
64
|
+
"case_id": prompt_id or "default",
|
|
65
|
+
"thread_id": str(uuid.uuid4()),
|
|
66
|
+
"actor_id": "sdk",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# 1. PII masking — local, never leaves the client
|
|
70
|
+
anon_messages, pii_entities = anonymize_messages(messages, domain, backend, context)
|
|
71
|
+
|
|
72
|
+
# 2. Direct LLM call
|
|
73
|
+
if provider == "openai":
|
|
74
|
+
from gateforge.providers.openai import call
|
|
75
|
+
elif provider == "anthropic":
|
|
76
|
+
from gateforge.providers.anthropic import call
|
|
77
|
+
else:
|
|
78
|
+
from gateforge.providers.gemini import call
|
|
79
|
+
|
|
80
|
+
content, meta = call(key, model, anon_messages, **kwargs)
|
|
81
|
+
|
|
82
|
+
# 3. Rehydration — local
|
|
83
|
+
final_content = rehydrate_text(content, domain, backend, context)
|
|
84
|
+
|
|
85
|
+
# 4. Async metrics — metadata only, never content
|
|
86
|
+
send_metrics_async(self.api_key, {
|
|
87
|
+
"model": meta["model"],
|
|
88
|
+
"provider": meta["provider"],
|
|
89
|
+
"prompt_tokens": meta["prompt_tokens"],
|
|
90
|
+
"completion_tokens": meta["completion_tokens"],
|
|
91
|
+
"cost_usd": meta["cost_usd"],
|
|
92
|
+
"latency_ms": meta["latency_ms"],
|
|
93
|
+
"pii_entities": pii_entities,
|
|
94
|
+
"pii_count": len(pii_entities),
|
|
95
|
+
"domain": domain,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return GatforgeResponse(
|
|
99
|
+
content=final_content,
|
|
100
|
+
model=meta["model"],
|
|
101
|
+
tokens=meta["total_tokens"],
|
|
102
|
+
prompt_tokens=meta["prompt_tokens"],
|
|
103
|
+
completion_tokens=meta["completion_tokens"],
|
|
104
|
+
cost_usd=meta["cost_usd"],
|
|
105
|
+
latency_ms=meta["latency_ms"],
|
|
106
|
+
pii_detected=pii_entities,
|
|
107
|
+
raw=meta.get("raw", {}),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def chat_stream(
|
|
111
|
+
self,
|
|
112
|
+
model: str,
|
|
113
|
+
messages: list[dict],
|
|
114
|
+
pii_domain: str | None = None,
|
|
115
|
+
prompt_id: str | None = None,
|
|
116
|
+
provider_key: str | None = None,
|
|
117
|
+
**kwargs,
|
|
118
|
+
):
|
|
119
|
+
raise NotImplementedError("Streaming is not yet implemented in the SDK.")
|
gateforge/config.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
GATEFORGE_API = "https://api.gateforge.dev"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class GatforgeConfig:
|
|
9
|
+
pii_domain: str = "generic"
|
|
10
|
+
pii_backend: str = "presidio"
|
|
11
|
+
pii_languages: list[str] = field(default_factory=lambda: ["en"])
|
|
12
|
+
custom_rules: list[dict] = field(default_factory=list)
|
|
13
|
+
default_model: str = "gpt-4o"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fetch_config(api_key: str) -> GatforgeConfig:
|
|
17
|
+
"""
|
|
18
|
+
Downloads user config from api.gateforge.dev.
|
|
19
|
+
Falls back to defaults silently if unreachable.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
resp = httpx.get(
|
|
23
|
+
f"{GATEFORGE_API}/sdk/config",
|
|
24
|
+
headers={"X-API-Key": api_key},
|
|
25
|
+
timeout=5.0,
|
|
26
|
+
)
|
|
27
|
+
if resp.status_code == 200:
|
|
28
|
+
data = resp.json()
|
|
29
|
+
return GatforgeConfig(
|
|
30
|
+
pii_domain=data.get("pii_domain", "generic"),
|
|
31
|
+
pii_backend=data.get("pii_backend", "presidio"),
|
|
32
|
+
pii_languages=data.get("pii_languages", ["en"]),
|
|
33
|
+
custom_rules=data.get("custom_rules", []),
|
|
34
|
+
default_model=data.get("default_model", "gpt-4o"),
|
|
35
|
+
)
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
return GatforgeConfig()
|
gateforge/metrics.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
GATEFORGE_API = "https://api.gateforge.dev"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def send_metrics_async(api_key: str, metrics: dict) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Sends metadata to api.gateforge.dev in a daemon thread.
|
|
10
|
+
Never sends content — only numeric metadata and entity category lists.
|
|
11
|
+
Never blocks or raises.
|
|
12
|
+
"""
|
|
13
|
+
def _send():
|
|
14
|
+
try:
|
|
15
|
+
import httpx
|
|
16
|
+
httpx.post(
|
|
17
|
+
f"{GATEFORGE_API}/sdk/metrics",
|
|
18
|
+
json={**metrics, "ts": datetime.now(timezone.utc).isoformat()},
|
|
19
|
+
headers={"X-API-Key": api_key},
|
|
20
|
+
timeout=3.0,
|
|
21
|
+
)
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
threading.Thread(target=_send, daemon=True).start()
|
gateforge/pii.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@lru_cache(maxsize=8)
|
|
5
|
+
def _get_firewall(domain: str, backend: str):
|
|
6
|
+
from privacy_firewall import create_firewall
|
|
7
|
+
return create_firewall(domain, detector_backend=backend)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def anonymize_messages(
|
|
11
|
+
messages: list[dict],
|
|
12
|
+
domain: str,
|
|
13
|
+
backend: str,
|
|
14
|
+
context: dict,
|
|
15
|
+
) -> tuple[list[dict], list[str]]:
|
|
16
|
+
"""
|
|
17
|
+
Anonymizes content of user messages locally.
|
|
18
|
+
Returns (anonymized_messages, deduplicated_entity_types).
|
|
19
|
+
Only role=user messages are processed; system/assistant pass through unchanged.
|
|
20
|
+
"""
|
|
21
|
+
fw = _get_firewall(domain, backend)
|
|
22
|
+
anonymized = []
|
|
23
|
+
all_entities: list[str] = []
|
|
24
|
+
|
|
25
|
+
for msg in messages:
|
|
26
|
+
if msg.get("role") == "user" and isinstance(msg.get("content"), str):
|
|
27
|
+
result = fw.anonymize(text=msg["content"], context=context)
|
|
28
|
+
if result.trace and hasattr(result.trace, "detected_entities"):
|
|
29
|
+
for e in result.trace.detected_entities:
|
|
30
|
+
entity_type = (
|
|
31
|
+
e.get("entity_type") if isinstance(e, dict)
|
|
32
|
+
else getattr(e, "entity_type", None)
|
|
33
|
+
)
|
|
34
|
+
if entity_type:
|
|
35
|
+
all_entities.append(entity_type)
|
|
36
|
+
anonymized.append({**msg, "content": result.sanitized_text})
|
|
37
|
+
else:
|
|
38
|
+
anonymized.append(msg)
|
|
39
|
+
|
|
40
|
+
return anonymized, list(set(all_entities))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rehydrate_text(text: str, domain: str, backend: str, context: dict) -> str:
|
|
44
|
+
"""Restores original PII values in LLM response text."""
|
|
45
|
+
fw = _get_firewall(domain, backend)
|
|
46
|
+
return fw.rehydrate(text=text, context=context)
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
PRICING = {
|
|
4
|
+
"claude-opus-4-8": (0.015, 0.075),
|
|
5
|
+
"claude-opus-4-5": (0.015, 0.075),
|
|
6
|
+
"claude-sonnet-4-6": (0.003, 0.015),
|
|
7
|
+
"claude-sonnet-4-5": (0.003, 0.015),
|
|
8
|
+
"claude-haiku-4-5": (0.0008, 0.004),
|
|
9
|
+
"claude-haiku-4-5-20251001":(0.0008, 0.004),
|
|
10
|
+
"claude-3-5-sonnet-20241022":(0.003, 0.015),
|
|
11
|
+
"claude-3-5-haiku-20241022": (0.0008, 0.004),
|
|
12
|
+
"claude-3-opus-20240229": (0.015, 0.075),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def call(
|
|
17
|
+
anthropic_key: str,
|
|
18
|
+
model: str,
|
|
19
|
+
messages: list[dict],
|
|
20
|
+
**kwargs,
|
|
21
|
+
) -> tuple[str, dict]:
|
|
22
|
+
import anthropic
|
|
23
|
+
|
|
24
|
+
client = anthropic.Anthropic(api_key=anthropic_key)
|
|
25
|
+
|
|
26
|
+
# Anthropic requires max_tokens; default to 1024 if not provided
|
|
27
|
+
kwargs.setdefault("max_tokens", 1024)
|
|
28
|
+
|
|
29
|
+
t0 = time.perf_counter()
|
|
30
|
+
response = client.messages.create(
|
|
31
|
+
model=model,
|
|
32
|
+
messages=messages,
|
|
33
|
+
**kwargs,
|
|
34
|
+
)
|
|
35
|
+
latency_ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
36
|
+
|
|
37
|
+
usage = response.usage
|
|
38
|
+
prompt_tokens = usage.input_tokens
|
|
39
|
+
completion_tokens = usage.output_tokens
|
|
40
|
+
|
|
41
|
+
input_price, output_price = PRICING.get(model, (0.003, 0.015))
|
|
42
|
+
cost = round(
|
|
43
|
+
(prompt_tokens / 1000 * input_price)
|
|
44
|
+
+ (completion_tokens / 1000 * output_price),
|
|
45
|
+
6,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
content = response.content[0].text if response.content else ""
|
|
49
|
+
|
|
50
|
+
return content, {
|
|
51
|
+
"model": model,
|
|
52
|
+
"provider": "anthropic",
|
|
53
|
+
"prompt_tokens": prompt_tokens,
|
|
54
|
+
"completion_tokens": completion_tokens,
|
|
55
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
56
|
+
"cost_usd": cost,
|
|
57
|
+
"latency_ms": latency_ms,
|
|
58
|
+
"raw": response.model_dump(),
|
|
59
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
PRICING = {
|
|
4
|
+
"gemini-2.5-pro": (0.00125, 0.010),
|
|
5
|
+
"gemini-2.5-flash": (0.000075, 0.0003),
|
|
6
|
+
"gemini-2.5-flash-lite": (0.000018, 0.000072),
|
|
7
|
+
"gemini-2.0-flash": (0.0001, 0.0004),
|
|
8
|
+
"gemini-2.0-flash-lite": (0.000018, 0.000072),
|
|
9
|
+
"gemini-1.5-pro": (0.00125, 0.005),
|
|
10
|
+
"gemini-1.5-flash": (0.000075, 0.0003),
|
|
11
|
+
"gemini-1.5-flash-8b": (0.0000375,0.00015),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def call(
|
|
16
|
+
gemini_key: str,
|
|
17
|
+
model: str,
|
|
18
|
+
messages: list[dict],
|
|
19
|
+
**kwargs,
|
|
20
|
+
) -> tuple[str, dict]:
|
|
21
|
+
from google import genai
|
|
22
|
+
|
|
23
|
+
client = genai.Client(api_key=gemini_key)
|
|
24
|
+
|
|
25
|
+
# Convert OpenAI-style messages to Gemini contents
|
|
26
|
+
contents = _messages_to_contents(messages)
|
|
27
|
+
|
|
28
|
+
t0 = time.perf_counter()
|
|
29
|
+
response = client.models.generate_content(
|
|
30
|
+
model=model,
|
|
31
|
+
contents=contents,
|
|
32
|
+
**kwargs,
|
|
33
|
+
)
|
|
34
|
+
latency_ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
35
|
+
|
|
36
|
+
usage = response.usage_metadata
|
|
37
|
+
prompt_tokens = usage.prompt_token_count or 0
|
|
38
|
+
completion_tokens = usage.candidates_token_count or 0
|
|
39
|
+
|
|
40
|
+
input_price, output_price = PRICING.get(model, (0.000075, 0.0003))
|
|
41
|
+
cost = round(
|
|
42
|
+
(prompt_tokens / 1000 * input_price)
|
|
43
|
+
+ (completion_tokens / 1000 * output_price),
|
|
44
|
+
6,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
content = response.text or ""
|
|
48
|
+
|
|
49
|
+
return content, {
|
|
50
|
+
"model": model,
|
|
51
|
+
"provider": "gemini",
|
|
52
|
+
"prompt_tokens": prompt_tokens,
|
|
53
|
+
"completion_tokens": completion_tokens,
|
|
54
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
55
|
+
"cost_usd": cost,
|
|
56
|
+
"latency_ms": latency_ms,
|
|
57
|
+
"raw": {"text": content, "usage": {"prompt_token_count": prompt_tokens, "candidates_token_count": completion_tokens}},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _messages_to_contents(messages: list[dict]) -> list:
|
|
62
|
+
"""Convert OpenAI chat messages format to Gemini contents list."""
|
|
63
|
+
contents = []
|
|
64
|
+
for msg in messages:
|
|
65
|
+
role = msg.get("role", "user")
|
|
66
|
+
text = msg.get("content", "")
|
|
67
|
+
# Gemini uses "user" and "model" roles
|
|
68
|
+
gemini_role = "model" if role == "assistant" else "user"
|
|
69
|
+
contents.append({"role": gemini_role, "parts": [{"text": text}]})
|
|
70
|
+
return contents
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
# Pricing: (input_per_1k, output_per_1k) in USD
|
|
4
|
+
PRICING = {
|
|
5
|
+
"gpt-4o": (0.005, 0.015),
|
|
6
|
+
"gpt-4o-mini": (0.00015, 0.0006),
|
|
7
|
+
"gpt-4.1": (0.002, 0.008),
|
|
8
|
+
"gpt-4.1-mini": (0.0001, 0.0004),
|
|
9
|
+
"gpt-4.1-nano": (0.00005, 0.0002),
|
|
10
|
+
"o1": (0.015, 0.060),
|
|
11
|
+
"o1-mini": (0.003, 0.012),
|
|
12
|
+
"o3-mini": (0.0011, 0.0044),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def call(
|
|
17
|
+
openai_key: str,
|
|
18
|
+
model: str,
|
|
19
|
+
messages: list[dict],
|
|
20
|
+
**kwargs,
|
|
21
|
+
) -> tuple[str, dict]:
|
|
22
|
+
from openai import OpenAI
|
|
23
|
+
|
|
24
|
+
client = OpenAI(api_key=openai_key)
|
|
25
|
+
t0 = time.perf_counter()
|
|
26
|
+
response = client.chat.completions.create(
|
|
27
|
+
model=model,
|
|
28
|
+
messages=messages,
|
|
29
|
+
**kwargs,
|
|
30
|
+
)
|
|
31
|
+
latency_ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
32
|
+
|
|
33
|
+
usage = response.usage
|
|
34
|
+
input_price, output_price = PRICING.get(model, (0.005, 0.015))
|
|
35
|
+
cost = round(
|
|
36
|
+
(usage.prompt_tokens / 1000 * input_price)
|
|
37
|
+
+ (usage.completion_tokens / 1000 * output_price),
|
|
38
|
+
6,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return response.choices[0].message.content, {
|
|
42
|
+
"model": model,
|
|
43
|
+
"provider": "openai",
|
|
44
|
+
"prompt_tokens": usage.prompt_tokens,
|
|
45
|
+
"completion_tokens": usage.completion_tokens,
|
|
46
|
+
"total_tokens": usage.total_tokens,
|
|
47
|
+
"cost_usd": cost,
|
|
48
|
+
"latency_ms": latency_ms,
|
|
49
|
+
"raw": response.model_dump(),
|
|
50
|
+
}
|
gateforge/response.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class GatforgeResponse:
|
|
6
|
+
content: str
|
|
7
|
+
model: str
|
|
8
|
+
tokens: int
|
|
9
|
+
prompt_tokens: int
|
|
10
|
+
completion_tokens: int
|
|
11
|
+
cost_usd: float
|
|
12
|
+
latency_ms: float
|
|
13
|
+
pii_detected: list[str]
|
|
14
|
+
raw: dict = field(default_factory=dict)
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gateforge-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Privacy-first LLMOps SDK — Automatic PII masking, cost tracking, and prompt management for LLM applications
|
|
5
|
+
Project-URL: Homepage, https://gateforge.dev
|
|
6
|
+
Project-URL: Documentation, https://gateforge.dev/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/gateforge/gateforge-sdk
|
|
8
|
+
Project-URL: Dashboard, https://app.gateforge.dev
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/gateforge/gateforge-sdk/issues
|
|
10
|
+
Author-email: Gateforge Team <support@gateforge.dev>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: anthropic,gemini,llm,llmops,mlops,openai,pii,privacy
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: pii-firewall[langdetect,presidio]
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'all'
|
|
28
|
+
Requires-Dist: google-genai>=1.0.0; extra == 'all'
|
|
29
|
+
Requires-Dist: openai>=1.0.0; extra == 'all'
|
|
30
|
+
Provides-Extra: anthropic
|
|
31
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: gemini
|
|
38
|
+
Requires-Dist: google-genai>=1.0.0; extra == 'gemini'
|
|
39
|
+
Provides-Extra: openai
|
|
40
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# Gateforge SDK
|
|
44
|
+
|
|
45
|
+
**Privacy-first LLMOps SDK** — Automatic PII masking, cost tracking, and prompt management for LLM applications.
|
|
46
|
+
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
50
|
+
## 🔒 What is Gateforge?
|
|
51
|
+
|
|
52
|
+
Gateforge is an LLMOps platform that automatically protects sensitive data (PII) in your LLM applications. The SDK processes everything **locally** — no content ever leaves your infrastructure.
|
|
53
|
+
|
|
54
|
+
> **Note:** This SDK is free and open source (MIT license). It requires a Gateforge account to use. We offer a generous free tier: **1,000 requests/month**. See [pricing](https://gateforge.dev/pricing) for details.
|
|
55
|
+
|
|
56
|
+
**Key Features:**
|
|
57
|
+
- ✅ **Automatic PII Detection & Masking** — Names, emails, SSN, phone numbers, credit cards, and more
|
|
58
|
+
- ✅ **Multi-Provider Support** — OpenAI, Anthropic, Gemini with unified interface
|
|
59
|
+
- ✅ **Cost Tracking** — Real-time metrics and dashboards
|
|
60
|
+
- ✅ **Domain-Specific Detection** — Healthcare, Finance, Legal, Generic
|
|
61
|
+
- ✅ **Custom Regex Patterns** — Add your own entity types
|
|
62
|
+
- ✅ **Zero Network Overhead** — All PII processing happens locally
|
|
63
|
+
|
|
64
|
+
## 🚀 Quick Start
|
|
65
|
+
|
|
66
|
+
### Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install gateforge-sdk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
With provider support:
|
|
73
|
+
```bash
|
|
74
|
+
pip install gateforge-sdk[openai] # OpenAI only
|
|
75
|
+
pip install gateforge-sdk[anthropic] # Anthropic only
|
|
76
|
+
pip install gateforge-sdk[gemini] # Google Gemini only
|
|
77
|
+
pip install gateforge-sdk[all] # All providers
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Get Your API Key
|
|
81
|
+
|
|
82
|
+
1. Sign up at [https://gateforge.dev](https://gateforge.dev)
|
|
83
|
+
2. Get your API key from [https://app.gateforge.dev/dashboard/keys](https://app.gateforge.dev/dashboard/keys)
|
|
84
|
+
|
|
85
|
+
### Basic Usage
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import gateforge
|
|
89
|
+
|
|
90
|
+
# Initialize once at startup
|
|
91
|
+
gateforge.init(
|
|
92
|
+
api_key="your-gateforge-api-key",
|
|
93
|
+
openai_key="your-openai-key",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Make LLM calls with automatic PII protection
|
|
97
|
+
response = gateforge.chat(
|
|
98
|
+
model="gpt-4.1-nano",
|
|
99
|
+
messages=[
|
|
100
|
+
{"role": "user", "content": "My email is john@example.com, can you help?"}
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print(response.content) # "Hello! I'd be happy to help..."
|
|
105
|
+
print(response.pii_detected) # ['EMAIL']
|
|
106
|
+
print(response.tokens) # 45
|
|
107
|
+
print(response.cost_usd) # 0.000023
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 📖 Examples
|
|
111
|
+
|
|
112
|
+
### Healthcare Domain
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
response = gateforge.chat(
|
|
116
|
+
model="gpt-4.1-nano",
|
|
117
|
+
messages=[
|
|
118
|
+
{
|
|
119
|
+
"role": "user",
|
|
120
|
+
"content": "Patient John Doe, SSN 123-45-6789, has hypertension. Recommend treatment."
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
pii_domain="healthcare" # Detects medical entities
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
print(response.pii_detected) # ['PERSON', 'SSN', 'SYMPTOM']
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Multiple Providers
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import gateforge
|
|
133
|
+
|
|
134
|
+
gateforge.init(
|
|
135
|
+
api_key="your-gateforge-key",
|
|
136
|
+
openai_key="sk-...",
|
|
137
|
+
anthropic_key="sk-ant-...",
|
|
138
|
+
gemini_key="...",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# OpenAI
|
|
142
|
+
r1 = gateforge.chat(model="gpt-4.1-nano", messages=[...])
|
|
143
|
+
|
|
144
|
+
# Anthropic
|
|
145
|
+
r2 = gateforge.chat(model="claude-haiku-4-5", messages=[...])
|
|
146
|
+
|
|
147
|
+
# Gemini
|
|
148
|
+
r3 = gateforge.chat(model="gemini-2.5-flash", messages=[...])
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Custom PII Patterns
|
|
152
|
+
|
|
153
|
+
Configure custom patterns in your dashboard at [https://app.gateforge.dev/dashboard/pii/settings](https://app.gateforge.dev/dashboard/pii/settings), and the SDK will download them automatically.
|
|
154
|
+
|
|
155
|
+
## 🔧 Configuration
|
|
156
|
+
|
|
157
|
+
### Initialization Options
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
gateforge.init(
|
|
161
|
+
api_key="your-gateforge-api-key", # Required
|
|
162
|
+
openai_key="sk-...", # Optional
|
|
163
|
+
anthropic_key="sk-ant-...", # Optional
|
|
164
|
+
gemini_key="...", # Optional
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Chat Parameters
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
response = gateforge.chat(
|
|
172
|
+
model="gpt-4.1-nano", # Required: model name
|
|
173
|
+
messages=[...], # Required: chat messages
|
|
174
|
+
pii_domain="generic", # Optional: "generic", "healthcare", "finance", "legal"
|
|
175
|
+
provider_key="sk-...", # Optional: override provider key
|
|
176
|
+
prompt_id="user-signup", # Optional: for prompt tracking
|
|
177
|
+
temperature=0.7, # Optional: model parameters
|
|
178
|
+
max_tokens=500, # Optional: model parameters
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Response Object
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
response = gateforge.chat(...)
|
|
186
|
+
|
|
187
|
+
response.content # str: The response content
|
|
188
|
+
response.pii_detected # list[str]: Detected PII types
|
|
189
|
+
response.tokens # int: Total tokens
|
|
190
|
+
response.prompt_tokens # int: Input tokens
|
|
191
|
+
response.completion_tokens # int: Output tokens
|
|
192
|
+
response.cost_usd # float: Cost in USD
|
|
193
|
+
response.latency_ms # float: Latency in milliseconds
|
|
194
|
+
response.model # str: Model used
|
|
195
|
+
response.raw # dict: Raw provider response
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 🔒 Privacy & Security
|
|
199
|
+
|
|
200
|
+
### How it Works
|
|
201
|
+
|
|
202
|
+
1. **Local Processing**: All PII detection happens locally in your environment
|
|
203
|
+
2. **Content Never Sent**: Only metadata (tokens, cost) is sent to Gateforge API
|
|
204
|
+
3. **Automatic Masking**: PII is replaced with tokens before sending to LLM providers
|
|
205
|
+
4. **Automatic Rehydration**: Responses are reconstructed with original context
|
|
206
|
+
|
|
207
|
+
### What Gets Detected?
|
|
208
|
+
|
|
209
|
+
- **Personal**: Names, emails, phone numbers, addresses
|
|
210
|
+
- **Financial**: Credit cards, bank accounts, SSN, tax IDs
|
|
211
|
+
- **Healthcare**: Medical record numbers, symptoms, diagnoses
|
|
212
|
+
- **Technical**: IP addresses, URLs, API keys
|
|
213
|
+
- **Custom**: Your own regex patterns
|
|
214
|
+
|
|
215
|
+
### Supported Backends
|
|
216
|
+
|
|
217
|
+
- **Presidio** (default): ML-based, highest accuracy
|
|
218
|
+
- **Regex**: Pattern matching, fastest
|
|
219
|
+
- **Hybrid**: Combined ML + regex
|
|
220
|
+
- **GLiNER**: Zero-shot NER
|
|
221
|
+
- **Transformers**: HuggingFace models
|
|
222
|
+
|
|
223
|
+
## 📊 Dashboard & Monitoring
|
|
224
|
+
|
|
225
|
+
View real-time metrics at [https://app.gateforge.dev/dashboard](https://app.gateforge.dev/dashboard):
|
|
226
|
+
|
|
227
|
+
- 📈 Request volume and trends
|
|
228
|
+
- 💰 Cost breakdown by model and provider
|
|
229
|
+
- ⚡ Latency analytics
|
|
230
|
+
- 🔒 PII detection statistics
|
|
231
|
+
- 🔑 API key management
|
|
232
|
+
|
|
233
|
+
## 🛠️ Advanced Usage
|
|
234
|
+
|
|
235
|
+
### Streaming (Coming Soon)
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
for chunk in gateforge.chat_stream(model="gpt-4.1-nano", messages=[...]):
|
|
239
|
+
print(chunk.content, end="", flush=True)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Async Support (Coming Soon)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
response = await gateforge.achat(model="gpt-4.1-nano", messages=[...])
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Direct Anonymization
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from gateforge.pii import anonymize_messages
|
|
252
|
+
|
|
253
|
+
context = {
|
|
254
|
+
"tenant_id": "user-123",
|
|
255
|
+
"case_id": "case-456",
|
|
256
|
+
"thread_id": "thread-789",
|
|
257
|
+
"actor_id": "user"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
anonymized, entities = anonymize_messages(
|
|
261
|
+
messages=[{"role": "user", "content": "My email is john@example.com"}],
|
|
262
|
+
domain="generic",
|
|
263
|
+
backend="presidio",
|
|
264
|
+
context=context
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
print(anonymized) # [{"role": "user", "content": "My email is [EMAIL_001]"}]
|
|
268
|
+
print(entities) # ['EMAIL']
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## 🌐 Supported Models
|
|
272
|
+
|
|
273
|
+
### OpenAI
|
|
274
|
+
- GPT-4.1-nano, GPT-4.1-mini, GPT-4.1, GPT-4.1-turbo
|
|
275
|
+
- GPT-4o, GPT-4o-mini
|
|
276
|
+
- GPT-3.5-turbo
|
|
277
|
+
|
|
278
|
+
### Anthropic
|
|
279
|
+
- Claude Haiku 4-5
|
|
280
|
+
- Claude Sonnet 4-5
|
|
281
|
+
- Claude Opus 4
|
|
282
|
+
|
|
283
|
+
### Google Gemini
|
|
284
|
+
- Gemini 2.5 Flash
|
|
285
|
+
- Gemini 2.5 Pro
|
|
286
|
+
|
|
287
|
+
## 📝 Configuration via Dashboard
|
|
288
|
+
|
|
289
|
+
### PII Settings
|
|
290
|
+
|
|
291
|
+
1. Go to [https://app.gateforge.dev/dashboard/pii/settings](https://app.gateforge.dev/dashboard/pii/settings)
|
|
292
|
+
2. Select domain: Generic, Healthcare, Finance, Legal
|
|
293
|
+
3. Choose backend: Presidio, Regex, Hybrid
|
|
294
|
+
4. Set language: Auto-detect or specific language
|
|
295
|
+
5. Add custom regex patterns
|
|
296
|
+
|
|
297
|
+
The SDK downloads your configuration automatically on init.
|
|
298
|
+
|
|
299
|
+
### Custom Patterns
|
|
300
|
+
|
|
301
|
+
Add patterns like:
|
|
302
|
+
```
|
|
303
|
+
Entity Type: EMPLOYEE_ID
|
|
304
|
+
Regex: EMP-\d{6}
|
|
305
|
+
Confidence: 0.95
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## 🐛 Troubleshooting
|
|
309
|
+
|
|
310
|
+
### ImportError: No module named 'gateforge'
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
pip install gateforge-sdk
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### RuntimeError: Call gateforge.init() first
|
|
317
|
+
|
|
318
|
+
Make sure to initialize before making calls:
|
|
319
|
+
```python
|
|
320
|
+
gateforge.init(api_key="your-key")
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### API Key Not Working
|
|
324
|
+
|
|
325
|
+
1. Verify your key at [https://app.gateforge.dev/dashboard/keys](https://app.gateforge.dev/dashboard/keys)
|
|
326
|
+
2. Check it's not revoked
|
|
327
|
+
3. Ensure you're using the correct environment
|
|
328
|
+
|
|
329
|
+
### PII Not Detected
|
|
330
|
+
|
|
331
|
+
1. Check your domain setting matches your use case
|
|
332
|
+
2. Try different backends (presidio is most accurate)
|
|
333
|
+
3. Add custom patterns for domain-specific entities
|
|
334
|
+
|
|
335
|
+
## 📚 Documentation
|
|
336
|
+
|
|
337
|
+
- **Website**: [https://gateforge.dev](https://gateforge.dev)
|
|
338
|
+
- **Dashboard**: [https://app.gateforge.dev](https://app.gateforge.dev)
|
|
339
|
+
- **API Docs**: [https://api.gateforge.dev/docs](https://api.gateforge.dev/docs)
|
|
340
|
+
|
|
341
|
+
## 🤝 Contributing
|
|
342
|
+
|
|
343
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
344
|
+
|
|
345
|
+
## 📄 License
|
|
346
|
+
|
|
347
|
+
MIT License - See [LICENSE](LICENSE) for details.
|
|
348
|
+
|
|
349
|
+
**Important:** While the SDK is open source, the Gateforge service is commercial. You need a Gateforge account (free tier available) to use this SDK.
|
|
350
|
+
|
|
351
|
+
## 🔗 Links
|
|
352
|
+
|
|
353
|
+
- [Website](https://gateforge.dev)
|
|
354
|
+
- [Dashboard](https://app.gateforge.dev)
|
|
355
|
+
- [API Health](https://api.gateforge.dev/v1/health)
|
|
356
|
+
- [Documentation](https://gateforge.dev/docs)
|
|
357
|
+
|
|
358
|
+
## 💬 Support
|
|
359
|
+
|
|
360
|
+
- **Email**: support@gateforge.dev
|
|
361
|
+
- **Issues**: [GitHub Issues](https://github.com/gateforge/gateforge-sdk/issues)
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
**Made with ❤️ by the Gateforge team**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
gateforge/__init__.py,sha256=jlIhvOjDeNBF6pq4-0So2hPir4AZXwB4DJD0SBdWXso,1480
|
|
2
|
+
gateforge/client.py,sha256=tgSQjGDzVr7J6fUZoxQJbxFtfMBsEtd9FUbdR5Fkkno,3866
|
|
3
|
+
gateforge/config.py,sha256=0She_mox_G8B3g0iBr4CdDCblMgfKEdnmur2bTb5fQo,1186
|
|
4
|
+
gateforge/metrics.py,sha256=k3EdEBDqzRhE4i5KTWImvlMwfMWt0xV4Nem0I9gD-IE,744
|
|
5
|
+
gateforge/pii.py,sha256=G4QHdweuWlOyLLEh5hfSv9Di29qK-VzNCO7OnbVMe1k,1651
|
|
6
|
+
gateforge/response.py,sha256=XrBKaGlfPbVgpX_ozhHusK0bb8GRpCU29Z5gQ92eNCE,290
|
|
7
|
+
gateforge/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
gateforge/providers/anthropic.py,sha256=XpU6TXODaw3oGDPJaRuHYcEpEDqRAHkPHY3_sNZ8wVo,1695
|
|
9
|
+
gateforge/providers/gemini.py,sha256=N1p6m0JJGXQos4Gu4WoYLJvoILgCjda2TkKFBBVMmrY,2197
|
|
10
|
+
gateforge/providers/openai.py,sha256=cODmvE2lQRWwbFRXcTDEhP_e0vw3ZmCEZaJYW-KpYvo,1384
|
|
11
|
+
gateforge_sdk-0.1.0.dist-info/METADATA,sha256=RxiZweYywfHyHHbwMMhb0he6yGhm4JP_5_xvhK1fw68,10885
|
|
12
|
+
gateforge_sdk-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
13
|
+
gateforge_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=1BtvvSjjJj2tVyp3LZfuNwrzoTkOcqm5O7n3jagI6uA,1629
|
|
14
|
+
gateforge_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gateforge
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
IMPORTANT NOTICE:
|
|
26
|
+
|
|
27
|
+
This SDK requires a Gateforge account and API key to function.
|
|
28
|
+
Service usage is subject to Gateforge's Terms of Service and Privacy Policy:
|
|
29
|
+
- Terms of Service: https://gateforge.dev/terms
|
|
30
|
+
- Privacy Policy: https://gateforge.dev/privacy
|
|
31
|
+
- Pricing: https://gateforge.dev/pricing
|
|
32
|
+
|
|
33
|
+
The Gateforge service (api.gateforge.dev) is a commercial SaaS offering
|
|
34
|
+
operated by Gateforge. Different pricing plans and usage limits apply.
|
|
35
|
+
|
|
36
|
+
Free tier: 1,000 requests/month
|
|
37
|
+
See https://gateforge.dev/pricing for details.
|