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,17 @@
1
+ """
2
+ Ragas Examples Package
3
+
4
+ This package contains official examples demonstrating how to use Ragas for evaluating
5
+ different types of AI applications including RAG systems, agents, prompts, workflows,
6
+ and LLM benchmarking.
7
+
8
+ Available example modules:
9
+ - agent_evals: Agent evaluation examples
10
+ - benchmark_llm: LLM benchmarking and comparison examples
11
+ - prompt_evals: Prompt evaluation examples
12
+ - rag_eval: RAG system evaluation examples
13
+ - text2sql: Text-to-SQL agent evaluation examples
14
+ - workflow_eval: Workflow evaluation examples
15
+ """
16
+
17
+ from ._version import __version__
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.3.5'
32
+ __version_tuple__ = version_tuple = (0, 3, 5)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,409 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from dataclasses import asdict, dataclass
5
+ from datetime import datetime
6
+ from typing import Any, Dict, Optional
7
+
8
+ import openai
9
+
10
+ SYSTEM_MESSAGE = """You are a mathematical problem-solving agent. You can only use these four atomic tools to solve problems:
11
+ - add(a, b): Add two numbers
12
+ - sub(a, b): Subtract b from a
13
+ - mul(a, b): Multiply two numbers
14
+ - div(a, b): Divide a by b
15
+
16
+ Your task is to break down complex mathematical expressions into a sequence of these atomic operations, following proper order of operations (parentheses, multiplication/division, addition/subtraction).
17
+
18
+ For each step, call the appropriate tool with the correct arguments. Work step by step, showing your reasoning.
19
+
20
+ When you have the final answer, respond with just the number."""
21
+
22
+
23
+ @dataclass
24
+ class TraceEvent:
25
+ """Single event in the application trace"""
26
+
27
+ event_type: (
28
+ str # "llm_call", "tool_execution", "error", "init", "result_extraction"
29
+ )
30
+ component: str # "openai_api", "math_tools", "agent", "parser"
31
+ data: Dict[str, Any]
32
+
33
+
34
+ @dataclass
35
+ class ToolResult:
36
+ tool_name: str
37
+ args: Dict[str, float]
38
+ result: float
39
+ step_number: int
40
+
41
+
42
+ class MathToolsAgent:
43
+ def __init__(
44
+ self,
45
+ client,
46
+ model_name: str = "gpt-4o",
47
+ system_message: str = SYSTEM_MESSAGE,
48
+ logdir: str = "logs",
49
+ ):
50
+ """
51
+ Initialize the LLM agent with OpenAI API
52
+
53
+ Args:
54
+ client: OpenAI client instance
55
+ model_name: Name of the model to use
56
+ system_message: System message for the agent
57
+ logdir: Directory to save trace logs
58
+ """
59
+
60
+ self.client = client
61
+ self.system_message = system_message
62
+ self.model_name = model_name
63
+ self.step_counter = 0
64
+ self.traces = []
65
+ self.logdir = logdir
66
+
67
+ # Create log directory if it doesn't exist
68
+ os.makedirs(self.logdir, exist_ok=True)
69
+
70
+ # Define available tools
71
+ self.tools = [
72
+ {
73
+ "type": "function",
74
+ "function": {
75
+ "name": "add",
76
+ "description": "Add two numbers together",
77
+ "parameters": {
78
+ "type": "object",
79
+ "properties": {
80
+ "a": {"type": "number", "description": "First number"},
81
+ "b": {"type": "number", "description": "Second number"},
82
+ },
83
+ "required": ["a", "b"],
84
+ },
85
+ },
86
+ },
87
+ {
88
+ "type": "function",
89
+ "function": {
90
+ "name": "sub",
91
+ "description": "Subtract second number from first number",
92
+ "parameters": {
93
+ "type": "object",
94
+ "properties": {
95
+ "a": {
96
+ "type": "number",
97
+ "description": "Number to subtract from",
98
+ },
99
+ "b": {
100
+ "type": "number",
101
+ "description": "Number to subtract",
102
+ },
103
+ },
104
+ "required": ["a", "b"],
105
+ },
106
+ },
107
+ },
108
+ {
109
+ "type": "function",
110
+ "function": {
111
+ "name": "mul",
112
+ "description": "Multiply two numbers together",
113
+ "parameters": {
114
+ "type": "object",
115
+ "properties": {
116
+ "a": {"type": "number", "description": "First number"},
117
+ "b": {"type": "number", "description": "Second number"},
118
+ },
119
+ "required": ["a", "b"],
120
+ },
121
+ },
122
+ },
123
+ {
124
+ "type": "function",
125
+ "function": {
126
+ "name": "div",
127
+ "description": "Divide first number by second number",
128
+ "parameters": {
129
+ "type": "object",
130
+ "properties": {
131
+ "a": {
132
+ "type": "number",
133
+ "description": "Number to divide (numerator)",
134
+ },
135
+ "b": {
136
+ "type": "number",
137
+ "description": "Number to divide by (denominator)",
138
+ },
139
+ },
140
+ "required": ["a", "b"],
141
+ },
142
+ },
143
+ },
144
+ ]
145
+
146
+ def add(self, a: float, b: float) -> float:
147
+ """Add two numbers"""
148
+ result = a + b
149
+
150
+ return result
151
+
152
+ def sub(self, a: float, b: float) -> float:
153
+ """Subtract b from a"""
154
+ result = a - b
155
+ return result
156
+
157
+ def mul(self, a: float, b: float) -> float:
158
+ """Multiply two numbers"""
159
+ result = a * b
160
+ return result
161
+
162
+ def div(self, a: float, b: float) -> float:
163
+ """Divide a by b"""
164
+ if b == 0:
165
+ raise ValueError("Division by zero")
166
+ result = a / b
167
+ return result
168
+
169
+ def _execute_tool_call(self, tool_call) -> str:
170
+ """Execute a tool call and return the result"""
171
+
172
+ self.traces.append(
173
+ TraceEvent(
174
+ event_type="tool_execution",
175
+ component="math_tools",
176
+ data={
177
+ "tool_name": tool_call.function.name,
178
+ "args": json.loads(tool_call.function.arguments),
179
+ },
180
+ )
181
+ )
182
+
183
+ function_name = tool_call.function.name
184
+ arguments = json.loads(tool_call.function.arguments)
185
+
186
+ # Execute the appropriate function
187
+ if function_name == "add":
188
+ result = self.add(arguments["a"], arguments["b"])
189
+ elif function_name == "sub":
190
+ result = self.sub(arguments["a"], arguments["b"])
191
+ elif function_name == "mul":
192
+ result = self.mul(arguments["a"], arguments["b"])
193
+ elif function_name == "div":
194
+ result = self.div(arguments["a"], arguments["b"])
195
+ else:
196
+ raise ValueError(f"Unknown function: {function_name}")
197
+
198
+ self.traces.append(
199
+ TraceEvent(
200
+ event_type="tool_result",
201
+ component="math_tools",
202
+ data={
203
+ "result": result,
204
+ },
205
+ )
206
+ )
207
+
208
+ return str(result)
209
+
210
+ def export_traces_to_log(
211
+ self, run_id: str, problem: str, final_result: Optional[float] = None
212
+ ):
213
+ """
214
+ Export traces to a log file with run_id
215
+
216
+ Args:
217
+ run_id: Unique identifier for this run
218
+ problem: The problem that was solved
219
+ final_result: The final result of the computation
220
+ """
221
+ timestamp = datetime.now().isoformat()
222
+ log_filename = (
223
+ f"run_{run_id}_{timestamp.replace(':', '-').replace('.', '-')}.json"
224
+ )
225
+ log_filepath = os.path.join(self.logdir, log_filename)
226
+
227
+ log_data = {
228
+ "run_id": run_id,
229
+ "timestamp": timestamp,
230
+ "problem": problem,
231
+ "final_result": final_result,
232
+ "model_name": self.model_name,
233
+ "traces": [asdict(trace) for trace in self.traces],
234
+ }
235
+
236
+ with open(log_filepath, "w") as f:
237
+ json.dump(log_data, f, indent=2)
238
+
239
+ logging.info(f"Traces exported to: {log_filepath}")
240
+ return log_filepath
241
+
242
+ def solve(
243
+ self, problem: str, max_iterations: int = 10, run_id: Optional[str] = None
244
+ ) -> Dict[str, Any]:
245
+ """
246
+ Solve a math problem using iterative planning with LLM and atomic tools
247
+
248
+ Args:
249
+ problem: Mathematical expression or problem to solve
250
+ max_iterations: Maximum number of LLM iterations to prevent infinite loops
251
+ run_id: Optional run identifier. If None, generates one automatically
252
+
253
+ Returns:
254
+ Final numerical result
255
+ """
256
+ # Generate run_id if not provided
257
+ if run_id is None:
258
+ run_id = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hash(problem) % 10000:04d}"
259
+
260
+ # Reset traces for each new problem
261
+ self.traces = []
262
+
263
+ logging.info(f"Solving: {problem} (Run ID: {run_id})")
264
+ logging.info("=" * 60)
265
+
266
+ # Reset state
267
+ self.execution_history = []
268
+ self.step_counter = 0
269
+
270
+ messages = [
271
+ {"role": "system", "content": self.system_message},
272
+ {
273
+ "role": "user",
274
+ "content": f"Solve this mathematical expression step by step: {problem}",
275
+ },
276
+ ]
277
+
278
+ iteration = 0
279
+ while iteration < max_iterations:
280
+ iteration += 1
281
+ logging.info(f"\n--- LLM Iteration {iteration} ---")
282
+
283
+ try:
284
+ self.traces.append(
285
+ TraceEvent(
286
+ event_type="llm_call",
287
+ component="openai_api",
288
+ data={
289
+ "model": self.model_name,
290
+ "messages": messages,
291
+ # "tools": [tool["function"] for tool in self.tools]
292
+ },
293
+ )
294
+ )
295
+
296
+ # Call OpenAI API with function calling
297
+ response = self.client.chat.completions.create(
298
+ model=self.model_name,
299
+ messages=messages,
300
+ tools=self.tools,
301
+ tool_choice="auto",
302
+ # temperature=0
303
+ )
304
+
305
+ message = response.choices[0].message
306
+ messages.append(message.model_dump())
307
+
308
+ self.traces.append(
309
+ TraceEvent(
310
+ event_type="llm_response",
311
+ component="openai_api",
312
+ data={
313
+ "content": message.content,
314
+ "tool_calls": (
315
+ [tool.model_dump() for tool in message.tool_calls]
316
+ if message.tool_calls
317
+ else []
318
+ ),
319
+ },
320
+ )
321
+ )
322
+
323
+ # Check if the model wants to call functions
324
+ if message.tool_calls:
325
+ logging.info(
326
+ f"LLM planning: {message.content or 'Executing tools...'}"
327
+ )
328
+
329
+ # Execute each tool call
330
+ for tool_call in message.tool_calls:
331
+ result = self._execute_tool_call(tool_call)
332
+
333
+ # Add tool result to conversation
334
+ messages.append(
335
+ {
336
+ "role": "tool",
337
+ "tool_call_id": tool_call.id,
338
+ "content": result,
339
+ }
340
+ )
341
+ else:
342
+ # No more tool calls - this should be the final answer
343
+ logging.info(f"LLM final response: {message.content}")
344
+
345
+ # Try to extract the numerical result
346
+ try:
347
+ # Look for a number in the response
348
+ import re
349
+
350
+ numbers = re.findall(r"-?\d+\.?\d*", message.content)
351
+ if numbers:
352
+ final_result = float(
353
+ numbers[-1]
354
+ ) # Take the last number found
355
+ logging.info("=" * 60)
356
+ logging.info(f"Final result: {final_result}")
357
+ self.traces.append(
358
+ TraceEvent(
359
+ event_type="result_extraction",
360
+ component="math_tools",
361
+ data={"final_result": final_result},
362
+ )
363
+ )
364
+
365
+ # Export traces to log file
366
+ log_filename = self.export_traces_to_log(
367
+ run_id, problem, final_result
368
+ )
369
+ return {"result": final_result, "log_file": log_filename}
370
+
371
+ else:
372
+ logging.info(
373
+ "Could not extract numerical result from LLM response"
374
+ )
375
+ break
376
+ except ValueError:
377
+ logging.info("Could not parse final result as number")
378
+ break
379
+
380
+ except Exception as e:
381
+ logging.info(f"Error in iteration {iteration}: {e}")
382
+ break
383
+
384
+ logging.info("Max iterations reached or error occurred")
385
+ # Export traces even if solve failed
386
+ return {
387
+ "result": 0,
388
+ "log_file": self.export_traces_to_log(run_id, problem, 0.0),
389
+ }
390
+
391
+
392
+ def get_default_agent(
393
+ model_name: str = "gpt-4o", logdir: str = "logs"
394
+ ) -> MathToolsAgent:
395
+ """Get a default instance of the MathToolsAgent with OpenAI client"""
396
+ openai_client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
397
+ return MathToolsAgent(client=openai_client, model_name=model_name, logdir=logdir)
398
+
399
+
400
+ if __name__ == "__main__":
401
+ # Example usage
402
+ client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
403
+ agent = MathToolsAgent(client, logdir="agent_logs")
404
+
405
+ problem = "((2 + 3) * 4) - (6 / 2)"
406
+ print(f"Problem: {problem}")
407
+
408
+ result = agent.solve(problem)
409
+ print(f"Result: {result}")
@@ -0,0 +1,78 @@
1
+ from .agent import get_default_agent
2
+
3
+ from ragas import Dataset, experiment
4
+ from ragas.metrics.numeric import numeric_metric
5
+ from ragas.metrics.result import MetricResult
6
+
7
+ math_agent = get_default_agent()
8
+
9
+
10
+ @numeric_metric(name="correctness", allowed_values=(0.0, 1.0))
11
+ def correctness_metric(prediction: float, actual: float):
12
+ """Calculate correctness of the prediction."""
13
+ if isinstance(prediction, str) and "ERROR" in prediction:
14
+ return 0.0
15
+ result = 1.0 if abs(prediction - actual) < 1e-5 else 0.0
16
+ return MetricResult(
17
+ value=result, reason=f"Prediction: {prediction}, Actual: {actual}"
18
+ )
19
+
20
+
21
+ def load_dataset():
22
+ # Create a dataset
23
+ dataset = Dataset(
24
+ name="test_dataset",
25
+ backend="local/csv",
26
+ root_dir=".",
27
+ )
28
+ # Create sample data for mathematical expressions and their results
29
+ math_problems = [
30
+ {"question": "15 - 3 / 4", "answer": 14.25},
31
+ {"question": "(2 + 3) * (6 - 2)", "answer": 20.0},
32
+ {"question": "100 / 5 + 3 * 2", "answer": 26.0},
33
+ {"question": "((2 * 3) + (4 * 5)) * ((6 - 2) / (8 / 4))", "answer": 52.0},
34
+ {"question": "2 + 3 * 4 - 5 / 6 + 7", "answer": 20.166666666666664},
35
+ {"question": "(10 / 2) + (20 / 4) + (30 / 6) + (40 / 8)", "answer": 20.0},
36
+ {"question": "1/3 + 1/3 + 1/3", "answer": 1.0},
37
+ ]
38
+
39
+ # Add the data to the dataset
40
+ for row in math_problems:
41
+ dataset.append(row)
42
+
43
+ dataset.save() # Save the dataset
44
+ return dataset
45
+
46
+
47
+ @experiment()
48
+ async def run_experiment(row):
49
+ question = row["question"]
50
+ expected_answer = row["answer"]
51
+
52
+ # Get the model's prediction
53
+ prediction = math_agent.solve(question)
54
+
55
+ # Calculate the correctness metric
56
+ correctness = correctness_metric.score(
57
+ prediction=prediction.get("result"), actual=expected_answer
58
+ )
59
+
60
+ return {
61
+ "question": question,
62
+ "expected_answer": expected_answer,
63
+ "prediction": prediction.get("result"),
64
+ "log_file": prediction.get("log_file"),
65
+ "correctness": correctness.value,
66
+ }
67
+
68
+
69
+ async def main():
70
+ dataset = load_dataset()
71
+ experiment_result = await run_experiment.arun(dataset)
72
+ print("Experiment_result: ", experiment_result)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ import asyncio
77
+
78
+ asyncio.run(main())
File without changes
@@ -0,0 +1,11 @@
1
+ id,customer_profile,expected_discount,description
2
+ 1,"Martha is a 70-year-old retiree who enjoys gardening. She has never enrolled in any academic course recently, has an annual pension of 50,000 dollars, signed up for our service nine years ago and never upgraded to premium.",15,"Senior only"
3
+ 2,"Arjun, aged 19, is a full-time computer-science undergraduate. His part-time job brings in about 45,000 dollars per year. He opened his account a year ago and has no premium membership.",15,"Student only"
4
+ 3,"Cynthia, a 40-year-old freelance artist, earns roughly 25,000 dollars a year. She is not studying anywhere, subscribed to our basic plan five years back and never upgraded to premium.",20,"Low income only"
5
+ 4,"Mr. Ocampo is 68, lives on social security of 20,000 dollars yearly, and joined our platform just two months ago after seeing an advertisement. He is not a student and has no premium status.",35,"Senior, low income, new customer (capped)"
6
+ 5,"Hannah is a 24-year-old postgraduate student doing her MBA. She earns about 18,000 dollars via internships, has been a premium member for three and a half years, and signed up thirty-six months ago.",35,"Student, low income, premium 3 yrs (capped)"
7
+ 6,"Leonardo is 64, turning 65 next month. His salary is exactly 30,000 dollars. He has maintained a premium subscription for two years and seven months and has been with us for five years.",10,"Premium 2+ yrs only"
8
+ 7,"Patricia celebrated her 65th birthday last week. She earns 55,000 dollars annually, bought premium last year so her premium tenure is one year and six months, and she created her account five months ago.",20,"Senior and new customer"
9
+ 8,"Gurdeep, age 66, draws a yearly income of 28,000 dollars, has enjoyed a premium subscription for three years and two months, and has been shopping with us for four years.",35,"Senior, low income, premium 3 yrs (capped)"
10
+ 9,"Maya, aged 22, is pursuing engineering, joined our service only eight weeks ago, makes around 35,000 dollars per annum, and holds no premium subscription.",20,"Student and new customer"
11
+ 10,"Oscar is 30 years old, a software developer making 45,000 dollars a year. He subscribed two years ago, uses only the basic plan, and is not attending any school.",0,"No rules apply"