3232from functools import cache
3333from typing import Any
3434
35- from openai import AsyncOpenAI
35+ from openai import AsyncOpenAI , NotFoundError
3636from pydantic import BaseModel , ConfigDict , Field
3737
3838from guardrails .registry import default_spec_registry
@@ -132,6 +132,22 @@ def _get_moderation_client() -> AsyncOpenAI:
132132 return AsyncOpenAI (** prepare_openai_kwargs ({}))
133133
134134
135+ async def _call_moderation_api (client : AsyncOpenAI , data : str ) -> Any :
136+ """Call the OpenAI moderation API.
137+
138+ Args:
139+ client: The OpenAI client to use.
140+ data: The text to analyze.
141+
142+ Returns:
143+ The moderation API response.
144+ """
145+ return await client .moderations .create (
146+ model = "omni-moderation-latest" ,
147+ input = data ,
148+ )
149+
150+
135151async def moderation (
136152 ctx : Any ,
137153 data : str ,
@@ -151,36 +167,29 @@ async def moderation(
151167 Returns:
152168 GuardrailResult: Indicates if tripwire was triggered, and details of flagged categories.
153169 """
154-
155- # Prefer reusing an existing OpenAI client from context ONLY if it targets the
156- # official OpenAI API. If it's any other provider (e.g., Ollama via base_url),
157- # fall back to the default OpenAI moderation client.
158- def _maybe_reuse_openai_client_from_ctx (context : Any ) -> AsyncOpenAI | None :
170+ client = None
171+ if ctx is not None :
172+ candidate = getattr (ctx , "guardrail_llm" , None )
173+ if isinstance (candidate , AsyncOpenAI ):
174+ client = candidate
175+
176+ # Try the context client first, fall back if moderation endpoint doesn't exist
177+ if client is not None :
159178 try :
160- candidate = getattr (context , "guardrail_llm" , None )
161- if not isinstance (candidate , AsyncOpenAI ):
162- return None
163-
164- # Attempt to discover the effective base URL in a best-effort way
165- base_url = getattr (candidate , "base_url" , None )
166- if base_url is None :
167- inner = getattr (candidate , "_client" , None )
168- base_url = getattr (inner , "base_url" , None ) or getattr (inner , "_base_url" , None )
169-
170- # Reuse only when clearly the official OpenAI endpoint
171- if base_url is None :
172- return candidate
173- if isinstance (base_url , str ) and "api.openai.com" in base_url :
174- return candidate
175- return None
176- except Exception :
177- return None
178-
179- client = _maybe_reuse_openai_client_from_ctx (ctx ) or _get_moderation_client ()
180- resp = await client .moderations .create (
181- model = "omni-moderation-latest" ,
182- input = data ,
183- )
179+ resp = await _call_moderation_api (client , data )
180+ except NotFoundError as e :
181+ # Moderation endpoint doesn't exist on this provider (e.g., third-party)
182+ # Fall back to the OpenAI client
183+ logger .debug (
184+ "Moderation endpoint not available on context client, falling back to OpenAI: %s" ,
185+ e ,
186+ )
187+ client = _get_moderation_client ()
188+ resp = await _call_moderation_api (client , data )
189+ else :
190+ # No context client, use fallback
191+ client = _get_moderation_client ()
192+ resp = await _call_moderation_api (client , data )
184193 results = resp .results or []
185194 if not results :
186195 return GuardrailResult (
0 commit comments