Skip to content

Commit 73c53c6

Browse files
feat: introduce guardrail step typ
1 parent 5f49d4a commit 73c53c6

File tree

3 files changed

+228
-70
lines changed

3 files changed

+228
-70
lines changed

src/openlayer/lib/tracing/enums.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55

66
class StepType(enum.Enum):
7-
USER_CALL = "user_call"
8-
CHAT_COMPLETION = "chat_completion"
97
AGENT = "agent"
8+
CHAT_COMPLETION = "chat_completion"
9+
GUARDRAIL = "guardrail"
10+
HANDOFF = "handoff"
1011
RETRIEVER = "retriever"
1112
TOOL = "tool"
12-
HANDOFF = "handoff"
13+
USER_CALL = "user_call"

src/openlayer/lib/tracing/steps.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import time
44
import uuid
5-
from typing import Any, Dict, List, Optional
5+
from typing import Any, Dict, Optional, List
66

77
from .. import utils
88
from . import enums
@@ -229,6 +229,39 @@ def to_dict(self) -> Dict[str, Any]:
229229
return step_dict
230230

231231

232+
class GuardrailStep(Step):
233+
"""Step for tracking guardrail execution."""
234+
235+
def __init__(self, **kwargs):
236+
super().__init__(**kwargs)
237+
self.step_type = enums.StepType.GUARDRAIL
238+
self.action: Optional[str] = None
239+
self.blocked_entities: Optional[List[str]] = None
240+
self.confidence_threshold: float = None
241+
self.reason: Optional[str] = None
242+
self.detected_entities: Optional[List[str]] = None
243+
self.redacted_entities: Optional[List[str]] = None
244+
self.block_strategy: Optional[str] = None
245+
self.data_type: Optional[str] = None
246+
247+
def to_dict(self) -> Dict[str, Any]:
248+
"""Dictionary representation of the GuardrailStep."""
249+
step_dict = super().to_dict()
250+
step_dict.update(
251+
{
252+
"action": self.action,
253+
"blockedEntities": self.blocked_entities,
254+
"confidenceThreshold": self.confidence_threshold,
255+
"reason": self.reason,
256+
"detectedEntities": self.detected_entities,
257+
"blockStrategy": self.block_strategy,
258+
"redactedEntities": self.redacted_entities,
259+
"dataType": self.data_type,
260+
}
261+
)
262+
return step_dict
263+
264+
232265
# ----------------------------- Factory function ----------------------------- #
233266
def step_factory(step_type: enums.StepType, *args, **kwargs) -> Step:
234267
"""Factory function to create a step based on the step_type."""
@@ -241,5 +274,6 @@ def step_factory(step_type: enums.StepType, *args, **kwargs) -> Step:
241274
enums.StepType.RETRIEVER: RetrieverStep,
242275
enums.StepType.TOOL: ToolStep,
243276
enums.StepType.HANDOFF: HandoffStep,
277+
enums.StepType.GUARDRAIL: GuardrailStep,
244278
}
245279
return step_type_mapping[step_type](*args, **kwargs)

src/openlayer/lib/tracing/tracer.py

Lines changed: 189 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ...types.inference_pipelines.data_stream_params import ConfigLlmData
1616
from .. import utils
1717
from . import enums, steps, traces
18+
from ..guardrails.base import GuardrailResult, GuardrailAction
1819

1920
logger = logging.getLogger(__name__)
2021

