Evaluation Pipeline and Langfuse Monitoring

#7
.gitignore DELETED
@@ -1,3 +0,0 @@
1
- assets/favicon.ico
2
- favicon.ico
3
- **/favicon.ico
 
 
 
 
evals/README.md DELETED
@@ -1,67 +0,0 @@
1
- # Evaluation Suite
2
-
3
- This directory contains a modular evaluation framework for the Fraud Model Explainability Assistant, built using the Strands Evaluation SDK while maintaining Langfuse + OpenTelemetry integration.
4
-
5
- ## Structure
6
-
7
- ```
8
- evals/
9
- ├── __init__.py # Package initialization
10
- ├── config.py # Shared evaluator configurations
11
- ├── task_functions.py # Agent wrapper functions
12
- ├── langfuse_reporter.py # Langfuse integration
13
- ├── dataset_loader.py # JSON to Case conversion
14
- ├── generate_experiment.py # SDK Experiment Generator
15
- ├── run_helpfulness.py # Helpfulness evaluation
16
- ├── run_trajectory.py # Trajectory evaluation
17
- └── run_full_suite.py # Full evaluation suite
18
- ```
19
-
20
- ## Usage
21
-
22
- ### Run Individual Evaluations
23
-
24
- ```bash
25
- # Helpfulness only
26
- python -m evals.run_helpfulness
27
-
28
- # Trajectory only
29
- python -m evals.run_trajectory
30
- ```
31
-
32
- ### Run Full Suite
33
-
34
- ```bash
35
- # All evaluators (Helpfulness, Faithfulness, Trajectory, Goal Success)
36
- python -m evals.run_full_suite
37
- ```
38
-
39
- ### Generate Synthetic Data
40
-
41
- ```bash
42
- # Generate new test cases using SDK ExperimentGenerator
43
- python -m evals.generate_experiment
44
- ```
45
- ## Features
46
-
47
- - **SDK-Aligned**: Uses `Experiment.run_evaluations()` framework
48
- - **Modular**: Each evaluation type in separate file
49
- - **Langfuse Integration**: Automatic score logging with trace correlation
50
- - **OpenTelemetry**: Full observability stack integration
51
- - **Extensible**: Easy to add new evaluators or metrics
52
-
53
- ## Evaluators
54
-
55
- 1. **Helpfulness** (`OutputEvaluator`): Rates answer quality (accuracy, completeness, clarity)
56
- 2. **Faithfulness** (`OutputEvaluator`): Verifies answer is grounded in retrieved context
57
- 3. **Trajectory** (`TrajectoryEvaluator`): Checks tool usage sequence
58
- 4. **Goal Success** (Heuristic): Matches expected key points in answer
59
-
60
- ## Comparison with `evaluate.py`
61
-
62
- The modular suite provides the same functionality as the monolithic `evaluate.py` but with:
63
- - ✅ Better code organization
64
- - ✅ SDK-standard patterns
65
- - ✅ Easier to extend with new evaluators
66
- - ✅ Built-in reporting (`report.run_display()`, `get_summary()`)
67
- - ✅ Maintains Langfuse + OTel integration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- """
2
- Evaluation suite for the Fraud Model Explainability Assistant.
3
-
4
- This package provides modular evaluation capabilities using the Strands SDK
5
- while maintaining integration with Langfuse and OpenTelemetry.
6
- """
7
-
8
- __version__ = "1.0.0"
 
 
 
 
 
 
 
 
 
