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.
Files changed (65) hide show
  1. {ragas_examples-0.3.6/ragas_examples.egg-info → ragas_examples-0.3.7}/PKG-INFO +1 -1
  2. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/evals.py +1 -1
  3. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/run_prompt.py +6 -5
  4. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/_version.py +3 -3
  5. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/evals.py +43 -45
  6. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/prompt.py +30 -21
  7. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/evals.py +22 -42
  8. ragas_examples-0.3.7/ragas_examples/judge_alignment/__init__.py +29 -0
  9. ragas_examples-0.3.7/ragas_examples/judge_alignment/evals.py +229 -0
  10. ragas_examples-0.3.7/ragas_examples/text2sql/__init__.py +53 -0
  11. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/analyze_errors.py +18 -53
  12. ragas_examples-0.3.7/ragas_examples/text2sql/data_utils.py +467 -0
  13. ragas_examples-0.3.7/ragas_examples/text2sql/db_utils.py +301 -0
  14. ragas_examples-0.3.7/ragas_examples/text2sql/evals.py +232 -0
  15. ragas_examples-0.3.7/ragas_examples/text2sql/text2sql_agent.py +133 -0
  16. {ragas_examples-0.3.6 → ragas_examples-0.3.7/ragas_examples.egg-info}/PKG-INFO +1 -1
  17. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/SOURCES.txt +4 -0
  18. ragas_examples-0.3.6/ragas_examples/text2sql/__init__.py +0 -36
  19. ragas_examples-0.3.6/ragas_examples/text2sql/data_utils.py +0 -380
  20. ragas_examples-0.3.6/ragas_examples/text2sql/db_utils.py +0 -458
  21. ragas_examples-0.3.6/ragas_examples/text2sql/evals.py +0 -387
  22. ragas_examples-0.3.6/ragas_examples/text2sql/text2sql_agent.py +0 -522
  23. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/LICENSE +0 -0
  24. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/README.md +0 -0
  25. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/gdrive_append_example.py +0 -0
  26. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/gdrive_backend_example.py +0 -0
  27. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/__init__.py +0 -0
  28. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/datasets/support_triage.csv +0 -0
  29. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/promptv1.txt +0 -0
  30. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/iterate_prompt/promptv2_fewshot.txt +0 -0
  31. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/oci_genai_example.py +0 -0
  32. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/pyproject.toml +0 -0
  33. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/__init__.py +0 -0
  34. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/__init__.py +0 -0
  35. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/agent.py +0 -0
  36. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/agent_evals/evals.py +2 -2
  37. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/__init__.py +0 -0
  38. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/benchmark_llm/datasets/discount_benchmark.csv +0 -0
  39. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/__init__.py +0 -0
  40. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/datasets/hf_doc_qa_eval.csv +0 -0
  41. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/improve_rag/rag.py +0 -0
  42. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/__init__.py +0 -0
  43. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/ambiguous_removal_request.json +0 -0
  44. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/duplicate_addition.json +0 -0
  45. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/contexts/repeated_removal.json +0 -0
  46. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/evals.py +0 -0
  47. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/llamaIndex_agent_evals/llamaindex_agent.py +0 -0
  48. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/__init__.py +0 -0
  49. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/evals.py +0 -0
  50. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/prompt_evals/prompt.py +0 -0
  51. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/__init__.py +0 -0
  52. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/evals.py +0 -0
  53. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/rag_eval/rag.py +0 -0
  54. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/datasets/booksql_sample.csv +0 -0
  55. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt.txt +0 -0
  56. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt_v2.txt +0 -0
  57. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/prompt_v3.txt +0 -0
  58. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/text2sql/validate_sql_dataset.py +0 -0
  59. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/__init__.py +0 -0
  60. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/evals.py +0 -0
  61. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples/workflow_eval/workflow.py +0 -0
  62. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/dependency_links.txt +0 -0
  63. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/requires.txt +0 -0
  64. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/ragas_examples.egg-info/top_level.txt +0 -0
  65. {ragas_examples-0.3.6 → ragas_examples-0.3.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragas-examples
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Official examples for the ragas project
5
5
  Author: Ragas Team
6
6
  License: Apache-2.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 asyncio.to_thread(run_prompt, row["text"], prompt_file=prompt_file)
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 OpenAI
3
+ from openai import AsyncOpenAI
4
4
 
5
- client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
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.6'
32
- __version_tuple__ = version_tuple = (0, 3, 6)
31
+ __version__ = version = '0.3.7'
32
+ __version_tuple__ = version_tuple = (0, 3, 7)
33
33
 
34
- __commit_id__ = commit_id = 'g49f47f18e'
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
- 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
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
- # Score the response
56
- score = discount_accuracy.score(
57
- prediction=response, expected_discount=row["expected_discount"]
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
- 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
- }
54
+ # Score the response
55
+ score = discount_accuracy.score(
56
+ prediction=response, expected_discount=row["expected_discount"]
57
+ )
69
58
 
70
- return benchmark_experiment
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
- # Get the directory where this file is located
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 = Dataset.load(
79
- name="discount_benchmark", backend="local/csv", root_dir=current_dir
80
- )
81
- return dataset
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 "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])
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(dataset, name=f"{run_id}-{exp_name}")
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 openai import OpenAI
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() -> OpenAI:
9
- """Lazily create an OpenAI client, requiring the API key only when used.
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 OpenAI(api_key=api_key)
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
- 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))
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
- # Download and prepare dataset
198
- dataset_path = download_and_save_dataset()
199
- dataset = create_ragas_dataset(dataset_path)
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
- # Initialize RAG and LLM clients
190
+ # Prepare dataset and initialize system
202
191
  logger.info("Initializing RAG system...")
203
- openai_client = get_openai_client()
204
- retriever = BM25Retriever()
205
- rag = RAG(llm_client=openai_client, retriever=retriever, model=model, mode=mode)
206
- llm = get_llm_client()
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=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(main(agentic_rag=agentic_mode))
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())