@@ -1200,7 +1201,7 @@ def _apply_input_guardrails(
12001201
guardrails: List[Any],
12011202
inputs: Dict[str, Any],
12021203
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
1203-
"""Apply guardrails to function inputs.
1204+
"""Apply guardrails to function inputs, creating guardrail steps.
12041205
12051206
Args:
12061207
guardrails: List of guardrail instances
@@ -1213,9 +1214,9 @@ def _apply_input_guardrails(
12131214
return inputs, {}
12141215

12151216
modified_inputs = inputs.copy()
1216-
guardrail_metadata = {}
1217+
overall_metadata = {}
12171218

1218-
for guardrail in guardrails:
1219+
for i, guardrail in enumerate(guardrails):
12191220
try:
12201221
# Import here to avoid circular imports
12211222
from ..guardrails.base import BaseGuardrail, GuardrailBlockedException
@@ -1227,50 +1228,112 @@ def _apply_input_guardrails(
12271228
if not guardrail.is_enabled():
12281229
continue
12291230

1230-
result = guardrail.check_input(modified_inputs)
1231+
# Create a guardrail step for this check
1232+
with create_step(
1233+
name=f"{guardrail.name} - Input",
1234+
step_type=enums.StepType.GUARDRAIL,
1235+
) as guardrail_step:
1236+
try:
1237+
# Apply the guardrail
1238+
result = guardrail.check_input(modified_inputs)
1239+
1240+
# Store guardrail metadata for main function step
1241+
guardrail_key = f"input_{guardrail.name.lower().replace(' ', '_')}"
1242+
overall_metadata[guardrail_key] = {
1243+
"action": result.action.value,
1244+
"reason": result.reason,
1245+
"metadata": result.metadata or {},
1246+
}
1247+
1248+
# Prepare step logging data
1249+
step_log_data = {
1250+
"action": result.action.value,
1251+
"reason": result.reason,
1252+
"data_type": "input",
1253+
"inputs": {"original_data": modified_inputs},
1254+
}
1255+
1256+
if result.action.value == "block":
1257+
# Handle the block according to strategy
1258+
final_inputs, block_metadata = _handle_guardrail_block(
1259+
guardrail=guardrail,
1260+
result=result,
1261+
modified_inputs=modified_inputs,
1262+
guardrail_metadata=overall_metadata,
1263+
guardrail_key=guardrail_key,
1264+
is_input=True,
1265+
)
12311266

1232-
# Store guardrail metadata
1233-
guardrail_key = f"input_{guardrail.name.lower().replace(' ', '_')}"
1234-
guardrail_metadata[guardrail_key] = {
1235-
"action": result.action.value,
1236-
"reason": result.reason,
1237-
"metadata": result.metadata or {},
1238-
}
1267+
# Add final output if different
1268+
if final_inputs != modified_inputs:
1269+
step_log_data["output"] = final_inputs
1270+
1271+
# Log once with all data
1272+
guardrail_step.log(**step_log_data)
1273+
return final_inputs, overall_metadata
1274+
1275+
elif (
1276+
result.action.value == "modify"
1277+
and result.modified_data is not None
1278+
):
1279+
step_log_data["output"] = result.modified_data
1280+
modified_inputs = result.modified_data
1281+
logger.debug("Guardrail %s modified inputs", guardrail.name)
1282+
1283+
else: # allow
1284+
step_log_data["output"] = modified_inputs
1285+
1286+
# Single log call with all data
1287+
guardrail_step.log(**step_log_data)
1288+
1289+
except Exception as e:
1290+
# Create error result for the guardrail step
1291+
error_result = GuardrailResult(
1292+
action=GuardrailAction.ALLOW, # Default to allow on error
1293+
reason=f"Guardrail error: {str(e)}",
1294+
metadata={"error": str(e), "error_type": type(e).__name__},
1295+
)
1296+
guardrail_step.log(
1297+
inputs={"original_data": modified_inputs},
1298+
output=modified_inputs,
1299+
)
12391300

1240-
if result.action.value == "block":
1241-
return _handle_guardrail_block(
1242-
guardrail=guardrail,
1243-
result=result,
1244-
modified_inputs=modified_inputs,
1245-
guardrail_metadata=guardrail_metadata,
1246-
guardrail_key=guardrail_key,
1247-
is_input=True,
1248-
)
1249-
elif result.action.value == "modify" and result.modified_data is not None:
1250-
modified_inputs = result.modified_data
1251-
logger.debug("Guardrail %s modified inputs", guardrail.name)
1301+
if hasattr(e, "guardrail_name"):
1302+
# Re-raise guardrail exceptions
1303+
raise
1304+
else:
1305+
# Log other exceptions but don't fail the trace
1306+
logger.error(
1307+
"Error applying input guardrail %s: %s", guardrail.name, e
1308+
)
1309+
guardrail_key = (
1310+
f"input_{guardrail.name.lower().replace(' ', '_')}"
1311+
)
1312+
overall_metadata[guardrail_key] = {
1313+
"action": "error",
1314+
"reason": str(e),
1315+
"metadata": {"error_type": type(e).__name__},
1316+
"guardrail_name": guardrail.name,
1317+
}
12521318

12531319
except Exception as e:
1320+
# Handle exceptions that occur outside the guardrail step context
12541321
if hasattr(e, "guardrail_name"):
1255-
# Re-raise guardrail exceptions
12561322
raise
12571323
else:
1258-
# Log other exceptions but don't fail the trace
1259-
logger.error("Error applying input guardrail %s: %s", guardrail.name, e)
1260-
guardrail_key = f"input_{guardrail.name.lower().replace(' ', '_')}"
1261-
guardrail_metadata[guardrail_key] = {
1262-
"action": "error",
1263-
"reason": str(e),
1264-
"metadata": {},
1265-
}
1324+
logger.error(
1325+
"Error setting up input guardrail %s: %s",
1326+
getattr(guardrail, "name", f"guardrail_{i}"),
1327+
e,
1328+
)
12661329

1267-
return modified_inputs, guardrail_metadata
1330+
return modified_inputs, overall_metadata
12681331

12691332

12701333
def _apply_output_guardrails(
12711334
guardrails: List[Any], output: Any, inputs: Dict[str, Any]
12721335
) -> Tuple[Any, Dict[str, Any]]:
1273-
"""Apply guardrails to function output.
1336+
"""Apply guardrails to function output, creating guardrail steps.
12741337
12751338
Args:
12761339
guardrails: List of guardrail instances
@@ -1284,9 +1347,9 @@ def _apply_output_guardrails(
12841347
return output, {}
12851348

12861349
modified_output = output
1287-
guardrail_metadata = {}
1350+
overall_metadata = {}
12881351

1289-
for guardrail in guardrails:
1352+
for i, guardrail in enumerate(guardrails):
12901353
try:
12911354
# Import here to avoid circular imports
12921355
from ..guardrails.base import BaseGuardrail, GuardrailBlockedException
@@ -1298,46 +1361,106 @@ def _apply_output_guardrails(
12981361
if not guardrail.is_enabled():
12991362
continue
13001363

1301-
result = guardrail.check_output(modified_output, inputs)
1364+
# Create a guardrail step for this check
1365+
with create_step(
1366+
name=f"{guardrail.name} - Output",
1367+
step_type=enums.StepType.GUARDRAIL,
1368+
) as guardrail_step:
1369+
try:
1370+
# Apply the guardrail
1371+
result = guardrail.check_output(modified_output, inputs)
1372+
1373+
# Store guardrail metadata for main function step
1374+
guardrail_key = f"output_{guardrail.name.lower().replace(' ', '_')}"
1375+
overall_metadata[guardrail_key] = {
1376+
"action": result.action.value,
1377+
"reason": result.reason,
1378+
"metadata": result.metadata or {},
1379+
}
1380+
1381+
# Prepare step logging data
1382+
step_log_data = {
1383+
"action": result.action.value,
1384+
"reason": result.reason,
1385+
"data_type": "output",
1386+
"inputs": {"original_data": modified_output},
1387+
}
1388+
1389+
if result.action.value == "block":
1390+
# Handle the block according to strategy
1391+
final_output, block_metadata = _handle_guardrail_block(
1392+
guardrail=guardrail,
1393+
result=result,
1394+
modified_output=modified_output,
1395+
guardrail_metadata=overall_metadata,
1396+
guardrail_key=guardrail_key,
1397+
is_input=False,
1398+
)
13021399

1303-
# Store guardrail metadata
1304-
guardrail_key = f"output_{guardrail.name.lower().replace(' ', '_')}"
1305-
guardrail_metadata[guardrail_key] = {
1306-
"action": result.action.value,
1307-
"reason": result.reason,
1308-
"metadata": result.metadata or {},
1309-
}
1400+
# Add final output if different
1401+
if final_output != modified_output:
1402+
step_log_data["output"] = final_output
1403+
1404+
# Log once with all data
1405+
guardrail_step.log(**step_log_data)
1406+
return final_output, overall_metadata
1407+
1408+
elif (
1409+
result.action.value == "modify"
1410+
and result.modified_data is not None
1411+
):
1412+
step_log_data["output"] = result.modified_data
1413+
modified_output = result.modified_data
1414+
logger.debug("Guardrail %s modified output", guardrail.name)
1415+
1416+
else: # allow
1417+
step_log_data["output"] = modified_output
1418+
1419+
# Single log call with all data
1420+
guardrail_step.log(**step_log_data)
1421+
1422+
except Exception as e:
1423+
# Create error result for the guardrail step
1424+
error_result = GuardrailResult(
1425+
action=GuardrailAction.ALLOW, # Default to allow on error
1426+
reason=f"Guardrail error: {str(e)}",
1427+
metadata={"error": str(e), "error_type": type(e).__name__},
1428+
)
1429+
guardrail_step.log(
1430+
inputs={"original_data": modified_output},
1431+
output=modified_output,
1432+
)
13101433

1311-
if result.action.value == "block":
1312-
return _handle_guardrail_block(
1313-
guardrail=guardrail,
1314-
result=result,
1315-
modified_output=modified_output,
1316-
guardrail_metadata=guardrail_metadata,
1317-
guardrail_key=guardrail_key,
1318-
is_input=False,
1319-
)
1320-
elif result.action.value == "modify" and result.modified_data is not None:
1321-
modified_output = result.modified_data
1322-
logger.debug("Guardrail %s modified output", guardrail.name)
1434+
if hasattr(e, "guardrail_name"):
1435+
# Re-raise guardrail exceptions
1436+
raise
1437+
else:
1438+
# Log other exceptions but don't fail the trace
1439+
logger.error(
1440+
"Error applying output guardrail %s: %s", guardrail.name, e
1441+
)
1442+
guardrail_key = (
1443+
f"output_{guardrail.name.lower().replace(' ', '_')}"
1444+
)
1445+
overall_metadata[guardrail_key] = {
1446+
"action": "error",
1447+
"reason": str(e),
1448+
"metadata": {"error_type": type(e).__name__},
1449+
}
1450+
guardrail_step.log(**overall_metadata[guardrail_key])
13231451

13241452
except Exception as e:
1453+
# Handle exceptions that occur outside the guardrail step context
13251454
if hasattr(e, "guardrail_name"):
1326-
# Re-raise guardrail exceptions
13271455
raise
13281456
else:
1329-
# Log other exceptions but don't fail the trace
13301457
logger.error(
1331-
"Error applying output guardrail %s: %s", guardrail.name, e
1458+
"Error setting up output guardrail %s: %s",
1459+
getattr(guardrail, "name", f"guardrail_{i}"),
1460+
e,
13321461
)
1333-
guardrail_key = f"output_{guardrail.name.lower().replace(' ', '_')}"
1334-
guardrail_metadata[guardrail_key] = {
1335-
"action": "error",
1336-
"reason": str(e),
1337-
"metadata": {},
1338-
}
13391462

1340-
return modified_output, guardrail_metadata
1463+
return modified_output, overall_metadata
13411464

13421465

13431466
def _handle_guardrail_block(

0 commit comments

Comments
 (0)