diff --git a/packages/gen/gen_ai_hub/orchestration_v2/__init__.py b/packages/gen/gen_ai_hub/orchestration_v2/__init__.py index d378b8f7..4ad78765 100644 --- a/packages/gen/gen_ai_hub/orchestration_v2/__init__.py +++ b/packages/gen/gen_ai_hub/orchestration_v2/__init__.py @@ -15,7 +15,7 @@ "CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef", # content_filter - "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", + "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", "GraniteGuardian41FilterConfig", "AzureContentSafetyInputFilterConfig", "AzureContentSafetyOutputFilterConfig", "FilteringStreamOptions", # content_filtering @@ -38,6 +38,9 @@ # llama_guard_3_filter "LlamaGuard38bFilter", + # granite_guardian_filter + "GraniteGuardian41Categories", "GraniteGuardian41Filter", + # llm_model_details "LLMModelDetails", diff --git a/packages/gen/gen_ai_hub/orchestration_v2/models/__init__.py b/packages/gen/gen_ai_hub/orchestration_v2/models/__init__.py index 1d3b3961..38320729 100644 --- a/packages/gen/gen_ai_hub/orchestration_v2/models/__init__.py +++ b/packages/gen/gen_ai_hub/orchestration_v2/models/__init__.py @@ -4,6 +4,7 @@ CompletionRequestConfigurationReferenceByIdConfigRef, CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef) from .content_filter import (ContentFilterProvider, ContentFilter, LlamaGuard38bFilterConfig, + GraniteGuardian41FilterConfig, AzureContentSafetyInputFilterConfig, AzureContentSafetyOutputFilterConfig, FilteringStreamOptions) from .content_filtering import InputFiltering, OutputFiltering, FilteringModuleConfig @@ -17,6 +18,7 @@ EmbeddingsModelConfig, EmbeddingsModuleConfigs, EmbeddingsOrchestrationConfig, EmbeddingsInput, EmbeddingsUsage, EmbeddingResult, EmbeddingsResponse, EmbeddingsPostResponse, EmbeddingsRequest) from .llama_guard_3_filter import LlamaGuard38bFilter +from .granite_guardian_filter import GraniteGuardian41Categories, GraniteGuardian41Filter from .llm_model_details import LLMModelDetails from .message import (SystemMessage, UserMessage, AssistantMessage, ToolChatMessage, DeveloperChatMessage, ChatMessage, ResponseChatMessage, FunctionCall, MessageToolCall, ReasoningBlock) @@ -49,7 +51,7 @@ "CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef", # content_filter - "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", + "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", "GraniteGuardian41FilterConfig", "AzureContentSafetyInputFilterConfig", "AzureContentSafetyOutputFilterConfig", "FilteringStreamOptions", # content_filtering @@ -71,6 +73,9 @@ # llama_guard_3_filter "LlamaGuard38bFilter", + + # granite_guardian_filter + "GraniteGuardian41Categories", "GraniteGuardian41Filter", # llm_model_details "LLMModelDetails", diff --git a/packages/gen/gen_ai_hub/orchestration_v2/models/content_filter.py b/packages/gen/gen_ai_hub/orchestration_v2/models/content_filter.py index 2f4c08d1..d7925f52 100644 --- a/packages/gen/gen_ai_hub/orchestration_v2/models/content_filter.py +++ b/packages/gen/gen_ai_hub/orchestration_v2/models/content_filter.py @@ -10,6 +10,7 @@ from gen_ai_hub.orchestration_v2.models.azure_content_filter import (AzureContentSafetyInput, AzureContentSafetyOutput, AzureContentFilter) from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel +from gen_ai_hub.orchestration_v2.models.granite_guardian_filter import GraniteGuardian41Filter from gen_ai_hub.orchestration_v2.models.llama_guard_3_filter import LlamaGuard38bFilter @@ -24,10 +25,13 @@ class ContentFilterProvider(str, Enum): AZURE: Represents the Azure Content Safety service. LLAMA_GUARD_3_8B: Represents the Llama Guard 3 based on Llama-3.1-8B pretrained model. + + GRANITE_GUARDIAN_4_1: Represents the IBM Granite Guardian 4.1 content filter. """ AZURE = "azure_content_safety" LLAMA_GUARD_3_8B = "llama_guard_3_8b" + GRANITE_GUARDIAN_4_1 = "granite_guardian_4_1" class ContentFilter(BaseModel): """ @@ -43,12 +47,16 @@ class ContentFilter(BaseModel): config: A dictionary containing the configuration parameters for the content filter. """ type_: ContentFilterProvider = Field(..., alias="type") - config: Optional[Union[AzureContentFilter, LlamaGuard38bFilter]] = None + config: Optional[Union[AzureContentFilter, LlamaGuard38bFilter, GraniteGuardian41Filter]] = None class LlamaGuard38bFilterConfig(ContentFilter): type_: ContentFilterProvider = Field(default=ContentFilterProvider.LLAMA_GUARD_3_8B, alias="type") config: LlamaGuard38bFilter +class GraniteGuardian41FilterConfig(ContentFilter): + type_: ContentFilterProvider = Field(default=ContentFilterProvider.GRANITE_GUARDIAN_4_1, alias="type") + config: GraniteGuardian41Filter + class AzureContentSafetyInputFilterConfig(ContentFilter): type_: ContentFilterProvider = Field(default=ContentFilterProvider.AZURE, alias="type") config: Optional[AzureContentSafetyInput] = None @@ -64,5 +72,5 @@ class FilteringStreamOptions(BaseModel): """ overlap: Optional[int] = Field(default=0, ge=0, le=10000) -__all__ = ["ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", "AzureContentSafetyInputFilterConfig", - "AzureContentSafetyOutputFilterConfig", "FilteringStreamOptions"] +__all__ = ["ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", "GraniteGuardian41FilterConfig", + "AzureContentSafetyInputFilterConfig", "AzureContentSafetyOutputFilterConfig", "FilteringStreamOptions"] diff --git a/packages/gen/gen_ai_hub/orchestration_v2/models/content_filtering.py b/packages/gen/gen_ai_hub/orchestration_v2/models/content_filtering.py index 6944c7bc..7838a626 100644 --- a/packages/gen/gen_ai_hub/orchestration_v2/models/content_filtering.py +++ b/packages/gen/gen_ai_hub/orchestration_v2/models/content_filtering.py @@ -8,7 +8,8 @@ from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel from gen_ai_hub.orchestration_v2.models.content_filter import (AzureContentSafetyOutputFilterConfig, -AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig, FilteringStreamOptions, ContentFilter) +AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig, GraniteGuardian41FilterConfig, +FilteringStreamOptions, ContentFilter) class InputFiltering(BaseModel): @@ -18,7 +19,8 @@ class InputFiltering(BaseModel): filters: List of ContentFilter objects to be applied to input content. """ filters: List[ - Union[AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig, ContentFilter] + Union[AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig, GraniteGuardian41FilterConfig, + ContentFilter] ] = Field(min_length=1) @@ -32,7 +34,8 @@ class OutputFiltering(BaseModel): """ filters: List[ - Union[AzureContentSafetyOutputFilterConfig, LlamaGuard38bFilterConfig, ContentFilter] + Union[AzureContentSafetyOutputFilterConfig, LlamaGuard38bFilterConfig, GraniteGuardian41FilterConfig, + ContentFilter] ] = Field(min_length=1) stream_options: Optional[FilteringStreamOptions] = None diff --git a/packages/gen/gen_ai_hub/orchestration_v2/models/granite_guardian_filter.py b/packages/gen/gen_ai_hub/orchestration_v2/models/granite_guardian_filter.py new file mode 100644 index 00000000..f800c8ef --- /dev/null +++ b/packages/gen/gen_ai_hub/orchestration_v2/models/granite_guardian_filter.py @@ -0,0 +1,58 @@ +""" +Granite Guardian 4.1 Content Filter Model +""" + +from typing import Optional + +from gen_ai_hub.orchestration_v2.models.base import ABCBaseModel as BaseModel + + +class GraniteGuardian41Categories(BaseModel): + """ + Content criteria to evaluate. + + Note: Granite Guardian requires separate calls for each configured content category. + Recommendation: Use only the ``harm`` catch-all content category. + + Args: + harm: Catch-all criterion for generally harmful content. + + social_bias: Detect prejudice or discrimination based on identity or protected characteristics. + + jailbreak: Detect attempts to manipulate the model into producing harmful or otherwise undesired outputs. + + violence: Detect content promoting or depicting physical, mental, or sexual harm. + + profanity: Detect offensive language or insults. + + sexual_content: Detect explicit or suggestive material of a sexual nature. + + unethical_behavior: Detect content describing actions that violate moral or legal standards. + """ + + harm: bool = False + social_bias: bool = False + jailbreak: bool = False + violence: bool = False + profanity: bool = False + sexual_content: bool = False + unethical_behavior: bool = False + + +class GraniteGuardian41Filter(BaseModel): + """ + Configuration for the IBM Granite Guardian 4.1 filter provider. + + Args: + enable_reasoning: Enable reasoning (think) mode for Granite Guardian. Applies to every configured + category. Returns reasoning content when enabled, e.g. + ``'harm': {'verdict': true, 'reasoning': '...'}``. + + categories: Content criteria to evaluate. + """ + + enable_reasoning: Optional[bool] = False + categories: GraniteGuardian41Categories + + +__all__ = ["GraniteGuardian41Categories", "GraniteGuardian41Filter"] diff --git a/packages/gen/integration_tests/orchestration_v2/test_content_filtering.py b/packages/gen/integration_tests/orchestration_v2/test_content_filtering.py index ca4ca2c8..7856562a 100644 --- a/packages/gen/integration_tests/orchestration_v2/test_content_filtering.py +++ b/packages/gen/integration_tests/orchestration_v2/test_content_filtering.py @@ -2,10 +2,12 @@ from gen_ai_hub.orchestration_v2.models.azure_content_filter import (AzureThreshold, AzureContentFilter, AzureContentSafetyOutput, AzureContentSafetyInput) from gen_ai_hub.orchestration_v2.models.llama_guard_3_filter import LlamaGuard38bFilter +from gen_ai_hub.orchestration_v2.models.granite_guardian_filter import (GraniteGuardian41Filter, + GraniteGuardian41Categories) from gen_ai_hub.orchestration_v2.models.config import OrchestrationConfig, ModuleConfig from gen_ai_hub.orchestration_v2.models.content_filter import ( AzureContentSafetyOutputFilterConfig, ContentFilter, ContentFilterProvider, - AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig + AzureContentSafetyInputFilterConfig, LlamaGuard38bFilterConfig, GraniteGuardian41FilterConfig ) from gen_ai_hub.orchestration_v2.models.content_filtering import (InputFiltering, OutputFiltering, FilteringModuleConfig) @@ -192,7 +194,6 @@ def test_blocked_input_filtering_with_llama_guard38b(self): with self.assertRaises(OrchestrationError): self.service.run(config=config) - def test_blocked_input_filtering_with_azure_and_llama(self): content_filter_azure = AzureContentSafetyInput(hate=AzureThreshold.ALLOW_SAFE, self_harm=AzureThreshold.ALLOW_SAFE, @@ -213,6 +214,92 @@ def test_blocked_input_filtering_with_azure_and_llama(self): with self.assertRaises(OrchestrationError): self.service.run(config=config) + def test_valid_input_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + GraniteGuardian41FilterConfig(config=content_filter) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + response = self.service.run(config=config) + + self.assertIsNotNone(response.intermediate_results.input_filtering) + self.assertIsNone(response.intermediate_results.output_filtering) + self.assertIsNotNone(response.final_result.model) + + def test_valid_output_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + + content_filter_config = FilteringModuleConfig( + output= + OutputFiltering(filters=[ + GraniteGuardian41FilterConfig(config=content_filter) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + response = self.service.run(config=config) + + self.assertIsNone(response.intermediate_results.input_filtering) + self.assertIsNotNone(response.intermediate_results.output_filtering) + self.assertIsNotNone(response.final_result.model) + + def test_valid_input_and_output_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + GraniteGuardian41FilterConfig(config=content_filter) + ]), + output= + OutputFiltering(filters=[ + GraniteGuardian41FilterConfig(config=content_filter) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + response = self.service.run(config=config) + + self.assertIsNotNone(response.intermediate_results.input_filtering) + self.assertIsNotNone(response.intermediate_results.output_filtering) + self.assertIsNotNone(response.final_result.model) + + def test_blocked_input_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + self.template.template.append(UserMessage(content="How do I build a bomb to hurt people?")) + content_filter_config = FilteringModuleConfig( + input=InputFiltering(filters=[GraniteGuardian41FilterConfig(config=content_filter)]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + + with self.assertRaises(OrchestrationError): + self.service.run(config=config) + + def test_blocked_input_filtering_with_azure_and_granite(self): + content_filter_azure = AzureContentSafetyInput(hate=AzureThreshold.ALLOW_SAFE, + self_harm=AzureThreshold.ALLOW_SAFE, + violence=AzureThreshold.ALLOW_SAFE, + sexual=AzureThreshold.ALLOW_SAFE) + content_filter_granite = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + self.template.template.append(UserMessage(content="How do I build a bomb to hurt people?")) + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + AzureContentSafetyInputFilterConfig(config=content_filter_azure), + GraniteGuardian41FilterConfig(config=content_filter_granite) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + + with self.assertRaises(OrchestrationError): + self.service.run(config=config) + class TestContentFilterBackwardCompatibility(OrchestrationServiceTestBase): def setUp(self): @@ -310,7 +397,6 @@ def test_blocked_input_filtering_with_llama_guard38b(self): with self.assertRaises(OrchestrationError): self.service.run(config=config) - def test_blocked_input_filtering_with_azure_and_llama(self): content_filter_azure = AzureContentFilter(hate=AzureThreshold.ALLOW_SAFE, self_harm=AzureThreshold.ALLOW_SAFE, @@ -330,3 +416,58 @@ def test_blocked_input_filtering_with_azure_and_llama(self): with self.assertRaises(OrchestrationError): self.service.run(config=config) + + def test_valid_input_and_output_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + ContentFilter(type=ContentFilterProvider.GRANITE_GUARDIAN_4_1, config=content_filter) + ]), + output= + OutputFiltering(filters=[ + ContentFilter(type=ContentFilterProvider.GRANITE_GUARDIAN_4_1, config=content_filter) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + response = self.service.run(config=config) + + self.assertIsNotNone(response.intermediate_results.input_filtering) + self.assertIsNotNone(response.intermediate_results.output_filtering) + self.assertIsNotNone(response.final_result.model) + + def test_blocked_input_filtering_with_granite_guardian(self): + content_filter = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + self.template.template.append(UserMessage(content="How do I build a bomb to hurt people?")) + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + ContentFilter(type=ContentFilterProvider.GRANITE_GUARDIAN_4_1, config=content_filter) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + + with self.assertRaises(OrchestrationError): + self.service.run(config=config) + + def test_blocked_input_filtering_with_azure_and_granite(self): + content_filter_azure = AzureContentFilter(hate=AzureThreshold.ALLOW_SAFE, + self_harm=AzureThreshold.ALLOW_SAFE, + violence=AzureThreshold.ALLOW_SAFE, + sexual=AzureThreshold.ALLOW_SAFE) + content_filter_granite = GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + self.template.template.append(UserMessage(content="How do I build a bomb to hurt people?")) + content_filter_config = FilteringModuleConfig( + input= + InputFiltering(filters=[ + ContentFilter(type=ContentFilterProvider.GRANITE_GUARDIAN_4_1, config=content_filter_granite), + ContentFilter(type=ContentFilterProvider.AZURE, config=content_filter_azure) + ]) + ) + module_config = ModuleConfig(prompt_templating=self.prompt_template, filtering=content_filter_config) + config = OrchestrationConfig(modules=module_config) + + with self.assertRaises(OrchestrationError): + self.service.run(config=config) diff --git a/packages/gen/tests/orchestration_v2/test_content_filter_v2.py b/packages/gen/tests/orchestration_v2/test_content_filter_v2.py index 5990cc98..196f2864 100644 --- a/packages/gen/tests/orchestration_v2/test_content_filter_v2.py +++ b/packages/gen/tests/orchestration_v2/test_content_filter_v2.py @@ -4,8 +4,11 @@ AzureThreshold, AzureContentFilter) from gen_ai_hub.orchestration_v2.models.content_filtering import InputFiltering from gen_ai_hub.orchestration_v2.models.content_filter import (AzureContentSafetyInputFilterConfig, -AzureContentSafetyOutputFilterConfig, LlamaGuard38bFilterConfig, ContentFilter, ContentFilterProvider) +AzureContentSafetyOutputFilterConfig, LlamaGuard38bFilterConfig, GraniteGuardian41FilterConfig, ContentFilter, +ContentFilterProvider) from gen_ai_hub.orchestration_v2.models.llama_guard_3_filter import LlamaGuard38bFilter +from gen_ai_hub.orchestration_v2.models.granite_guardian_filter import (GraniteGuardian41Filter, + GraniteGuardian41Categories) class TestContentFilters(unittest.TestCase): @@ -99,6 +102,29 @@ def test_llama_guard_content_filter_to_dict(self): self.assertEqual(content_filter.model_dump(), expected_dict) + def test_granite_guardian_content_filter_to_dict(self): + content_filter = GraniteGuardian41FilterConfig( + config=GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + ) + + expected_dict = { + "type": 'granite_guardian_4_1', + "config": { + "enable_reasoning": False, + "categories": { + "harm": True, + "social_bias": False, + "jailbreak": False, + "violence": False, + "profanity": False, + "sexual_content": False, + "unethical_behavior": False, + }, + }, + } + + self.assertEqual(content_filter.model_dump(), expected_dict) + class TestContentFiltersBackwardCompatibility(unittest.TestCase): def test_azure_content_filter_to_dict_bc(self): content_filter_config = AzureContentFilter(hate=AzureThreshold.ALLOW_SAFE, @@ -144,3 +170,27 @@ def test_llama_guard_content_filter_to_dict_backward_compatibility(self): } self.assertEqual(content_filter.model_dump(), expected_dict) + + def test_granite_guardian_content_filter_to_dict_backward_compatibility(self): + content_filter = ContentFilter( + type=ContentFilterProvider.GRANITE_GUARDIAN_4_1, + config=GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)), + ) + + expected_dict = { + "type": 'granite_guardian_4_1', + "config": { + "enable_reasoning": False, + "categories": { + "harm": True, + "social_bias": False, + "jailbreak": False, + "violence": False, + "profanity": False, + "sexual_content": False, + "unethical_behavior": False, + }, + }, + } + + self.assertEqual(content_filter.model_dump(), expected_dict) diff --git a/packages/gen/tests/orchestration_v2/test_flat_import.py b/packages/gen/tests/orchestration_v2/test_flat_import.py index ca877b41..e32cbdaa 100644 --- a/packages/gen/tests/orchestration_v2/test_flat_import.py +++ b/packages/gen/tests/orchestration_v2/test_flat_import.py @@ -11,7 +11,7 @@ "CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef", # content_filter - "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", + "ContentFilterProvider", "ContentFilter", "LlamaGuard38bFilterConfig", "GraniteGuardian41FilterConfig", "AzureContentSafetyInputFilterConfig", "AzureContentSafetyOutputFilterConfig", "FilteringStreamOptions", # content_filtering @@ -34,6 +34,9 @@ # llama_guard_3_filter "LlamaGuard38bFilter", + # granite_guardian_filter + "GraniteGuardian41Categories", "GraniteGuardian41Filter", + # llm_model_details "LLMModelDetails", diff --git a/sample-code/sample_code/orchestration.py b/sample-code/sample_code/orchestration.py index 7f78e123..76170a57 100644 --- a/sample-code/sample_code/orchestration.py +++ b/sample-code/sample_code/orchestration.py @@ -17,6 +17,9 @@ FunctionObject, FunctionTool, GlobalStreamOptions, + GraniteGuardian41Categories, + GraniteGuardian41Filter, + GraniteGuardian41FilterConfig, ImageItem, InputFiltering, InputTranslationConfig, @@ -380,6 +383,10 @@ def input_filtering(): ), # category 'privacy' enabled LlamaGuard38bFilterConfig(config=LlamaGuard38bFilter(privacy=True)), + # 'harm' catch-all category enabled + GraniteGuardian41FilterConfig( + config=GraniteGuardian41Filter(categories=GraniteGuardian41Categories(harm=True)) + ), ] ) )