Feature/Safety settings to google genai (#4737)

* add safety settings to google genai

* add safety settings to google genai
This commit is contained in:
Henry Heng
2025-06-26 12:54:47 +01:00
committed by GitHub
parent c78b5326b6
commit 9efb70e04c
5 changed files with 139 additions and 82 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ import { getFileFromStorage } from '../../src/storageUtils'
import { ICommonObject, IFileUpload } from '../../src/Interface'
import { BaseMessageLike } from '@langchain/core/messages'
import { IFlowState } from './Interface.Agentflow'
import { mapMimeTypeToInputField } from '../../src/utils'
import { handleEscapeCharacters, mapMimeTypeToInputField } from '../../src/utils'
export const addImagesToMessages = async (
options: ICommonObject,
@@ -354,7 +354,7 @@ export const getPastChatHistoryImageMessages = async (
}
}
const documents: string = await fileLoaderNodeInstance.init(nodeData, '', nodeOptions)
messageWithFileUploads += `<doc name='${upload.name}'>${documents}</doc>\n\n`
messageWithFileUploads += `<doc name='${upload.name}'>${handleEscapeCharacters(documents, true)}</doc>\n\n`
}
}
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content
@@ -2,7 +2,7 @@ import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
import type { SafetySetting } from '@google/generative-ai'
import { BaseCache } from '@langchain/core/caches'
import { ICommonObject, IMultiModalOption, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { convertMultiOptionsToStringArray, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { getModels, MODEL_TYPE } from '../../../src/modelLoader'
import { ChatGoogleGenerativeAI } from './FlowiseChatGoogleGenerativeAI'
import { GoogleGenerativeAIChatInput } from '@langchain/google-genai'
@@ -22,7 +22,7 @@ class GoogleGenerativeAI_ChatModels implements INode {
constructor() {
this.label = 'ChatGoogleGenerativeAI'
this.name = 'chatGoogleGenerativeAI'
this.version = 3.0
this.version = 3.1
this.type = 'ChatGoogleGenerativeAI'
this.icon = 'GoogleGemini.svg'
this.category = 'Chat Models'
@@ -101,58 +101,75 @@ class GoogleGenerativeAI_ChatModels implements INode {
additionalParams: true
},
{
label: 'Harm Category',
name: 'harmCategory',
type: 'multiOptions',
label: 'Safety Settings',
name: 'safetySettings',
type: 'array',
description:
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_attribute_definitions">official guide</a> on how to use Harm Category',
options: [
'Safety settings for the model. Refer to the <a href="https://ai.google.dev/gemini-api/docs/safety-settings">official guide</a> on how to use Safety Settings',
array: [
{
label: 'Dangerous',
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT
label: 'Harm Category',
name: 'harmCategory',
type: 'options',
options: [
{
label: 'Dangerous',
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
description: 'Promotes, facilitates, or encourages harmful acts.'
},
{
label: 'Harassment',
name: HarmCategory.HARM_CATEGORY_HARASSMENT,
description: 'Negative or harmful comments targeting identity and/or protected attributes.'
},
{
label: 'Hate Speech',
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
description: 'Content that is rude, disrespectful, or profane.'
},
{
label: 'Sexually Explicit',
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
description: 'Contains references to sexual acts or other lewd content.'
},
{
label: 'Civic Integrity',
name: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
description: 'Election-related queries.'
}
]
},
{
label: 'Harassment',
name: HarmCategory.HARM_CATEGORY_HARASSMENT
},
{
label: 'Hate Speech',
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH
},
{
label: 'Sexually Explicit',
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT
}
],
optional: true,
additionalParams: true
},
{
label: 'Harm Block Threshold',
name: 'harmBlockThreshold',
type: 'multiOptions',
description:
'Refer to <a target="_blank" href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_setting_thresholds">official guide</a> on how to use Harm Block Threshold',
options: [
{
label: 'Low and Above',
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
},
{
label: 'Medium and Above',
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
},
{
label: 'None',
name: HarmBlockThreshold.BLOCK_NONE
},
{
label: 'Only High',
name: HarmBlockThreshold.BLOCK_ONLY_HIGH
},
{
label: 'Threshold Unspecified',
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED
label: 'Harm Block Threshold',
name: 'harmBlockThreshold',
type: 'options',
options: [
{
label: 'None',
name: HarmBlockThreshold.BLOCK_NONE,
description: 'Always show regardless of probability of unsafe content'
},
{
label: 'Only High',
name: HarmBlockThreshold.BLOCK_ONLY_HIGH,
description: 'Block when high probability of unsafe content'
},
{
label: 'Medium and Above',
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
description: 'Block when medium or high probability of unsafe content'
},
{
label: 'Low and Above',
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
description: 'Block when low, medium or high probability of unsafe content'
},
{
label: 'Threshold Unspecified (Default Threshold)',
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED,
description: 'Threshold is unspecified, block using default threshold'
}
]
}
],
optional: true,
@@ -195,8 +212,8 @@ class GoogleGenerativeAI_ChatModels implements INode {
const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string
const topP = nodeData.inputs?.topP as string
const topK = nodeData.inputs?.topK as string
const harmCategory = nodeData.inputs?.harmCategory as string
const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string
const _safetySettings = nodeData.inputs?.safetySettings as string
const cache = nodeData.inputs?.cache as BaseCache
const streaming = nodeData.inputs?.streaming as boolean
const baseUrl = nodeData.inputs?.baseUrl as string | undefined
@@ -220,17 +237,32 @@ class GoogleGenerativeAI_ChatModels implements INode {
if (temperature) obj.temperature = parseFloat(temperature)
if (baseUrl) obj.baseUrl = baseUrl
// Safety Settings
let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory)
let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold)
if (harmCategories.length != harmBlockThresholds.length)
throw new Error(`Harm Category & Harm Block Threshold are not the same length`)
const safetySettings: SafetySetting[] = harmCategories.map((harmCategory, index) => {
return {
category: harmCategory as HarmCategory,
threshold: harmBlockThresholds[index] as HarmBlockThreshold
let safetySettings: SafetySetting[] = []
if (_safetySettings) {
try {
const parsedSafetySettings = typeof _safetySettings === 'string' ? JSON.parse(_safetySettings) : _safetySettings
if (Array.isArray(parsedSafetySettings)) {
const validSettings = parsedSafetySettings
.filter((setting: any) => setting.harmCategory && setting.harmBlockThreshold)
.map((setting: any) => ({
category: setting.harmCategory as HarmCategory,
threshold: setting.harmBlockThreshold as HarmBlockThreshold
}))
// Remove duplicates by keeping only the first occurrence of each harm category
const seenCategories = new Set<HarmCategory>()
safetySettings = validSettings.filter((setting) => {
if (seenCategories.has(setting.category)) {
return false
}
seenCategories.add(setting.category)
return true
})
}
} catch (error) {
console.warn('Failed to parse safety settings:', error)
}
})
}
if (safetySettings.length > 0) obj.safetySettings = safetySettings
const multiModalOption: IMultiModalOption = {
+1 -1
View File
@@ -758,7 +758,7 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = [], orgI
}
}
const documents: string = await fileLoaderNodeInstance.init(nodeData, '', options)
messageWithFileUploads += `<doc name='${upload.name}'>${documents}</doc>\n\n`
messageWithFileUploads += `<doc name='${upload.name}'>${handleEscapeCharacters(documents, true)}</doc>\n\n`
}
}
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content
@@ -5,16 +5,18 @@ import { Chip, Box, Button, IconButton } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { IconTrash, IconPlus } from '@tabler/icons-react'
import NodeInputHandler from '@/views/canvas/NodeInputHandler'
import DocStoreInputHandler from '@/views/docstore/DocStoreInputHandler'
import { showHideInputs } from '@/utils/genericHelper'
import { cloneDeep } from 'lodash'
import { flowContext } from '@/store/context/ReactFlowContext'
export const ArrayRenderer = ({ inputParam, data, disabled }) => {
export const ArrayRenderer = ({ inputParam, data, disabled, isDocStore = false }) => {
const [arrayItems, setArrayItems] = useState([]) // these are the actual values. Ex: [{name: 'John', age: 30}, {name: 'Jane', age: 25}]
const [itemParameters, setItemParameters] = useState([]) // these are the input parameters for each array item. Ex: [{label: 'Name', type: 'string', display: true}, {label: 'age', type: 'number', display: false}]
const theme = useTheme()
const customization = useSelector((state) => state.customization)
const { reactFlowInstance } = useContext(flowContext)
const flowContextValue = useContext(flowContext)
const { reactFlowInstance } = flowContextValue || {}
// Handler for when input values change within array items
const handleItemInputChange = ({ inputParam: changedParam, newValue }, itemIndex) => {
@@ -70,6 +72,9 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
}, [data, inputParam])
const updateOutputAnchors = (items, type, indexToDelete) => {
// Skip output anchor updates for DocStore context
if (isDocStore || !reactFlowInstance) return
if (data.name !== 'conditionAgentflow' && data.name !== 'conditionAgentAgentflow') return
const updatedOutputs = items.map((_, i) => ({
@@ -221,20 +226,35 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
{/* Render input fields for array item */}
{itemParameters[index]
.filter((param) => param.display !== false)
.map((param, _index) => (
<NodeInputHandler
disabled={disabled}
key={_index}
inputParam={param}
data={itemData}
isAdditionalParams={true}
parentParamForArray={inputParam}
arrayIndex={index}
onCustomDataChange={({ inputParam, newValue }) => {
handleItemInputChange({ inputParam, newValue }, index)
}}
/>
))}
.map((param, _index) => {
if (isDocStore) {
return (
<DocStoreInputHandler
disabled={disabled}
key={_index}
inputParam={param}
data={itemData}
onNodeDataChange={({ inputParam, newValue }) => {
handleItemInputChange({ inputParam, newValue }, index)
}}
/>
)
}
return (
<NodeInputHandler
disabled={disabled}
key={_index}
inputParam={param}
data={itemData}
isAdditionalParams={true}
parentParamForArray={inputParam}
arrayIndex={index}
onCustomDataChange={({ inputParam, newValue }) => {
handleItemInputChange({ inputParam, newValue }, index)
}}
/>
)
})}
</Box>
)
})}
@@ -257,5 +277,6 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
ArrayRenderer.propTypes = {
inputParam: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
disabled: PropTypes.bool
disabled: PropTypes.bool,
isDocStore: PropTypes.bool
}
@@ -17,6 +17,7 @@ import { SwitchInput } from '@/ui-component/switch/Switch'
import { JsonEditorInput } from '@/ui-component/json/JsonEditor'
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
import { ArrayRenderer } from '@/ui-component/array/ArrayRenderer'
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
import CredentialInputHandler from '@/views/canvas/CredentialInputHandler'
@@ -259,6 +260,9 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh
</div>
</>
)}
{inputParam.type === 'array' && (
<ArrayRenderer inputParam={inputParam} data={data} disabled={disabled} isDocStore={true} />
)}
{(data.name === 'cheerioWebScraper' ||
data.name === 'puppeteerWebScraper' ||
data.name === 'playwrightWebScraper') &&