-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1aed4f8
commit 81a8653
Showing
11 changed files
with
253 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
.venv | ||
.env | ||
__pycache__/ | ||
*.py[cod] | ||
__pycache__ | ||
*.py[cod] | ||
.pytest_cache | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# LLM Prompt Evaluation Tool | ||
|
||
This tool allows you to evaluate how well your LLM responses match an ideal answer by comparing generated questions and answers. You can use the tool either by calling the comparison functions manually in your code or by passing a CSV file containing test cases. The tool also supports visualizing your test scores on a chart, with results automatically grouped by different prompt IDs. | ||
|
||
## Key Parameters | ||
|
||
- **ideal_answer (required):** | ||
The reference or "ideal" answer that your LLM response is compared against. | ||
**Example:** | ||
``` | ||
"Blockchain is like a digital ledger that everyone can see but no one can change." | ||
``` | ||
|
||
- **llm_response (optional):** | ||
Your LLM's response. If prompt is provided in `optional_params`, `llm_response` can be left as `None`. | ||
|
||
- **optional_params (optional):** | ||
A JSON-like dictionary that may include extra details for the test. It has the following structure: | ||
- `prompt`: A string with the prompt to be used (if you want to override the `prompt_id` prompt). | ||
- `context`: (Optional) Additional context for the prompt. | ||
- `prompt_id`: (Optional) A unique identifier for the prompt to be fetched online from Lamoom Service. | ||
**Example:** | ||
```json | ||
{ | ||
"prompt": "Explain blockchain to a beginner.", | ||
"context": {}, | ||
"prompt_id": "beginner_blockchain" | ||
} | ||
``` | ||
|
||
## Using the Tool | ||
|
||
### 1. Manual Testing | ||
|
||
You can manually call the `compare()` method by passing the required `ideal_answer` and (optionally) `llm_response` and `optional_params`. Each call will automatically accumulate the test results based on the provided (or default) `prompt_id`. | ||
|
||
**Example:** | ||
|
||
```python | ||
ideal_answer = ( | ||
"Blockchain is like a digital notebook that everyone can see, but no one can secretly change. " | ||
"Imagine a shared Google Doc where every change is recorded forever, and no one can edit past entries." | ||
) | ||
optional_params = { | ||
"prompt": "Explain the concept of blockchain to someone with no technical background." | ||
} | ||
|
||
test_response = TestLLMResponse(openai_key=os.environ.get("OPENAI_KEY")) | ||
# When llm_response is not passed, it defaults to None. | ||
result = test_response.compare(ideal_answer, optional_params=optional_params) | ||
|
||
# Print individual question details | ||
for question in result.questions: | ||
print(question.to_dict()) | ||
|
||
# Print overall score details | ||
print(result.score.to_dict()) | ||
``` | ||
|
||
### 2. Testing with CSV | ||
|
||
You can also pass multiple test cases using a CSV file. The CSV file should contain the following columns: | ||
|
||
- **ideal_answer:** (Required) The ideal answer text. | ||
- **optional_params:** (Optional) A JSON string containing the optional parameters. | ||
- **llm_response:** (Optional) If not provided, this value is set to `None`. | ||
|
||
Multiple rows can be included, and you can use different `prompt_id` values to test various prompts. | ||
|
||
**Example CSV Content:** | ||
|
||
```csv | ||
ideal_answer,optional_params,llm_response | ||
"Blockchain is a secure, immutable digital ledger.","{\"prompt\": \"Explain blockchain simply.\", \"prompt_id\": \"simple_blockchain\"}", | ||
"Blockchain is like a shared Google Doc that records every change.","{\"prompt\": \"Describe blockchain in simple terms.\", \"prompt_id\": \"google_doc_blockchain\"}", | ||
``` | ||
|
||
**Usage Example:** | ||
|
||
```python | ||
csv_file_path = "test_data.csv" | ||
test_response = TestLLMResponse(openai_key=os.environ.get("OPENAI_KEY")) | ||
accumulated_results = test_response.compare_from_csv(csv_file_path) | ||
``` | ||
|
||
### 3. Visualizing Test Scores | ||
|
||
After running tests (whether manually or using a CSV), the results are automatically accumulated by `prompt_id`. To see a visual chart of test scores, use the provided visualization function. | ||
|
||
**Example:** | ||
|
||
```python | ||
visualize_test_results(accumulated_results) | ||
``` | ||
|
||
This function will generate a line chart with the x-axis representing the test instance number (as integers) and the y-axis representing the score percentage. Each line on the chart corresponds to a different `prompt_id`. | ||
|
||
## Summary | ||
|
||
- **ideal_answer** is the only required parameter. | ||
- **llm_response** and **optional_params** are optional, with `optional_params` offering extra configuration (like a custom prompt and a unique `prompt_id`). | ||
- You can compare responses either manually or via CSV (which supports multiple test cases). | ||
- The tool accumulates results for each `prompt_id` across multiple calls. | ||
- Use the visualization function to see your test scores on an easy-to-read chart. | ||
|
||
Enjoy using the tool to refine and evaluate your LLM prompts! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import pandas as pd | ||
import json | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
def parse_csv_file(file_path: str) -> list: | ||
""" | ||
Reads a CSV file and returns a list of dictionaries with keys: | ||
- ideal_answer | ||
- llm_response | ||
- optional_params (parsed as dict if not empty) | ||
""" | ||
try: | ||
df = pd.read_csv(file_path) | ||
except Exception as e: | ||
logger.error(f"Error reading CSV file: {e}") | ||
return [] | ||
|
||
test_cases = [] | ||
for _, row in df.iterrows(): | ||
case = { | ||
"ideal_answer": row.get("ideal_answer"), | ||
"llm_response": row.get("llm_response") | ||
} | ||
opt_params = row.get("optional_params") | ||
if pd.notna(opt_params) and opt_params: | ||
try: | ||
case["optional_params"] = json.loads(opt_params) | ||
except json.JSONDecodeError as e: | ||
logger.error(f"Error parsing optional_params: {e}") | ||
case["optional_params"] = None | ||
else: | ||
case["optional_params"] = None | ||
test_cases.append(case) | ||
|
||
return test_cases |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"python.testing.pytestArgs": [ | ||
"tests" | ||
], | ||
"python.testing.unittestEnabled": false, | ||
"python.testing.pytestEnabled": true | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from lamoom_cicd import TestLLMResponse | ||
import os | ||
import dotenv | ||
|
||
dotenv.load_dotenv(dotenv.find_dotenv()) | ||
|
||
def test_compare(): | ||
ideal_answer = """Blockchain is like a digital notebook that everyone can see | ||
but no one can secretly change. Imagine a shared Google Doc where every change | ||
is recorded forever, and no one can erase or edit past entries. | ||
Instead of one company controlling it, thousands of computers around | ||
the world keep copies, making it nearly impossible to hack or fake. | ||
This is why it’s used for things like Bitcoin—to keep transactions | ||
secure and transparent without needing a bank in the middle.""" | ||
optional_params = {'prompt': "Explain the concept of blockchain to someone with no technical background."} | ||
|
||
test_response = TestLLMResponse(openai_key=os.environ.get("OPENAI_KEY")) | ||
result = test_response.compare(ideal_answer, optional_params=optional_params) | ||
|
||
assert 'score' in result.score.to_dict() | ||
assert 'passed' in result.score.to_dict() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from lamoom_cicd import TestLLMResponse | ||
import os | ||
import dotenv | ||
dotenv.load_dotenv(dotenv.find_dotenv()) | ||
|
||
|
||
def test_csv_compare(): | ||
test_response = TestLLMResponse(threshold=70, openai_key=os.environ.get("OPENAI_KEY")) | ||
results = test_response.compare_from_csv("tests/test_data.csv") | ||
|
||
test_response.visualize_test_results() | ||
|
||
assert 'score' in results[0].score.to_dict() | ||
assert 'passed' in results[0].score.to_dict() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ideal_answer,optional_params | ||
"Blockchain is like a digital notebook that everyone can see but no one can secretly change. Imagine a shared Google Doc where every change is recorded forever, and no one can erase or edit past entries. Instead of one company controlling it, thousands of computers around the world keep copies, making it nearly impossible to hack or fake. This is why it’s used for things like Bitcoin—to keep transactions secure and transparent without needing a bank in the middle.", "{\"prompt\": \"Explain blockchain to someone with no technical background.\"}" |