Skip to content

Commit 5957c02

Browse files
authored
refactor(library): make context bloat verdict flow-independent (#2190)
Return RailOutcome from context-bloat checks so execution backends can derive the same block, transform, or allow verdict from the configured source and input. Update both Colang dialects and cover the action and flow contracts. Follow-up on #2150 Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
1 parent 8cfaa12 commit 5957c02

4 files changed

Lines changed: 245 additions & 79 deletions

File tree

nemoguardrails/library/context_bloat_detection/actions.py

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333
import logging
3434
import math
3535
from collections import Counter
36-
from typing import List, Optional, TypedDict
36+
from typing import List, Literal, Optional, TypedDict
3737

3838
from nemoguardrails import RailsConfig
3939
from nemoguardrails.actions import action
40+
from nemoguardrails.actions.rail_outcome import RailOutcome, TransformTarget
4041

4142
log = logging.getLogger(__name__)
4243

@@ -54,6 +55,9 @@ class ContextBloatResult(TypedDict):
5455
metrics: dict
5556

5657

58+
ContextBloatSource = Literal["input", "retrieval"]
59+
60+
5761
def _stratified_sample(text: str, sample_chars: int) -> str:
5862
third = sample_chars // 3
5963
mid = len(text) // 2
@@ -162,21 +166,48 @@ def _check_repetition(text: str, cfg, detections: List[str], metrics: dict) -> O
162166
return None
163167

164168

169+
def _context_bloat_outcome(source: ContextBloatSource, original_text: str, result: ContextBloatResult) -> RailOutcome:
170+
targets = {
171+
"input": TransformTarget.USER_MESSAGE,
172+
"retrieval": TransformTarget.RELEVANT_CHUNKS,
173+
}
174+
175+
metadata = {
176+
"is_bloat": result["is_bloat"],
177+
"action": result["action"],
178+
"detections": result["detections"],
179+
"metrics": result["metrics"],
180+
}
181+
if result["is_bloat"] and result["action"] == "reject":
182+
return RailOutcome.block(reason=result["reason"], metadata=metadata)
183+
if result["is_bloat"] and result["action"] == "truncate" and result["text"] != original_text:
184+
return RailOutcome.transform(
185+
[(targets[source], result["text"])],
186+
reason=result["reason"],
187+
metadata=metadata,
188+
)
189+
return RailOutcome.allow(reason=result["reason"], metadata=metadata)
190+
191+
165192
@action()
166-
async def context_bloat_detection(text: str, config: RailsConfig) -> ContextBloatResult:
193+
async def context_bloat_detection(source: ContextBloatSource, text: str, config: RailsConfig) -> RailOutcome:
167194
"""Detect context-bloat / context-manipulation attacks.
168195
Check order is cheapest first to enable early-exit.
169196
170197
Args:
198+
source: Whether the text came from input or retrieved chunks.
171199
text: The text to inspect (joined chunks or user message).
172200
config: RailsConfig with rails.config.context_bloat_detection settings.
173201
174202
Returns:
175-
ContextBloatResult with is_bloat flag, processed text, reason, metrics.
203+
The allow, block, or transform verdict with detection evidence.
176204
"""
205+
if source not in ("input", "retrieval"):
206+
raise ValueError("source must be either 'input' or 'retrieval'")
177207
_validate_config(config)
178208
cfg = config.rails.config.context_bloat_detection
179209

210+
original_text = text
180211
char_count = len(text) if text else 0
181212
detections: List[str] = []
182213
metrics: dict = {"chars": char_count}
@@ -185,13 +216,17 @@ async def context_bloat_detection(text: str, config: RailsConfig) -> ContextBloa
185216
detections.append("size_cap_exceeded")
186217
log.info(f"context bloat detected: size_cap_exceeded | chars={char_count}")
187218
if cfg.action == "reject":
188-
return ContextBloatResult(
189-
is_bloat=True,
190-
action=cfg.action,
191-
text=text,
192-
reason="size_cap_exceeded",
193-
detections=detections,
194-
metrics=metrics,
219+
return _context_bloat_outcome(
220+
source,
221+
original_text,
222+
ContextBloatResult(
223+
is_bloat=True,
224+
action=cfg.action,
225+
text=text,
226+
reason="size_cap_exceeded",
227+
detections=detections,
228+
metrics=metrics,
229+
),
195230
)
196231
if cfg.action == "truncate":
197232
text = text[: cfg.max_chars]
@@ -200,17 +235,21 @@ async def context_bloat_detection(text: str, config: RailsConfig) -> ContextBloa
200235
for check in (_check_entropy, _check_longest_run, _check_repetition):
201236
result = check(text, cfg, detections, metrics)
202237
if result is not None:
203-
return result
238+
return _context_bloat_outcome(source, original_text, result)
204239

205240
is_bloat = bool(detections)
206241
reason = ", ".join(detections) if detections else None
207242
if is_bloat:
208243
log.info(f"context bloat detected: {reason} | metrics={metrics}")
209-
return ContextBloatResult(
210-
is_bloat=is_bloat,
211-
action=cfg.action,
212-
text=text,
213-
reason=reason,
214-
detections=detections,
215-
metrics=metrics,
244+
return _context_bloat_outcome(
245+
source,
246+
original_text=original_text,
247+
result=ContextBloatResult(
248+
is_bloat=is_bloat,
249+
action=cfg.action,
250+
text=text,
251+
reason=reason,
252+
detections=detections,
253+
metrics=metrics,
254+
),
216255
)

nemoguardrails/library/context_bloat_detection/flows.co

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
flow context bloat detection on retrieval
55
"""Detect bloated or padded content in retrieved RAG chunks."""
66
global $relevant_chunks
7-
$bloat_result = await ContextBloatDetectionAction(text=$relevant_chunks)
8-
if $bloat_result.is_bloat and $bloat_result.action == "reject"
7+
$bloat_result = await ContextBloatDetectionAction(source="retrieval", text=$relevant_chunks)
8+
if $bloat_result.is_blocked
99
bot inform retrieval bloated
1010
abort
11-
if $bloat_result.is_bloat and $bloat_result.action == "truncate"
12-
$relevant_chunks = $bloat_result.text
11+
if $bloat_result.is_transform
12+
$relevant_chunks = $bloat_result.transform_text["relevant_chunks"]
1313

1414
flow bot inform retrieval bloated
1515
bot say "The retrieved sources for this question appeared oversized or padded. I'm not using them to avoid context manipulation."
@@ -19,12 +19,12 @@ flow bot inform retrieval bloated
1919
flow context bloat detection on input
2020
"""Detect bloated or padded user-supplied content."""
2121
global $user_message
22-
$bloat_result = await ContextBloatDetectionAction(text=$user_message)
23-
if $bloat_result.is_bloat and $bloat_result.action == "reject"
22+
$bloat_result = await ContextBloatDetectionAction(source="input", text=$user_message)
23+
if $bloat_result.is_blocked
2424
bot refuse bloated input
2525
abort
26-
if $bloat_result.is_bloat and $bloat_result.action == "truncate"
27-
$user_message = $bloat_result.text
26+
if $bloat_result.is_transform
27+
$user_message = $bloat_result.transform_text["user_message"]
2828

2929
flow bot refuse bloated input
3030
bot say "Your message appears oversized or padded with repetitive content. Please send a shorter message focused on your question."

nemoguardrails/library/context_bloat_detection/flows.v1.co

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
define subflow context bloat detection on retrieval
55
"""Detect bloated or padded content in retrieved RAG chunks."""
6-
$bloat_result = execute context_bloat_detection(text=$relevant_chunks)
7-
if $bloat_result.is_bloat and $bloat_result.action == "reject"
6+
$bloat_result = execute context_bloat_detection(source="retrieval", text=$relevant_chunks)
7+
if $bloat_result.is_blocked
88
bot inform retrieval bloated
99
stop
10-
if $bloat_result.is_bloat and $bloat_result.action == "truncate"
11-
$relevant_chunks = $bloat_result.text
10+
if $bloat_result.is_transform
11+
$relevant_chunks = $bloat_result.transform_text["relevant_chunks"]
1212

1313
define bot inform retrieval bloated
1414
"The retrieved sources for this question appeared oversized or padded. I'm not using them to avoid context manipulation."
@@ -17,12 +17,12 @@ define bot inform retrieval bloated
1717

1818
define subflow context bloat detection on input
1919
"""Detect bloated or padded user-supplied content."""
20-
$bloat_result = execute context_bloat_detection(text=$user_message)
21-
if $bloat_result.is_bloat and $bloat_result.action == "reject"
20+
$bloat_result = execute context_bloat_detection(source="input", text=$user_message)
21+
if $bloat_result.is_blocked
2222
bot refuse bloated input
2323
stop
24-
if $bloat_result.is_bloat and $bloat_result.action == "truncate"
25-
$user_message = $bloat_result.text
24+
if $bloat_result.is_transform
25+
$user_message = $bloat_result.transform_text["user_message"]
2626

2727
define bot refuse bloated input
2828
"Your message appears oversized or padded with repetitive content. Please send a shorter message focused on your question."

0 commit comments

Comments
 (0)