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.
- ragas_ainative-0.1.0/LICENSE +21 -0
- ragas_ainative-0.1.0/PKG-INFO +116 -0
- ragas_ainative-0.1.0/README.md +85 -0
- ragas_ainative-0.1.0/pyproject.toml +62 -0
- ragas_ainative-0.1.0/ragas_ainative/__init__.py +42 -0
- ragas_ainative-0.1.0/ragas_ainative/config.py +225 -0
- ragas_ainative-0.1.0/ragas_ainative/provision.py +174 -0
- ragas_ainative-0.1.0/ragas_ainative.egg-info/PKG-INFO +116 -0
- ragas_ainative-0.1.0/ragas_ainative.egg-info/SOURCES.txt +12 -0
- ragas_ainative-0.1.0/ragas_ainative.egg-info/dependency_links.txt +1 -0
- ragas_ainative-0.1.0/ragas_ainative.egg-info/requires.txt +5 -0
- ragas_ainative-0.1.0/ragas_ainative.egg-info/top_level.txt +1 -0
- ragas_ainative-0.1.0/setup.cfg +4 -0
- ragas_ainative-0.1.0/tests/test_config.py +309 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ragas_ainative
|
|
@@ -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"
|