@@ -55,7 +55,7 @@ var _ = Describe("ClassifyCategory", func() {
5555
5656 category , score , err := classifier .ClassifyCategory ("This is about politics" )
5757
58- Expect (err ).To ( BeNil ())
58+ Expect (err ).ToNot ( HaveOccurred ())
5959 Expect (category ).To (Equal ("politics" ))
6060 Expect (score ).To (BeNumerically ("~" , 0.95 , 0.001 ))
6161 })
@@ -70,7 +70,7 @@ var _ = Describe("ClassifyCategory", func() {
7070
7171 category , score , err := classifier .ClassifyCategory ("Ambiguous text" )
7272
73- Expect (err ).To ( BeNil ())
73+ Expect (err ).ToNot ( HaveOccurred ())
7474 Expect (category ).To (Equal ("" ))
7575 Expect (score ).To (BeNumerically ("~" , 0.3 , 0.001 ))
7676 })
@@ -82,7 +82,8 @@ var _ = Describe("ClassifyCategory", func() {
8282
8383 category , score , err := classifier .ClassifyCategory ("Some text" )
8484
85- Expect (err ).ToNot (BeNil ())
85+ Expect (err ).To (HaveOccurred ())
86+ Expect (err .Error ()).To (ContainSubstring ("classification error" ))
8687 Expect (category ).To (Equal ("" ))
8788 Expect (score ).To (BeNumerically ("~" , 0.0 , 0.001 ))
8889 })
@@ -97,8 +98,7 @@ var _ = Describe("ClassifyCategory", func() {
9798
9899 category , score , err := classifier .ClassifyCategory ("" )
99100
100- // Should still attempt classification
101- Expect (err ).To (BeNil ())
101+ Expect (err ).ToNot (HaveOccurred ())
102102 Expect (category ).To (Equal ("technology" ))
103103 Expect (score ).To (BeNumerically ("~" , 0.8 , 0.001 ))
104104 })
@@ -113,7 +113,7 @@ var _ = Describe("ClassifyCategory", func() {
113113
114114 category , score , err := classifier .ClassifyCategory ("Some text" )
115115
116- Expect (err ).To ( BeNil ())
116+ Expect (err ).ToNot ( HaveOccurred ())
117117 Expect (category ).To (Equal ("" ))
118118 Expect (score ).To (BeNumerically ("~" , 0.8 , 0.001 ))
119119 })
@@ -163,7 +163,7 @@ var _ = Describe("CheckForJailbreak", func() {
163163
164164 isJailbreak , jailbreakType , confidence , err := classifier .CheckForJailbreak ("This is a jailbreak attempt" )
165165
166- Expect (err ).To ( BeNil ())
166+ Expect (err ).ToNot ( HaveOccurred ())
167167 Expect (isJailbreak ).To (BeTrue ())
168168 Expect (jailbreakType ).To (Equal ("jailbreak" ))
169169 Expect (confidence ).To (BeNumerically ("~" , 0.9 , 0.001 ))
@@ -179,7 +179,7 @@ var _ = Describe("CheckForJailbreak", func() {
179179
180180 isJailbreak , jailbreakType , confidence , err := classifier .CheckForJailbreak ("This is a normal question" )
181181
182- Expect (err ).To ( BeNil ())
182+ Expect (err ).ToNot ( HaveOccurred ())
183183 Expect (isJailbreak ).To (BeFalse ())
184184 Expect (jailbreakType ).To (Equal ("benign" ))
185185 Expect (confidence ).To (BeNumerically ("~" , 0.9 , 0.001 ))
@@ -195,7 +195,7 @@ var _ = Describe("CheckForJailbreak", func() {
195195
196196 isJailbreak , jailbreakType , confidence , err := classifier .CheckForJailbreak ("Ambiguous text" )
197197
198- Expect (err ).To ( BeNil ())
198+ Expect (err ).ToNot ( HaveOccurred ())
199199 Expect (isJailbreak ).To (BeFalse ())
200200 Expect (jailbreakType ).To (Equal ("jailbreak" ))
201201 Expect (confidence ).To (BeNumerically ("~" , 0.5 , 0.001 ))
@@ -208,7 +208,7 @@ var _ = Describe("CheckForJailbreak", func() {
208208
209209 isJailbreak , jailbreakType , confidence , err := classifier .CheckForJailbreak ("Some text" )
210210
211- Expect (err ).ToNot ( BeNil ())
211+ Expect (err ).To ( HaveOccurred ())
212212 Expect (err .Error ()).To (ContainSubstring ("jailbreak classification failed" ))
213213 Expect (isJailbreak ).To (BeFalse ())
214214 Expect (jailbreakType ).To (Equal ("" ))
@@ -225,10 +225,184 @@ var _ = Describe("CheckForJailbreak", func() {
225225
226226 isJailbreak , jailbreakType , confidence , err := classifier .CheckForJailbreak ("Some text" )
227227
228- Expect (err ).ToNot (BeNil ())
228+ Expect (err ).To (HaveOccurred ())
229+ Expect (err .Error ()).To (ContainSubstring ("unknown jailbreak class index" ))
229230 Expect (isJailbreak ).To (BeFalse ())
230231 Expect (jailbreakType ).To (Equal ("" ))
231232 Expect (confidence ).To (BeNumerically ("~" , 0.0 , 0.001 ))
232233 })
233234 })
234235})
236+
237+ type PIIInferenceResponse struct {
238+ classifyTokensResult candle_binding.TokenClassificationResult
239+ classifyTokensError error
240+ }
241+
242+ type MockPIIInference struct {
243+ PIIInferenceResponse
244+ responseMap map [string ]PIIInferenceResponse
245+ }
246+
247+ func (m * MockPIIInference ) ClassifyTokens (text string , configPath string ) (candle_binding.TokenClassificationResult , error ) {
248+ if response , exists := m .responseMap [text ]; exists {
249+ return response .classifyTokensResult , response .classifyTokensError
250+ }
251+ return m .classifyTokensResult , m .classifyTokensError
252+ }
253+
254+ var _ = Describe ("PIIClassification" , func () {
255+ var (
256+ classifier * Classifier
257+ mockPIIModel * MockPIIInference
258+ )
259+
260+ BeforeEach (func () {
261+ mockPIIModel = & MockPIIInference {}
262+ cfg := & config.RouterConfig {}
263+ cfg .Classifier .PIIModel .ModelID = "test-pii-model"
264+ cfg .Classifier .PIIModel .Threshold = 0.7
265+
266+ classifier = & Classifier {
267+ piiInference : mockPIIModel ,
268+ Config : cfg ,
269+ PIIMapping : & PIIMapping {
270+ LabelToIdx : map [string ]int {"PERSON" : 0 , "EMAIL" : 1 },
271+ IdxToLabel : map [string ]string {"0" : "PERSON" , "1" : "EMAIL" },
272+ },
273+ }
274+ })
275+
276+ Describe ("ClassifyPII" , func () {
277+ Context ("when PII entities are detected above threshold" , func () {
278+ It ("should return detected PII types" , func () {
279+ mockPIIModel .classifyTokensResult = candle_binding.TokenClassificationResult {
280+ Entities : []candle_binding.TokenEntity {
281+ {
282+ EntityType : "PERSON" ,
283+ Text : "John Doe" ,
284+ Start : 0 ,
285+ End : 8 ,
286+ Confidence : 0.9 ,
287+ },
288+ {
289+ EntityType : "EMAIL" ,
290+ 291+ Start : 9 ,
292+ End : 25 ,
293+ Confidence : 0.8 ,
294+ },
295+ },
296+ }
297+
298+ piiTypes ,
err := classifier .
ClassifyPII (
"John Doe [email protected] " )
299+
300+ Expect (err ).ToNot (HaveOccurred ())
301+ Expect (piiTypes ).To (ConsistOf ("PERSON" , "EMAIL" ))
302+ })
303+ })
304+
305+ Context ("when PII entities are detected below threshold" , func () {
306+ It ("should filter out low confidence entities" , func () {
307+ mockPIIModel .classifyTokensResult = candle_binding.TokenClassificationResult {
308+ Entities : []candle_binding.TokenEntity {
309+ {
310+ EntityType : "PERSON" ,
311+ Text : "John Doe" ,
312+ Start : 0 ,
313+ End : 8 ,
314+ Confidence : 0.9 ,
315+ },
316+ {
317+ EntityType : "EMAIL" ,
318+ 319+ Start : 9 ,
320+ End : 25 ,
321+ Confidence : 0.5 ,
322+ },
323+ },
324+ }
325+
326+ piiTypes ,
err := classifier .
ClassifyPII (
"John Doe [email protected] " )
327+
328+ Expect (err ).ToNot (HaveOccurred ())
329+ Expect (piiTypes ).To (ConsistOf ("PERSON" ))
330+ })
331+ })
332+
333+ Context ("when no PII is detected" , func () {
334+ It ("should return empty list" , func () {
335+ mockPIIModel .classifyTokensResult = candle_binding.TokenClassificationResult {
336+ Entities : []candle_binding.TokenEntity {},
337+ }
338+
339+ piiTypes , err := classifier .ClassifyPII ("Some text" )
340+
341+ Expect (err ).ToNot (HaveOccurred ())
342+ Expect (piiTypes ).To (BeEmpty ())
343+ })
344+ })
345+
346+ Context ("when model inference fails" , func () {
347+ It ("should return error" , func () {
348+ mockPIIModel .classifyTokensError = errors .New ("PII model inference failed" )
349+
350+ piiTypes , err := classifier .ClassifyPII ("Some text" )
351+
352+ Expect (err ).To (HaveOccurred ())
353+ Expect (err .Error ()).To (ContainSubstring ("PII token classification error" ))
354+ Expect (piiTypes ).To (BeNil ())
355+ })
356+ })
357+ })
358+
359+ Describe ("AnalyzeContentForPII" , func () {
360+ Context ("when some texts contain PII" , func () {
361+ It ("should return detailed analysis for each text" , func () {
362+ mockPIIModel .responseMap = make (map [string ]PIIInferenceResponse )
363+ mockPIIModel .responseMap ["Alice Smith" ] = PIIInferenceResponse {
364+ classifyTokensResult : candle_binding.TokenClassificationResult {
365+ Entities : []candle_binding.TokenEntity {
366+ {
367+ EntityType : "PERSON" ,
368+ Text : "Alice" ,
369+ Start : 0 ,
370+ End : 5 ,
371+ Confidence : 0.9 ,
372+ },
373+ },
374+ },
375+ classifyTokensError : nil ,
376+ }
377+
378+ mockPIIModel .responseMap ["No PII here" ] = PIIInferenceResponse {}
379+
380+ contentList := []string {"Alice Smith" , "No PII here" }
381+ hasPII , results , err := classifier .AnalyzeContentForPII (contentList )
382+
383+ Expect (err ).ToNot (HaveOccurred ())
384+ Expect (hasPII ).To (BeTrue ())
385+ Expect (results ).To (HaveLen (2 ))
386+ Expect (results [0 ].HasPII ).To (BeTrue ())
387+ Expect (results [0 ].Entities ).To (HaveLen (1 ))
388+ Expect (results [0 ].Entities [0 ].EntityType ).To (Equal ("PERSON" ))
389+ Expect (results [0 ].Entities [0 ].Text ).To (Equal ("Alice" ))
390+ Expect (results [1 ].HasPII ).To (BeFalse ())
391+ Expect (results [1 ].Entities ).To (BeEmpty ())
392+ })
393+ })
394+
395+ Context ("when model inference fails" , func () {
396+ It ("should return false for hasPII and empty results" , func () {
397+ mockPIIModel .classifyTokensError = errors .New ("model failed" )
398+
399+ contentList := []string {"Text 1" , "Text 2" }
400+ hasPII , results , err := classifier .AnalyzeContentForPII (contentList )
401+
402+ Expect (err ).ToNot (HaveOccurred ())
403+ Expect (hasPII ).To (BeFalse ())
404+ Expect (results ).To (BeEmpty ())
405+ })
406+ })
407+ })
408+ })
0 commit comments