ragas-examples 0.3.5__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.
Files changed (34) hide show
  1. ragas_examples/__init__.py +17 -0
  2. ragas_examples/_version.py +34 -0
  3. ragas_examples/agent_evals/__init__.py +0 -0
  4. ragas_examples/agent_evals/agent.py +409 -0
  5. ragas_examples/agent_evals/evals.py +78 -0
  6. ragas_examples/benchmark_llm/__init__.py +0 -0
  7. ragas_examples/benchmark_llm/datasets/discount_benchmark.csv +11 -0
  8. ragas_examples/benchmark_llm/evals.py +274 -0
  9. ragas_examples/benchmark_llm/prompt.py +71 -0
  10. ragas_examples/prompt_evals/__init__.py +0 -0
  11. ragas_examples/prompt_evals/evals.py +75 -0
  12. ragas_examples/prompt_evals/prompt.py +32 -0
  13. ragas_examples/rag_eval/__init__.py +0 -0
  14. ragas_examples/rag_eval/evals.py +84 -0
  15. ragas_examples/rag_eval/rag.py +481 -0
  16. ragas_examples/text2sql/__init__.py +36 -0
  17. ragas_examples/text2sql/analyze_errors.py +198 -0
  18. ragas_examples/text2sql/data_utils.py +380 -0
  19. ragas_examples/text2sql/datasets/booksql_sample.csv +100 -0
  20. ragas_examples/text2sql/db_utils.py +458 -0
  21. ragas_examples/text2sql/evals.py +387 -0
  22. ragas_examples/text2sql/prompt.txt +111 -0
  23. ragas_examples/text2sql/prompt_v2.txt +135 -0
  24. ragas_examples/text2sql/prompt_v3.txt +144 -0
  25. ragas_examples/text2sql/text2sql_agent.py +522 -0
  26. ragas_examples/text2sql/validate_sql_dataset.py +317 -0
  27. ragas_examples/workflow_eval/__init__.py +0 -0
  28. ragas_examples/workflow_eval/evals.py +107 -0
  29. ragas_examples/workflow_eval/workflow.py +670 -0
  30. ragas_examples-0.3.5.dist-info/METADATA +130 -0
  31. ragas_examples-0.3.5.dist-info/RECORD +34 -0
  32. ragas_examples-0.3.5.dist-info/WHEEL +5 -0
  33. ragas_examples-0.3.5.dist-info/licenses/LICENSE +201 -0
  34. ragas_examples-0.3.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,274 @@
