Skip to content

Commit ce3213b

Browse files
Add error handling for observation mode robustness
- Wrap process_event() call in state.py with try-except to ensure events are always added regardless of compliance check failures - Add try-except in monitor.py check_event() and update_state() to handle exceptions from individual properties gracefully - Add test_monitor_handles_buggy_property_gracefully test to verify buggy properties don't crash the monitor This ensures observation mode is robust: events are always processed even if compliance checking fails. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 4e28e19 commit ce3213b

File tree

3 files changed

+68
-10
lines changed

3 files changed

+68
-10
lines changed

openhands-sdk/openhands/sdk/conversation/compliance/monitor.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,22 @@ def check_event(self, event: LLMConvertibleEvent) -> list[ComplianceViolation]:
5757
violations: list[ComplianceViolation] = []
5858

5959
for prop in self.properties:
60-
violation = prop.check(event, self.state)
61-
if violation is not None:
62-
violations.append(violation)
63-
logger.warning(
64-
"API compliance violation detected: %s - %s (event_id=%s)",
65-
violation.property_name,
66-
violation.description,
67-
violation.event_id,
60+
try:
61+
violation = prop.check(event, self.state)
62+
if violation is not None:
63+
violations.append(violation)
64+
logger.warning(
65+
"API compliance violation detected: %s - %s (event_id=%s)",
66+
violation.property_name,
67+
violation.description,
68+
violation.event_id,
69+
)
70+
except Exception as e:
71+
logger.exception(
72+
"Error checking compliance property %s for event %s: %s",
73+
prop.name,
74+
event.id,
75+
e,
6876
)
6977

7078
return violations
@@ -78,7 +86,15 @@ def update_state(self, event: LLMConvertibleEvent) -> None:
7886
event: The event that was just processed.
7987
"""
8088
for prop in self.properties:
81-
prop.update_state(event, self.state)
89+
try:
90+
prop.update_state(event, self.state)
91+
except Exception as e:
92+
logger.exception(
93+
"Error updating state for property %s on event %s: %s",
94+
prop.name,
95+
event.id,
96+
e,
97+
)
8298

8399
def process_event(self, event: LLMConvertibleEvent) -> list[ComplianceViolation]:
84100
"""Check an event and update state.

openhands-sdk/openhands/sdk/conversation/state.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,12 @@ def add_event(self, event: Event) -> None:
550550
"""
551551
# Check for compliance violations only for LLM-convertible events
552552
if isinstance(event, LLMConvertibleEvent):
553-
self.compliance_monitor.process_event(event)
553+
try:
554+
self.compliance_monitor.process_event(event)
555+
except Exception as e:
556+
logger.exception(
557+
"Error checking compliance for event %s: %s", event.id, e
558+
)
554559

555560
# Add to event log regardless of violations (observation mode)
556561
self._events.append(event)

tests/sdk/conversation/compliance/test_monitor.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
APIComplianceMonitor,
55
InterleavedMessageProperty,
66
)
7+
from openhands.sdk.conversation.compliance.base import (
8+
APICompliancePropertyBase,
9+
ComplianceState,
10+
ComplianceViolation,
11+
)
12+
from openhands.sdk.event import LLMConvertibleEvent
713
from tests.sdk.conversation.compliance.conftest import (
814
make_action_event,
915
make_observation_event,
@@ -147,3 +153,34 @@ def test_monitor_parallel_tool_calls():
147153
violations = monitor.process_event(make_user_message_event())
148154
assert len(violations) == 1
149155
assert "call_paris" in str(violations[0].context)
156+
157+
158+
def test_monitor_handles_buggy_property_gracefully():
159+
"""Monitor should handle exceptions in property checks gracefully.
160+
161+
A buggy property that raises an exception should not crash the monitor.
162+
Instead, the exception is logged and processing continues. This ensures
163+
observation mode is robust against buggy properties.
164+
"""
165+
166+
class BuggyProperty(APICompliancePropertyBase):
167+
@property
168+
def name(self) -> str:
169+
return "buggy"
170+
171+
def check(
172+
self,
173+
event: LLMConvertibleEvent,
174+
state: ComplianceState,
175+
) -> ComplianceViolation | None:
176+
raise ValueError("Oops!")
177+
178+
monitor = APIComplianceMonitor(properties=[BuggyProperty()])
179+
180+
# Should not raise - the exception should be caught and logged
181+
# We expect an empty list because the buggy property doesn't return a violation
182+
# (it raises instead)
183+
violations = monitor.process_event(make_user_message_event())
184+
185+
# The monitor should continue working despite the error
186+
assert violations == []

0 commit comments

Comments
 (0)