|
23 | 23 |
|
24 | 24 | from nemoguardrails import RailsConfig |
25 | 25 | from nemoguardrails.actions import action |
| 26 | +from nemoguardrails.exceptions import LLMCallException |
26 | 27 | from nemoguardrails.imports import check_optional_dependency |
27 | 28 | from tests.utils import TestChat |
28 | 29 |
|
@@ -109,6 +110,67 @@ def failing_rail_action(**params): |
109 | 110 | assert error["error"]["param"] == "failing safety check" |
110 | 111 |
|
111 | 112 |
|
| 113 | +@pytest.mark.asyncio |
| 114 | +@pytest.mark.parametrize( |
| 115 | + "action_error", |
| 116 | + [ |
| 117 | + pytest.param(RuntimeError("Action execution failed"), id="runtime-error"), |
| 118 | + pytest.param(LLMCallException(RuntimeError("LLM call failed")), id="llm-call-error"), |
| 119 | + ], |
| 120 | +) |
| 121 | +async def test_sequential_streaming_action_execution_failure(action_error: Exception): |
| 122 | + """Test fail-closed behavior in sequential streaming when a rail action fails.""" |
| 123 | + |
| 124 | + @action(is_system_action=True) |
| 125 | + def failing_rail_action(**params): |
| 126 | + raise action_error |
| 127 | + |
| 128 | + config = RailsConfig.from_content( |
| 129 | + config={ |
| 130 | + "models": [{"type": "main", "engine": "openai", "model": "gpt-3.5-turbo"}], |
| 131 | + "rails": { |
| 132 | + "output": { |
| 133 | + "parallel": False, |
| 134 | + "flows": ["failing safety check"], |
| 135 | + "streaming": { |
| 136 | + "enabled": True, |
| 137 | + "chunk_size": 4, |
| 138 | + }, |
| 139 | + } |
| 140 | + }, |
| 141 | + }, |
| 142 | + colang_content=""" |
| 143 | + define user express greeting |
| 144 | + "hi" |
| 145 | + define flow |
| 146 | + user express greeting |
| 147 | + bot express greeting |
| 148 | +
|
| 149 | + define subflow failing safety check |
| 150 | + execute failing_rail_action |
| 151 | + """, |
| 152 | + ) |
| 153 | + |
| 154 | + llm_completions = [ |
| 155 | + 'bot express greeting\n "Hello there! How can I help you?"', |
| 156 | + ] |
| 157 | + |
| 158 | + chat = TestChat(config, llm_completions=llm_completions, streaming=True) |
| 159 | + chat.app.register_action(failing_rail_action) |
| 160 | + |
| 161 | + chunks = await collect_streaming_chunks(chat.app.stream_async(messages=[{"role": "user", "content": "Hi!"}])) |
| 162 | + |
| 163 | + internal_error_chunks = find_internal_error_chunks(chunks) |
| 164 | + assert len(internal_error_chunks) == 1 |
| 165 | + error = internal_error_chunks[0]["error"] |
| 166 | + assert error == { |
| 167 | + "message": "Internal error in failing safety check rail: Action failing_rail_action failed with status: failed", |
| 168 | + "type": "internal_error", |
| 169 | + "param": "failing safety check", |
| 170 | + "code": "rail_execution_failure", |
| 171 | + } |
| 172 | + |
| 173 | + |
112 | 174 | @pytest.mark.asyncio |
113 | 175 | async def test_streaming_internal_error_format(): |
114 | 176 | """Test that streaming internal errors have the correct format.""" |
|
0 commit comments