ragas-ainative 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AINative Studio
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.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragas-ainative
3
+ Version: 0.1.0
4
+ Summary: Configure ragas RAG evaluation to use AINative's free LLMs + embeddings — no OpenAI key needed.
5
+ Author-email: AINative Studio <dev@ainative.studio>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/AINative-Studio/ragas-ainative
8
+ Project-URL: Documentation, https://docs.ainative.studio
9
+ Project-URL: Repository, https://github.com/AINative-Studio/ragas-ainative
10
+ Project-URL: Issues, https://github.com/AINative-Studio/ragas-ainative/issues
11
+ Keywords: ragas,rag-evaluation,rag,ainative,free-embeddings,free-llm,zerodb,auto-provisioning,faithfulness,answer-relevancy,claude,cursor
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.28
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # ragas-ainative
33
+
34
+ Configure [ragas](https://github.com/explodinggradients/ragas) to use AINative's free LLMs + embeddings for RAG evaluation. No OpenAI key needed.
35
+
36
+ **Zero setup.** Evaluate faithfulness, answer relevancy, and more with Llama 3.3 70B — completely free.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install ragas-ainative
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from ragas_ainative import configure_ragas
48
+ from ragas.metrics import faithfulness, answer_relevancy
49
+ from ragas import evaluate
50
+
51
+ configure_ragas() # Auto-provisions free API key
52
+
53
+ results = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
54
+ print(results)
55
+ ```
56
+
57
+ ## How It Works
58
+
59
+ `ragas-ainative` is a companion package (NOT a fork) that configures ragas to use AINative's free, OpenAI-compatible API. It sets `OPENAI_API_KEY` and `OPENAI_API_BASE` so ragas routes all LLM judge calls and embeddings through AINative.
60
+
61
+ On first use, the package auto-provisions a free API key (72-hour TTL). Claim your account at [ainative.studio/signup](https://ainative.studio/signup) for permanent access.
62
+
63
+ ## Available Models
64
+
65
+ ```python
66
+ from ragas_ainative import configure_ragas
67
+
68
+ configure_ragas(model="llama") # meta-llama/Llama-3.3-70B-Instruct (default)
69
+ configure_ragas(model="qwen") # qwen3-coder-flash
70
+ configure_ragas(model="deepseek") # deepseek-4-flash
71
+ configure_ragas(model="kimi") # kimi-k2
72
+ ```
73
+
74
+ ## Store Results to ZeroDB
75
+
76
+ Track evaluation scores over time:
77
+
78
+ ```python
79
+ from ragas_ainative import configure_ragas, store_results
80
+
81
+ configure_ragas()
82
+
83
+ # After evaluation
84
+ results = {"faithfulness": 0.85, "answer_relevancy": 0.92}
85
+ store_results(results, dataset_name="my-rag-pipeline")
86
+ # Stored to ZeroDB — searchable and trackable
87
+ ```
88
+
89
+ ## Explicit API Key
90
+
91
+ ```python
92
+ configure_ragas(api_key="your-key-here")
93
+ ```
94
+
95
+ Or set the environment variable:
96
+
97
+ ```bash
98
+ export AINATIVE_API_KEY=your-key-here
99
+ ```
100
+
101
+ ## API Key Resolution Order
102
+
103
+ 1. Explicit `api_key` parameter
104
+ 2. `AINATIVE_API_KEY` environment variable
105
+ 3. `ZERODB_API_KEY` environment variable
106
+ 4. `~/.zerodb/credentials.json` (shared with zerodb ecosystem)
107
+ 5. Auto-provision via instant-db
108
+
109
+ ## Requirements
110
+
111
+ - Python >= 3.9
112
+ - ragas >= 0.1.0
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,85 @@
1
+ # ragas-ainative
2
+
3
+ Configure [ragas](https://github.com/explodinggradients/ragas) to use AINative's free LLMs + embeddings for RAG evaluation. No OpenAI key needed.
4
+
5
+ **Zero setup.** Evaluate faithfulness, answer relevancy, and more with Llama 3.3 70B — completely free.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install ragas-ainative
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from ragas_ainative import configure_ragas
17
+ from ragas.metrics import faithfulness, answer_relevancy
18
+ from ragas import evaluate
19
+
20
+ configure_ragas() # Auto-provisions free API key
21
+
22
+ results = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
23
+ print(results)
24
+ ```
25
+
26
+ ## How It Works
27
+
28
+ `ragas-ainative` is a companion package (NOT a fork) that configures ragas to use AINative's free, OpenAI-compatible API. It sets `OPENAI_API_KEY` and `OPENAI_API_BASE` so ragas routes all LLM judge calls and embeddings through AINative.
29
+
30
+ On first use, the package auto-provisions a free API key (72-hour TTL). Claim your account at [ainative.studio/signup](https://ainative.studio/signup) for permanent access.
31
+
32
+ ## Available Models
33
+
34
+ ```python
35
+ from ragas_ainative import configure_ragas
36
+
37
+ configure_ragas(model="llama") # meta-llama/Llama-3.3-70B-Instruct (default)
38
+ configure_ragas(model="qwen") # qwen3-coder-flash
39
+ configure_ragas(model="deepseek") # deepseek-4-flash
40
+ configure_ragas(model="kimi") # kimi-k2
41
+ ```
42
+
43
+ ## Store Results to ZeroDB
44
+
45
+ Track evaluation scores over time:
46
+
47
+ ```python
48
+ from ragas_ainative import configure_ragas, store_results
49
+
50
+ configure_ragas()
51
+
52
+ # After evaluation
53
+ results = {"faithfulness": 0.85, "answer_relevancy": 0.92}
54
+ store_results(results, dataset_name="my-rag-pipeline")
55
+ # Stored to ZeroDB — searchable and trackable
56
+ ```
57
+
58
+ ## Explicit API Key
59
+
60
+ ```python
61
+ configure_ragas(api_key="your-key-here")
62
+ ```
63
+
64
+ Or set the environment variable:
65
+
66
+ ```bash
67
+ export AINATIVE_API_KEY=your-key-here
68
+ ```
69
+
70
+ ## API Key Resolution Order
71
+
72
+ 1. Explicit `api_key` parameter
73
+ 2. `AINATIVE_API_KEY` environment variable
74
+ 3. `ZERODB_API_KEY` environment variable
75
+ 4. `~/.zerodb/credentials.json` (shared with zerodb ecosystem)
76
+ 5. Auto-provision via instant-db
77
+
78
+ ## Requirements
79
+
80
+ - Python >= 3.9
81
+ - ragas >= 0.1.0
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ragas-ainative"
7
+ version = "0.1.0"
8
+ description = "Configure ragas RAG evaluation to use AINative's free LLMs + embeddings — no OpenAI key needed."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "AINative Studio", email = "dev@ainative.studio"},
14
+ ]
15
+ keywords = [
16
+ "ragas",
17
+ "rag-evaluation",
18
+ "rag",
19
+ "ainative",
20
+ "free-embeddings",
21
+ "free-llm",
22
+ "zerodb",
23
+ "auto-provisioning",
24
+ "faithfulness",
25
+ "answer-relevancy",
26
+ "claude",
27
+ "cursor",
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 4 - Beta",
31
+ "Intended Audience :: Developers",
32
+ "License :: OSI Approved :: MIT License",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3.9",
35
+ "Programming Language :: Python :: 3.10",
36
+ "Programming Language :: Python :: 3.11",
37
+ "Programming Language :: Python :: 3.12",
38
+ "Programming Language :: Python :: 3.13",
39
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
40
+ "Topic :: Software Development :: Libraries :: Python Modules",
41
+ ]
42
+ dependencies = [
43
+ "requests>=2.28",
44
+ ]
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "pytest>=7.0",
49
+ "pytest-cov>=4.0",
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/AINative-Studio/ragas-ainative"
54
+ Documentation = "https://docs.ainative.studio"
55
+ Repository = "https://github.com/AINative-Studio/ragas-ainative"
56
+ Issues = "https://github.com/AINative-Studio/ragas-ainative/issues"
57
+
58
+ [tool.setuptools.packages.find]
59
+ include = ["ragas_ainative*"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
@@ -0,0 +1,42 @@
1
+ """
2
+ ragas-ainative — Configure ragas to use AINative's free LLMs + embeddings.
3
+
4
+ No OpenAI key needed. Evaluate your RAG pipelines for free with Llama, Qwen,
5
+ DeepSeek, and Kimi models.
6
+
7
+ Usage:
8
+ from ragas_ainative import configure_ragas
9
+ from ragas.metrics import faithfulness, answer_relevancy
10
+ from ragas import evaluate
11
+
12
+ configure_ragas() # sets AINative as eval LLM + embeddings
13
+
14
+ results = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
15
+
16
+ Refs #3954
17
+ """
18
+
19
+ from ragas_ainative.config import (
20
+ configure_ragas,
21
+ get_llm_config,
22
+ get_embeddings_config,
23
+ store_results,
24
+ MODELS,
25
+ DEFAULT_MODEL,
26
+ API_BASE,
27
+ EMBEDDINGS_MODEL,
28
+ )
29
+ from ragas_ainative.provision import resolve_api_key
30
+
31
+ __all__ = [
32
+ "configure_ragas",
33
+ "get_llm_config",
34
+ "get_embeddings_config",
35
+ "store_results",
36
+ "resolve_api_key",
37
+ "MODELS",
38
+ "DEFAULT_MODEL",
39
+ "API_BASE",
40
+ "EMBEDDINGS_MODEL",
41
+ ]
42
+ __version__ = "0.1.0"
@@ -0,0 +1,225 @@
1
+ """
2
+ ragas-ainative — Configuration
3
+
4
+ Sets environment variables so ragas uses AINative's OpenAI-compatible
5
+ endpoint for LLM-based evaluation and embeddings.
6
+
7
+ Ragas reads OPENAI_API_KEY and OPENAI_API_BASE to configure its default
8
+ LLM judge and embeddings. We set these to AINative values.
9
+
10
+ Refs #3954
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ from datetime import datetime
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ import requests
22
+
23
+ from ragas_ainative.provision import resolve_api_key
24
+
25
+ API_BASE = "https://api.ainative.studio/api/v1"
26
+ MEMORY_API_BASE = "https://api.ainative.studio/api/v1/public/memory/v2"
27
+ EMBEDDINGS_ENDPOINT = "https://api.ainative.studio/api/v1/embeddings"
28
+
29
+ # Model aliases -> full model identifiers
30
+ MODELS: Dict[str, str] = {
31
+ # Meta Llama
32
+ "llama": "meta-llama/Llama-3.3-70B-Instruct",
33
+ "llama-70b": "meta-llama/Llama-3.3-70B-Instruct",
34
+ "llama-8b": "meta-llama/Llama-3.1-8B-Instruct",
35
+ # Qwen
36
+ "qwen": "qwen3-coder-flash",
37
+ "qwen-coder": "qwen3-coder-flash",
38
+ # DeepSeek
39
+ "deepseek": "deepseek-4-flash",
40
+ "deepseek-flash": "deepseek-4-flash",
41
+ # Kimi
42
+ "kimi": "kimi-k2",
43
+ }
44
+
45
+ DEFAULT_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
46
+ EMBEDDINGS_MODEL = "bge-m3"
47
+
48
+
49
+ def get_model(alias: str) -> str:
50
+ """
51
+ Resolve a model alias to its full identifier.
52
+
53
+ Args:
54
+ alias: Short alias (e.g. "llama", "qwen") or full model ID.
55
+
56
+ Returns:
57
+ Full model identifier string.
58
+ """
59
+ return MODELS.get(alias, alias)
60
+
61
+
62
+ def get_llm_config(
63
+ model: Optional[str] = None,
64
+ api_key: Optional[str] = None,
65
+ api_base: Optional[str] = None,
66
+ ) -> dict:
67
+ """
68
+ Return env var overrides for ragas LLM judge.
69
+
70
+ Args:
71
+ model: Model alias or full ID. Defaults to Llama 3.3 70B.
72
+ api_key: Explicit API key. Auto-provisions if not provided.
73
+ api_base: Override API base URL.
74
+
75
+ Returns:
76
+ Dict with OPENAI_API_KEY, OPENAI_API_BASE, model name.
77
+ """
78
+ key = resolve_api_key(api_key)
79
+ base = api_base or API_BASE
80
+ model_id = get_model(model) if model else DEFAULT_MODEL
81
+
82
+ return {
83
+ "OPENAI_API_KEY": key,
84
+ "OPENAI_API_BASE": base,
85
+ "model": model_id,
86
+ }
87
+
88
+
89
+ def get_embeddings_config(
90
+ api_key: Optional[str] = None,
91
+ api_base: Optional[str] = None,
92
+ model: Optional[str] = None,
93
+ ) -> dict:
94
+ """
95
+ Return config for AINative embeddings used by ragas.
96
+
97
+ Args:
98
+ api_key: Explicit API key. Uses resolved key if not provided.
99
+ api_base: Override API base URL.
100
+ model: Embeddings model name. Defaults to bge-m3.
101
+
102
+ Returns:
103
+ Dict with embeddings API configuration.
104
+ """
105
+ key = resolve_api_key(api_key)
106
+ base = api_base or API_BASE
107
+ emb_model = model or EMBEDDINGS_MODEL
108
+
109
+ return {
110
+ "OPENAI_API_KEY": key,
111
+ "OPENAI_API_BASE": base,
112
+ "embeddings_model": emb_model,
113
+ }
114
+
115
+
116
+ def configure_ragas(
117
+ model: Optional[str] = None,
118
+ api_key: Optional[str] = None,
119
+ api_base: Optional[str] = None,
120
+ verbose: bool = True,
121
+ ) -> str:
122
+ """
123
+ Configure ragas to use AINative's free LLMs and embeddings.
124
+
125
+ Sets environment variables that ragas reads for its LLM judge
126
+ and embeddings:
127
+ - OPENAI_API_KEY -> AINative API key
128
+ - OPENAI_API_BASE -> AINative OpenAI-compatible endpoint
129
+
130
+ Also sets AINATIVE_API_KEY for ecosystem compatibility.
131
+
132
+ Args:
133
+ model: Model alias or full ID for the judge LLM.
134
+ Defaults to Llama 3.3 70B.
135
+ api_key: Explicit API key. Auto-provisions if not provided.
136
+ api_base: Override API base URL.
137
+ verbose: Print configuration summary to stderr.
138
+
139
+ Returns:
140
+ The API key being used.
141
+
142
+ Example:
143
+ from ragas_ainative import configure_ragas
144
+ from ragas.metrics import faithfulness, answer_relevancy
145
+ from ragas import evaluate
146
+
147
+ configure_ragas()
148
+ results = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
149
+ """
150
+ config = get_llm_config(model=model, api_key=api_key, api_base=api_base)
151
+
152
+ # Set env vars for ragas (uses OpenAI SDK under the hood)
153
+ os.environ["OPENAI_API_KEY"] = config["OPENAI_API_KEY"]
154
+ os.environ["OPENAI_API_BASE"] = config["OPENAI_API_BASE"]
155
+
156
+ # Also set AINATIVE_API_KEY for ecosystem tools
157
+ os.environ["AINATIVE_API_KEY"] = config["OPENAI_API_KEY"]
158
+
159
+ if verbose:
160
+ key_preview = config["OPENAI_API_KEY"][:12] + "..."
161
+ print(
162
+ f"\n ragas-ainative configured!\n"
163
+ f"\n"
164
+ f" Judge LLM: {config['model']}\n"
165
+ f" Embeddings: {EMBEDDINGS_MODEL}\n"
166
+ f" API Base: {config['OPENAI_API_BASE']}\n"
167
+ f" API Key: {key_preview}\n"
168
+ f"\n"
169
+ f" All ragas evaluations will now use AINative's free LLMs.\n",
170
+ file=sys.stderr,
171
+ )
172
+
173
+ return config["OPENAI_API_KEY"]
174
+
175
+
176
+ def store_results(
177
+ results: dict,
178
+ dataset_name: str = "ragas-eval",
179
+ api_key: Optional[str] = None,
180
+ ) -> Optional[dict]:
181
+ """
182
+ Store ragas evaluation results to ZeroDB for tracking over time.
183
+
184
+ Sends the evaluation scores as a memory entry to ZeroDB's
185
+ /remember endpoint, making it searchable and trackable.
186
+
187
+ Args:
188
+ results: Ragas evaluation results dict (scores per metric).
189
+ dataset_name: Name to tag the evaluation. Defaults to "ragas-eval".
190
+ api_key: Explicit API key. Uses resolved key if not provided.
191
+
192
+ Returns:
193
+ ZeroDB response dict, or None if storage fails.
194
+ """
195
+ key = resolve_api_key(api_key)
196
+
197
+ payload = {
198
+ "entity": f"ragas-eval:{dataset_name}",
199
+ "content": json.dumps({
200
+ "type": "ragas_evaluation",
201
+ "dataset": dataset_name,
202
+ "scores": results,
203
+ "evaluated_at": datetime.now(tz=None).isoformat(),
204
+ }),
205
+ "metadata": {
206
+ "source": "ragas-ainative",
207
+ "dataset": dataset_name,
208
+ },
209
+ }
210
+
211
+ try:
212
+ resp = requests.post(
213
+ f"{MEMORY_API_BASE}/remember",
214
+ json=payload,
215
+ headers={
216
+ "Authorization": f"Bearer {key}",
217
+ "Content-Type": "application/json",
218
+ },
219
+ timeout=15,
220
+ )
221
+ if resp.status_code in (200, 201):
222
+ return resp.json()
223
+ return None
224
+ except requests.RequestException:
225
+ return None
@@ -0,0 +1,174 @@
1
+ """
2
+ ragas-ainative — Auto-Provisioning
3
+
4
+ Zero-friction provisioning for ragas users.
5
+ On first use, if no API key is found, automatically provisions
6
+ a free AINative account (72-hour TTL) that can be claimed later.
7
+
8
+ Credential resolution order:
9
+ 1. Explicit api_key parameter
10
+ 2. AINATIVE_API_KEY environment variable
11
+ 3. ZERODB_API_KEY environment variable (shared with zerodb ecosystem)
12
+ 4. ~/.zerodb/credentials.json (shared credential store)
13
+ 5. Auto-provision via /api/v1/public/instant-db
14
+
15
+ Refs #3954
16
+ """
17
+
18
+ import json
19
+ import os
20
+ import sys
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+ from typing import Optional
24
+
25
+ import requests
26
+
27
+ ZERODB_DIR = Path.home() / ".zerodb"
28
+ CREDS_PATH = ZERODB_DIR / "credentials.json"
29
+ CONFIG_PATH = ZERODB_DIR / "config.json"
30
+ CLOUD_API_URL = "https://api.ainative.studio"
31
+ PROVISION_ENDPOINT = "/api/v1/public/instant-db"
32
+
33
+
34
+ def resolve_api_key(explicit_key: Optional[str] = None) -> str:
35
+ """
36
+ Resolve an API key from all available sources.
37
+
38
+ Args:
39
+ explicit_key: Key passed directly by the user.
40
+
41
+ Returns:
42
+ A valid API key string.
43
+
44
+ Raises:
45
+ RuntimeError: If auto-provisioning fails and no key is available.
46
+ """
47
+ # 1. Explicit parameter
48
+ if explicit_key:
49
+ return explicit_key
50
+
51
+ # 2. Environment variables
52
+ env_key = (
53
+ os.environ.get("AINATIVE_API_KEY")
54
+ or os.environ.get("ZERODB_API_KEY")
55
+ )
56
+ if env_key:
57
+ return env_key
58
+
59
+ # 3. Credentials file (shared with zerodb ecosystem)
60
+ creds = _load_credentials()
61
+ if creds:
62
+ return creds
63
+
64
+ # 4. Auto-provision
65
+ return _auto_provision()
66
+
67
+
68
+ def _load_credentials() -> Optional[str]:
69
+ """Load API key from ~/.zerodb/credentials.json."""
70
+ if CREDS_PATH.exists():
71
+ try:
72
+ data = json.loads(CREDS_PATH.read_text())
73
+ key = data.get("api_key")
74
+ if key:
75
+ return key
76
+ except (json.JSONDecodeError, KeyError):
77
+ pass
78
+
79
+ if CONFIG_PATH.exists():
80
+ try:
81
+ data = json.loads(CONFIG_PATH.read_text())
82
+ key = data.get("api_key") or data.get("cloud_api_key")
83
+ if key:
84
+ return key
85
+ except (json.JSONDecodeError, KeyError):
86
+ pass
87
+
88
+ return None
89
+
90
+
91
+ def _auto_provision() -> str:
92
+ """
93
+ Auto-provision a free AINative account for ragas evaluation.
94
+
95
+ Returns:
96
+ API key string.
97
+
98
+ Raises:
99
+ RuntimeError: If provisioning fails.
100
+ """
101
+ print(
102
+ "\n No API key found — provisioning a free AINative account for ragas...",
103
+ file=sys.stderr,
104
+ )
105
+
106
+ try:
107
+ resp = requests.post(
108
+ f"{CLOUD_API_URL}{PROVISION_ENDPOINT}",
109
+ json={"agree_terms": True, "source": "ragas-ainative"},
110
+ timeout=15,
111
+ )
112
+
113
+ if resp.status_code == 429:
114
+ raise RuntimeError(
115
+ "Rate limited — too many provisions from this IP. "
116
+ "Sign up at https://ainative.studio/signup and set AINATIVE_API_KEY."
117
+ )
118
+
119
+ if resp.status_code not in (200, 201):
120
+ raise RuntimeError(
121
+ f"Provisioning failed (HTTP {resp.status_code}). "
122
+ "Sign up at https://ainative.studio/signup and set AINATIVE_API_KEY."
123
+ )
124
+
125
+ data = resp.json()
126
+ api_key = data.get("api_key", "")
127
+ if not api_key:
128
+ raise RuntimeError("Provisioning returned empty API key.")
129
+
130
+ _save_credentials(data)
131
+ _print_success(data)
132
+ return api_key
133
+
134
+ except requests.RequestException as exc:
135
+ raise RuntimeError(
136
+ f"Network error during provisioning: {exc}. "
137
+ "Set AINATIVE_API_KEY manually or sign up at https://ainative.studio/signup"
138
+ ) from exc
139
+
140
+
141
+ def _save_credentials(data: dict) -> None:
142
+ """Save provisioned credentials to ~/.zerodb/ for ecosystem sharing."""
143
+ ZERODB_DIR.mkdir(parents=True, exist_ok=True)
144
+
145
+ creds = {
146
+ "api_key": data.get("api_key", ""),
147
+ "project_id": data.get("project_id", ""),
148
+ "base_url": data.get("base_url", CLOUD_API_URL),
149
+ "expires_at": data.get("expires_at", ""),
150
+ "claim_url": data.get("claim_url", ""),
151
+ "provisioned_at": datetime.now(tz=None).isoformat(),
152
+ "source": "ragas-ainative",
153
+ }
154
+
155
+ CREDS_PATH.write_text(json.dumps(creds, indent=2) + "\n")
156
+
157
+
158
+ def _print_success(data: dict) -> None:
159
+ """Print success message with claim URL."""
160
+ expires = data.get("expires_at", "72 hours")
161
+ claim_url = data.get("claim_url", "https://ainative.studio/signup")
162
+ api_key = data.get("api_key", "")
163
+
164
+ print(
165
+ f"\n Auto-provisioned! Free RAG evaluation API ready.\n"
166
+ f"\n"
167
+ f" API Key: {api_key[:12]}...\n"
168
+ f" Expires: {expires}\n"
169
+ f" Saved to: ~/.zerodb/credentials.json\n"
170
+ f"\n"
171
+ f" To keep access permanently, claim your account:\n"
172
+ f" {claim_url}\n",
173
+ file=sys.stderr,
174
+ )
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragas-ainative
3
+ Version: 0.1.0
4
+ Summary: Configure ragas RAG evaluation to use AINative's free LLMs + embeddings — no OpenAI key needed.
5
+ Author-email: AINative Studio <dev@ainative.studio>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/AINative-Studio/ragas-ainative
8
+ Project-URL: Documentation, https://docs.ainative.studio
9
+ Project-URL: Repository, https://github.com/AINative-Studio/ragas-ainative
10
+ Project-URL: Issues, https://github.com/AINative-Studio/ragas-ainative/issues
11
+ Keywords: ragas,rag-evaluation,rag,ainative,free-embeddings,free-llm,zerodb,auto-provisioning,faithfulness,answer-relevancy,claude,cursor
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.28
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # ragas-ainative
33
+
34
+ Configure [ragas](https://github.com/explodinggradients/ragas) to use AINative's free LLMs + embeddings for RAG evaluation. No OpenAI key needed.
35
+
36
+ **Zero setup.** Evaluate faithfulness, answer relevancy, and more with Llama 3.3 70B — completely free.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install ragas-ainative
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from ragas_ainative import configure_ragas
48
+ from ragas.metrics import faithfulness, answer_relevancy
49
+ from ragas import evaluate
50
+
51
+ configure_ragas() # Auto-provisions free API key
52
+
53
+ results = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
54
+ print(results)
55
+ ```
56
+
57
+ ## How It Works
58
+
59
+ `ragas-ainative` is a companion package (NOT a fork) that configures ragas to use AINative's free, OpenAI-compatible API. It sets `OPENAI_API_KEY` and `OPENAI_API_BASE` so ragas routes all LLM judge calls and embeddings through AINative.
60
+
61
+ On first use, the package auto-provisions a free API key (72-hour TTL). Claim your account at [ainative.studio/signup](https://ainative.studio/signup) for permanent access.
62
+
63
+ ## Available Models
64
+
65
+ ```python
66
+ from ragas_ainative import configure_ragas
67
+
68
+ configure_ragas(model="llama") # meta-llama/Llama-3.3-70B-Instruct (default)
69
+ configure_ragas(model="qwen") # qwen3-coder-flash
70
+ configure_ragas(model="deepseek") # deepseek-4-flash
71
+ configure_ragas(model="kimi") # kimi-k2
72
+ ```
73
+
74
+ ## Store Results to ZeroDB
75
+
76
+ Track evaluation scores over time:
77
+
78
+ ```python
79
+ from ragas_ainative import configure_ragas, store_results
80
+
81
+ configure_ragas()
82
+
83
+ # After evaluation
84
+ results = {"faithfulness": 0.85, "answer_relevancy": 0.92}
85
+ store_results(results, dataset_name="my-rag-pipeline")
86
+ # Stored to ZeroDB — searchable and trackable
87
+ ```
88
+
89
+ ## Explicit API Key
90
+
91
+ ```python
92
+ configure_ragas(api_key="your-key-here")
93
+ ```
94
+
95
+ Or set the environment variable:
96
+
97
+ ```bash
98
+ export AINATIVE_API_KEY=your-key-here
99
+ ```
100
+
101
+ ## API Key Resolution Order
102
+
103
+ 1. Explicit `api_key` parameter
104
+ 2. `AINATIVE_API_KEY` environment variable
105
+ 3. `ZERODB_API_KEY` environment variable
106
+ 4. `~/.zerodb/credentials.json` (shared with zerodb ecosystem)
107
+ 5. Auto-provision via instant-db
108
+
109
+ ## Requirements
110
+
111
+ - Python >= 3.9
112
+ - ragas >= 0.1.0
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ragas_ainative/__init__.py
5
+ ragas_ainative/config.py
6
+ ragas_ainative/provision.py
7
+ ragas_ainative.egg-info/PKG-INFO
8
+ ragas_ainative.egg-info/SOURCES.txt
9
+ ragas_ainative.egg-info/dependency_links.txt
10
+ ragas_ainative.egg-info/requires.txt
11
+ ragas_ainative.egg-info/top_level.txt
12
+ tests/test_config.py
@@ -0,0 +1,5 @@
1
+ requests>=2.28
2
+
3
+ [dev]
4
+ pytest>=7.0
5
+ pytest-cov>=4.0
@@ -0,0 +1 @@
1
+ ragas_ainative
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,309 @@
1
+ """
2
+ Tests for ragas-ainative configuration.
3
+
4
+ Refs #3954
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from unittest.mock import patch, MagicMock
10
+
11
+ import pytest
12
+
13
+ from ragas_ainative.config import (
14
+ configure_ragas,
15
+ get_llm_config,
16
+ get_embeddings_config,
17
+ get_model,
18
+ store_results,
19
+ MODELS,
20
+ DEFAULT_MODEL,
21
+ API_BASE,
22
+ EMBEDDINGS_MODEL,
23
+ MEMORY_API_BASE,
24
+ )
25
+ from ragas_ainative.provision import (
26
+ resolve_api_key,
27
+ _load_credentials,
28
+ _auto_provision,
29
+ _save_credentials,
30
+ ZERODB_DIR,
31
+ CREDS_PATH,
32
+ CONFIG_PATH,
33
+ )
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Model resolution
38
+ # ---------------------------------------------------------------------------
39
+
40
+ class TestGetModel:
41
+ """Tests for model alias resolution."""
42
+
43
+ def test_llama_alias(self):
44
+ assert get_model("llama") == "meta-llama/Llama-3.3-70B-Instruct"
45
+
46
+ def test_qwen_alias(self):
47
+ assert get_model("qwen") == "qwen3-coder-flash"
48
+
49
+ def test_deepseek_alias(self):
50
+ assert get_model("deepseek") == "deepseek-4-flash"
51
+
52
+ def test_kimi_alias(self):
53
+ assert get_model("kimi") == "kimi-k2"
54
+
55
+ def test_passthrough_full_id(self):
56
+ assert get_model("meta-llama/Llama-3.3-70B-Instruct") == "meta-llama/Llama-3.3-70B-Instruct"
57
+
58
+ def test_unknown_returns_as_is(self):
59
+ assert get_model("custom-model") == "custom-model"
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # LLM config
64
+ # ---------------------------------------------------------------------------
65
+
66
+ class TestGetLlmConfig:
67
+ """Tests for LLM judge config generation."""
68
+
69
+ @patch("ragas_ainative.config.resolve_api_key", return_value="test-key")
70
+ def test_default_config(self, mock_resolve):
71
+ config = get_llm_config()
72
+ assert config["OPENAI_API_KEY"] == "test-key"
73
+ assert config["OPENAI_API_BASE"] == API_BASE
74
+ assert config["model"] == DEFAULT_MODEL
75
+
76
+ @patch("ragas_ainative.config.resolve_api_key", return_value="test-key")
77
+ def test_custom_model(self, mock_resolve):
78
+ config = get_llm_config(model="qwen")
79
+ assert config["model"] == "qwen3-coder-flash"
80
+
81
+ @patch("ragas_ainative.config.resolve_api_key", return_value="test-key")
82
+ def test_custom_base(self, mock_resolve):
83
+ config = get_llm_config(api_base="https://custom.api/v1")
84
+ assert config["OPENAI_API_BASE"] == "https://custom.api/v1"
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Embeddings config
89
+ # ---------------------------------------------------------------------------
90
+
91
+ class TestGetEmbeddingsConfig:
92
+ """Tests for embeddings config generation."""
93
+
94
+ @patch("ragas_ainative.config.resolve_api_key", return_value="emb-key")
95
+ def test_default_embeddings_config(self, mock_resolve):
96
+ config = get_embeddings_config()
97
+ assert config["OPENAI_API_KEY"] == "emb-key"
98
+ assert config["embeddings_model"] == EMBEDDINGS_MODEL
99
+
100
+ @patch("ragas_ainative.config.resolve_api_key", return_value="emb-key")
101
+ def test_custom_embeddings_model(self, mock_resolve):
102
+ config = get_embeddings_config(model="custom-emb")
103
+ assert config["embeddings_model"] == "custom-emb"
104
+
105
+
106
+ # ---------------------------------------------------------------------------
107
+ # configure_ragas
108
+ # ---------------------------------------------------------------------------
109
+
110
+ class TestConfigureRagas:
111
+ """Tests for the main configure_ragas() function."""
112
+
113
+ @patch("ragas_ainative.config.resolve_api_key", return_value="ragas-key-123456789")
114
+ def test_sets_env_vars(self, mock_resolve):
115
+ with patch.dict(os.environ, {}, clear=False):
116
+ result = configure_ragas(verbose=False)
117
+ assert result == "ragas-key-123456789"
118
+ assert os.environ["OPENAI_API_KEY"] == "ragas-key-123456789"
119
+ assert os.environ["OPENAI_API_BASE"] == API_BASE
120
+ assert os.environ["AINATIVE_API_KEY"] == "ragas-key-123456789"
121
+
122
+ @patch("ragas_ainative.config.resolve_api_key", return_value="ragas-key-123456789")
123
+ def test_verbose_output(self, mock_resolve, capsys):
124
+ configure_ragas(verbose=True)
125
+ captured = capsys.readouterr()
126
+ assert "ragas-ainative configured" in captured.err
127
+
128
+ @patch("ragas_ainative.config.resolve_api_key", return_value="ragas-key-123456789")
129
+ def test_returns_api_key(self, mock_resolve):
130
+ result = configure_ragas(verbose=False)
131
+ assert result == "ragas-key-123456789"
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # store_results
136
+ # ---------------------------------------------------------------------------
137
+
138
+ class TestStoreResults:
139
+ """Tests for storing evaluation results to ZeroDB."""
140
+
141
+ @patch("ragas_ainative.config.resolve_api_key", return_value="store-key")
142
+ @patch("ragas_ainative.config.requests")
143
+ def test_store_results_success(self, mock_requests, mock_resolve):
144
+ mock_resp = MagicMock()
145
+ mock_resp.status_code = 201
146
+ mock_resp.json.return_value = {"id": "mem-1", "status": "stored"}
147
+ mock_requests.post.return_value = mock_resp
148
+
149
+ result = store_results(
150
+ results={"faithfulness": 0.85, "answer_relevancy": 0.92},
151
+ dataset_name="my-rag-test",
152
+ )
153
+
154
+ assert result is not None
155
+ assert result["id"] == "mem-1"
156
+ mock_requests.post.assert_called_once()
157
+ call_kwargs = mock_requests.post.call_args
158
+ assert "remember" in call_kwargs[0][0]
159
+
160
+ @patch("ragas_ainative.config.resolve_api_key", return_value="store-key")
161
+ @patch("ragas_ainative.config.requests")
162
+ def test_store_results_failure_returns_none(self, mock_requests, mock_resolve):
163
+ mock_resp = MagicMock()
164
+ mock_resp.status_code = 500
165
+ mock_requests.post.return_value = mock_resp
166
+
167
+ result = store_results(results={"score": 0.5})
168
+ assert result is None
169
+
170
+ @patch("ragas_ainative.config.resolve_api_key", return_value="store-key")
171
+ @patch("ragas_ainative.config.requests")
172
+ def test_store_results_network_error_returns_none(self, mock_requests, mock_resolve):
173
+ import requests as real_requests
174
+ mock_requests.RequestException = real_requests.RequestException
175
+ mock_requests.post.side_effect = real_requests.ConnectionError("fail")
176
+
177
+ result = store_results(results={"score": 0.5})
178
+ assert result is None
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # API key resolution
183
+ # ---------------------------------------------------------------------------
184
+
185
+ class TestResolveApiKey:
186
+ """Tests for the multi-source API key resolution."""
187
+
188
+ def test_explicit_key_takes_priority(self):
189
+ result = resolve_api_key(explicit_key="explicit-key")
190
+ assert result == "explicit-key"
191
+
192
+ @patch.dict(os.environ, {"AINATIVE_API_KEY": "env-key"}, clear=False)
193
+ def test_ainative_env_var(self):
194
+ result = resolve_api_key()
195
+ assert result == "env-key"
196
+
197
+ @patch.dict(os.environ, {"ZERODB_API_KEY": "zerodb-key"}, clear=False)
198
+ def test_zerodb_env_var(self):
199
+ env = os.environ.copy()
200
+ env.pop("AINATIVE_API_KEY", None)
201
+ with patch.dict(os.environ, env, clear=True):
202
+ os.environ["ZERODB_API_KEY"] = "zerodb-key"
203
+ result = resolve_api_key()
204
+ assert result == "zerodb-key"
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Credential file loading
209
+ # ---------------------------------------------------------------------------
210
+
211
+ class TestLoadCredentials:
212
+ """Tests for credential file loading."""
213
+
214
+ def test_returns_none_when_no_files(self, tmp_path):
215
+ with patch("ragas_ainative.provision.CREDS_PATH", tmp_path / "missing.json"):
216
+ with patch("ragas_ainative.provision.CONFIG_PATH", tmp_path / "missing2.json"):
217
+ result = _load_credentials()
218
+ assert result is None
219
+
220
+ def test_reads_credentials_json(self, tmp_path):
221
+ creds_file = tmp_path / "credentials.json"
222
+ creds_file.write_text(json.dumps({"api_key": "file-key"}))
223
+
224
+ with patch("ragas_ainative.provision.CREDS_PATH", creds_file):
225
+ with patch("ragas_ainative.provision.CONFIG_PATH", tmp_path / "missing.json"):
226
+ result = _load_credentials()
227
+ assert result == "file-key"
228
+
229
+
230
+ # ---------------------------------------------------------------------------
231
+ # Auto-provisioning
232
+ # ---------------------------------------------------------------------------
233
+
234
+ class TestAutoProvision:
235
+ """Tests for the auto-provisioning endpoint."""
236
+
237
+ @patch("ragas_ainative.provision.requests")
238
+ @patch("ragas_ainative.provision._save_credentials")
239
+ @patch("ragas_ainative.provision._print_success")
240
+ def test_successful_provision(self, mock_print, mock_save, mock_requests):
241
+ mock_resp = MagicMock()
242
+ mock_resp.status_code = 201
243
+ mock_resp.json.return_value = {
244
+ "api_key": "new-key-12345678",
245
+ "project_id": "proj-1",
246
+ "claim_url": "https://ainative.studio/claim/abc",
247
+ }
248
+ mock_requests.post.return_value = mock_resp
249
+
250
+ result = _auto_provision()
251
+
252
+ assert result == "new-key-12345678"
253
+ mock_save.assert_called_once()
254
+
255
+ @patch("ragas_ainative.provision.requests")
256
+ def test_rate_limited(self, mock_requests):
257
+ import requests as real_requests
258
+ mock_requests.RequestException = real_requests.RequestException
259
+ mock_resp = MagicMock()
260
+ mock_resp.status_code = 429
261
+ mock_requests.post.return_value = mock_resp
262
+
263
+ with pytest.raises(RuntimeError, match="Rate limited"):
264
+ _auto_provision()
265
+
266
+ @patch("ragas_ainative.provision.requests")
267
+ def test_server_error(self, mock_requests):
268
+ import requests as real_requests
269
+ mock_requests.RequestException = real_requests.RequestException
270
+ mock_resp = MagicMock()
271
+ mock_resp.status_code = 500
272
+ mock_requests.post.return_value = mock_resp
273
+
274
+ with pytest.raises(RuntimeError, match="Provisioning failed"):
275
+ _auto_provision()
276
+
277
+ @patch("ragas_ainative.provision.requests")
278
+ def test_network_error(self, mock_requests):
279
+ import requests as real_requests
280
+ mock_requests.RequestException = real_requests.RequestException
281
+ mock_requests.post.side_effect = real_requests.ConnectionError("fail")
282
+
283
+ with pytest.raises(RuntimeError, match="Network error"):
284
+ _auto_provision()
285
+
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # Credential persistence
289
+ # ---------------------------------------------------------------------------
290
+
291
+ class TestSaveCredentials:
292
+ """Tests for credential persistence."""
293
+
294
+ def test_saves_to_file(self, tmp_path):
295
+ creds_dir = tmp_path / ".zerodb"
296
+ creds_path = creds_dir / "credentials.json"
297
+
298
+ with patch("ragas_ainative.provision.ZERODB_DIR", creds_dir):
299
+ with patch("ragas_ainative.provision.CREDS_PATH", creds_path):
300
+ _save_credentials({
301
+ "api_key": "saved-key",
302
+ "project_id": "proj-1",
303
+ "claim_url": "https://ainative.studio/claim/abc",
304
+ })
305
+
306
+ assert creds_path.exists()
307
+ data = json.loads(creds_path.read_text())
308
+ assert data["api_key"] == "saved-key"
309
+ assert data["source"] == "ragas-ainative"