evals/config.py DELETED
@@ -1,59 +0,0 @@
1
- """
2
- Shared configuration for evaluators and models.
3
- """
4
- from strands.models.openai import OpenAIModel
5
- from strands_evals.evaluators import OutputEvaluator, TrajectoryEvaluator, FaithfulnessEvaluator
6
-
7
- # Configure OpenAI model for evaluators
8
- eval_model = OpenAIModel(model_id="gpt-4o")
9
-
10
- # Helpfulness Evaluator
11
- helpfulness_evaluator = OutputEvaluator(
12
- rubric="""
13
- Evaluate the response based on:
14
- 1. Accuracy - Is the information factually correct?
15
- 2. Completeness - Does it fully answer the question?
16
- 3. Clarity - Is it easy to understand?
17
-
18
- Score 1.0 if all criteria are met excellently.
19
- Score 0.5 if some criteria are partially met.
20
- Score 0.0 if the response is inadequate or incorrect.
21
- """,
22
- include_inputs=True,
23
- model=eval_model
24
- )
25
-
26
- # Faithfulness Evaluator (Generic for now, to enable data generation)
27
- faithfulness_evaluator = OutputEvaluator(
28
- rubric="""
29
- Evaluate if the response is faithful to the retrieved context.
30
-
31
- Score 1.0 if fully supported by context.
32
- Score 0.5 if partially supported or context unavailable.
33
- Score 0.0 if contains hallucinations or contradicts context.
34
-
35
- Penalize heavily for information NOT in the context.
36
- """,
37
- include_inputs=True,
38
- model=eval_model
39
- )
40
-
41
- # Trajectory Evaluator
42
- trajectory_evaluator = TrajectoryEvaluator(
43
- rubric="""
44
- Evaluate the tool usage trajectory:
45
- 1. Correct tool selection - Were the right tools chosen?
46
- 2. Proper sequence - Logical order (Retrieve -> Analyze -> Explain)?
47
- 3. Efficiency - No unnecessary tools?
48
-
49
- Score 1.0 if optimal tools used correctly.
50
- Score 0.5 if correct tools but suboptimal sequence.
51
- SCORE 0.0 if wrong tools or major inefficiencies.
52
- """,
53
- include_inputs=True,
54
- model=eval_model
55
- )
56
-
57
- # Key Points Evaluator (Custom)
58
- from evals.key_points_evaluator import KeyPointsEvaluator
59
- key_points_evaluator = KeyPointsEvaluator()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/dataset_loader.py DELETED
@@ -1,35 +0,0 @@
1
- """
2
- Dataset loader that converts JSON to SDK Case objects.
3
- """
4
- import json
5
- from typing import List
6
- from strands_evals import Case
7
-
8
-
9
- def load_cases_from_json(filepath: str) -> List[Case]:
10
- """
11
- Load test cases from JSON dataset and convert to SDK Case objects.
12
-
13
- Args:
14
- filepath: Path to JSON dataset file
15
-
16
- Returns:
17
- List of Case objects
18
- """
19
- with open(filepath) as f:
20
- data = json.load(f)
21
-
22
- cases = []
23
- for item in data:
24
- case = Case(
25
- name=item["id"],
26
- input=item["question"],
27
- expected_output=None, # Not used for LLM-based evaluation
28
- metadata={
29
- "expected_key_points": item.get("expected_answer_key_points", []),
30
- "expected_intent": item.get("expected_intent", "")
31
- }
32
- )
33
- cases.append(case)
34
-
35
- return cases
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/generate_experiment.py DELETED
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Generate synthetic test cases using Strands ExperimentGenerator.
4
-
5
- This script replaces the custom generate_data.py and uses the SDK
6
- to generate diverse, high-quality test cases for the fraud agent.
7
- """
8
- import os
9
- import json
10
- import asyncio
11
- from dotenv import load_dotenv
12
-
13
- load_dotenv()
14
-
15
- from typing import List, Dict
16
- from strands_evals.generators import ExperimentGenerator
17
- from strands_evals.evaluators import OutputEvaluator
18
- from evals.config import eval_model
19
-
20
- # Context description for the generator
21
- CONTEXT = """
22
- You are generating test cases for a Fraud Model Explainability Assistant for a financial services company.
23
- The assistant uses RAG and tools to explain fraud scores (0-1000), SHAP values, and compliance checks.
24
-
25
- Users are typically:
26
- 1. Fraud Analysts (investigating specific cases)
27
- 2. Data Scientists (monitoring model performance)
28
- 3. Compliance Officers (checking for Fair Lending bias)
29
- 4. Executives (asking for high-level summaries)
30
-
31
- Tools available:
32
- - get_application_summary(app_id): Returns score, risk level.
33
- - explain_fraud_score(app_id): Returns SHAP feature contributions.
34
- - compare_to_population(app_id): Returns stats vs approved/denied.
35
- - check_fair_lending_flags(app_id): Returns bias analysis.
36
- - get_identity_network(app_id): Returns linked applications.
37
- """
38
-
39
- async def generate():
40
- print("🚀 Starting Experiment Generation with SDK...")
41
-
42
- # Initialize generator with str input/output
43
- generator = ExperimentGenerator[str, str](
44
- input_type=str,
45
- output_type=str,
46
- model=eval_model
47
- )
48
-
49
- # Generate experiment
50
- print(" Generating cases (this may take a minute)...")
51
- experiment = await generator.from_context_async(
52
- context=CONTEXT,
53
- num_cases=10, # Generate 10 new cases
54
- evaluator=OutputEvaluator, # Pass class, let generator create rubric
55
- task_description="Explain fraud model decisions and risk factors.",
56
- num_topics=5 # Split across different topics (High Risk, Compliance, etc.)
57
- )
58
-
59
- print(f"✅ Generated {len(experiment.cases)} new test cases.")
60
-
61
- # Convert to our JSON format
62
- new_cases = []
63
- for i, case in enumerate(experiment.cases):
64
- # Metadata might be None
65
- metadata = case.metadata if case.metadata else {}
66
- new_case = {
67
- "id": f"synth_sdk_{i+1}",
68
- "question": case.input,
69
- "expected_intent": metadata.get("topic", "General"),
70
- "expected_answer_key_points": [case.expected_output] if case.expected_output else []
71
- }
72
- new_cases.append(new_case)
73
- print(f" - [{new_case['expected_intent']}] {new_case['question'][:60]}...")
74
-
75
- # Load existing cases to append (optional, or overwrite)
76
- output_path = "evaluation/dataset_sdk.json"
77
-
78
- # Saving to a new file to avoid overwriting the main dataset during this test
79
- with open(output_path, "w") as f:
80
- json.dump(new_cases, f, indent=2)
81
-
82
- print(f"\n💾 Saved {len(new_cases)} cases to {output_path}")
83
- print(" Review the file and merge into evaluation/dataset.json if desired.")
84
-
85
- if __name__ == "__main__":
86
- asyncio.run(generate())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/key_points_evaluator.py DELETED
@@ -1,102 +0,0 @@
1
- """
2
- Custom evaluator for checking if specific key points are present in the response.
3
- """
4
- from typing import Any, List
5
- from strands_evals.evaluators import Evaluator
6
- from strands_evals.types.evaluation import EvaluationData, EvaluationOutput
7
- from strands_evals.types.trace import EvaluationLevel
8
- from typing_extensions import TypeVar
9
-
10
- InputT = TypeVar("InputT")
11
- OutputT = TypeVar("OutputT")
12
-
13
- class KeyPointsEvaluator(Evaluator[InputT, OutputT]):
14
- """Evaluates output by checking for presence of expected key points (keywords/phrases)."""
15
-
16
- evaluation_level = EvaluationLevel.TRACE_LEVEL
17
-
18
- def __init__(self, version: str = "v1"):
19
- super().__init__()
20
- self.version = version
21
-
22
- def evaluate(self, evaluation_case: EvaluationData[InputT, OutputT]) -> List[EvaluationOutput]:
23
- """Synchronous evaluation."""
24
- return self._do_evaluation(evaluation_case)
25
-
26
- async def evaluate_async(self, evaluation_case: EvaluationData[InputT, OutputT]) -> List[EvaluationOutput]:
27
- """Asynchronous evaluation."""
28
- return self._do_evaluation(evaluation_case)
29
-
30
- def _do_evaluation(self, evaluation_case: EvaluationData[InputT, OutputT]) -> List[EvaluationOutput]:
31
- """
32
- Check if expected key points are present in the actual output.
33
- Expects 'expected_key_points' list in case metadata.
34
- """
35
- # Get actual output
36
- actual_output = str(evaluation_case.actual_output)
37
-
38
- # Get expectations from case metadata (which is attached to evaluation_case)
39
- # Note: The SDK passes the whole Case object or relevant parts.
40
- # However, EvaluationData typically has input/output.
41
- # Metadata is likely accessible if evaluation_case is constructed from a Case.
42
- # But SDK EvaluationData doesn't strictly carry metadata field in all versions.
43
- # We rely on how Experiment constructs it.
44
-
45
- # EXPERIMENTAL: The SDK's Experiment loop constructs EvaluationData.
46
- # If it doesn't pass metadata, we need to inspect the source 'case'.
47
- # But Evaluator.evaluate receives EvaluationData, not Case.
48
- # Wait, Strands SDK 1.22 might have metadata on EvaluationData?
49
- # Let's check the type definition if needed.
50
- # For now, assuming we can access it or we need a workaround.
51
-
52
- # Workaround: For this custom evaluator to work with Experiment,
53
- # the Experiment must pass metadata.
54
-
55
- # Actually, looking at the Experiment source (which we can't see right now but inferred),
56
- # it might be easier to pass expected_output as the key points string?
57
- # Dataset loader sets: expected_key_points in metadata.
58
-
59
- # Let's try to access metadata if it exists on EvaluationData,
60
- # Otherwise fall back to a safe default.
61
-
62
- key_points = []
63
- if hasattr(evaluation_case, 'metadata') and evaluation_case.metadata:
64
- key_points = evaluation_case.metadata.get("expected_key_points", [])
65
-
66
- # Calculate score
67
- if not key_points:
68
- return [EvaluationOutput(
69
- score=1.0,
70
- test_pass=True,
71
- reason="No key points defined for this case.",
72
- label="N/A"
73
- )]
74
-
75
- hits = 0
76
- misses = []
77
-
78
- for point in key_points:
79
- point_lower = point.lower()
80
- output_lower = actual_output.lower()
81
-
82
- if point_lower in output_lower:
83
- hits += 1
84
- # partial match check (heuristic from run_full_suite)
85
- elif any(word in output_lower for word in point_lower.split() if len(word) > 4):
86
- hits += 0.5
87
- misses.append(f"{point} (Partial)")
88
- else:
89
- misses.append(point)
90
-
91
- score = min(1.0, hits / len(key_points))
92
-
93
- reason = f"Matched {hits}/{len(key_points)} key points."
94
- if misses:
95
- reason += f" Missed: {', '.join(misses[:3])}..."
96
-
97
- return [EvaluationOutput(
98
- score=score,
99
- test_pass=score >= 0.7, # 70% threshold
100
- reason=reason,
101
- label=f"{int(score*100)}%"
102
- )]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/langfuse_reporter.py DELETED
@@ -1,96 +0,0 @@
1
- """
2
- Langfuse integration for logging evaluation results.
3
- """
4
- import os
5
- import base64
6
- import httpx
7
- from typing import List, Dict
8
- from opentelemetry import trace
9
-
10
-
11
- class LangfuseClient:
12
- """Client for logging evaluation scores to Langfuse."""
13
-
14
- def __init__(self):
15
- self.secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
16
- self.public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
17
- self.base_url = os.environ.get("LANGFUSE_BASE_URL", "https://cloud.langfuse.com").rstrip("/")
18
-
19
- if not (self.secret_key and self.public_key):
20
- print("⚠ Langfuse credentials missing. Scoring disabled.")
21
- self.enabled = False
22
- return
23
-
24
- self.enabled = True
25
- auth_str = f"{self.public_key}:{self.secret_key}"
26
- auth_bytes = auth_str.encode("ascii")
27
- base64_auth = base64.b64encode(auth_bytes).decode("ascii")
28
- self.headers = {
29
- "Authorization": f"Basic {base64_auth}",
30
- "Content-Type": "application/json"
31
- }
32
-
33
- def score_trace(self, trace_id: str, name: str, value: float, comment: str = None):
34
- """Log a single score to Langfuse."""
35
- if not self.enabled:
36
- return
37
-
38
- url = f"{self.base_url}/api/public/scores"
39
- payload = {
40
- "traceId": trace_id,
41
- "name": name,
42
- "value": value,
43
- "comment": comment
44
- }
45
-
46
- try:
47
- resp = httpx.post(url, json=payload, headers=self.headers, timeout=10.0)
48
- if resp.status_code not in (200, 201):
49
- print(f" ⚠ Failed to log score {name}: {resp.status_code} - {resp.text}")
50
- except Exception as e:
51
- print(f" ⚠ Error logging score: {e}")
52
-
53
-
54
- class LangfuseReporter:
55
- """Reporter that logs SDK experiment results to Langfuse."""
56
-
57
- def __init__(self, langfuse_client: LangfuseClient):
58
- self.lf_client = langfuse_client
59
- self.tracer = trace.get_tracer("evaluation_reporter")
60
-
61
- def log_experiment_results(self, reports, case_names: List[str], evaluator_names: List[str] = None) -> Dict[str, str]:
62
- """
63
- Log SDK experiment results to Langfuse with OpenTelemetry trace correlation.
64
-
65
- Args:
66
- reports: List of evaluation reports from Experiment.run_evaluations()
67
- case_names: List of case names to create trace IDs for
68
- evaluator_names: Optional list of evaluator names. If not provided, generic names will be used.
69
-
70
- Returns:
71
- Dict mapping case names to trace IDs
72
- """
73
- trace_ids = {}
74
-
75
- for i, case_name in enumerate(case_names):
76
- # Create OTel span for this case
77
- with self.tracer.start_as_current_span(f"Eval: {case_name}") as span:
78
- # Get Trace ID
79
- trace_id_int = span.get_span_context().trace_id
80
- trace_id_hex = "{:032x}".format(trace_id_int)
81
- trace_ids[case_name] = trace_id_hex
82
-
83
- # Log all evaluator scores for this case
84
- for j, report in enumerate(reports):
85
- if i < len(report.scores):
86
- eval_name = evaluator_names[j] if evaluator_names and j < len(evaluator_names) else f"Evaluator_{j}"
87
- eval_name = eval_name.replace("Evaluator", "")
88
-
89
- self.lf_client.score_trace(
90
- trace_id=trace_id_hex,
91
- name=eval_name,
92
- value=report.scores[i],
93
- comment=report.reasons[i] if i < len(report.reasons) else None
94
- )
95
-
96
- return trace_ids
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/run_full_suite.py DELETED
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Run the full evaluation suite on the fraud agent.
4
-
5
- This runs all evaluators (Helpfulness, Faithfulness, Trajectory, Goal Success)
6
- and logs results to Langfuse with OpenTelemetry trace correlation.
7
- """
8
- import json
9
- from strands_evals import Experiment
10
- from strands_evals.types.evaluation import EvaluationData
11
- from evals.config import helpfulness_evaluator, faithfulness_evaluator, trajectory_evaluator, key_points_evaluator
12
- from evals.task_functions import get_fraud_explanation_with_trace
13
- from evals.dataset_loader import load_cases_from_json
14
- from evals.langfuse_reporter import LangfuseClient, LangfuseReporter
15
- from evals.utils import get_report_summary
16
-
17
-
18
- def main():
19
- print("🚀 Starting Full Evaluation Suite\n")
20
- print("="*60)
21
-
22
- # Load test cases
23
- cases = load_cases_from_json("evaluation/dataset.json")
24
- print(f"📋 Loaded {len(cases)} test cases\n")
25
-
26
- # Initialize Langfuse
27
- lf_client = LangfuseClient()
28
- reporter = LangfuseReporter(lf_client)
29
-
30
- # Run evaluations with all evaluators
31
- print("Running evaluations with SDK Experiment framework...")
32
- print("Evaluators: Helpfulness, Faithfulness, Trajectory\n")
33
-
34
- experiment = Experiment(
35
- cases=cases,
36
- evaluators=[
37
- helpfulness_evaluator,
38
- faithfulness_evaluator,
39
- trajectory_evaluator,
40
- key_points_evaluator
41
- ]
42
- )
43
-
44
- reports = experiment.run_evaluations(get_fraud_explanation_with_trace)
45
-
46
- # Display results for each evaluator
47
- print("\n" + "="*60)
48
- print("EVALUATION RESULTS")
49
- print("="*60 + "\n")
50
-
51
- for i, report in enumerate(reports):
52
- evaluator_name = type(experiment.evaluators[i]).__name__
53
- print(f"\n### {evaluator_name} ###")
54
- report.display()
55
-
56
- summary = get_report_summary(report)
57
- print(f"\n📊 {evaluator_name} Summary:")
58
- print(f" Pass Rate: {summary['pass_rate']:.1%}")
59
- print(f" Average Score: {summary['average_score']:.2f}")
60
- print()
61
-
62
- # Log to Langfuse
63
- print("\n" + "="*60)
64
- print("📤 Logging to Langfuse...")
65
- case_names = [case.name for case in cases]
66
- evaluator_names = [type(e).__name__ for e in experiment.evaluators]
67
- trace_ids = reporter.log_experiment_results(reports, case_names, evaluator_names)
68
- print(f" ✅ Logged {len(trace_ids)} traces with {len(reports)} metrics each")
69
-
70
- # Save experiment
71
- experiment.to_file("experiment_files/full_suite_eval")
72
- print("\n💾 Experiment saved to ./experiment_files/full_suite_eval.json")
73
-
74
- # Final summary
75
- print("\n" + "="*60)
76
- print("✅ EVALUATION COMPLETE")
77
- print("="*60)
78
- print(f"\nTotal Cases: {len(cases)}")
79
- print(f"Evaluators: {len(reports)}")
80
- print(f"Langfuse Traces: {len(trace_ids)}")
81
- print(f"\nView results in Langfuse dashboard")
82
-
83
-
84
- if __name__ == "__main__":
85
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/run_helpfulness.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Run helpfulness evaluation on the fraud agent.
4
-
5
- This is equivalent to basic_eval.py from the SDK quickstart,
6
- but customized for the fraud explainability use case.
7
- """
8
- from strands_evals import Experiment
9
- from evals.config import helpfulness_evaluator
10
- from evals.task_functions import get_fraud_explanation
11
- from evals.dataset_loader import load_cases_from_json
12
- from evals.langfuse_reporter import LangfuseClient, LangfuseReporter
13
- from evals.utils import get_report_summary
14
-
15
-
16
- def main():
17
- print("=== Helpfulness Evaluation ===\n")
18
-
19
- # Load test cases
20
- cases = load_cases_from_json("evaluation/dataset.json")
21
- print(f"Loaded {len(cases)} test cases\n")
22
-
23
- # Create experiment
24
- experiment = Experiment(
25
- cases=cases,
26
- evaluators=[helpfulness_evaluator]
27
- )
28
-
29
- # Run evaluations
30
- print("Running evaluations...")
31
- reports = experiment.run_evaluations(get_fraud_explanation)
32
-
33
- # Display SDK results
34
- print("\n" + "="*60)
35
- reports[0].display()
36
-
37
- # Get summary
38
- summary = get_report_summary(reports[0])
39
- print(f"\n📊 Summary:")
40
- print(f" Pass Rate: {summary['pass_rate']:.1%}")
41
- print(f" Average Score: {summary['average_score']:.2f}")
42
-
43
- # Log to Langfuse
44
- print("\n📤 Logging to Langfuse...")
45
- lf_client = LangfuseClient()
46
- reporter = LangfuseReporter(lf_client)
47
- case_names = [case.name for case in cases]
48
- evaluator_names = [type(e).__name__ for e in experiment.evaluators]
49
- trace_ids = reporter.log_experiment_results(reports, case_names, evaluator_names)
50
- print(f" Logged {len(trace_ids)} traces to Langfuse")
51
-
52
- # Save experiment
53
- experiment.to_file("experiment_files/helpfulness_eval")
54
- print("\n💾 Experiment saved to ./experiment_files/helpfulness_eval.json")
55
-
56
-
57
- if __name__ == "__main__":
58
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/run_trajectory.py DELETED
@@ -1,57 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Run trajectory evaluation on the fraud agent.
4
-
5
- This evaluates whether the agent uses the correct tools in the right sequence.
6
- """
7
- from strands_evals import Experiment
8
- from evals.config import trajectory_evaluator
9
- from evals.task_functions import get_fraud_explanation_with_trace
10
- from evals.dataset_loader import load_cases_from_json
11
- from evals.langfuse_reporter import LangfuseClient, LangfuseReporter
12
- from evals.utils import get_report_summary
13
-
14
-
15
- def main():
16
- print("=== Trajectory Evaluation ===\n")
17
-
18
- # Load test cases
19
- cases = load_cases_from_json("evaluation/dataset.json")
20
- print(f"Loaded {len(cases)} test cases\n")
21
-
22
- # Create experiment
23
- experiment = Experiment(
24
- cases=cases,
25
- evaluators=[trajectory_evaluator]
26
- )
27
-
28
- # Run evaluations
29
- print("Running evaluations...")
30
- reports = experiment.run_evaluations(get_fraud_explanation_with_trace)
31
-
32
- # Display SDK results
33
- print("\n" + "="*60)
34
- reports[0].display()
35
-
36
- # Get summary
37
- summary = get_report_summary(reports[0])
38
- print(f"\n📊 Summary:")
39
- print(f" Pass Rate: {summary['pass_rate']:.1%}")
40
- print(f" Average Score: {summary['average_score']:.2f}")
41
-
42
- # Log to Langfuse
43
- print("\n📤 Logging to Langfuse...")
44
- lf_client = LangfuseClient()
45
- reporter = LangfuseReporter(lf_client)
46
- case_names = [case.name for case in cases]
47
- evaluator_names = [type(e).__name__ for e in experiment.evaluators]
48
- trace_ids = reporter.log_experiment_results(reports, case_names, evaluator_names)
49
- print(f" Logged {len(trace_ids)} traces to Langfuse")
50
-
51
- # Save experiment
52
- experiment.to_file("experiment_files/trajectory_eval")
53
- print("\n💾 Experiment saved to ./experiment_files/trajectory_eval.json")
54
-
55
-
56
- if __name__ == "__main__":
57
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/task_functions.py DELETED
@@ -1,64 +0,0 @@
1
- """
2
- Task functions that wrap the fraud agent for evaluation.
3
- """
4
- from typing import List, Tuple
5
- from strands_evals import Case
6
- from app import query_agent
7
-
8
-
9
- def extract_context_and_tools(agent_result) -> Tuple[str, List[str]]:
10
- """Extracts retrieved text and tool names from AgentResult."""
11
- context = []
12
- tool_calls = []
13
-
14
- if not hasattr(agent_result, 'trace') or not agent_result.trace:
15
- return "", []
16
-
17
- for span in agent_result.trace.spans:
18
- # Check for tool execution spans
19
- if hasattr(span, 'span_type') and str(span.span_type) == 'tool_execution':
20
- # Tool Name
21
- tool_name = span.tool_call.name
22
- tool_calls.append(tool_name)
23
-
24
- # Context from Search/Load Tools
25
- if 'confluence' in tool_name or 'get_application_summary' in tool_name or 'compare' in tool_name:
26
- context.append(f"Source ({tool_name}): {span.tool_result.content}")
27
-
28
- return "\n\n".join(context), tool_calls
29
-
30
-
31
- def get_fraud_explanation(case: Case) -> str:
32
- """
33
- Task function for basic output evaluation.
34
-
35
- Args:
36
- case: Test case with input question
37
-
38
- Returns:
39
- Agent's response as string
40
- """
41
- result = query_agent(case.input, return_full_result=False)
42
- return str(result)
43
-
44
-
45
- def get_fraud_explanation_with_trace(case: Case) -> dict:
46
- """
47
- Task function for trajectory and faithfulness evaluation.
48
-
49
- Args:
50
- case: Test case with input question
51
-
52
- Returns:
53
- Dict with output, trajectory, and context
54
- """
55
- result = query_agent(case.input, return_full_result=True)
56
-
57
- # Extract context and tools
58
- context, tools = extract_context_and_tools(result)
59
-
60
- return {
61
- "output": str(result),
62
- "trajectory": tools,
63
- "context": context
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evals/utils.py DELETED
@@ -1,15 +0,0 @@
1
- """
2
- Utility functions for evaluations.
3
- """
4
-
5
- def get_report_summary(report):
6
- """
7
- Calculate summary metrics from an EvaluationReport.
8
-
9
- Returns:
10
- dict: A dictionary containing 'pass_rate' and 'average_score'.
11
- """
12
- return {
13
- "pass_rate": sum(report.test_passes) / len(report.test_passes) if report.test_passes else 0,
14
- "average_score": report.overall_score
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evaluate.py ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import json
4
+ import asyncio
5
+ import base64
6
+ import httpx
7
+ from typing import List, Dict, Any
8
+ from openai import AsyncOpenAI
9
+
10
+ # OpenTelemetry
11
+ from opentelemetry import trace
12
+ from opentelemetry.sdk.trace import TracerProvider
13
+
14
+ # App imports (triggers setup_telemetry)
15
+ from app import query_agent
16
+
17
+ tracer = trace.get_tracer("evaluation_script")
18
+
19
+ # Initialize OpenAI Client for Evaluation
20
+ aclient = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
21
+
22
+ class LangfuseClient:
23
+ def __init__(self):
24
+ self.secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
25
+ self.public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
26
+ self.base_url = os.environ.get("LANGFUSE_BASE_URL", "https://cloud.langfuse.com").rstrip("/")
27
+
28
+ if not (self.secret_key and self.public_key):
29
+ print("⚠ Langfuse credentials missing. Scoring disabled.")
30
+ self.enabled = False
31
+ return
32
+
33
+ self.enabled = True
34
+ auth_str = f"{self.public_key}:{self.secret_key}"
35
+ auth_bytes = auth_str.encode("ascii")
36
+ base64_auth = base64.b64encode(auth_bytes).decode("ascii")
37
+ self.headers = {
38
+ "Authorization": f"Basic {base64_auth}",
39
+ "Content-Type": "application/json"
40
+ }
41
+
42
+ def score_trace(self, trace_id: str, name: str, value: float, comment: str = None):
43
+ if not self.enabled:
44
+ return
45
+
46
+ url = f"{self.base_url}/api/public/scores"
47
+ payload = {
48
+ "traceId": trace_id,
49
+ "name": name,
50
+ "value": value,
51
+ "comment": comment
52
+ }
53
+
54
+ try:
55
+ # Synchronous call for simplicity in this script
56
+ resp = httpx.post(url, json=payload, headers=self.headers, timeout=10.0)
57
+ if resp.status_code not in (200, 201):
58
+ print(f" ⚠ Failed to log score {name}: {resp.status_code} - {resp.text}")
59
+ except Exception as e:
60
+ print(f" ⚠ Error logging score: {e}")
61
+
62
+ async def evaluate_helpfulness(question: str, answer: str) -> dict:
63
+ """Evaluates if the answer is helpful using LLM."""
64
+ prompt = f"""
65
+ You are an expert evaluator. Rate the helpfulness of the AI response to the user question.
66
+
67
+ Question: {question}
68
+ Response: {answer}
69
+
70
+ Score 0.0 to 1.0 (1.0 is most helpful).
71
+ Provide reasoning.
72
+
73
+ Output JSON: {{"score": float, "reason": "string"}}
74
+ """
75
+ try:
76
+ response = await aclient.chat.completions.create(
77
+ model="gpt-4o",
78
+ messages=[{"role": "user", "content": prompt}],
79
+ response_format={"type": "json_object"},
80
+ temperature=0
81
+ )
82
+ return json.loads(response.choices[0].message.content)
83
+ except Exception as e:
84
+ print(f" ⚠ Eval Error: {e}")
85
+ return {"score": 0.0, "reason": "Evaluation failed"}
86
+
87
+ async def evaluate_faithfulness(question: str, answer: str, context: str) -> dict:
88
+ """Evaluates if the answer is faithful to the context."""
89
+ if not context:
90
+ return {"score": 0.5, "reason": "No context available for faithfulness check."}
91
+
92
+ prompt = f"""
93
+ You are an expert evaluator. Rate the faithfulness of the AI response to the retrieved context.
94
+
95
+ Context:
96
+ {context[:10000]}... (truncated)
97
+
98
+ Question: {question}
99
+ Response: {answer}
100
+
101
+ Score 0.0 to 1.0 (1.0 is fully supported by context).
102
+ If the response contains information NOT in the context (hallucination), penalize heavily.
103
+
104
+ Output JSON: {{"score": float, "reason": "string"}}
105
+ """
106
+ try:
107
+ response = await aclient.chat.completions.create(
108
+ model="gpt-4o",
109
+ messages=[{"role": "user", "content": prompt}],
110
+ response_format={"type": "json_object"},
111
+ temperature=0
112
+ )
113
+ return json.loads(response.choices[0].message.content)
114
+ except Exception as e:
115
+ print(f" ⚠ Faithfulness Eval Error: {e}")
116
+ return {"score": 0.0, "reason": "Evaluation failed"}
117
+
118
+ async def evaluate_trajectory(question: str, tool_calls: List[str], rubric: str) -> dict:
119
+ """Evaluates if the tool usage followed the rubric."""
120
+ prompt = f"""
121
+ You are an expert evaluator. Rate the agent's execution trajectory against the rubric.
122
+
123
+ Rubric: {rubric}
124
+
125
+ Question: {question}
126
+ Tool Sequence: {json.dumps(tool_calls)}
127
+
128
+ Score 0.0 to 1.0 (1.0 is perfect adherence).
129
+ Did it skip required steps? Did it use irrelevant tools?
130
+
131
+ Output JSON: {{"score": float, "reason": "string"}}
132
+ """
133
+ try:
134
+ response = await aclient.chat.completions.create(
135
+ model="gpt-4o",
136
+ messages=[{"role": "user", "content": prompt}],
137
+ response_format={"type": "json_object"},
138
+ temperature=0
139
+ )
140
+ return json.loads(response.choices[0].message.content)
141
+ except Exception as e:
142
+ print(f" ⚠ Trajectory Eval Error: {e}")
143
+ return {"score": 0.0, "reason": "Evaluation failed"}
144
+
145
+ def extract_context_and_tools(agent_result) -> tuple[str, List[str]]:
146
+ """Extracts retrieved text and tool names from AgentResult."""
147
+ context = []
148
+ tool_calls = []
149
+
150
+ if not hasattr(agent_result, 'trace') or not agent_result.trace:
151
+ return "", []
152
+
153
+ for span in agent_result.trace.spans:
154
+ # Check for tool execution spans (simplified check)
155
+ if hasattr(span, 'span_type') and str(span.span_type) == 'tool_execution':
156
+ # Tool Name
157
+ tool_name = span.tool_call.name
158
+ tool_calls.append(tool_name)
159
+
160
+ # Context from Search/Load Tools
161
+ if 'confluence' in tool_name or 'get_application_summary' in tool_name or 'compare' in tool_name:
162
+ context.append(f"Source ({tool_name}): {span.tool_result.content}")
163
+
164
+ return "\n\n".join(context), tool_calls
165
+
166
+ async def run_evaluation():
167
+ print("🚀 Starting Evaluation Pipeline (Custom LLM Evals: Helpfulness, Faithfulness, Trajectory)...")
168
+
169
+ # Initialize Client
170
+ lf_client = LangfuseClient()
171
+
172
+ # Load dataset
173
+ try:
174
+ with open("evaluation/dataset.json", "r") as f:
175
+ dataset = json.load(f)
176
+ except FileNotFoundError:
177
+ print("❌ evaluation/dataset.json not found.")
178
+ return
179
+
180
+ print(f"📋 Loaded {len(dataset)} test cases.")
181
+
182
+ results = []
183
+
184
+ for case in dataset:
185
+ case_id = case["id"]
186
+ question = case["question"]
187
+ expected_key_points = case["expected_answer_key_points"]
188
+
189
+ print(f"\n🧪 Running Case: {case_id}")
190
+
191
+ # Start a span for this evaluation case
192
+ with tracer.start_as_current_span(f"Eval: {case_id}") as span:
193
+ # Get Trace ID (OTel stores it as int, needs hex conversion)
194
+ trace_id_int = span.get_span_context().trace_id
195
+ trace_id_hex = "{:032x}".format(trace_id_int)
196
+
197
+ # Add metadata
198
+ span.set_attribute("evaluation.case_id", case_id)
199
+ span.set_attribute("evaluation.question", question)
200
+
201
+ # 1. Run Agent (Request Full Result)
202
+ # The agent's internal spans will be nested under this span automatically
203
+ result_obj = query_agent(question, return_full_result=True)
204
+ answer = str(result_obj)
205
+
206
+ # Extract Internals
207
+ context_text, tool_sequence = extract_context_and_tools(result_obj)
208
+ # Log extracted data for debug
209
+ span.set_attribute("evaluation.tool_sequence", json.dumps(tool_sequence))
210
+
211
+ # 2. Run Evaluators & Log Scores
212
+
213
+ # A. Helpfulness
214
+ help_res = await evaluate_helpfulness(question, answer)
215
+ lf_client.score_trace(trace_id=trace_id_hex, name="Helpfulness", value=help_res["score"], comment=help_res["reason"])
216
+ print(f" ✅ Helpfulness: {help_res['score']:.2f}")
217
+
218
+ # B. Faithfulness
219
+ faith_res = await evaluate_faithfulness(question, answer, context_text)
220
+ lf_client.score_trace(trace_id=trace_id_hex, name="Faithfulness", value=faith_res["score"], comment=faith_res["reason"])
221
+ print(f" 📖 Faithfulness: {faith_res['score']:.2f}")
222
+
223
+ # C. Trajectory
224
+ rubric = "1. Retrieve data (summary). 2. Analyze specifics/compare. 3. Check compliance if relevant. 4. Explain."
225
+ traj_res = await evaluate_trajectory(question, tool_sequence, rubric)
226
+ lf_client.score_trace(trace_id=trace_id_hex, name="Trajectory", value=traj_res["score"], comment=traj_res["reason"])
227
+ print(f" 👣 Trajectory: {traj_res['score']:.2f} ({len(tool_sequence)} tools)")
228
+
229
+ # D. Goal Success
230
+ hits = 0
231
+ if expected_key_points:
232
+ for point in expected_key_points:
233
+ if point.lower() in answer.lower():
234
+ hits += 1
235
+ elif any(word in answer.lower() for word in point.split() if len(word) > 4):
236
+ hits += 0.5
237
+ success_rate = min(1.0, hits / len(expected_key_points))
238
+ else:
239
+ success_rate = 1.0
240
+
241
+ lf_client.score_trace(trace_id=trace_id_hex, name="Goal Success", value=success_rate, comment=f"Matched {hits}/{len(expected_key_points)} key points")
242
+ print(f" 🎯 Goal Success: {success_rate:.2f}")
243
+
244
+ results.append({
245
+ "case_id": case_id,
246
+ "trace_id": trace_id_hex,
247
+ "helpfulness": help_res["score"],
248
+ "faithfulness": faith_res["score"],
249
+ "trajectory": traj_res["score"],
250
+ "goal_success": success_rate
251
+ })
252
+
253
+ # Summary
254
+ print("\n📊 Evaluation Summary")
255
+ print(json.dumps(results, indent=2))
256
+
257
+ if __name__ == "__main__":
258
+ asyncio.run(run_evaluation())
evaluation/dataset.json DELETED
@@ -1,314 +0,0 @@
1
- [
2
- {
3
- "id": "case_1",
4
- "question": "Why was application APP-78432 flagged as high risk?",
5
- "expected_intent": "Analyze why application APP-78432 was flagged",
6
- "expected_answer_key_points": [
7
- "Fraud Score: 449",
8
- "Risk Level: MEDIUM",
9
- "Model: XGBoost Fraud Ensemble v3.2",
10
- "Decision: APPROVED"
11
- ]
12
- },
13
- {
14
- "id": "case_2",
15
- "question": "Check fair lending compliance for APP-55555",
16
- "expected_intent": "Check fair lending compliance",
17
- "expected_answer_key_points": [
18
- "No geographic, name-based, or age-related discrimination",
19
- "Adverse Impact Ratio: 0.94",
20
- "Status: COMPLIANT"
21
- ]
22
- },
23
- {
24
- "id": "case_3",
25
- "question": "Explain the fraud score for APP-12345 and compare it to approved applications",
26
- "expected_intent": "Explain fraud score and compare to population",
27
- "expected_answer_key_points": [
28
- "Fraud Score: 850",
29
- "Risk Level: HIGH",
30
- "Suspicious Pattern: High velocity of applications",
31
- "Identity verification usage"
32
- ]
33
- },
34
- {
35
- "id": "synth_4_synth_high_risk_1",
36
- "question": "Why is this account flagged as high risk for synthetic identity fraud?",
37
- "expected_intent": "Understand the reasons behind the high-risk classification.",
38
- "expected_answer_key_points": [
39
- "Unusual account activity patterns",
40
- "Mismatch between identity attributes",
41
- "Use of known fraudulent identifiers"
42
- ]
43
- },
44
- {
45
- "id": "synth_5_synth_high_risk_2",
46
- "question": "What specific behaviors in the transaction history indicate synthetic identity fraud?",
47
- "expected_intent": "Identify behaviors that suggest synthetic identity fraud.",
48
- "expected_answer_key_points": [
49
- "Multiple transactions with inconsistent locations",
50
- "Rapid account activity after dormancy",
51
- "Attempts to access high-value services"
52
- ]
53
- },
54
- {
55
- "id": "synth_6_synth_high_risk_3",
56
- "question": "How does the model differentiate between synthetic identities and legitimate customers?",
57
- "expected_intent": "Understand the model's methodology for distinguishing between synthetic and legitimate identities.",
58
- "expected_answer_key_points": [
59
- "Advanced pattern recognition algorithms",
60
- "Comparison against known legitimate customer profiles",
61
- "Analysis of identity verification documentation"
62
- ]
63
- },
64
- {
65
- "id": "synth_7_synth_high_risk_4",
66
- "question": "What are the common data points used by the model to detect synthetic identities?",
67
- "expected_intent": "Identify key data points used by the model for detection.",
68
- "expected_answer_key_points": [
69
- "Social security number validation",
70
- "Cross-referencing with public records",
71
- "Analysis of digital footprint and device information"
72
- ]
73
- },
74
- {
75
- "id": "synth_8_low_risk_fp_1",
76
- "question": "Why was this transaction flagged as fraudulent despite being low risk?",
77
- "expected_intent": "Understand the reasons behind a false positive classification.",
78
- "expected_answer_key_points": [
79
- "Unusual transaction pattern",
80
- "Customer purchase history",
81
- "Changes in location or device used"
82
- ]
83
- },
84
- {
85
- "id": "synth_9_low_risk_fp_2",
86
- "question": "Can you explain why this account's activity is considered suspicious?",
87
- "expected_intent": "Gain insights into the factors influencing the fraud model's decision.",
88
- "expected_answer_key_points": [
89
- "High volume of transactions",
90
- "Transaction value deviations",
91
- "Comparison with typical user behavior"
92
- ]
93
- },
94
- {
95
- "id": "synth_10_low_risk_fp_3",
96
- "question": "What factors led to this low-risk transaction being marked as fraud?",
97
- "expected_intent": "Identify specific elements that triggered the fraud alert.",
98
- "expected_answer_key_points": [
99
- "Mismatch in expected transaction time",
100
- "Discrepancy in user verification",
101
- "Recent account changes or updates"
102
- ]
103
- },
104
- {
105
- "id": "synth_11_low_risk_fp_4",
106
- "question": "Why did our system mistakenly flag this legitimate transaction?",
107
- "expected_intent": "Determine the cause of the system error leading to a false positive.",
108
- "expected_answer_key_points": [
109
- "Error in model threshold setting",
110
- "Anomaly detection misclassification",
111
- "Lack of data on new customer behavior"
112
- ]
113
- },
114
- {
115
- "id": "synth_12_synth_borderline_case_1",
116
- "question": "Why is this transaction flagged as suspicious when the customer has a long history of legitimate purchases?",
117
- "expected_intent": "Understand mixed signals causing a fraud alert.",
118
- "expected_answer_key_points": [
119
- "Analyzing recent transaction behavior",
120
- "Comparing with customer's purchase history",
121
- "Highlighting unusual transaction patterns"
122
- ]
123
- },
124
- {
125
- "id": "synth_13_synth_borderline_case_2",
126
- "question": "Can you explain why this low amount transaction is considered high-risk while the customer has been verified recently?",
127
- "expected_intent": "Clarify factors contributing to risk evaluation.",
128
- "expected_answer_key_points": [
129
- "Risk assessment includes transaction context",
130
- "Verification status vs. transaction attributes",
131
- "Analysis of current vs. past behavior"
132
- ]
133
- },
134
- {
135
- "id": "synth_14_synth_borderline_case_3",
136
- "question": "This merchant is reputable, so why are some of their transactions flagged for potential fraud?",
137
- "expected_intent": "Investigate reasons behind inconsistent fraud signals.",
138
- "expected_answer_key_points": [
139
- "Merchant transaction volume vs. individual transactions",
140
- "Potential changes in merchant activities",
141
- "Flagged patterns specific to current transactions"
142
- ]
143
- },
144
- {
145
- "id": "synth_15_synth_borderline_case_4",
146
- "question": "Why is this account flagged when the user is known for frequent travel and varied spending patterns?",
147
- "expected_intent": "Identify causes of false positives in fraud detection.",
148
- "expected_answer_key_points": [
149
- "Account flagged due to recent activity anomalies",
150
- "Analysis of travel-related spending patterns",
151
- "Consideration of geographic and spending behavior"
152
- ]
153
- },
154
- {
155
- "id": "synth_16_fair_lending_compliance_1",
156
- "question": "Why was the loan application of a minority applicant flagged as high risk by the model?",
157
- "expected_intent": "Understand the factors leading to the high-risk categorization for minority applicants.",
158
- "expected_answer_key_points": [
159
- "Model's risk assessment criteria",
160
- "Applicant's credit history and income",
161
- "Any bias or unfair treatment in the model"
162
- ]
163
- },
164
- {
165
- "id": "synth_17_fair_lending_compliance_2",
166
- "question": "Can you explain if the model's decision impacts applicants from low-income neighborhoods differently?",
167
- "expected_intent": "Determine if there is any disparate impact based on the applicant's neighborhood.",
168
- "expected_answer_key_points": [
169
- "Impact of location on risk scoring",
170
- "Comparison of approval rates by neighborhood",
171
- "Fair lending compliance measures in place"
172
- ]
173
- },
174
- {
175
- "id": "synth_18_fair_lending_compliance_3",
176
- "question": "What measures are in place to ensure that the model's decisions comply with fair lending regulations?",
177
- "expected_intent": "Identify compliance protocols for fair lending regulations within the model.",
178
- "expected_answer_key_points": [
179
- "Regular bias audits",
180
- "Use of fair lending algorithms",
181
- "Continuous monitoring for compliance"
182
- ]
183
- },
184
- {
185
- "id": "synth_19_fair_lending_compliance_4",
186
- "question": "How does the model ensure equal treatment for applicants with similar financial profiles but from different demographic groups?",
187
- "expected_intent": "Verify the model's fairness and equality in decision-making across demographics.",
188
- "expected_answer_key_points": [
189
- "Demographic parity in decision-making",
190
- "Comparative analysis of similar profiles",
191
- "Adjustments made to mitigate bias"
192
- ]
193
- },
194
- {
195
- "id": "synth_20_synth_high_risk_1",
196
- "question": "Why is the model flagging an unusually high number of transactions as high-risk this month?",
197
- "expected_intent": "Understand the reason behind the spike in high-risk transaction flags.",
198
- "expected_answer_key_points": [
199
- "Change in transaction patterns",
200
- "Adjustment in model thresholds",
201
- "New data inputs affecting model behavior"
202
- ]
203
- },
204
- {
205
- "id": "synth_21_synth_low_accuracy_2",
206
- "question": "What could be causing the recent drop in model accuracy for identifying fraudulent transactions?",
207
- "expected_intent": "Identify factors leading to decreased model accuracy.",
208
- "expected_answer_key_points": [
209
- "Data drift or changes in data distribution",
210
- "Outdated model parameters",
211
- "Need for model retraining"
212
- ]
213
- },
214
- {
215
- "id": "synth_22_synth_segment_discrepancy_3",
216
- "question": "Why is the model performing poorly on certain customer segments?",
217
- "expected_intent": "Explain performance discrepancies across customer segments.",
218
- "expected_answer_key_points": [
219
- "Segment-specific behavior not well-captured",
220
- "Imbalanced training data for segments",
221
- "Potential need for segment-specific features"
222
- ]
223
- },
224
- {
225
- "id": "synth_23_synth_feature_importance_4",
226
- "question": "What are the most influential features the model uses to determine fraud risk?",
227
- "expected_intent": "Identify key features driving model predictions.",
228
- "expected_answer_key_points": [
229
- "List of top contributing features",
230
- "Feature importance ranking",
231
- "Impact of each feature on the model's decision"
232
- ]
233
- },
234
- {
235
- "id": "synth_sdk_1",
236
- "question": "Application ID: 1023456",
237
- "expected_intent": "General",
238
- "expected_answer_key_points": [
239
- "Fraud Score: 750\nRisk Level: High\nSHAP Values:\n- Transaction History Impact: +200\n- Credit Score Impact: +150\n- Recent Loan Applications Impact: -50\n- Employment History Impact: +100\nExplanation: The application has a high fraud score primarily due to the transaction history and credit score, which together contribute significantly to the high risk level. The recent loan applications positively influence the score by lowering it slightly, but not enough to offset the other risk factors. Employment history further increases the score. It's essential to focus on improving transaction patterns to lower the fraud score."
240
- ]
241
- },
242
- {
243
- "id": "synth_sdk_2",
244
- "question": "Application ID: 4321XYZ\n- Request from Fraud Analyst: \"Please provide a detailed breakdown of the fraud score for this application. Include key feature contributions and provide an explanation for any features that heavily influence the score.\"\n- Tools and Capabilities Requested:\n - use explain_fraud_score(4321XYZ) to highlight SHAP values.\n - identify high-contributing features impacting fraud score.\n - analyze borderline scores and explain if any apply.",
245
- "expected_intent": "General",
246
- "expected_answer_key_points": [
247
- "Fraud Score: 680 (Moderate Risk)\nKey SHAP Contribution Analysis:\n1. \"Credit History Length\": +45 (Positive impact due to long credit history)\n2. \"Number of Recent Applications\": +100 (Negative impact due to recent application spike)\n3. \"Unusual Geolocation Activity\": +150 (High negative impact due to recent activity in a high-risk zone)\n4. \"Income Verification\": -65 (Positive impact due to verified and stable income)\n\nExplanation:\n- The fraud score is boosted to a higher risk mainly due to the unusual geolocation activity and the number of recent applications, which are significant red flags in fraud detection. While the credit history length and verified income provide a buffer reducing the risk level somewhat, the overall risk remains moderate.\n- Due to the combination and magnitude of these factors, the borderline score reflects an increased risk with possibilities of both legitimate and fraudulent activities present."
248
- ]
249
- },
250
- {
251
- "id": "synth_sdk_3",
252
- "question": "Task: Analyze the performance of the fraud detection model over the past six months.\n\nData Provided: \n- List of application IDs from the last six months\n- Access to tools: compare_to_population, explain_fraud_score\n\nKey Question: What trends can be identified in the distribution of fraud scores and SHAP value importance over this period?",
253
- "expected_intent": "General",
254
- "expected_answer_key_points": [
255
- "Output:\n1. Statistical report indicating any significant trends or shifts in fraud scores over the six-month period using compare_to_population.\n2. Identification of any changes in SHAP feature importance that may indicate shifts in model predictions.\n3. Summary of observations highlighting potential model drift or changes in population characteristics that could impact fraud detection accuracy."
256
- ]
257
- },
258
- {
259
- "id": "synth_sdk_4",
260
- "question": "A data scientist is analyzing the fraud detection model's performance over the past six months for applications in a specific region. They want to understand how the model's scoring distribution has shifted over this period and identify any significant changes in feature importance that might correspond with this shift. Use the tools compare_to_population and explain_fraud_score. The focus is on applications in the Midwest region, with application IDs ranging from 1001 to 1050. The model should track score distribution shifts and highlight any significant changes in SHAP feature importance.",
261
- "expected_intent": "General",
262
- "expected_answer_key_points": [
263
- "1. Provide a trend report showing score distribution changes over the past six months for the specified application range in the Midwest, indicating whether fraud scores have increased, decreased, or remained stable.\n2. Use compare_to_population to determine how these changes compare to national trends, noting any significant deviations.\n3. Highlight significant shifts in SHAP value contributions for key features over the same period, indicating which features have become more or less significant in model decision-making.\n4. Correlate findings with any historical applications data to suggest possible reasons for observed shifts, such as policy changes or external economic factors."
264
- ]
265
- },
266
- {
267
- "id": "synth_sdk_5",
268
- "question": "The Compliance Officer wants to verify if there is any evidence of gender bias in fraud scoring for the application ID: F12345. Use the 'check_fair_lending_flags' to detect bias indicators and 'compare_to_population' to analyze demographic-specific fraud score patterns.",
269
- "expected_intent": "General",
270
- "expected_answer_key_points": [
271
- "The 'check_fair_lending_flags' tool indicates a potential bias against female applicants due to disparate impact ratios below threshold in the fraud scoring process, indicating females are disproportionately denied compared to males. 'compare_to_population' further shows that female applicants have consistently higher average fraud scores compared to male counterparts for similar profiles, suggesting an underlying model bias."
272
- ]
273
- },
274
- {
275
- "id": "synth_sdk_6",
276
- "question": "compliance_officer: \"I need to verify if application 1234 has any fair lending bias issues. Check for any bias flags and provide basic SHAP contributions for this case.\"",
277
- "expected_intent": "General",
278
- "expected_answer_key_points": [
279
- "1. Use check_fair_lending_flags(app_id=1234) to identify if there are any bias issues. The tool will return 'No Bias Detected' or 'Potential Bias Detected'.\n2. If bias is present, explain_fraud_score(app_id=1234) to understand which features contribute most to the score.\n3. Report: 'No Bias Detected. Main factors influencing the fraud score are {feature1, feature2} contributing {value1, value2}.' OR 'Potential Bias Detected. SHAP analysis reveals major contributors: {biased_feature} with {value}.'"
280
- ]
281
- },
282
- {
283
- "id": "synth_sdk_7",
284
- "question": "You are tasked with preparing an executive summary that highlights the current trends in fraud detection for the past quarter for the executive team of the financial services company. Use the following application IDs: ['A12345', 'B67890', 'C13579'].",
285
- "expected_intent": "General",
286
- "expected_answer_key_points": [
287
- "The executive summary should include:\n1. A concise summary of the fraud scores and risk levels for each application using 'get_application_summary' for the IDs 'A12345', 'B67890', 'C13579'.\n2. A high-level analysis showing any evident trends in fraud scores over the quarter period.\n3. A brief mention of major contributing factors to the high fraud scores, utilizing SHAP values (not the specifics, just an overview of the top features contributing).\n4. A concise explanation of the application network status for each of these IDs using 'get_identity_network'.\n\nThe summary should be formatted in a report-friendly manner, prioritizing clarity and brevity for executive review."
288
- ]
289
- },
290
- {
291
- "id": "synth_sdk_8",
292
- "question": "Generate a high-level summary for executives on the fraud score trends and key contributing factors for applications over the last quarter. Include how applications are linked within the identity network for potential collusion. Use the following application IDs: ['A123', 'B456', 'C789'].",
293
- "expected_intent": "General",
294
- "expected_answer_key_points": [
295
- "The analysis for the last quarter indicates a rising trend in fraud scores, with the average score increasing by approximately 15% compared to the previous period. Key contributing factors identified through SHAP analysis include unusually high transaction volumes and discrepancies in reported income. Specific to applications ['A123', 'B456', 'C789'], notable features contributing to high fraud scores were similar across the board, indicating potential systemic fraud patterns. Moreover, the get_identity_network for these applications reveals interconnected relationships, suggesting possible collusion attempts. The network analysis indicates that several other applications are linked through shared contact information and transaction patterns, necessitating further investigation into these connections. Overall, the data suggests an evolving strategy that involves increasing sophistication in application profiles that require continued vigilance and adaptation of fraud detection measures."
296
- ]
297
- },
298
- {
299
- "id": "synth_sdk_9",
300
- "question": "Application ID: 12345; Requested Action: Analyze impact of linked applications on fraud score using get_identity_network.",
301
- "expected_intent": "General",
302
- "expected_answer_key_points": [
303
- "get_identity_network(12345) returns {\"linked_applications\": [\"11111\", \"22222\", \"33333\"]}. explain_fraud_score(12345) returns {\"SHAP_values\": {\"income\": -100, \"credit_history\": 50, \"linked_applications\": 200}}. compare_to_population(12345) shows linked applications influence fraud score to be 20% higher than similar approved applications. Risk level classified as 'Moderate Risk' due to negative impact from linked applications."
304
- ]
305
- },
306
- {
307
- "id": "synth_sdk_10",
308
- "question": "Application ID: 12345\nTask: Investigate the fraud score and impact of linked applications using the get_identity_network tool. Provide an analysis of how these linked applications influence the fraud score and SHAP values for Application ID 12345. Use compare_to_population to understand how these impacts differ from the general population.",
309
- "expected_intent": "General",
310
- "expected_answer_key_points": [
311
- "1. Identity Network Output: Application 12345 is linked with applications 56789, 98765, and 65432.\n2. Fraud Score Analysis: The fraud score for Application 12345 is 850. Linked applications have high fraud scores: Application 56789 (900), Application 98765 (875), and Application 65432 (910).\n3. SHAP Values: Key features that increased the fraud score include 'cross-application suspicious transactions' and 'irregular account activities.'\n4. Population Comparison: Compared to the approved applications' average fraud score of 500, Application 12345 and its linked applications show significantly higher risk, suggesting a potential fraud ring.\n5. Conclusion: The linkage to high-risk applications within the identity network explains the elevated fraud score for Application 12345, highlighting the influence of network associations in detecting fraud patterns."
312
- ]
313
- }
314
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -12,7 +12,6 @@ gradio>=4.0.0
12
 
13
  # HTTP client
14
  httpx>=0.24.0
15
- opentelemetry-exporter-otlp>=1.39.1
16
 
17
  # Environment variable management
18
  python-dotenv>=1.0.0
 
12
 
13
  # HTTP client
14
  httpx>=0.24.0
 
15
 
16
  # Environment variable management
17
  python-dotenv>=1.0.0