Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,16 @@ routing:
threshold: 0.85
description: Detect jailbreak content in the model's own output.

# Hallucination rules check the model's answer against the grounding
# context the request carried (tool results or retrieved context), so they
# only exist once the model has answered; the selected decision's
# hallucination plugin enforces on them. use_nli asks the detector for
# span-level NLI explanations.
hallucination:
- name: ungrounded_claims
use_nli: true
description: Detect claims the grounding context does not support.

pii:
- name: restricted_pii
threshold: 0.85
Expand Down
13 changes: 13 additions & 0 deletions config/fragments/signal/hallucination/grounded-answer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Hallucination signal.
#
# Checks the model's answer against the grounding context the request carried
# (tool results or retrieved context), so it is only available after the model
# has answered. The hallucination plugin of the decision selected for the
# request enforces on it; decision rules cannot read it. use_nli asks the
# detector for span-level NLI explanations.
routing:
signals:
hallucination:
- name: ungrounded_claims
use_nli: true
description: Detect claims the grounding context does not support.
4 changes: 4 additions & 0 deletions dashboard/frontend/src/components/HeaderDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ const HEADER_INFO: Record<
label: 'Jailbreak Signal',
type: 'danger',
},
'x-vsr-matched-hallucination': {
label: 'Hallucination Signal',
type: 'warning',
},
'x-vsr-matched-pii': {
label: 'PII Signal',
type: 'warning',
Expand Down
1 change: 1 addition & 0 deletions dashboard/frontend/src/components/chatRequestSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const RESPONSE_HEADER_KEYS = [
'x-vsr-matched-modality',
'x-vsr-matched-authz',
'x-vsr-matched-jailbreak',
'x-vsr-matched-hallucination',
'x-vsr-matched-pii',
'x-vsr-matched-kb',
'x-vsr-matched-conversation',
Expand Down
51 changes: 29 additions & 22 deletions dashboard/frontend/src/lib/dslSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@ import { getCapabilityPluginFieldSchema } from './dslCapabilityPluginSchemas'
import type { FieldSchema } from './dslSchemaTypes'
export type { FieldSchema } from './dslSchemaTypes'

const PII_SIGNAL_FIELDS: FieldSchema[] = [
{
key: 'threshold',
label: 'Threshold',
type: 'number',
required: true,
placeholder: '0.8',
description: 'Minimum confidence for PII detection (0.0-1.0)',
},
{
key: 'pii_types_allowed',
label: 'PII Types Allowed',
type: 'string[]',
placeholder: 'e.g. EMAIL_ADDRESS',
description: 'PII types to allow through (others trigger signal)',
},
{
key: 'include_history',
label: 'Include History',
type: 'boolean',
description: 'Include conversation history in detection',
},
{ key: 'description', label: 'Description', type: 'string' },
]

const JAILBREAK_SIGNAL_FIELDS: FieldSchema[] = [
{
key: 'method',
Expand Down Expand Up @@ -310,31 +335,13 @@ export function getSignalFieldSchema(signalType: string): FieldSchema[] {
]
case 'jailbreak':
return JAILBREAK_SIGNAL_FIELDS
case 'pii':
case 'hallucination':
return [
{
key: 'threshold',
label: 'Threshold',
type: 'number',
required: true,
placeholder: '0.8',
description: 'Minimum confidence for PII detection (0.0-1.0)',
},
{
key: 'pii_types_allowed',
label: 'PII Types Allowed',
type: 'string[]',
placeholder: 'e.g. EMAIL_ADDRESS',
description: 'PII types to allow through (others trigger signal)',
},
{
key: 'include_history',
label: 'Include History',
type: 'boolean',
description: 'Include conversation history in detection',
},
{ key: 'use_nli', label: 'Use NLI Explanations', type: 'boolean' },
{ key: 'description', label: 'Description', type: 'string' },
]
case 'pii':
return PII_SIGNAL_FIELDS
case 'kb':
return [
{
Expand Down
155 changes: 78 additions & 77 deletions dashboard/frontend/src/pages/ConfigPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,84 @@ interface ConfigPageProps {

// Removed maskAddress - no longer needed after removing endpoint visibility toggle

function removeSignalByName(cfg: ConfigData, type: SignalType, targetName: string) {
// match by type and name to remove the signal from the config
if (!cfg.signals) cfg.signals = {}

switch (type) {
case 'Keywords':
cfg.signals.keywords = (cfg.signals.keywords || []).filter((s) => s.name !== targetName)
break
case 'Embeddings':
cfg.signals.embeddings = (cfg.signals.embeddings || []).filter((s) => s.name !== targetName)
break
case 'Domain':
cfg.signals.domains = (cfg.signals.domains || []).filter((s) => s.name !== targetName)
break
case 'Preference':
cfg.signals.preferences = (cfg.signals.preferences || []).filter((s) => s.name !== targetName)
break
case 'Fact Check':
cfg.signals.fact_check = (cfg.signals.fact_check || []).filter((s) => s.name !== targetName)
break
case 'User Feedback':
cfg.signals.user_feedbacks = (cfg.signals.user_feedbacks || []).filter(
(s) => s.name !== targetName,
)
break
case 'Reask':
cfg.signals.reasks = (cfg.signals.reasks || []).filter((s) => s.name !== targetName)
break
case 'Language':
cfg.signals.language = (cfg.signals.language || []).filter((s) => s.name !== targetName)
break
case 'Context':
cfg.signals.context = (cfg.signals.context || []).filter((s) => s.name !== targetName)
break
case 'Structure':
cfg.signals.structure = (cfg.signals.structure || []).filter((s) => s.name !== targetName)
break
case 'Complexity':
cfg.signals.complexity = (cfg.signals.complexity || []).filter((s) => s.name !== targetName)
break
case 'Modality':
cfg.signals.modality = (cfg.signals.modality || []).filter((s) => s.name !== targetName)
break
case 'Authz':
cfg.signals.role_bindings = (cfg.signals.role_bindings || []).filter(
(s) => s.name !== targetName,
)
break
case 'Jailbreak':
cfg.signals.jailbreak = (cfg.signals.jailbreak || []).filter((s) => s.name !== targetName)
break
case 'Hallucination':
cfg.signals.hallucination = (cfg.signals.hallucination || []).filter(
(s) => s.name !== targetName,
)
break
case 'PII':
cfg.signals.pii = (cfg.signals.pii || []).filter((s) => s.name !== targetName)
break
case 'KB':
cfg.signals.kb = (cfg.signals.kb || []).filter((s) => s.name !== targetName)
break
case 'Metadata':
cfg.signals.metadata = (cfg.signals.metadata || []).filter((s) => s.name !== targetName)
break
case 'Classifier':
cfg.signals.classifiers = (cfg.signals.classifiers || []).filter((s) => s.name !== targetName)
break
case 'Conversation':
cfg.signals.conversation = (cfg.signals.conversation || []).filter(
(s) => s.name !== targetName,
)
break
default:
break
}
}

const ConfigPage: React.FC<ConfigPageProps> = ({ activeSection = 'global-config' }) => {
const { isReadonly } = useReadonly()
const { user } = useAuth()
Expand Down Expand Up @@ -250,83 +328,6 @@ const ConfigPage: React.FC<ConfigPageProps> = ({ activeSection = 'global-config'
.map((item) => item.trim())
.filter(Boolean)

const removeSignalByName = (cfg: ConfigData, type: SignalType, targetName: string) => {
// match by type and name to remove the signal from the config
if (!cfg.signals) cfg.signals = {}

switch (type) {
case 'Keywords':
cfg.signals.keywords = (cfg.signals.keywords || []).filter((s) => s.name !== targetName)
break
case 'Embeddings':
cfg.signals.embeddings = (cfg.signals.embeddings || []).filter((s) => s.name !== targetName)
break
case 'Domain':
cfg.signals.domains = (cfg.signals.domains || []).filter((s) => s.name !== targetName)
break
case 'Preference':
cfg.signals.preferences = (cfg.signals.preferences || []).filter(
(s) => s.name !== targetName,
)
break
case 'Fact Check':
cfg.signals.fact_check = (cfg.signals.fact_check || []).filter((s) => s.name !== targetName)
break
case 'User Feedback':
cfg.signals.user_feedbacks = (cfg.signals.user_feedbacks || []).filter(
(s) => s.name !== targetName,
)
break
case 'Reask':
cfg.signals.reasks = (cfg.signals.reasks || []).filter((s) => s.name !== targetName)
break
case 'Language':
cfg.signals.language = (cfg.signals.language || []).filter((s) => s.name !== targetName)
break
case 'Context':
cfg.signals.context = (cfg.signals.context || []).filter((s) => s.name !== targetName)
break
case 'Structure':
cfg.signals.structure = (cfg.signals.structure || []).filter((s) => s.name !== targetName)
break
case 'Complexity':
cfg.signals.complexity = (cfg.signals.complexity || []).filter((s) => s.name !== targetName)
break
case 'Modality':
cfg.signals.modality = (cfg.signals.modality || []).filter((s) => s.name !== targetName)
break
case 'Authz':
cfg.signals.role_bindings = (cfg.signals.role_bindings || []).filter(
(s) => s.name !== targetName,
)
break
case 'Jailbreak':
cfg.signals.jailbreak = (cfg.signals.jailbreak || []).filter((s) => s.name !== targetName)
break
case 'PII':
cfg.signals.pii = (cfg.signals.pii || []).filter((s) => s.name !== targetName)
break
case 'KB':
cfg.signals.kb = (cfg.signals.kb || []).filter((s) => s.name !== targetName)
break
case 'Metadata':
cfg.signals.metadata = (cfg.signals.metadata || []).filter((s) => s.name !== targetName)
break
case 'Classifier':
cfg.signals.classifiers = (cfg.signals.classifiers || []).filter(
(s) => s.name !== targetName,
)
break
case 'Conversation':
cfg.signals.conversation = (cfg.signals.conversation || []).filter(
(s) => s.name !== targetName,
)
break
default:
break
}
}

const removeDecisionByName = (cfg: ConfigData, targetName: string) => {
cfg.decisions = (cfg.decisions || []).filter((d) => d.name !== targetName)
}
Expand Down
Loading
Loading