Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add detailed explanations for failed assertions #7

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# IntentGuard Roadmap

- [ ] Failed Assertion Explanations (https://github.com/kdunee/intentguard/issues/2)
- [x] Failed Assertion Explanations (https://github.com/kdunee/intentguard/issues/2)
- Detailed reasoning for why assertions failed
- Suggestions for fixing the issues

Expand Down
32 changes: 30 additions & 2 deletions intentguard/intentguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from litellm import completion

from intentguard.intentguard_options import IntentGuardOptions
from intentguard.prompts import system_prompt, reponse_schema
from intentguard.prompts import system_prompt, reponse_schema, explanation_prompt


class IntentGuard:
Expand Down Expand Up @@ -66,8 +66,9 @@ def assert_code(
final_result = self._vote_on_results(results)

if not final_result:
explanation = self._generate_explanation(prompt, options)
raise AssertionError(
f'Expected "{expectation}" to be true, but it was false'
f'Expected "{expectation}" to be true, but it was false. Explanation: {explanation}'
)

def _generate_objects_text(self, params: Dict[str, object]) -> str:
Expand Down Expand Up @@ -147,6 +148,33 @@ def _send_completion_request(
)
return json.loads(response.choices[0].message.content)["result"]

def _generate_explanation(self, prompt: str, options: IntentGuardOptions) -> str:
"""
Generate a detailed explanation for a failed assertion using the LLM.

This method sends a request to the LLM to generate a human-readable explanation
for why the given expectation was not met based on the provided objects.

Args:
objects_text (str): The formatted string of object source codes.
expectation (str): The condition that was evaluated.
options (IntentGuardOptions): The options for the LLM request.

Returns:
str: A detailed explanation of why the assertion failed.
"""
messages = [
{"content": explanation_prompt, "role": "system"},
{"content": prompt, "role": "user"},
]

response = completion(
model=options.model,
messages=messages,
temperature=1e-3,
)
return response.choices[0].message.content

def _vote_on_results(self, results: list) -> bool:
"""
Determine the final result based on voting.
Expand Down
46 changes: 46 additions & 0 deletions intentguard/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ def another_method(self):
}
```"""

explanation_prompt = """Analyze Python code to explain why a given condition against named objects (classes or methods) failed.

You will receive:
- A list of named objects, where each object's value is the code of a class or method.
- A text of a condition that uses these object names.
- An indication that the condition was not met.

Your task is to provide a detailed explanation of why the condition was not fulfilled for the given objects.

# Steps

1. Parse the input to identify and load the classes or methods from the named objects.
2. Analyze the code structure of each named object, ensuring to understand the classes, methods, and their interactions.
3. Evaluate the specified condition using the parsed objects, referring to them by their given names.
4. Determine why the condition does not hold true based on the code analysis.
5. Provide a detailed explanation of the reasons for the failure.

# Output Format

Output a text explanation detailing why the condition was not met.

# Examples

### Input
**Objects:**
{obj1}:
```py
class MyClass:
def method(self):
return 5
```

{obj2}:
```py
class AnotherClass:
def another_method(self):
return 10
```

**Failed Condition:**
"{obj1} has method another_method and {obj2} has method method"

### Output
The condition was not met because {obj1} does not have a method named 'another_method' and {obj2} does not have a method named 'method'.
"""

reponse_schema = {
"name": "boolean_result",
"strict": True,
Expand Down
3 changes: 2 additions & 1 deletion tests/test_intentguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ def test_assert_code_true(self):
)

def test_assert_code_false(self):
with self.assertRaises(AssertionError):
with self.assertRaises(AssertionError) as cm:
self.guard.assert_code(
"{class} should not have any methods", {"class": IntentGuard}
)
self.assertIn("Explanation:", str(cm.exception))

def test_guard_options(self):
self.guard.assert_code(
Expand Down