1
+ import argparse
2
+ import asyncio
3
+ import datetime
4
+ import json
5
+ import os
6
+ import sys
7
+ from typing import List, Optional
8
+
9
+ import pandas as pd
10
+
11
+ from ragas import experiment
12
+ from ragas.dataset import Dataset
13
+ from ragas.metrics.discrete import discrete_metric
14
+ from ragas.metrics.result import MetricResult
15
+
16
+ from .prompt import DEFAULT_MODEL, run_prompt
17
+
18
+
19
+ @discrete_metric(name="discount_accuracy", allowed_values=["correct", "incorrect"])
20
+ def discount_accuracy(prediction: str, expected_discount):
21
+ """Check if the discount prediction is correct."""
22
+ parsed_json = json.loads(prediction)
23
+ predicted_discount = parsed_json.get("discount_percentage")
24
+ expected_discount_int = int(expected_discount)
25
+
26
+ if predicted_discount == expected_discount_int:
27
+ return MetricResult(
28
+ value="correct",
29
+ reason=f"Correctly calculated discount={expected_discount_int}%",
30
+ )
31
+ else:
32
+ return MetricResult(
33
+ value="incorrect",
34
+ reason=f"Expected discount={expected_discount_int}%; Got discount={predicted_discount}%",
35
+ )
36
+
37
+
38
+ def create_benchmark_experiment(model_name: str, experiment_name: str):
39
+ """Factory function to create experiment functions for different models."""
40
+
41
+ @experiment()
42
+ async def benchmark_experiment(row):
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
54
+
55
+ # Score the response
56
+ score = discount_accuracy.score(
57
+ prediction=response, expected_discount=row["expected_discount"]
58
+ )
59
+
60
+ return {
61
+ **row,
62
+ "model": model_name,
63
+ "experiment_name": experiment_name,
64
+ "response": response,
65
+ "predicted_discount": predicted_discount,
66
+ "score": score.value,
67
+ "score_reason": score.reason,
68
+ }
69
+
70
+ return benchmark_experiment
71
+
72
+
73
+ def load_dataset():
74
+ """Load the dataset from CSV file."""
75
+ # Get the directory where this file is located
76
+ current_dir = os.path.dirname(os.path.abspath(__file__))
77
+
78
+ dataset = Dataset.load(
79
+ name="discount_benchmark", backend="local/csv", root_dir=current_dir
80
+ )
81
+ return dataset
82
+
83
+
84
+ def compare_inputs_to_output(
85
+ inputs: List[str], output_path: Optional[str] = None
86
+ ) -> str:
87
+ """Compare multiple experiment CSVs and write a combined CSV.
88
+
89
+ - Requires 'id' column in all inputs; uses it as the alignment key
90
+ - Builds output with id + canonical columns + per-experiment response/score/reason columns
91
+ - Returns the full output path
92
+ """
93
+ if not inputs or len(inputs) < 2:
94
+ raise ValueError("At least two input CSV files are required for comparison")
95
+
96
+ # Load all inputs
97
+ dataframes = []
98
+ experiment_names = []
99
+ for path in inputs:
100
+ df = pd.read_csv(path)
101
+ if "experiment_name" not in df.columns:
102
+ raise ValueError(f"Missing 'experiment_name' column in {path}")
103
+ exp_name = str(df["experiment_name"].iloc[0])
104
+ experiment_names.append(exp_name)
105
+ dataframes.append(df)
106
+
107
+ canonical_cols = ["customer_profile", "description", "expected_discount"]
108
+ base_df = dataframes[0]
109
+
110
+ # Require 'id' in all inputs
111
+ if not all("id" in df.columns for df in dataframes):
112
+ raise ValueError(
113
+ "All input CSVs must contain an 'id' column to align rows. Re-run experiments after adding 'id' to your dataset."
114
+ )
115
+
116
+ # Validate duplicates and matching sets of IDs
117
+ key_sets = []
118
+ for idx, df in enumerate(dataframes):
119
+ keys = df["id"].astype(str)
120
+ if keys.duplicated().any():
121
+ dupes = keys[keys.duplicated()].head(3).tolist()
122
+ raise ValueError(
123
+ f"Input {inputs[idx]} contains duplicate id values. Examples: {dupes}"
124
+ )
125
+ key_sets.append(set(keys.tolist()))
126
+
127
+ base_keys = key_sets[0]
128
+ for i, ks in enumerate(key_sets[1:], start=1):
129
+ if ks != base_keys:
130
+ missing_in_other = list(base_keys - ks)[:5]
131
+ missing_in_base = list(ks - base_keys)[:5]
132
+ raise ValueError(
133
+ "Inputs do not contain the same set of IDs.\n"
134
+ f"- Missing in file {i + 1}: {missing_in_other}\n"
135
+ f"- Extra in file {i + 1}: {missing_in_base}"
136
+ )
137
+
138
+ # Validate canonical columns exist in base
139
+ missing = [c for c in canonical_cols if c not in base_df.columns]
140
+ if missing:
141
+ raise ValueError(f"First CSV missing required columns: {missing}")
142
+
143
+ # Build combined on base order using 'id' as alignment key
144
+ base_ids_str = base_df["id"].astype(str)
145
+ combined = base_df[["id"] + canonical_cols].copy()
146
+
147
+ # Append per-experiment outputs by aligned ID
148
+ for df, exp_name in zip(dataframes, experiment_names):
149
+ df = df.copy()
150
+ df["id"] = df["id"].astype(str)
151
+ df = df.set_index("id")
152
+ for col in ["response", "score", "score_reason"]:
153
+ if col not in df.columns:
154
+ raise ValueError(
155
+ f"Column '{col}' not found in one input. Please provide per-row '{col}'."
156
+ )
157
+ combined[f"{exp_name}_response"] = base_ids_str.map(df["response"])
158
+ combined[f"{exp_name}_score"] = base_ids_str.map(df["score"])
159
+ combined[f"{exp_name}_score_reason"] = base_ids_str.map(df["score_reason"])
160
+
161
+ # Determine output path
162
+ current_dir = os.path.dirname(os.path.abspath(__file__))
163
+ experiments_dir = os.path.join(current_dir, "experiments")
164
+ os.makedirs(experiments_dir, exist_ok=True)
165
+
166
+ if output_path is None or output_path.strip() == "":
167
+ run_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
168
+ output_path = os.path.join(experiments_dir, f"{run_id}-comparison.csv")
169
+ else:
170
+ # If relative path, place under experiments dir
171
+ if not os.path.isabs(output_path):
172
+ output_path = os.path.join(experiments_dir, output_path)
173
+
174
+ # Sort by id for user-friendly reading
175
+ if "id" in combined.columns:
176
+ combined = combined.sort_values(by="id").reset_index(drop=True)
177
+ combined.to_csv(output_path, index=False)
178
+
179
+ # Print per-experiment accuracy summary
180
+ for df, exp_name in zip(dataframes, experiment_names):
181
+ try:
182
+ acc = (df["score"] == "correct").mean()
183
+ print(f"{exp_name} Accuracy: {acc:.2%}")
184
+ except Exception:
185
+ pass
186
+
187
+ return output_path
188
+
189
+
190
+ async def run_command(model: str, name: Optional[str]) -> None:
191
+ """Run a single experiment using the provided model and name."""
192
+ if "OPENAI_API_KEY" not in os.environ:
193
+ print("❌ Error: OpenAI API key not found!")
194
+ print("Please set your API key: export OPENAI_API_KEY=your_actual_key")
195
+ return
196
+
197
+ print("Loading dataset...")
198
+ dataset = load_dataset()
199
+ print(f"Dataset loaded with {len(dataset)} samples")
200
+
201
+ run_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
202
+ exp_name = name or model
203
+
204
+ # Ensure output directory exists (experiment framework saves under experiments/)
205
+ current_dir = os.path.dirname(os.path.abspath(__file__))
206
+ experiments_dir = os.path.join(current_dir, "experiments")
207
+ os.makedirs(experiments_dir, exist_ok=True)
208
+
209
+ benchmark_experiment = create_benchmark_experiment(
210
+ model_name=model, experiment_name=exp_name
211
+ )
212
+ print(f"Running model evaluation ({model})...")
213
+ results = await benchmark_experiment.arun(dataset, name=f"{run_id}-{exp_name}")
214
+ print(f"✅ {exp_name}: {len(results)} cases evaluated")
215
+ print(f"Results saved to: {os.path.join(experiments_dir, results.name)}.csv")
216
+
217
+ # Accuracy summary
218
+ accuracy = sum(1 for r in results if r["score"] == "correct") / max(1, len(results))
219
+ print(f"{exp_name} Accuracy: {accuracy:.2%}")
220
+
221
+
222
+ def compare_command(inputs: List[str], output: Optional[str]) -> None:
223
+ output_path = compare_inputs_to_output(inputs, output)
224
+ print(f"Combined comparison saved to: {output_path}")
225
+
226
+
227
+ def build_parser() -> argparse.ArgumentParser:
228
+ parser = argparse.ArgumentParser(description="Benchmark LLM evaluation CLI")
229
+ subparsers = parser.add_subparsers(dest="command", required=True)
230
+
231
+ # run subcommand
232
+ run_parser = subparsers.add_parser("run", help="Run a single experiment")
233
+ run_parser.add_argument(
234
+ "--model", type=str, default=DEFAULT_MODEL, help="Model name to evaluate"
235
+ )
236
+ run_parser.add_argument(
237
+ "--name",
238
+ type=str,
239
+ default=None,
240
+ help="Experiment name (defaults to model name)",
241
+ )
242
+
243
+ # compare subcommand
244
+ cmp_parser = subparsers.add_parser(
245
+ "compare", help="Combine multiple experiment CSVs"
246
+ )
247
+ cmp_parser.add_argument(
248
+ "--inputs", nargs="+", required=True, help="Input CSV files to compare"
249
+ )
250
+ cmp_parser.add_argument(
251
+ "--output",
252
+ type=str,
253
+ default=None,
254
+ help="Output CSV path (defaults to experiments/<timestamp>-comparison.csv)",
255
+ )
256
+
257
+ return parser
258
+
259
+
260
+ if __name__ == "__main__":
261
+ parser = build_parser()
262
+ args = parser.parse_args()
263
+
264
+ if args.command == "run":
265
+ import asyncio
266
+
267
+ asyncio.run(run_command(model=args.model, name=args.name))
268
+ sys.exit(0)
269
+ elif args.command == "compare":
270
+ compare_command(inputs=args.inputs, output=args.output)
271
+ sys.exit(0)
272
+ else:
273
+ parser.print_help()
274
+ sys.exit(2)
@@ -0,0 +1,71 @@
1
+ import os
2
+
3
+ from openai import OpenAI
4
+
5
+ DEFAULT_MODEL = "gpt-4.1-nano-2025-04-14"
6
+
7
+
8
+ def get_client() -> OpenAI:
9
+ """Lazily create an OpenAI client, requiring the API key only when used.
10
+
11
+ This avoids raising errors during module import (e.g., when running --help).
12
+ """
13
+ api_key = os.getenv("OPENAI_API_KEY")
14
+ if not api_key:
15
+ raise RuntimeError(
16
+ "OPENAI_API_KEY is not set. Please export it before running prompts."
17
+ )
18
+ return OpenAI(api_key=api_key)
19
+
20
+
21
+ SYSTEM_PROMPT = """
22
+ You are a discount calculation assistant. I will provide a customer profile and you must calculate their discount percentage and explain your reasoning.
23
+
24
+ Discount rules:
25
+ - Age 65+ OR student status: 15% discount
26
+ - Annual income < $30,000: 20% discount
27
+ - Premium member for 2+ years: 10% discount
28
+ - New customer (< 6 months): 5% discount
29
+
30
+ Rules can stack up to a maximum of 35% discount.
31
+
32
+ Respond in JSON format only:
33
+ {
34
+ "discount_percentage": number,
35
+ "reason": "clear explanation of which rules apply and calculations",
36
+ "applied_rules": ["list", "of", "applied", "rule", "names"]
37
+ }
38
+ """
39
+
40
+
41
+ def run_prompt(prompt: str, model: str = DEFAULT_MODEL):
42
+ """Run the discount calculation prompt with the specified model."""
43
+ client = get_client()
44
+ response = client.chat.completions.create(
45
+ model=model,
46
+ response_format={"type": "json_object"},
47
+ messages=[
48
+ {"role": "system", "content": SYSTEM_PROMPT},
49
+ {"role": "user", "content": prompt},
50
+ ],
51
+ )
52
+ response = response.choices[0].message.content.strip()
53
+ return response
54
+
55
+
56
+ if __name__ == "__main__":
57
+ customer_profile = """
58
+ Customer Profile:
59
+ - Name: Sarah Johnson
60
+ - Age: 67
61
+ - Student: No
62
+ - Annual Income: $45,000
63
+ - Premium Member: Yes, for 3 years
64
+ - Account Age: 3 years
65
+ """
66
+ print("=== System Prompt ===")
67
+ print(SYSTEM_PROMPT)
68
+ print("\n=== Customer Profile ===")
69
+ print(customer_profile)
70
+ print(f"\n=== Running Prompt with default model {DEFAULT_MODEL} ===")
71
+ print(run_prompt(customer_profile, model=DEFAULT_MODEL))
File without changes
@@ -0,0 +1,75 @@
1
+ from ragas import Dataset, experiment
2
+ from ragas.metrics.discrete import discrete_metric
3
+ from ragas.metrics.result import MetricResult
4
+
5
+ from .prompt import run_prompt
6
+
7
+
8
+ @discrete_metric(name="accuracy", allowed_values=["pass", "fail"])
9
+ def my_metric(prediction: str, actual: str):
10
+ """Calculate accuracy of the prediction."""
11
+ return (
12
+ MetricResult(value="pass", reason="")
13
+ if prediction == actual
14
+ else MetricResult(value="fail", reason="")
15
+ )
16
+
17
+
18
+ @experiment()
19
+ async def run_experiment(row):
20
+ response = run_prompt(row["text"])
21
+ score = my_metric.score(prediction=response, actual=row["label"])
22
+
23
+ experiment_view = {
24
+ **row,
25
+ "response": response,
26
+ "score": score.value,
27
+ }
28
+ return experiment_view
29
+
30
+
31
+ def load_dataset():
32
+ # Create a dataset
33
+ dataset = Dataset(
34
+ name="test_dataset",
35
+ backend="local/csv",
36
+ root_dir=".",
37
+ )
38
+ dataset_dict = [
39
+ {"text": "I loved the movie! It was fantastic.", "label": "positive"},
40
+ {"text": "The movie was terrible and boring.", "label": "negative"},
41
+ {"text": "It was an average film, nothing special.", "label": "positive"},
42
+ {"text": "Absolutely amazing! Best movie of the year.", "label": "positive"},
43
+ {"text": "I did not like it at all, very disappointing.", "label": "negative"},
44
+ {"text": "It was okay, not the best but not the worst.", "label": "positive"},
45
+ {
46
+ "text": "I have mixed feelings about it, some parts were good, others not so much.",
47
+ "label": "positive",
48
+ },
49
+ {"text": "What a masterpiece! I would watch it again.", "label": "positive"},
50
+ {
51
+ "text": "I would not recommend it to anyone, it was that bad.",
52
+ "label": "negative",
53
+ },
54
+ ]
55
+
56
+ for sample in dataset_dict:
57
+ row = {"text": sample["text"], "label": sample["label"]}
58
+ dataset.append(row)
59
+
60
+ # make sure to save it
61
+ dataset.save()
62
+ return dataset
63
+
64
+
65
+ async def main():
66
+ dataset = load_dataset()
67
+ experiment_results = await run_experiment.arun(dataset)
68
+ print("Experiment completed successfully!")
69
+ print("Experiment results:", experiment_results)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ import asyncio
74
+
75
+ asyncio.run(main())
@@ -0,0 +1,32 @@
1
+ import os
2
+
3
+ from openai import OpenAI
4
+
5
+ client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
6
+
7
+
8
+ SYSTEM_PROMPT = """
9
+ You are a helpful assistant. I will provide a movie review and you will classify it as either positive or negative.
10
+ Please respond with "positive" or "negative" only.
11
+ """
12
+
13
+
14
+ def run_prompt(prompt: str):
15
+ response = client.chat.completions.create(
16
+ model="gpt-4o",
17
+ messages=[
18
+ {"role": "system", "content": SYSTEM_PROMPT},
19
+ {"role": "user", "content": prompt},
20
+ ],
21
+ )
22
+ response = (
23
+ response.choices[0].message.content.strip()
24
+ if response.choices[0].message.content
25
+ else ""
26
+ )
27
+ return response
28
+
29
+
30
+ if __name__ == "__main__":
31
+ prompt = "The movie was fantastic and I loved every moment of it!"
32
+ print(run_prompt(prompt))
File without changes
@@ -0,0 +1,84 @@
1
+ import os
2
+
3
+ from openai import OpenAI
4
+
5
+ from ragas import Dataset, experiment
6
+ from ragas.llms import instructor_llm_factory
7
+ from ragas.metrics import DiscreteMetric
8
+
9
+ from .rag import default_rag_client
10
+
11
+ openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
12
+ rag_client = default_rag_client(llm_client=openai_client)
13
+ llm = instructor_llm_factory("openai", model="gpt-4o", client=openai_client)
14
+
15
+
16
+ def load_dataset():
17
+ dataset = Dataset(
18
+ name="test_dataset",
19
+ backend="local/csv",
20
+ root_dir=".",
21
+ )
22
+
23
+ data_samples = [
24
+ {
25
+ "question": "What is ragas 0.3",
26
+ "grading_notes": "- experimentation as the central pillar - provides abstraction for datasets, experiments and metrics - supports evals for RAG, LLM workflows and Agents",
27
+ },
28
+ {
29
+ "question": "how are experiment results stored in ragas 0.3?",
30
+ "grading_notes": "- configured using different backends like local, gdrive, etc - stored under experiments/ folder in the backend storage",
31
+ },
32
+ {
33
+ "question": "What metrics are supported in ragas 0.3?",
34
+ "grading_notes": "- provides abstraction for discrete, numerical and ranking metrics",
35
+ },
36
+ ]
37
+
38
+ for sample in data_samples:
39
+ row = {"question": sample["question"], "grading_notes": sample["grading_notes"]}
40
+ dataset.append(row)
41
+
42
+ # make sure to save it
43
+ dataset.save()
44
+ return dataset
45
+
46
+
47
+ my_metric = DiscreteMetric(
48
+ name="correctness",
49
+ prompt="Check if the response contains points mentioned from the grading notes and return 'pass' or 'fail'.\nResponse: {response} Grading Notes: {grading_notes}",
50
+ allowed_values=["pass", "fail"],
51
+ )
52
+
53
+
54
+ @experiment()
55
+ async def run_experiment(row):
56
+ response = rag_client.query(row["question"])
57
+
58
+ score = my_metric.score(
59
+ llm=llm,
60
+ response=response.get("answer", " "),
61
+ grading_notes=row["grading_notes"],
62
+ )
63
+
64
+ experiment_view = {
65
+ **row,
66
+ "response": response.get("answer", ""),
67
+ "score": score.value,
68
+ "log_file": response.get("logs", " "),
69
+ }
70
+ return experiment_view
71
+
72
+
73
+ async def main():
74
+ dataset = load_dataset()
75
+ print("dataset loaded successfully", dataset)
76
+ experiment_results = await run_experiment.arun(dataset)
77
+ print("Experiment completed successfully!")
78
+ print("Experiment results:", experiment_results)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ import asyncio
83
+
84
+ asyncio.run(main())