|
3 | 3 | from typing import Optional |
4 | 4 |
|
5 | 5 | from fastapi import HTTPException |
6 | | -from ogx_client import ( |
7 | | - APIConnectionError, |
8 | | - AsyncOgxClient, |
9 | | -) |
10 | | -from ogx_client import ( |
11 | | - APIStatusError as LLSApiStatusError, |
12 | | -) |
| 6 | +from ogx_client import APIConnectionError, APIStatusError, AsyncOgxClient |
| 7 | +from pydantic_ai.exceptions import AgentRunError |
13 | 8 |
|
14 | 9 | from configuration import AppConfig |
| 10 | +from log import get_logger |
15 | 11 | from models.api.requests import QueryRequest |
16 | 12 | from models.api.responses.error import ( |
17 | 13 | InternalServerErrorResponse, |
18 | 14 | NotFoundResponse, |
19 | 15 | ServiceUnavailableResponse, |
20 | 16 | UnprocessableEntityResponse, |
21 | 17 | ) |
22 | | -from models.common import ShieldModerationPassed, ShieldModerationResult |
23 | | -from models.config import ShieldConfiguration |
| 18 | +from models.common.moderation import ( |
| 19 | + ShieldModerationPassed, |
| 20 | + ShieldModerationResult, |
| 21 | +) |
| 22 | +from models.config import QuestionValidityConfig, RedactionConfig, ShieldConfiguration |
| 23 | +from pydantic_ai_lightspeed.capabilities.base import AbstractSafetyCapability |
| 24 | +from pydantic_ai_lightspeed.capabilities.question_validity._capability import ( |
| 25 | + QuestionValidity, |
| 26 | +) |
| 27 | +from pydantic_ai_lightspeed.capabilities.redaction._capability import ( |
| 28 | + PiiRedactionCapability, |
| 29 | +) |
| 30 | +from utils.agents.error_handler import map_agent_inference_error |
| 31 | + |
| 32 | +logger = get_logger(__name__) |
24 | 33 |
|
25 | 34 |
|
26 | 35 | def validate_shield_ids_override( |
@@ -59,6 +68,63 @@ def validate_shield_ids_override( |
59 | 68 | raise HTTPException(**response.model_dump()) |
60 | 69 |
|
61 | 70 |
|
| 71 | +async def run_shield_moderation_v2( |
| 72 | + input_text: str, |
| 73 | + shield_configs: list[ShieldConfiguration], |
| 74 | + selected_shield_ids: Optional[list[str]] = None, |
| 75 | +) -> ShieldModerationResult: |
| 76 | + """Run v2 shield moderation on input text. |
| 77 | +
|
| 78 | + Iterates through configured shields and runs moderation checks. |
| 79 | +
|
| 80 | + Parameters: |
| 81 | + input_text: The text to moderate. |
| 82 | + shield_configs: List of shield configurations to evaluate. |
| 83 | + selected_shield_ids: Optional list of shield names to filter by. |
| 84 | +
|
| 85 | + Returns: |
| 86 | + Result indicating if content was blocked or passed. |
| 87 | + """ |
| 88 | + selected_shield_configs = get_shields_for_request( |
| 89 | + shield_configs, selected_shield_ids |
| 90 | + ) |
| 91 | + |
| 92 | + for shield_config in selected_shield_configs: |
| 93 | + shield = build_shield(shield_config) |
| 94 | + |
| 95 | + try: |
| 96 | + shield_result = await shield.run(input_text) |
| 97 | + # APIConnectionError and APIStatusError from ogx should not be raised from model_request, |
| 98 | + # because they will be caught inside AsyncOpenAI and transferred into openai's |
| 99 | + # APIConnectionError. The openai's exceptions will further transferred into ModelHTTPError |
| 100 | + # or ModelAPIError by _map_api_errors in OpenAIResponseModel. |
| 101 | + except (AgentRunError, RuntimeError) as exc: |
| 102 | + model_id = getattr(shield_config.config, "model_id", "unknown-shield-model") |
| 103 | + response = map_agent_inference_error(exc, model_id) |
| 104 | + raise HTTPException(**response.model_dump()) from exc |
| 105 | + |
| 106 | + if shield_result.decision == "blocked": |
| 107 | + return shield_result |
| 108 | + |
| 109 | + return ShieldModerationPassed() |
| 110 | + |
| 111 | + |
| 112 | +def build_shield(shield_config: ShieldConfiguration) -> AbstractSafetyCapability: |
| 113 | + """Build a safety capability instance from a shield configuration. |
| 114 | +
|
| 115 | + Parameters: |
| 116 | + shield_config: The shield configuration to build from. |
| 117 | +
|
| 118 | + Returns: |
| 119 | + The constructed safety capability. |
| 120 | + """ |
| 121 | + match shield_config.config: |
| 122 | + case QuestionValidityConfig(): |
| 123 | + return QuestionValidity(shield_config.config) |
| 124 | + case RedactionConfig(): |
| 125 | + return PiiRedactionCapability(shield_config.config) |
| 126 | + |
| 127 | + |
62 | 128 | async def run_shield_moderation( |
63 | 129 | _client: AsyncOgxClient, |
64 | 130 | _input_text: str, |
@@ -124,7 +190,7 @@ async def append_turn_to_conversation( |
124 | 190 | cause=str(e), |
125 | 191 | ) |
126 | 192 | raise HTTPException(**error_response.model_dump()) from e |
127 | | - except LLSApiStatusError as e: |
| 193 | + except APIStatusError as e: |
128 | 194 | error_response = InternalServerErrorResponse.generic() |
129 | 195 | raise HTTPException(**error_response.model_dump()) from e |
130 | 196 |
|
|
0 commit comments