rag-sentinel 0.1.1__py3-none-any.whl → 0.1.3__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.
- rag_sentinel/__init__.py +1 -1
- rag_sentinel/evaluator.py +150 -165
- {rag_sentinel-0.1.1.dist-info → rag_sentinel-0.1.3.dist-info}/METADATA +1 -1
- rag_sentinel-0.1.3.dist-info/RECORD +12 -0
- rag_sentinel-0.1.1.dist-info/RECORD +0 -12
- {rag_sentinel-0.1.1.dist-info → rag_sentinel-0.1.3.dist-info}/WHEEL +0 -0
- {rag_sentinel-0.1.1.dist-info → rag_sentinel-0.1.3.dist-info}/entry_points.txt +0 -0
- {rag_sentinel-0.1.1.dist-info → rag_sentinel-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {rag_sentinel-0.1.1.dist-info → rag_sentinel-0.1.3.dist-info}/top_level.txt +0 -0
rag_sentinel/__init__.py
CHANGED
rag_sentinel/evaluator.py
CHANGED
|
@@ -15,8 +15,16 @@ import pandas as pd
|
|
|
15
15
|
import mlflow
|
|
16
16
|
from dotenv import load_dotenv
|
|
17
17
|
from datasets import Dataset
|
|
18
|
-
from ragas import evaluate
|
|
19
|
-
from ragas.
|
|
18
|
+
from ragas import evaluate
|
|
19
|
+
from ragas.run_config import RunConfig
|
|
20
|
+
from ragas.metrics import (
|
|
21
|
+
Faithfulness,
|
|
22
|
+
AnswerRelevancy,
|
|
23
|
+
ContextPrecision,
|
|
24
|
+
AnswerCorrectness,
|
|
25
|
+
)
|
|
26
|
+
from langchain_openai import ChatOpenAI, OpenAIEmbeddings, AzureChatOpenAI, AzureOpenAIEmbeddings
|
|
27
|
+
from langchain_ollama import ChatOllama, OllamaEmbeddings
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
# =============================================================================
|
|
@@ -24,222 +32,196 @@ from ragas.metrics import faithfulness, answer_relevancy, context_precision, ans
|
|
|
24
32
|
# =============================================================================
|
|
25
33
|
|
|
26
34
|
|
|
27
|
-
def
|
|
35
|
+
def load_config(yaml_file='rag_eval_config.yaml'):
|
|
28
36
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
value: String that may contain placeholders
|
|
33
|
-
env_vars: Dictionary of environment variables
|
|
34
|
-
ini_config: ConfigParser object with ini file contents
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
str: Value with all placeholders resolved
|
|
38
|
-
"""
|
|
39
|
-
if not isinstance(value, str):
|
|
40
|
-
return value
|
|
41
|
-
|
|
42
|
-
# Resolve ${ENV:VAR_NAME} - reads from environment variables
|
|
43
|
-
env_pattern = r'\$\{ENV:([^}]+)\}'
|
|
44
|
-
def env_replacer(match):
|
|
45
|
-
var_name = match.group(1)
|
|
46
|
-
return env_vars.get(var_name, '')
|
|
47
|
-
value = re.sub(env_pattern, env_replacer, value)
|
|
48
|
-
|
|
49
|
-
# Resolve ${INI:section.key} - reads from config.ini
|
|
50
|
-
ini_pattern = r'\$\{INI:([^}]+)\}'
|
|
51
|
-
def ini_replacer(match):
|
|
52
|
-
path = match.group(1)
|
|
53
|
-
parts = path.split('.')
|
|
54
|
-
if len(parts) == 2:
|
|
55
|
-
section, key = parts
|
|
56
|
-
if ini_config.has_option(section, key):
|
|
57
|
-
return ini_config.get(section, key)
|
|
58
|
-
return ''
|
|
59
|
-
value = re.sub(ini_pattern, ini_replacer, value)
|
|
60
|
-
|
|
61
|
-
return value
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def resolve_config(obj, env_vars, ini_config):
|
|
65
|
-
"""
|
|
66
|
-
Recursively resolve all placeholders in a configuration object.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
obj: Configuration object (dict, list, or str)
|
|
70
|
-
env_vars: Dictionary of environment variables
|
|
71
|
-
ini_config: ConfigParser object
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
Configuration object with all placeholders resolved
|
|
75
|
-
"""
|
|
76
|
-
if isinstance(obj, dict):
|
|
77
|
-
return {k: resolve_config(v, env_vars, ini_config) for k, v in obj.items()}
|
|
78
|
-
elif isinstance(obj, list):
|
|
79
|
-
return [resolve_config(item, env_vars, ini_config) for item in obj]
|
|
80
|
-
elif isinstance(obj, str):
|
|
81
|
-
return resolve_placeholder(obj, env_vars, ini_config)
|
|
82
|
-
return obj
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def load_config():
|
|
86
|
-
"""
|
|
87
|
-
Load and merge configuration from .env, config.ini, and rag_eval_config.yaml.
|
|
37
|
+
Load configuration from YAML file with values resolved from .env and config.ini.
|
|
88
38
|
|
|
89
39
|
Returns:
|
|
90
40
|
dict: Fully resolved configuration dictionary
|
|
91
41
|
"""
|
|
92
|
-
# Load environment variables from .env file
|
|
93
42
|
load_dotenv('.env')
|
|
94
|
-
env_vars = dict(os.environ)
|
|
95
|
-
|
|
96
|
-
# Load INI configuration
|
|
97
|
-
ini_config = configparser.ConfigParser()
|
|
98
|
-
ini_config.read('config.ini')
|
|
99
|
-
|
|
100
|
-
# Load YAML configuration and resolve all placeholders
|
|
101
|
-
with open('rag_eval_config.yaml', 'r') as f:
|
|
102
|
-
yaml_config = yaml.safe_load(f)
|
|
103
43
|
|
|
104
|
-
|
|
44
|
+
ini = configparser.ConfigParser()
|
|
45
|
+
ini.read('config.ini')
|
|
46
|
+
|
|
47
|
+
def resolve(obj):
|
|
48
|
+
if isinstance(obj, dict):
|
|
49
|
+
return {k: resolve(v) for k, v in obj.items()}
|
|
50
|
+
if isinstance(obj, list):
|
|
51
|
+
return [resolve(i) for i in obj]
|
|
52
|
+
if isinstance(obj, str):
|
|
53
|
+
# Resolve ${ENV:VAR} and ${INI:section.key} placeholders
|
|
54
|
+
result = re.sub(r'\$\{ENV:([^}]+)\}', lambda m: os.getenv(m.group(1), ''), obj)
|
|
55
|
+
result = re.sub(r'\$\{INI:([^.]+)\.([^}]+)\}',
|
|
56
|
+
lambda m: ini.get(m.group(1), m.group(2), fallback=''), result)
|
|
57
|
+
# Convert types
|
|
58
|
+
if result.lower() == 'true': return True
|
|
59
|
+
if result.lower() == 'false': return False
|
|
60
|
+
try:
|
|
61
|
+
if '.' in result: return float(result)
|
|
62
|
+
except ValueError:
|
|
63
|
+
pass
|
|
64
|
+
return result
|
|
65
|
+
return obj
|
|
66
|
+
|
|
67
|
+
with open(yaml_file, 'r') as f:
|
|
68
|
+
return resolve(yaml.safe_load(f))
|
|
105
69
|
|
|
106
70
|
|
|
107
71
|
def get_llm(config):
|
|
108
|
-
"""Initialize LLM based on
|
|
109
|
-
|
|
110
|
-
provider = llm_config['provider'].lower()
|
|
72
|
+
"""Initialize LLM based on config."""
|
|
73
|
+
provider = config['ragas']['llm']['provider']
|
|
111
74
|
|
|
112
|
-
if provider ==
|
|
113
|
-
|
|
75
|
+
if provider == "azure":
|
|
76
|
+
azure_config = config['ragas']['llm']['azure']
|
|
114
77
|
return AzureChatOpenAI(
|
|
115
|
-
azure_endpoint=
|
|
116
|
-
api_key=
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
temperature=
|
|
78
|
+
azure_endpoint=azure_config['endpoint'],
|
|
79
|
+
api_key=azure_config['api_key'],
|
|
80
|
+
deployment_name=azure_config['deployment_name'],
|
|
81
|
+
model=azure_config['model'],
|
|
82
|
+
temperature=azure_config['temperature'],
|
|
83
|
+
api_version=azure_config['api_version']
|
|
120
84
|
)
|
|
121
|
-
elif provider ==
|
|
122
|
-
|
|
85
|
+
elif provider == "openai":
|
|
86
|
+
openai_config = config['ragas']['llm']['openai']
|
|
123
87
|
return ChatOpenAI(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
88
|
+
model=openai_config['model'],
|
|
89
|
+
temperature=openai_config['temperature'],
|
|
90
|
+
api_key=openai_config['api_key']
|
|
127
91
|
)
|
|
128
|
-
elif provider ==
|
|
129
|
-
|
|
92
|
+
elif provider == "ollama":
|
|
93
|
+
ollama_config = config['ragas']['llm']['ollama']
|
|
130
94
|
return ChatOllama(
|
|
131
|
-
base_url=
|
|
132
|
-
model=
|
|
133
|
-
temperature=
|
|
95
|
+
base_url=ollama_config['base_url'],
|
|
96
|
+
model=ollama_config['model'],
|
|
97
|
+
temperature=ollama_config['temperature']
|
|
134
98
|
)
|
|
135
99
|
else:
|
|
136
|
-
raise ValueError(f"
|
|
100
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
|
137
101
|
|
|
138
102
|
|
|
139
103
|
def get_embeddings(config):
|
|
140
|
-
"""Initialize embeddings based on
|
|
141
|
-
|
|
142
|
-
provider = emb_config['provider'].lower()
|
|
104
|
+
"""Initialize embeddings based on config."""
|
|
105
|
+
provider = config['ragas']['embeddings']['provider']
|
|
143
106
|
|
|
144
|
-
if provider ==
|
|
145
|
-
|
|
107
|
+
if provider == "azure":
|
|
108
|
+
azure_config = config['ragas']['embeddings']['azure']
|
|
146
109
|
return AzureOpenAIEmbeddings(
|
|
147
|
-
azure_endpoint=
|
|
148
|
-
api_key=
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
azure_endpoint=azure_config['endpoint'],
|
|
111
|
+
api_key=azure_config['api_key'],
|
|
112
|
+
deployment=azure_config['deployment_name'],
|
|
113
|
+
api_version=azure_config['api_version']
|
|
151
114
|
)
|
|
152
|
-
elif provider ==
|
|
153
|
-
|
|
115
|
+
elif provider == "openai":
|
|
116
|
+
openai_config = config['ragas']['embeddings']['openai']
|
|
154
117
|
return OpenAIEmbeddings(
|
|
155
|
-
|
|
156
|
-
|
|
118
|
+
model=openai_config['model'],
|
|
119
|
+
api_key=openai_config['api_key']
|
|
157
120
|
)
|
|
158
|
-
elif provider ==
|
|
159
|
-
|
|
121
|
+
elif provider == "ollama":
|
|
122
|
+
ollama_config = config['ragas']['embeddings']['ollama']
|
|
160
123
|
return OllamaEmbeddings(
|
|
161
|
-
base_url=
|
|
162
|
-
model=
|
|
124
|
+
base_url=ollama_config['base_url'],
|
|
125
|
+
model=ollama_config['model']
|
|
163
126
|
)
|
|
164
127
|
else:
|
|
165
|
-
raise ValueError(f"
|
|
128
|
+
raise ValueError(f"Unsupported embeddings provider: {provider}")
|
|
166
129
|
|
|
167
130
|
|
|
168
131
|
def get_metrics(config):
|
|
169
|
-
"""Get
|
|
132
|
+
"""Get Ragas metrics based on config."""
|
|
170
133
|
metric_map = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
134
|
+
"Faithfulness": Faithfulness(),
|
|
135
|
+
"AnswerRelevancy": AnswerRelevancy(),
|
|
136
|
+
"ContextPrecision": ContextPrecision(),
|
|
137
|
+
"AnswerCorrectness": AnswerCorrectness(),
|
|
175
138
|
}
|
|
176
|
-
|
|
139
|
+
|
|
140
|
+
metric_names = config['ragas']['metrics']
|
|
141
|
+
return [metric_map[name] for name in metric_names if name in metric_map]
|
|
177
142
|
|
|
178
143
|
|
|
179
144
|
def get_auth_headers_and_cookies(config):
|
|
180
|
-
"""Get authentication headers and cookies."""
|
|
181
|
-
auth_config = config
|
|
182
|
-
auth_type = auth_config.get('type', 'none')
|
|
145
|
+
"""Get authentication headers and cookies based on config."""
|
|
146
|
+
auth_config = config['backend']['auth']
|
|
147
|
+
auth_type = auth_config.get('type', 'none')
|
|
148
|
+
|
|
183
149
|
headers = {}
|
|
184
150
|
cookies = {}
|
|
185
151
|
|
|
186
|
-
if auth_type ==
|
|
187
|
-
cookie_name = auth_config
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
token = auth_config.get('bearer_token', '')
|
|
193
|
-
if token:
|
|
194
|
-
headers['Authorization'] = f'Bearer {token}'
|
|
195
|
-
elif auth_type == 'header':
|
|
196
|
-
header_name = auth_config.get('header_name', '')
|
|
197
|
-
header_value = auth_config.get('header_value', '')
|
|
198
|
-
if header_name and header_value:
|
|
199
|
-
headers[header_name] = header_value
|
|
152
|
+
if auth_type == "cookie":
|
|
153
|
+
cookies[auth_config['cookie_name']] = auth_config['cookie_value']
|
|
154
|
+
elif auth_type == "bearer":
|
|
155
|
+
headers['Authorization'] = f"Bearer {auth_config['bearer_token']}"
|
|
156
|
+
elif auth_type == "header":
|
|
157
|
+
headers[auth_config['header_name']] = auth_config['header_value']
|
|
200
158
|
|
|
201
159
|
return headers, cookies
|
|
202
160
|
|
|
203
161
|
|
|
204
162
|
def extract_response_data(response, endpoint_config):
|
|
205
163
|
"""Extract data from API response."""
|
|
206
|
-
|
|
207
|
-
|
|
164
|
+
if endpoint_config.get('stream', False):
|
|
165
|
+
return "".join(chunk.decode() for chunk in response.iter_content(chunk_size=None))
|
|
208
166
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
167
|
+
# Try to parse as JSON first
|
|
168
|
+
try:
|
|
169
|
+
data = response.json()
|
|
170
|
+
response_key = endpoint_config.get('response_key')
|
|
171
|
+
if response_key:
|
|
172
|
+
return data.get(response_key)
|
|
173
|
+
return data
|
|
174
|
+
except:
|
|
175
|
+
# If JSON parsing fails, return as plain text
|
|
176
|
+
return response.text
|
|
218
177
|
|
|
219
178
|
|
|
220
179
|
def make_api_request(base_url, endpoint_config, query, chat_id, auth_headers, auth_cookies, verify_ssl=True):
|
|
221
180
|
"""Make API request to backend."""
|
|
222
|
-
url = base_url
|
|
223
|
-
method = endpoint_config.get('method', 'POST')
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
body
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
181
|
+
url = base_url + endpoint_config['path']
|
|
182
|
+
method = endpoint_config.get('method', 'POST')
|
|
183
|
+
|
|
184
|
+
headers = {**endpoint_config.get('headers', {}), **auth_headers}
|
|
185
|
+
|
|
186
|
+
# Flexible body preparation
|
|
187
|
+
body = {}
|
|
188
|
+
for key, value in endpoint_config.get('body', {}).items():
|
|
189
|
+
if isinstance(value, str) and ("{query}" in value or "{chat_id}" in value):
|
|
190
|
+
body[key] = value.format(query=query, chat_id=chat_id)
|
|
191
|
+
elif key == "chat_id":
|
|
192
|
+
try:
|
|
193
|
+
body[key] = int(chat_id)
|
|
194
|
+
except (ValueError, TypeError):
|
|
195
|
+
body[key] = chat_id
|
|
196
|
+
else:
|
|
197
|
+
body[key] = value
|
|
198
|
+
|
|
199
|
+
if method.upper() == 'POST':
|
|
200
|
+
resp = requests.post(
|
|
201
|
+
url,
|
|
202
|
+
json=body,
|
|
203
|
+
headers=headers,
|
|
204
|
+
cookies=auth_cookies,
|
|
205
|
+
stream=endpoint_config.get('stream', False),
|
|
206
|
+
verify=verify_ssl
|
|
207
|
+
)
|
|
208
|
+
elif method.upper() == 'GET':
|
|
209
|
+
resp = requests.get(
|
|
210
|
+
url,
|
|
211
|
+
params=body,
|
|
212
|
+
headers=headers,
|
|
213
|
+
cookies=auth_cookies,
|
|
214
|
+
verify=verify_ssl
|
|
215
|
+
)
|
|
234
216
|
else:
|
|
235
|
-
|
|
217
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
236
218
|
|
|
237
|
-
|
|
238
|
-
return
|
|
219
|
+
resp.raise_for_status()
|
|
220
|
+
return resp
|
|
239
221
|
|
|
240
222
|
|
|
241
223
|
def get_context(config, query, chat_id, auth_headers, auth_cookies):
|
|
242
|
-
"""
|
|
224
|
+
"""Retrieve context from backend API."""
|
|
243
225
|
base_url = config['backend']['base_url']
|
|
244
226
|
endpoint_config = config['backend']['endpoints']['context']
|
|
245
227
|
verify_ssl = config['backend'].get('verify_ssl', True)
|
|
@@ -247,9 +229,12 @@ def get_context(config, query, chat_id, auth_headers, auth_cookies):
|
|
|
247
229
|
response = make_api_request(base_url, endpoint_config, query, chat_id, auth_headers, auth_cookies, verify_ssl)
|
|
248
230
|
context = extract_response_data(response, endpoint_config)
|
|
249
231
|
|
|
250
|
-
if isinstance(context,
|
|
251
|
-
return [
|
|
252
|
-
|
|
232
|
+
if isinstance(context, str):
|
|
233
|
+
return [context]
|
|
234
|
+
elif isinstance(context, list):
|
|
235
|
+
return context
|
|
236
|
+
else:
|
|
237
|
+
return [str(context)]
|
|
253
238
|
|
|
254
239
|
|
|
255
240
|
def get_answer(config, query, chat_id, auth_headers, auth_cookies):
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
rag_sentinel/__init__.py,sha256=KS1u5vgA5NxVHPFebebP2cTgt26nRKlC8dfGPy_Y2kM,672
|
|
2
|
+
rag_sentinel/cli.py,sha256=EacFBn_7TBsP1CPw9MmNUyGcAG-MO7c1UDz2v3wPkz4,8499
|
|
3
|
+
rag_sentinel/evaluator.py,sha256=6HcVv5quf0Hnxz3UkauuaODFt4xAH9Bo61dBZCxGQSw,12296
|
|
4
|
+
rag_sentinel/templates/.env.template,sha256=FabB1i4pUkU8gdNLRt2D8mgltY4AudClq8rx6vS33xc,1120
|
|
5
|
+
rag_sentinel/templates/config.ini.template,sha256=OeW21j4LXxXnFCPVvOZhdZOq1id0BQLgwS5ruXSrXBQ,1016
|
|
6
|
+
rag_sentinel/templates/rag_eval_config.yaml,sha256=zRPMOngALsbhgQbkKeNvXc8VVxzDJrASpSIpGTpVKlk,3080
|
|
7
|
+
rag_sentinel-0.1.3.dist-info/licenses/LICENSE,sha256=0bRNV4OZXZGaeA4PLR0CZKk1peLyIw977fV9K5jAGws,1074
|
|
8
|
+
rag_sentinel-0.1.3.dist-info/METADATA,sha256=D6mF4IRJ3r23_4Puc9EXJkFnsOw9O-6KAwZz1mfZWhM,2716
|
|
9
|
+
rag_sentinel-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
+
rag_sentinel-0.1.3.dist-info/entry_points.txt,sha256=ZBQp5JLnfMLjgLX3UdzX2rainIgvqkq36Wtf_VLa9ak,55
|
|
11
|
+
rag_sentinel-0.1.3.dist-info/top_level.txt,sha256=qk1BCc3wrLshA3-jf0Jb4bFlZF-ARIjZfYaqAW1Aq0E,13
|
|
12
|
+
rag_sentinel-0.1.3.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
rag_sentinel/__init__.py,sha256=jrVnGWJh60e1fucEP8DuQtFaXQcls8Rsw9tuUfKI87Y,672
|
|
2
|
-
rag_sentinel/cli.py,sha256=EacFBn_7TBsP1CPw9MmNUyGcAG-MO7c1UDz2v3wPkz4,8499
|
|
3
|
-
rag_sentinel/evaluator.py,sha256=tE_pqwaauxsZ3QU2yq-PxW6Ig1bqz31SAnydDcwAJfQ,12915
|
|
4
|
-
rag_sentinel/templates/.env.template,sha256=FabB1i4pUkU8gdNLRt2D8mgltY4AudClq8rx6vS33xc,1120
|
|
5
|
-
rag_sentinel/templates/config.ini.template,sha256=OeW21j4LXxXnFCPVvOZhdZOq1id0BQLgwS5ruXSrXBQ,1016
|
|
6
|
-
rag_sentinel/templates/rag_eval_config.yaml,sha256=zRPMOngALsbhgQbkKeNvXc8VVxzDJrASpSIpGTpVKlk,3080
|
|
7
|
-
rag_sentinel-0.1.1.dist-info/licenses/LICENSE,sha256=0bRNV4OZXZGaeA4PLR0CZKk1peLyIw977fV9K5jAGws,1074
|
|
8
|
-
rag_sentinel-0.1.1.dist-info/METADATA,sha256=j3sueNd2mWD350G05M_hYWgyHq8CJWj9aeVR9K7r7uM,2716
|
|
9
|
-
rag_sentinel-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
-
rag_sentinel-0.1.1.dist-info/entry_points.txt,sha256=ZBQp5JLnfMLjgLX3UdzX2rainIgvqkq36Wtf_VLa9ak,55
|
|
11
|
-
rag_sentinel-0.1.1.dist-info/top_level.txt,sha256=qk1BCc3wrLshA3-jf0Jb4bFlZF-ARIjZfYaqAW1Aq0E,13
|
|
12
|
-
rag_sentinel-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|