Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/guardrails/guardrail_hooks/javelin/javelin.py: 17%

114 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1from datetime import datetime 

2from typing import TYPE_CHECKING, Final 

3 

4from fastapi import HTTPException 

5 

6import litellm 

7from litellm._logging import verbose_proxy_logger 

8from litellm.integrations.custom_guardrail import CustomGuardrail 

9from litellm.llms.custom_httpx.http_handler import ( 

10 get_async_httpx_client, 

11 httpxSpecialProvider, 

12) 

13from litellm.proxy._types import UserAPIKeyAuth 

14from litellm.secret_managers.main import get_secret_str 

15from litellm.types.guardrails import GuardrailEventHooks 

16from litellm.types.proxy.guardrails.guardrail_hooks.javelin import ( 

17 JavelinGuardInput, 

18 JavelinGuardRequest, 

19 JavelinGuardResponse, 

20) 

21from litellm.types.utils import CallTypesLiteral, GuardrailStatus 

22 

23if TYPE_CHECKING: 23 ↛ 24line 23 didn't jump to line 24 because the condition on line 23 was never true

24 from litellm.types.proxy.guardrails.guardrail_hooks.base import GuardrailConfigModel 

25 

26 

27class JavelinGuardrail(CustomGuardrail): 

28 @classmethod 

29 def get_supported_event_hooks(cls) -> list[GuardrailEventHooks]: 

30 return [ 

31 GuardrailEventHooks.pre_call, 

32 ] 

33 

34 def __init__( 

35 self, 

36 api_key: str | None = None, 

37 api_base: str | None = None, 

38 default_on: bool = True, 

39 guardrail_name: str = "trustsafety", 

40 javelin_guard_name: str | None = None, 

41 api_version: str = "v1", 

42 metadata: dict | None = None, 

43 config: dict | None = None, 

44 application: str | None = None, 

45 **kwargs, 

46 ): 

47 """ 

48 Initialize the JavelinGuardrail class. 

49 

50 This calls: {api_base}/{api_version}/guardrail/{guardrail_name}/apply 

51 

52 Args: 

53 api_key: str = None, 

54 api_base: str = None, 

55 default_on: bool = True, 

56 api_version: str = "v1", 

57 guardrail_name: str = "trustsafety", 

58 metadata: Optional[Dict] = None, 

59 config: Optional[Dict] = None, 

60 application: Optional[str] = None, 

61 """ 

62 

63 self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback) 

64 self.javelin_api_key = api_key or get_secret_str("JAVELIN_API_KEY") 

65 self.api_base = api_base or get_secret_str("JAVELIN_API_BASE") or "https://api-dev.javelin.live" 

66 self.api_version = api_version 

67 self.guardrail_name = guardrail_name 

68 self.javelin_guard_name = javelin_guard_name or guardrail_name 

69 self.default_on = default_on 

70 self.metadata = metadata 

71 self.config = config 

72 self.application = application 

73 verbose_proxy_logger.debug( 

74 "Javelin Guardrail: Initialized with guardrail_name=%s, javelin_guard_name=%s, api_base=%s, api_version=%s", 

75 self.guardrail_name, 

76 self.javelin_guard_name, 

77 self.api_base, 

78 self.api_version, 

79 ) 

80 

81 kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks())) 

82 super().__init__(guardrail_name=guardrail_name, default_on=default_on, **kwargs) 

83 

84 async def call_javelin_guard( 

85 self, 

86 request: JavelinGuardRequest, 

87 event_type: GuardrailEventHooks, 

88 ) -> JavelinGuardResponse: 

89 """ 

90 Call the Javelin guard API. 

91 """ 

92 start_time: Final = datetime.now() 

93 # Create a new request with metadata if it's not already set 

94 if request.get("metadata") is None and self.metadata is not None: 

95 request = {**request, "metadata": self.metadata} 

96 headers: Final = { 

97 "x-javelin-apikey": self.javelin_api_key, 

98 } 

99 if self.application: 

100 headers["x-javelin-application"] = self.application 

101 

102 status: GuardrailStatus = "guardrail_failed_to_respond" 

103 javelin_response: JavelinGuardResponse | None = None 

104 exception_str = "" 

105 

106 try: 

107 verbose_proxy_logger.debug("Javelin Guardrail: Calling Javelin guard API with request: %s", request) 

108 url: Final = f"{self.api_base}/{self.api_version}/guardrail/{self.javelin_guard_name}/apply" 

109 verbose_proxy_logger.debug("Javelin Guardrail: Calling URL: %s", url) 

110 response: Final = await self.async_handler.post( 

111 url=url, 

112 headers=headers, 

113 json=dict(request), 

114 ) 

115 verbose_proxy_logger.debug("Javelin Guardrail: Javelin guard API response: %s", response.json()) 

116 response_data: Final = response.json() 

117 # Ensure the response has the required assessments field 

118 if "assessments" not in response_data: 

119 response_data["assessments"] = [] 

120 

121 javelin_response = {"assessments": response_data.get("assessments", [])} 

122 status = "success" 

123 return javelin_response 

124 except Exception as e: 

125 status = "guardrail_failed_to_respond" 

126 exception_str = str(e) 

127 return {"assessments": []} 

128 finally: 

129 #################################################### 

130 # Create Guardrail Trace for logging on Langfuse, Datadog, etc. 

131 #################################################### 

132 guardrail_json_response: Exception | str | dict | list[dict] = {} 

133 if status == "success" and javelin_response is not None: 

134 guardrail_json_response = dict(javelin_response) 

135 else: 

136 guardrail_json_response = exception_str 

137 

