Skip to content

Commit 9efb70e

Browse files
authored
Feature/Safety settings to google genai (#4737)
* add safety settings to google genai * add safety settings to google genai
1 parent c78b532 commit 9efb70e

File tree

5 files changed

+139
-82
lines changed

5 files changed

+139
-82
lines changed

packages/components/nodes/agentflow/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getFileFromStorage } from '../../src/storageUtils'
44
import { ICommonObject, IFileUpload } from '../../src/Interface'
55
import { BaseMessageLike } from '@langchain/core/messages'
66
import { IFlowState } from './Interface.Agentflow'
7-
import { mapMimeTypeToInputField } from '../../src/utils'
7+
import { handleEscapeCharacters, mapMimeTypeToInputField } from '../../src/utils'
88

99
export const addImagesToMessages = async (
1010
options: ICommonObject,
@@ -354,7 +354,7 @@ export const getPastChatHistoryImageMessages = async (
354354
}
355355
}
356356
const documents: string = await fileLoaderNodeInstance.init(nodeData, '', nodeOptions)
357-
messageWithFileUploads += `<doc name='${upload.name}'>${documents}</doc>\n\n`
357+
messageWithFileUploads += `<doc name='${upload.name}'>${handleEscapeCharacters(documents, true)}</doc>\n\n`
358358
}
359359
}
360360
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content

packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { HarmBlockThreshold, HarmCategory } from '@google/generative-ai'
22
import type { SafetySetting } from '@google/generative-ai'
33
import { BaseCache } from '@langchain/core/caches'
44
import { ICommonObject, IMultiModalOption, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
5-
import { convertMultiOptionsToStringArray, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
5+
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
66
import { getModels, MODEL_TYPE } from '../../../src/modelLoader'
77
import { ChatGoogleGenerativeAI } from './FlowiseChatGoogleGenerativeAI'
88
import { GoogleGenerativeAIChatInput } from '@langchain/google-genai'
@@ -22,7 +22,7 @@ class GoogleGenerativeAI_ChatModels implements INode {
2222
constructor() {
2323
this.label = 'ChatGoogleGenerativeAI'
2424
this.name = 'chatGoogleGenerativeAI'
25-
this.version = 3.0
25+
this.version = 3.1
2626
this.type = 'ChatGoogleGenerativeAI'
2727
this.icon = 'GoogleGemini.svg'
2828
this.category = 'Chat Models'
@@ -101,58 +101,75 @@ class GoogleGenerativeAI_ChatModels implements INode {
101101
additionalParams: true
102102
},
103103
{
104-
label: 'Harm Category',
105-
name: 'harmCategory',
106-
type: 'multiOptions',
104+
label: 'Safety Settings',
105+
name: 'safetySettings',
106+
type: 'array',
107107
description:
108-
'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',
109-
options: [
108+
'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',
109+
array: [
110110
{
111-
label: 'Dangerous',
112-
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT
111+
label: 'Harm Category',
112+
name: 'harmCategory',
113+
type: 'options',
114+
options: [
115+
{
116+
label: 'Dangerous',
117+
name: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
118+
description: 'Promotes, facilitates, or encourages harmful acts.'
119+
},
120+
{
121+
label: 'Harassment',
122+
name: HarmCategory.HARM_CATEGORY_HARASSMENT,
123+
description: 'Negative or harmful comments targeting identity and/or protected attributes.'
124+
},
125+
{
126+
label: 'Hate Speech',
127+
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
128+
description: 'Content that is rude, disrespectful, or profane.'
129+
},
130+
{
131+
label: 'Sexually Explicit',
132+
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
133+
description: 'Contains references to sexual acts or other lewd content.'
134+
},
135+
{
136+
label: 'Civic Integrity',
137+
name: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
138+
description: 'Election-related queries.'
139+
}
140+
]
113141
},
114142
{
115-
label: 'Harassment',
116-
name: HarmCategory.HARM_CATEGORY_HARASSMENT
117-
},
118-
{
119-
label: 'Hate Speech',
120-
name: HarmCategory.HARM_CATEGORY_HATE_SPEECH
121-
},
122-
{
123-
label: 'Sexually Explicit',
124-
name: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT
125-
}
126-
],
127-
optional: true,
128-
additionalParams: true
129-
},
130-
{
131-
label: 'Harm Block Threshold',
132-
name: 'harmBlockThreshold',
133-
type: 'multiOptions',
134-
description:
135-
'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',
136-
options: [
137-
{
138-
label: 'Low and Above',
139-
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
140-
},
141-
{
142-
label: 'Medium and Above',
143-
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE
144-
},
145-
{
146-
label: 'None',
147-
name: HarmBlockThreshold.BLOCK_NONE
148-
},
149-
{
150-
label: 'Only High',
151-
name: HarmBlockThreshold.BLOCK_ONLY_HIGH
152-
},
153-
{
154-
label: 'Threshold Unspecified',
155-
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED
143+
label: 'Harm Block Threshold',
144+
name: 'harmBlockThreshold',
145+
type: 'options',
146+
options: [
147+
{
148+
label: 'None',
149+
name: HarmBlockThreshold.BLOCK_NONE,
150+
description: 'Always show regardless of probability of unsafe content'
151+
},
152+
{
153+
label: 'Only High',
154+
name: HarmBlockThreshold.BLOCK_ONLY_HIGH,
155+
description: 'Block when high probability of unsafe content'
156+
},
157+
{
158+
label: 'Medium and Above',
159+
name: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
160+
description: 'Block when medium or high probability of unsafe content'
161+
},
162+
{
163+
label: 'Low and Above',
164+
name: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
165+
description: 'Block when low, medium or high probability of unsafe content'
166+
},
167+
{
168+
label: 'Threshold Unspecified (Default Threshold)',
169+
name: HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED,
170+
description: 'Threshold is unspecified, block using default threshold'
171+
}
172+
]
156173
}
157174
],
158175
optional: true,
@@ -195,8 +212,8 @@ class GoogleGenerativeAI_ChatModels implements INode {
195212
const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string
196213
const topP = nodeData.inputs?.topP as string
197214
const topK = nodeData.inputs?.topK as string
198-
const harmCategory = nodeData.inputs?.harmCategory as string
199-
const harmBlockThreshold = nodeData.inputs?.harmBlockThreshold as string
215+
const _safetySettings = nodeData.inputs?.safetySettings as string
216+
200217
const cache = nodeData.inputs?.cache as BaseCache
201218
const streaming = nodeData.inputs?.streaming as boolean
202219
const baseUrl = nodeData.inputs?.baseUrl as string | undefined
@@ -220,17 +237,32 @@ class GoogleGenerativeAI_ChatModels implements INode {
220237
if (temperature) obj.temperature = parseFloat(temperature)
221238
if (baseUrl) obj.baseUrl = baseUrl
222239

223-
// Safety Settings
224-
let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory)
225-
let harmBlockThresholds: string[] = convertMultiOptionsToStringArray(harmBlockThreshold)
226-
if (harmCategories.length != harmBlockThresholds.length)
227-
throw new Error(`Harm Category & Harm Block Threshold are not the same length`)
228-
const safetySettings: SafetySetting[] = harmCategories.map((harmCategory, index) => {
229-
return {
230-
category: harmCategory as HarmCategory,
231-
threshold: harmBlockThresholds[index] as HarmBlockThreshold
240+
let safetySettings: SafetySetting[] = []
241+
if (_safetySettings) {
242+
try {
243+
const parsedSafetySettings = typeof _safetySettings === 'string' ? JSON.parse(_safetySettings) : _safetySettings
244+
if (Array.isArray(parsedSafetySettings)) {
245+
const validSettings = parsedSafetySettings
246+
.filter((setting: any) => setting.harmCategory && setting.harmBlockThreshold)
247+
.map((setting: any) => ({
248+
category: setting.harmCategory as HarmCategory,
249+
threshold: setting.harmBlockThreshold as HarmBlockThreshold
250+
}))
251+
252+
// Remove duplicates by keeping only the first occurrence of each harm category
253+
const seenCategories = new Set<HarmCategory>()
254+
safetySettings = validSettings.filter((setting) => {
255+
if (seenCategories.has(setting.category)) {
256+
return false
257+
}
258+
seenCategories.add(setting.category)
259+
return true
260+
})
261+
}
262+
} catch (error) {
263+
console.warn('Failed to parse safety settings:', error)
232264
}
233-
})
265+
}
234266
if (safetySettings.length > 0) obj.safetySettings = safetySettings
235267

236268
const multiModalOption: IMultiModalOption = {

packages/components/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = [], orgI
758758
}
759759
}
760760
const documents: string = await fileLoaderNodeInstance.init(nodeData, '', options)
761-
messageWithFileUploads += `<doc name='${upload.name}'>${documents}</doc>\n\n`
761+
messageWithFileUploads += `<doc name='${upload.name}'>${handleEscapeCharacters(documents, true)}</doc>\n\n`
762762
}
763763
}
764764
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content

packages/ui/src/ui-component/array/ArrayRenderer.jsx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import { Chip, Box, Button, IconButton } from '@mui/material'
55
import { useTheme } from '@mui/material/styles'
66
import { IconTrash, IconPlus } from '@tabler/icons-react'
77
import NodeInputHandler from '@/views/canvas/NodeInputHandler'
8+
import DocStoreInputHandler from '@/views/docstore/DocStoreInputHandler'
89
import { showHideInputs } from '@/utils/genericHelper'
910
import { cloneDeep } from 'lodash'
1011
import { flowContext } from '@/store/context/ReactFlowContext'
1112

12-
export const ArrayRenderer = ({ inputParam, data, disabled }) => {
13+
export const ArrayRenderer = ({ inputParam, data, disabled, isDocStore = false }) => {
1314
const [arrayItems, setArrayItems] = useState([]) // these are the actual values. Ex: [{name: 'John', age: 30}, {name: 'Jane', age: 25}]
1415
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}]
1516
const theme = useTheme()
1617
const customization = useSelector((state) => state.customization)
17-
const { reactFlowInstance } = useContext(flowContext)
18+
const flowContextValue = useContext(flowContext)
19+
const { reactFlowInstance } = flowContextValue || {}
1820

1921
// Handler for when input values change within array items
2022
const handleItemInputChange = ({ inputParam: changedParam, newValue }, itemIndex) => {
@@ -70,6 +72,9 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
7072
}, [data, inputParam])
7173

7274
const updateOutputAnchors = (items, type, indexToDelete) => {
75+
// Skip output anchor updates for DocStore context
76+
if (isDocStore || !reactFlowInstance) return
77+
7378
if (data.name !== 'conditionAgentflow' && data.name !== 'conditionAgentAgentflow') return
7479

7580
const updatedOutputs = items.map((_, i) => ({
@@ -221,20 +226,35 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
221226
{/* Render input fields for array item */}
222227
{itemParameters[index]
223228
.filter((param) => param.display !== false)
224-
.map((param, _index) => (
225-
<NodeInputHandler
226-
disabled={disabled}
227-
key={_index}
228-
inputParam={param}
229-
data={itemData}
230-
isAdditionalParams={true}
231-
parentParamForArray={inputParam}
232-
arrayIndex={index}
233-
onCustomDataChange={({ inputParam, newValue }) => {
234-
handleItemInputChange({ inputParam, newValue }, index)
235-
}}
236-
/>
237-
))}
229+
.map((param, _index) => {
230+
if (isDocStore) {
231+
return (
232+
<DocStoreInputHandler
233+
disabled={disabled}
234+
key={_index}
235+
inputParam={param}
236+
data={itemData}
237+
onNodeDataChange={({ inputParam, newValue }) => {
238+
handleItemInputChange({ inputParam, newValue }, index)
239+
}}
240+
/>
241+
)
242+
}
243+
return (
244+
<NodeInputHandler
245+
disabled={disabled}
246+
key={_index}
247+
inputParam={param}
248+
data={itemData}
249+
isAdditionalParams={true}
250+
parentParamForArray={inputParam}
251+
arrayIndex={index}
252+
onCustomDataChange={({ inputParam, newValue }) => {
253+
handleItemInputChange({ inputParam, newValue }, index)
254+
}}
255+
/>
256+
)
257+
})}
238258
</Box>
239259
)
240260
})}
@@ -257,5 +277,6 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
257277
ArrayRenderer.propTypes = {
258278
inputParam: PropTypes.object.isRequired,
259279
data: PropTypes.object.isRequired,
260-
disabled: PropTypes.bool
280+
disabled: PropTypes.bool,
281+
isDocStore: PropTypes.bool
261282
}

packages/ui/src/views/docstore/DocStoreInputHandler.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SwitchInput } from '@/ui-component/switch/Switch'
1717
import { JsonEditorInput } from '@/ui-component/json/JsonEditor'
1818
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
1919
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
20+
import { ArrayRenderer } from '@/ui-component/array/ArrayRenderer'
2021
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
2122
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
2223
import CredentialInputHandler from '@/views/canvas/CredentialInputHandler'
@@ -259,6 +260,9 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh
259260
</div>
260261
</>
261262
)}
263+
{inputParam.type === 'array' && (
264+
<ArrayRenderer inputParam={inputParam} data={data} disabled={disabled} isDocStore={true} />
265+
)}
262266
{(data.name === 'cheerioWebScraper' ||
263267
data.name === 'puppeteerWebScraper' ||
264268
data.name === 'playwrightWebScraper') &&

0 commit comments

Comments
 (0)