ragas-examples 0.3.6__tar.gz → 0.3.7__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_examples-0.3.6/ragas_examples.egg-info → ragas_examples-0.3.7}/PKG-INFO +1 -1
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/evals.py +1 -1
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/run_prompt.py +6 -5
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/_version.py +3 -3
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/evals.py +43 -45
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/prompt.py +30 -21
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/evals.py +22 -42
- ragas_examples-0.3.7/ragas_examples/judge_alignment/__init__.py +29 -0
- ragas_examples-0.3.7/ragas_examples/judge_alignment/evals.py +229 -0
- ragas_examples-0.3.7/ragas_examples/text2sql/__init__.py +53 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/analyze_errors.py +18 -53
- ragas_examples-0.3.7/ragas_examples/text2sql/data_utils.py +467 -0
- ragas_examples-0.3.7/ragas_examples/text2sql/db_utils.py +301 -0
- ragas_examples-0.3.7/ragas_examples/text2sql/evals.py +232 -0
- ragas_examples-0.3.7/ragas_examples/text2sql/text2sql_agent.py +133 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7/ragas_examples.egg-info}/PKG-INFO +1 -1
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/SOURCES.txt +4 -0
- ragas_examples-0.3.6/ragas_examples/text2sql/__init__.py +0 -36
- ragas_examples-0.3.6/ragas_examples/text2sql/data_utils.py +0 -380
- ragas_examples-0.3.6/ragas_examples/text2sql/db_utils.py +0 -458
- ragas_examples-0.3.6/ragas_examples/text2sql/evals.py +0 -387
- ragas_examples-0.3.6/ragas_examples/text2sql/text2sql_agent.py +0 -522
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/LICENSE +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/README.md +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/gdrive_append_example.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/gdrive_backend_example.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/datasets/support_triage.csv +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/promptv1.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/promptv2_fewshot.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/oci_genai_example.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/pyproject.toml +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/agent.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/evals.py +2 -2
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/datasets/discount_benchmark.csv +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/datasets/hf_doc_qa_eval.csv +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/rag.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/ambiguous_removal_request.json +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/duplicate_addition.json +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/repeated_removal.json +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/evals.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/llamaindex_agent.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/evals.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/prompt.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/evals.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/rag.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/datasets/booksql_sample.csv +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt_v2.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt_v3.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/validate_sql_dataset.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/__init__.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/evals.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/workflow.py +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/dependency_links.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/requires.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/top_level.txt +0 -0
- {ragas_examples-0.3.6 → ragas_examples-0.3.7}/setup.cfg +0 -0
|
@@ -69,7 +69,7 @@ def priority_accuracy(prediction: str, expected_priority: str):
|
|
|
69
69
|
async def support_triage_experiment(row, prompt_file: str, experiment_name: str):
|
|
70
70
|
"""Experiment function for support triage evaluation."""
|
|
71
71
|
# Get model response
|
|
72
|
-
response = await
|
|
72
|
+
response = await run_prompt(row["text"], prompt_file=prompt_file)
|
|
73
73
|
|
|
74
74
|
# Parse response to extract predicted values
|
|
75
75
|
try:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from openai import
|
|
3
|
+
from openai import AsyncOpenAI
|
|
4
4
|
|
|
5
|
-
client =
|
|
5
|
+
client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def load_prompt(prompt_file: str) -> str:
|
|
@@ -11,12 +11,12 @@ def load_prompt(prompt_file: str) -> str:
|
|
|
11
11
|
return f.read().strip()
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def run_prompt(ticket_text: str, prompt_file: str = "promptv1.txt"):
|
|
14
|
+
async def run_prompt(ticket_text: str, prompt_file: str = "promptv1.txt"):
|
|
15
15
|
"""Run the prompt against a customer support ticket"""
|
|
16
16
|
system_prompt = load_prompt(prompt_file)
|
|
17
17
|
user_message = f'Ticket: "{ticket_text}"'
|
|
18
18
|
|
|
19
|
-
response = client.chat.completions.create(
|
|
19
|
+
response = await client.chat.completions.create(
|
|
20
20
|
model="gpt-5-mini-2025-08-07",
|
|
21
21
|
response_format={"type": "json_object"},
|
|
22
22
|
messages=[
|
|
@@ -33,9 +33,10 @@ def run_prompt(ticket_text: str, prompt_file: str = "promptv1.txt"):
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
if __name__ == "__main__":
|
|
36
|
+
import asyncio
|
|
36
37
|
# Test with a sample customer support ticket
|
|
37
38
|
test_ticket = "SSO via Okta succeeds then bounces me back to /login with no session. Colleagues can sign in. I tried clearing cookies; same result. Error in devtools: state mismatch. I'm blocked from our boards."
|
|
38
39
|
print("Test ticket:")
|
|
39
40
|
print(f'"{test_ticket}"')
|
|
40
41
|
print("\nResponse:")
|
|
41
|
-
print(run_prompt(test_ticket))
|
|
42
|
+
print(asyncio.run(run_prompt(test_ticket)))
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.3.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
31
|
+
__version__ = version = '0.3.7'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 3, 7)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gf31c365e9'
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
import asyncio
|
|
3
2
|
import datetime
|
|
4
3
|
import json
|
|
5
4
|
import os
|
|
@@ -7,6 +6,10 @@ import sys
|
|
|
7
6
|
from typing import List, Optional
|
|
8
7
|
|
|
9
8
|
import pandas as pd
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
# Load environment variables
|
|
12
|
+
load_dotenv(".env")
|
|
10
13
|
|
|
11
14
|
from ragas import experiment
|
|
12
15
|
from ragas.dataset import Dataset
|
|
@@ -35,50 +38,44 @@ def discount_accuracy(prediction: str, expected_discount):
|
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Get model response (run blocking call in thread to keep async runner responsive)
|
|
44
|
-
response = await asyncio.to_thread(
|
|
45
|
-
run_prompt, row["customer_profile"], model=model_name
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
# Parse response (strict JSON mode expected)
|
|
49
|
-
try:
|
|
50
|
-
parsed_json = json.loads(response)
|
|
51
|
-
predicted_discount = parsed_json.get("discount_percentage")
|
|
52
|
-
except Exception:
|
|
53
|
-
predicted_discount = None
|
|
41
|
+
@experiment()
|
|
42
|
+
async def benchmark_experiment(row, model_name: str):
|
|
43
|
+
"""Benchmark experiment function that evaluates a model on discount calculation."""
|
|
44
|
+
# Get model response
|
|
45
|
+
response = await run_prompt(row["customer_profile"], model=model_name)
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
47
|
+
# Parse response (strict JSON mode expected)
|
|
48
|
+
try:
|
|
49
|
+
parsed_json = json.loads(response)
|
|
50
|
+
predicted_discount = parsed_json.get("discount_percentage")
|
|
51
|
+
except Exception:
|
|
52
|
+
predicted_discount = None
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"response": response,
|
|
65
|
-
"predicted_discount": predicted_discount,
|
|
66
|
-
"score": score.value,
|
|
67
|
-
"score_reason": score.reason,
|
|
68
|
-
}
|
|
54
|
+
# Score the response
|
|
55
|
+
score = discount_accuracy.score(
|
|
56
|
+
prediction=response, expected_discount=row["expected_discount"]
|
|
57
|
+
)
|
|
69
58
|
|
|
70
|
-
return
|
|
59
|
+
return {
|
|
60
|
+
**row,
|
|
61
|
+
"model": model_name,
|
|
62
|
+
"response": response,
|
|
63
|
+
"predicted_discount": predicted_discount,
|
|
64
|
+
"score": score.value,
|
|
65
|
+
"score_reason": score.reason,
|
|
66
|
+
}
|
|
71
67
|
|
|
72
68
|
|
|
73
69
|
def load_dataset():
|
|
74
|
-
"""Load the dataset from CSV file."""
|
|
75
|
-
|
|
70
|
+
"""Load the dataset from CSV file. Downloads from GitHub if not found locally."""
|
|
71
|
+
import urllib.request
|
|
76
72
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
77
|
-
|
|
78
|
-
dataset
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
dataset_path = os.path.join(current_dir, "datasets", "discount_benchmark.csv")
|
|
74
|
+
# Download dataset from GitHub if it doesn't exist locally
|
|
75
|
+
if not os.path.exists(dataset_path):
|
|
76
|
+
os.makedirs(os.path.dirname(dataset_path), exist_ok=True)
|
|
77
|
+
urllib.request.urlretrieve("https://raw.githubusercontent.com/explodinggradients/ragas/main/examples/ragas_examples/benchmark_llm/datasets/discount_benchmark.csv", dataset_path)
|
|
78
|
+
return Dataset.load(name="discount_benchmark", backend="local/csv", root_dir=current_dir)
|
|
82
79
|
|
|
83
80
|
|
|
84
81
|
def compare_inputs_to_output(
|
|
@@ -98,9 +95,9 @@ def compare_inputs_to_output(
|
|
|
98
95
|
experiment_names = []
|
|
99
96
|
for path in inputs:
|
|
100
97
|
df = pd.read_csv(path)
|
|
101
|
-
if "
|
|
102
|
-
raise ValueError(f"Missing '
|
|
103
|
-
exp_name = str(df["
|
|
98
|
+
if "model" not in df.columns:
|
|
99
|
+
raise ValueError(f"Missing 'model' column in {path}")
|
|
100
|
+
exp_name = str(df["model"].iloc[0])
|
|
104
101
|
experiment_names.append(exp_name)
|
|
105
102
|
dataframes.append(df)
|
|
106
103
|
|
|
@@ -206,11 +203,12 @@ async def run_command(model: str, name: Optional[str]) -> None:
|
|
|
206
203
|
experiments_dir = os.path.join(current_dir, "experiments")
|
|
207
204
|
os.makedirs(experiments_dir, exist_ok=True)
|
|
208
205
|
|
|
209
|
-
benchmark_experiment = create_benchmark_experiment(
|
|
210
|
-
model_name=model, experiment_name=exp_name
|
|
211
|
-
)
|
|
212
206
|
print(f"Running model evaluation ({model})...")
|
|
213
|
-
results = await benchmark_experiment.arun(
|
|
207
|
+
results = await benchmark_experiment.arun(
|
|
208
|
+
dataset,
|
|
209
|
+
name=f"{run_id}-{exp_name}",
|
|
210
|
+
model_name=model
|
|
211
|
+
)
|
|
214
212
|
print(f"✅ {exp_name}: {len(results)} cases evaluated")
|
|
215
213
|
print(f"Results saved to: {os.path.join(experiments_dir, results.name)}.csv")
|
|
216
214
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
from openai import AsyncOpenAI
|
|
5
|
+
|
|
6
|
+
# Load environment variables
|
|
7
|
+
load_dotenv(".env")
|
|
4
8
|
|
|
5
9
|
DEFAULT_MODEL = "gpt-4.1-nano-2025-04-14"
|
|
6
10
|
|
|
7
11
|
|
|
8
|
-
def get_client() ->
|
|
9
|
-
"""Lazily create an
|
|
12
|
+
def get_client() -> AsyncOpenAI:
|
|
13
|
+
"""Lazily create an AsyncOpenAI client, requiring the API key only when used.
|
|
10
14
|
|
|
11
15
|
This avoids raising errors during module import (e.g., when running --help).
|
|
12
16
|
"""
|
|
@@ -15,7 +19,7 @@ def get_client() -> OpenAI:
|
|
|
15
19
|
raise RuntimeError(
|
|
16
20
|
"OPENAI_API_KEY is not set. Please export it before running prompts."
|
|
17
21
|
)
|
|
18
|
-
return
|
|
22
|
+
return AsyncOpenAI(api_key=api_key)
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
SYSTEM_PROMPT = """
|
|
@@ -38,10 +42,10 @@ Respond in JSON format only:
|
|
|
38
42
|
"""
|
|
39
43
|
|
|
40
44
|
|
|
41
|
-
def run_prompt(prompt: str, model: str = DEFAULT_MODEL):
|
|
45
|
+
async def run_prompt(prompt: str, model: str = DEFAULT_MODEL):
|
|
42
46
|
"""Run the discount calculation prompt with the specified model."""
|
|
43
47
|
client = get_client()
|
|
44
|
-
response = client.chat.completions.create(
|
|
48
|
+
response = await client.chat.completions.create(
|
|
45
49
|
model=model,
|
|
46
50
|
response_format={"type": "json_object"},
|
|
47
51
|
messages=[
|
|
@@ -54,18 +58,23 @@ def run_prompt(prompt: str, model: str = DEFAULT_MODEL):
|
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
if __name__ == "__main__":
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
import asyncio
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
customer_profile = """
|
|
65
|
+
Customer Profile:
|
|
66
|
+
- Name: Sarah Johnson
|
|
67
|
+
- Age: 67
|
|
68
|
+
- Student: No
|
|
69
|
+
- Annual Income: $45,000
|
|
70
|
+
- Premium Member: Yes, for 3 years
|
|
71
|
+
- Account Age: 3 years
|
|
72
|
+
"""
|
|
73
|
+
print("=== System Prompt ===")
|
|
74
|
+
print(SYSTEM_PROMPT)
|
|
75
|
+
print("\n=== Customer Profile ===")
|
|
76
|
+
print(customer_profile)
|
|
77
|
+
print(f"\n=== Running Prompt with default model {DEFAULT_MODEL} ===")
|
|
78
|
+
print(await run_prompt(customer_profile, model=DEFAULT_MODEL))
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
@@ -75,21 +75,6 @@ def create_ragas_dataset(dataset_path: Path) -> Dataset:
|
|
|
75
75
|
return dataset
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
# Initialize OpenAI client and models
|
|
79
|
-
def get_openai_client():
|
|
80
|
-
"""Get AsyncOpenAI client with proper error handling."""
|
|
81
|
-
api_key = os.environ.get("OPENAI_API_KEY")
|
|
82
|
-
if not api_key:
|
|
83
|
-
raise ValueError(
|
|
84
|
-
"OPENAI_API_KEY environment variable is not set. "
|
|
85
|
-
"Please set your OpenAI API key: export OPENAI_API_KEY='your_key'"
|
|
86
|
-
)
|
|
87
|
-
return AsyncOpenAI(api_key=api_key)
|
|
88
|
-
|
|
89
|
-
def get_llm_client():
|
|
90
|
-
"""Get LLM client for metrics evaluation."""
|
|
91
|
-
openai_client = get_openai_client()
|
|
92
|
-
return instructor_llm_factory("openai", model="gpt-5-mini", client=openai_client)
|
|
93
78
|
|
|
94
79
|
|
|
95
80
|
def construct_mlflow_trace_url(trace_id: str, mlflow_host: str = "http://127.0.0.1:5000") -> str:
|
|
@@ -194,29 +179,34 @@ async def run_experiment(mode: str = "naive", model: str = "gpt-5-mini", name: O
|
|
|
194
179
|
Returns:
|
|
195
180
|
List of experiment results
|
|
196
181
|
"""
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
182
|
+
# Check for OpenAI API key
|
|
183
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
184
|
+
if not api_key:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
"OPENAI_API_KEY environment variable is not set. "
|
|
187
|
+
"Please set your OpenAI API key: export OPENAI_API_KEY='your_key'"
|
|
188
|
+
)
|
|
200
189
|
|
|
201
|
-
#
|
|
190
|
+
# Prepare dataset and initialize system
|
|
202
191
|
logger.info("Initializing RAG system...")
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
dataset = create_ragas_dataset(download_and_save_dataset())
|
|
193
|
+
|
|
194
|
+
# Initialize RAG system with inline client creation
|
|
195
|
+
openai_client = AsyncOpenAI(api_key=api_key)
|
|
196
|
+
rag = RAG(
|
|
197
|
+
llm_client=openai_client,
|
|
198
|
+
retriever=BM25Retriever(),
|
|
199
|
+
model=model,
|
|
200
|
+
mode=mode
|
|
201
|
+
)
|
|
207
202
|
logger.info("RAG system initialized!")
|
|
208
203
|
|
|
209
|
-
# Generate experiment name if not provided
|
|
210
|
-
if name is None:
|
|
211
|
-
mode_label = "agenticrag" if mode == "agentic" else "naiverag"
|
|
212
|
-
name = f"{datetime.now().strftime('%Y%m%d-%H%M%S')}_{mode_label}"
|
|
213
|
-
|
|
214
204
|
# Run evaluation experiment
|
|
215
205
|
experiment_results = await evaluate_rag.arun(
|
|
216
206
|
dataset,
|
|
217
|
-
name=name,
|
|
207
|
+
name=name or f"{datetime.now().strftime('%Y%m%d-%H%M%S')}_{'agenticrag' if mode == 'agentic' else 'naiverag'}",
|
|
218
208
|
rag=rag,
|
|
219
|
-
llm=
|
|
209
|
+
llm=instructor_llm_factory("openai", model="gpt-5-mini", client=openai_client)
|
|
220
210
|
)
|
|
221
211
|
|
|
222
212
|
# Print basic results
|
|
@@ -230,28 +220,18 @@ async def run_experiment(mode: str = "naive", model: str = "gpt-5-mini", name: O
|
|
|
230
220
|
return experiment_results
|
|
231
221
|
|
|
232
222
|
|
|
233
|
-
async def main(agentic_rag: bool = False):
|
|
234
|
-
"""
|
|
235
|
-
Main function to run the evaluation pipeline.
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
agentic_rag: If True, use agentic RAG; otherwise use naive RAG
|
|
239
|
-
"""
|
|
240
|
-
mode = "agentic" if agentic_rag else "naive"
|
|
241
|
-
return await run_experiment(mode=mode, model="gpt-5-mini")
|
|
242
|
-
|
|
243
|
-
|
|
244
223
|
if __name__ == "__main__":
|
|
245
224
|
import sys
|
|
246
225
|
|
|
247
226
|
# Simple command line argument parsing
|
|
248
227
|
agentic_mode = "--agentic" in sys.argv
|
|
228
|
+
mode = "agentic" if agentic_mode else "naive"
|
|
249
229
|
|
|
250
230
|
if agentic_mode:
|
|
251
231
|
logger.info("Running in AGENTIC mode")
|
|
252
232
|
else:
|
|
253
233
|
logger.info("Running in NAIVE mode")
|
|
254
234
|
|
|
255
|
-
asyncio.run(
|
|
235
|
+
asyncio.run(run_experiment(mode=mode, model="gpt-5-mini"))
|
|
256
236
|
|
|
257
237
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""LLM-as-judge alignment evaluation example.
|
|
2
|
+
|
|
3
|
+
Functions:
|
|
4
|
+
- load_dataset: Load annotated dataset with human judgments
|
|
5
|
+
- judge_experiment: Run evaluation (Judge → Compare)
|
|
6
|
+
- judge_alignment: Alignment metric comparing judge and human labels
|
|
7
|
+
|
|
8
|
+
Metrics:
|
|
9
|
+
- accuracy_metric: Baseline judge metric
|
|
10
|
+
- accuracy_metric_v2: Improved judge metric with few-shot examples
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .evals import (
|
|
14
|
+
load_dataset,
|
|
15
|
+
judge_experiment,
|
|
16
|
+
judge_alignment,
|
|
17
|
+
accuracy_metric,
|
|
18
|
+
accuracy_metric_v2,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"load_dataset",
|
|
23
|
+
"judge_experiment",
|
|
24
|
+
"judge_alignment",
|
|
25
|
+
"accuracy_metric",
|
|
26
|
+
"accuracy_metric_v2",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM-as-Judge alignment evaluation example.
|
|
3
|
+
|
|
4
|
+
Evaluates how well an LLM judge aligns with human judgments by:
|
|
5
|
+
- Using pre-existing responses from the dataset
|
|
6
|
+
- LLM judge evaluates each response
|
|
7
|
+
- Measuring alignment between judge and human labels
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from dotenv import load_dotenv
|
|
17
|
+
from openai import AsyncOpenAI
|
|
18
|
+
|
|
19
|
+
from ragas import Dataset, experiment
|
|
20
|
+
from ragas.llms import instructor_llm_factory
|
|
21
|
+
from ragas.metrics import DiscreteMetric
|
|
22
|
+
from ragas.metrics.discrete import discrete_metric
|
|
23
|
+
from ragas.metrics.result import MetricResult
|
|
24
|
+
|
|
25
|
+
load_dotenv()
|
|
26
|
+
|
|
27
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
30
|
+
logging.getLogger("openai._base_client").setLevel(logging.WARNING)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Define baseline judge metric with simple prompt
|
|
34
|
+
accuracy_metric = DiscreteMetric(
|
|
35
|
+
name="accuracy",
|
|
36
|
+
prompt="Check if the response contains points mentioned from the grading notes and return 'pass' or 'fail'.\n\nResponse: {response}\nGrading Notes: {grading_notes}",
|
|
37
|
+
allowed_values=["pass", "fail"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Define improved judge metric with enhanced evaluation criteria
|
|
41
|
+
accuracy_metric_v2 = DiscreteMetric(
|
|
42
|
+
name="accuracy",
|
|
43
|
+
prompt="""Evaluate if the response covers ALL the key concepts from the grading notes. Accept semantic equivalents but carefully check for missing concepts.
|
|
44
|
+
|
|
45
|
+
ABBREVIATION GUIDE - decode these correctly:
|
|
46
|
+
• Financial: val=valuation, post-$=post-money, rev=revenue, ARR/MRR=Annual/Monthly Recurring Revenue, COGS=Cost of Goods Sold, Opex=Operating Expenses, LTV=Lifetime Value, CAC=Customer Acquisition Cost
|
|
47
|
+
• Business: mkt=market, reg/regs=regulation/regulatory, corp gov=corporate governance, integr=integration, S&M=Sales & Marketing, R&D=Research & Development, acq=acquisition
|
|
48
|
+
• Technical: sys=system, elim=elimination, IP=Intellectual Property, TAM=Total Addressable Market, diff=differentiation
|
|
49
|
+
• Metrics: NPS=Net Promoter Score, SROI=Social Return on Investment, proj=projection, cert=certification
|
|
50
|
+
|
|
51
|
+
EVALUATION APPROACH:
|
|
52
|
+
|
|
53
|
+
Step 1 - Parse grading notes into distinct concepts:
|
|
54
|
+
- Separate by commas, semicolons, or line breaks
|
|
55
|
+
- Each item is a concept that must be verified
|
|
56
|
+
- Example: "*Gross Margin* >40%, CAC, LTV:CAC >3:1" = 3 concepts
|
|
57
|
+
|
|
58
|
+
Step 2 - For each concept, check if it's addressed:
|
|
59
|
+
- Accept semantic equivalents (e.g., "customer acquisition cost" = "CAC")
|
|
60
|
+
- Accept implicit coverage when it's clear (e.g., "revenue forecasting" covers "historical vs forecasted rev")
|
|
61
|
+
- Be flexible on exact numbers (e.g., "around 40%" acceptable for ">40%")
|
|
62
|
+
|
|
63
|
+
Step 3 - Count missing concepts:
|
|
64
|
+
- Missing 0 concepts = PASS
|
|
65
|
+
- Missing 1+ concepts = FAIL (even one genuinely missing concept should fail)
|
|
66
|
+
- Exception: If a long list (10+ items) has 1 very minor detail missing but all major points covered, use judgment
|
|
67
|
+
|
|
68
|
+
CRITICAL RULES:
|
|
69
|
+
|
|
70
|
+
1. Do NOT require exact wording - "market demand" = "mkt demand" = "demand analysis"
|
|
71
|
+
|
|
72
|
+
2. Markers (* or !) mean important, not mandatory exact phrases:
|
|
73
|
+
- "*traction evidence*" can be satisfied by discussing metrics, growth, or validation
|
|
74
|
+
- "!unbiased assumptions" can be satisfied by discussing assumption methodology
|
|
75
|
+
|
|
76
|
+
3. Numbers should be mentioned but accept approximations:
|
|
77
|
+
- "$47B to $10B" can be "$47 billion dropped to around $10 billion"
|
|
78
|
+
- "LTV:CAC >3:1" can be "LTV to CAC ratio of at least 3 to 1" or "3x or higher"
|
|
79
|
+
|
|
80
|
+
4. FAIL only when concepts are genuinely absent:
|
|
81
|
+
- If notes mention "liquidation prefs, anti-dilution, board seats" but response only has board seats → FAIL
|
|
82
|
+
- If notes mention "scalability, tech debt, IP" but response never discusses technical risks → FAIL
|
|
83
|
+
- If notes mention "GDPR compliance" and response never mentions GDPR or EU regulations → FAIL
|
|
84
|
+
|
|
85
|
+
5. PASS when ALL concepts present:
|
|
86
|
+
- All concepts covered, even with different wording → PASS
|
|
87
|
+
- Concepts addressed implicitly when clearly implied → PASS
|
|
88
|
+
- Minor phrasing differences → PASS
|
|
89
|
+
- One or more concepts genuinely absent → FAIL
|
|
90
|
+
|
|
91
|
+
Response: {response}
|
|
92
|
+
|
|
93
|
+
Grading Notes: {grading_notes}
|
|
94
|
+
|
|
95
|
+
Are ALL distinct concepts from the grading notes covered in the response (accepting semantic equivalents and implicit coverage)?""",
|
|
96
|
+
allowed_values=["pass", "fail"],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_dataset(csv_path: Optional[Path] = None) -> Dataset:
|
|
101
|
+
"""Load annotated dataset with human judgments.
|
|
102
|
+
|
|
103
|
+
Expected columns: question, grading_notes, response, target (pass/fail)
|
|
104
|
+
"""
|
|
105
|
+
path = csv_path or (Path(__file__).resolve().parent / "datasets" / "benchmark_df.csv")
|
|
106
|
+
df = pd.read_csv(path)
|
|
107
|
+
|
|
108
|
+
dataset = Dataset(name="llm_judge_alignment", backend="local/csv", root_dir=".")
|
|
109
|
+
|
|
110
|
+
for _, row in df.iterrows():
|
|
111
|
+
dataset.append({
|
|
112
|
+
"question": row["question"],
|
|
113
|
+
"grading_notes": row["grading_notes"],
|
|
114
|
+
"response": row["response"],
|
|
115
|
+
"target": str(row["target"]).strip().lower(),
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return dataset
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@discrete_metric(name="judge_alignment", allowed_values=["pass", "fail"])
|
|
122
|
+
def judge_alignment(judge_label: str, human_label: str) -> MetricResult:
|
|
123
|
+
"""Compare judge decision with human label."""
|
|
124
|
+
judge = judge_label.strip().lower()
|
|
125
|
+
human = human_label.strip().lower()
|
|
126
|
+
|
|
127
|
+
if judge == human:
|
|
128
|
+
return MetricResult(value="pass", reason=f"Judge={judge}; Human={human}")
|
|
129
|
+
|
|
130
|
+
return MetricResult(value="fail", reason=f"Judge={judge}; Human={human}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@experiment()
|
|
134
|
+
async def judge_experiment(
|
|
135
|
+
row: Dict[str, Any],
|
|
136
|
+
accuracy_metric: DiscreteMetric,
|
|
137
|
+
llm,
|
|
138
|
+
):
|
|
139
|
+
"""Run complete evaluation: Judge → Compare with human."""
|
|
140
|
+
# Step 1: Get response (in production, this is where you'd call your LLM app)
|
|
141
|
+
# For this evaluation, we use pre-existing responses from the dataset
|
|
142
|
+
app_response = row["response"]
|
|
143
|
+
|
|
144
|
+
# Step 2: Judge evaluates the response
|
|
145
|
+
judge_score = await accuracy_metric.ascore(
|
|
146
|
+
question=row["question"],
|
|
147
|
+
grading_notes=row["grading_notes"],
|
|
148
|
+
response=app_response,
|
|
149
|
+
llm=llm,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Step 3: Compare judge decision with human target
|
|
153
|
+
alignment = judge_alignment.score(
|
|
154
|
+
judge_label=judge_score.value,
|
|
155
|
+
human_label=row["target"]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
**row,
|
|
160
|
+
"judge_label": judge_score.value,
|
|
161
|
+
"judge_critique": judge_score.reason,
|
|
162
|
+
"alignment": alignment.value,
|
|
163
|
+
"alignment_reason": alignment.reason,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
async def main():
|
|
168
|
+
"""Example: evaluate judge with baseline prompt."""
|
|
169
|
+
# Load dataset
|
|
170
|
+
dataset = load_dataset()
|
|
171
|
+
logger.info(f"Loaded dataset with {len(dataset)} samples")
|
|
172
|
+
|
|
173
|
+
# Initialize LLM client
|
|
174
|
+
logger.info("Initializing LLM client with model: gpt-4o-mini")
|
|
175
|
+
openai_client = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
|
176
|
+
llm = instructor_llm_factory("openai", model="gpt-4o-mini", client=openai_client)
|
|
177
|
+
|
|
178
|
+
# Run baseline evaluation
|
|
179
|
+
logger.info("Running baseline evaluation...")
|
|
180
|
+
results = await judge_experiment.arun(
|
|
181
|
+
dataset,
|
|
182
|
+
name="judge_baseline_v1_gpt-4o-mini",
|
|
183
|
+
accuracy_metric=accuracy_metric,
|
|
184
|
+
llm=llm,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
passed = sum(1 for r in results if r["alignment"] == "pass")
|
|
188
|
+
total = len(results)
|
|
189
|
+
logger.info(f"✅ Baseline alignment: {passed}/{total} passed ({passed/total:.1%})")
|
|
190
|
+
|
|
191
|
+
return results
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def main_v2():
|
|
195
|
+
"""Evaluate judge with improved v2 prompt."""
|
|
196
|
+
# Load dataset
|
|
197
|
+
dataset = load_dataset()
|
|
198
|
+
logger.info(f"Loaded dataset with {len(dataset)} samples")
|
|
199
|
+
|
|
200
|
+
# Initialize LLM client
|
|
201
|
+
logger.info("Initializing LLM client with model: gpt-4o-mini")
|
|
202
|
+
openai_client = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
|
203
|
+
llm = instructor_llm_factory("openai", model="gpt-4o-mini", client=openai_client)
|
|
204
|
+
|
|
205
|
+
# Run v2 evaluation with improved prompt
|
|
206
|
+
logger.info("Running v2 evaluation with improved prompt...")
|
|
207
|
+
results = await judge_experiment.arun(
|
|
208
|
+
dataset,
|
|
209
|
+
name="judge_accuracy_v2_gpt-4o-mini",
|
|
210
|
+
accuracy_metric=accuracy_metric_v2,
|
|
211
|
+
llm=llm,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
passed = sum(1 for r in results if r["alignment"] == "pass")
|
|
215
|
+
total = len(results)
|
|
216
|
+
logger.info(f"✅ V2 alignment: {passed}/{total} passed ({passed/total:.1%})")
|
|
217
|
+
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
import asyncio
|
|
223
|
+
import sys
|
|
224
|
+
|
|
225
|
+
# Run v2 if --v2 flag is passed, otherwise run baseline
|
|
226
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--v2":
|
|
227
|
+
asyncio.run(main_v2())
|
|
228
|
+
else:
|
|
229
|
+
asyncio.run(main())
|