138 # Create a clean request data copy for logging (without guardrail responses) 

139 clean_request_data: Final = { 

140 "input": request.get("input", {}), 

141 "metadata": request.get("metadata", {}), 

142 "config": request.get("config", {}), 

143 } 

144 # Remove any existing guardrail logging information to prevent recursion 

145 if "metadata" in clean_request_data and clean_request_data["metadata"]: 

146 clean_request_data["metadata"] = { 

147 k: v 

148 for k, v in clean_request_data["metadata"].items() 

149 if k != "standard_logging_guardrail_information" 

150 } 

151 

152 self.add_standard_logging_guardrail_information_to_request_data( 

153 guardrail_json_response=guardrail_json_response, 

154 request_data=clean_request_data, 

155 guardrail_status=status, 

156 start_time=start_time.timestamp(), 

157 end_time=datetime.now().timestamp(), 

158 duration=(datetime.now() - start_time).total_seconds(), 

159 event_type=event_type, 

160 ) 

161 

162 async def async_pre_call_hook( 

163 self, 

164 user_api_key_dict: UserAPIKeyAuth, 

165 cache: litellm.DualCache, 

166 data: dict, 

167 call_type: CallTypesLiteral, 

168 ) -> Exception | str | dict | None: 

169 """ 

170 Pre-call hook for the Javelin guardrail. 

171 """ 

172 from litellm.litellm_core_utils.prompt_templates.common_utils import ( 

173 get_last_user_message, 

174 ) 

175 from litellm.proxy.common_utils.callback_utils import ( 

176 add_guardrail_to_applied_guardrails_header, 

177 ) 

178 

179 verbose_proxy_logger.debug("Javelin Guardrail: pre_call_hook") 

180 verbose_proxy_logger.debug("Javelin Guardrail: Request data: %s", data) 

181 

182 event_type: Final[GuardrailEventHooks] = GuardrailEventHooks.pre_call 

183 if self.should_run_guardrail(data=data, event_type=event_type) is not True: 

184 verbose_proxy_logger.debug("Javelin Guardrail: not running guardrail. Guardrail is disabled.") 

185 return data 

186 

187 if "messages" not in data: 

188 return data 

189 

190 text: Final = get_last_user_message(data["messages"]) 

191 if text is None: 

192 return data 

193 

194 clean_metadata = {} 

195 if self.metadata: 

196 clean_metadata = {k: v for k, v in self.metadata.items() if k != "standard_logging_guardrail_information"} 

197 

198 javelin_guard_request: Final = JavelinGuardRequest( 

199 input=JavelinGuardInput(text=text), 

200 metadata=clean_metadata, 

201 config=self.config if self.config else {}, 

202 ) 

203 

204 javelin_response: Final = await self.call_javelin_guard( 

205 request=javelin_guard_request, event_type=GuardrailEventHooks.pre_call 

206 ) 

207 

208 assessments: Final = javelin_response.get("assessments", []) 

209 reject_prompt = "" 

210 should_reject = False 

211 

212 # Debug: Log the full Javelin response 

213 verbose_proxy_logger.debug("Javelin Guardrail: Full Javelin response: %s", javelin_response) 

214 

215 for assessment in assessments: 

216 verbose_proxy_logger.debug("Javelin Guardrail: Processing assessment: %s", assessment) 

217 for assessment_type, assessment_data in assessment.items(): 

218 verbose_proxy_logger.debug( 

219 "Javelin Guardrail: Processing assessment_type: %s, data: %s", 

220 assessment_type, 

221 assessment_data, 

222 ) 

223 # Check if this assessment indicates rejection 

224 if assessment_data.get("request_reject") is True: 

225 should_reject = True 

226 verbose_proxy_logger.debug( 

227 "Javelin Guardrail: Request rejected by Javelin guardrail: %s (assessment_type: %s)", 

228 self.guardrail_name, 

229 assessment_type, 

230 ) 

231 

232 results = assessment_data.get("results", {}) 

233 reject_prompt = str(results.get("reject_prompt", "")) 

234 

235 verbose_proxy_logger.debug( 

236 "Javelin Guardrail: Extracted reject_prompt: '%s'", 

237 reject_prompt, 

238 ) 

239 break 

240 if should_reject: 

241 break 

242 

243 verbose_proxy_logger.debug( 

244 "Javelin Guardrail: should_reject=%s, reject_prompt='%s'", 

245 should_reject, 

246 reject_prompt, 

247 ) 

248 

249 if should_reject: 

250 if not reject_prompt: 

251 reject_prompt = f"Request blocked by Javelin guardrails due to {self.guardrail_name} violation." 

252 

253 verbose_proxy_logger.debug( 

254 "Javelin Guardrail: Blocking request with reject_prompt: '%s'", 

255 reject_prompt, 

256 ) 

257 

258 # Raise HTTPException to prevent the request from going to the LLM 

259 raise HTTPException( 

260 status_code=500, 

261 detail={ 

262 "error": "Violated guardrail policy", 

263 "javelin_guardrail_response": javelin_response, 

264 "reject_prompt": reject_prompt, 

265 }, 

266 ) 

267 

268 add_guardrail_to_applied_guardrails_header(request_data=data, guardrail_name=self.guardrail_name) 

269 

270 return data 

271 

272 @staticmethod 

273 def get_config_model() -> type["GuardrailConfigModel"] | None: 

274 """ 

275 Get the config model for the Javelin guardrail. 

276 """ 

277 from litellm.types.proxy.guardrails.guardrail_hooks.javelin import ( 

278 JavelinGuardrailConfigModel, 

279 ) 

280 

281 return JavelinGuardrailConfigModel