Skip to content

Commit a11d279

Browse files
rlundeen2Copilot
andauthored
MAINT: Updating backend to use registry to create converters (microsoft#2112)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3f92836 commit a11d279

35 files changed

Lines changed: 624 additions & 618 deletions

frontend/src/components/Chat/ChatWindow.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,9 +2247,9 @@ describe("ChatWindow Integration", () => {
22472247
parameters: [
22482248
{
22492249
name: "encoding_func",
2250-
type_name: "Literal['b64encode', 'urlsafe_b64encode']",
2250+
type_name: "str",
22512251
required: false,
2252-
default_value: "b64encode",
2252+
default: "b64encode",
22532253
choices: ["b64encode", "urlsafe_b64encode"],
22542254
},
22552255
],

frontend/src/components/Chat/ConverterPanel.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const MOCK_CATALOG = {
3737
supported_input_types: ['text'],
3838
supported_output_types: ['text'],
3939
parameters: [
40-
{ name: 'caesar_offset', type_name: 'int', required: true, default_value: null, choices: null, description: 'Offset value.' },
40+
{ name: 'caesar_offset', type_name: 'int', required: true, default: null, choices: null, description: 'Offset value.' },
4141
],
4242
is_llm_based: false,
4343
description: 'Caesar cipher encoding.',
@@ -63,7 +63,7 @@ const MOCK_CATALOG = {
6363
supported_input_types: ['text'],
6464
supported_output_types: ['text'],
6565
parameters: [
66-
{ name: 'uppercase', type_name: 'bool', required: false, default_value: 'false', choices: null, description: 'Use uppercase.' },
66+
{ name: 'uppercase', type_name: 'bool', required: false, default: 'false', choices: null, description: 'Use uppercase.' },
6767
],
6868
is_llm_based: false,
6969
description: 'Bool param test.',
@@ -73,7 +73,7 @@ const MOCK_CATALOG = {
7373
supported_input_types: ['text'],
7474
supported_output_types: ['text'],
7575
parameters: [
76-
{ name: 'mode', type_name: 'str', required: false, default_value: 'fast', choices: ['fast', 'slow'], description: 'Speed mode.' },
76+
{ name: 'mode', type_name: 'str', required: false, default: 'fast', choices: ['fast', 'slow'], description: 'Speed mode.' },
7777
],
7878
is_llm_based: false,
7979
description: 'Choice param test.',
@@ -83,7 +83,7 @@ const MOCK_CATALOG = {
8383
supported_input_types: ['text'],
8484
supported_output_types: ['text'],
8585
parameters: [
86-
{ name: 'template_file_path', type_name: 'str', required: false, default_value: null, choices: null, description: 'Path to template.' },
86+
{ name: 'template_file_path', type_name: 'str', required: false, default: null, choices: null, description: 'Path to template.' },
8787
],
8888
is_llm_based: false,
8989
description: 'File path param test.',
@@ -773,7 +773,7 @@ describe('ConverterPanel edge cases', () => {
773773
expect(screen.getByTestId('converter-tab-custom_type')).toBeInTheDocument()
774774
})
775775

776-
it('handles converters with Optional[bool] param type', async () => {
776+
it('handles optional bool params (rendered as a bool Switch)', async () => {
777777
const catalogWithOptionalBool = {
778778
items: [
779779
...MOCK_CATALOG.items,
@@ -782,7 +782,7 @@ describe('ConverterPanel edge cases', () => {
782782
supported_input_types: ['text'],
783783
supported_output_types: ['text'],
784784
parameters: [
785-
{ name: 'flag', type_name: 'Optional[bool]', required: false, default_value: 'true', choices: null, description: 'Optional bool.' },
785+
{ name: 'flag', type_name: 'bool', required: false, default: 'true', choices: null, description: 'Optional bool.' },
786786
],
787787
is_llm_based: false,
788788
description: 'Optional bool param test.',
@@ -795,7 +795,7 @@ describe('ConverterPanel edge cases', () => {
795795
await waitForList()
796796
await openComboboxAndSelect('OptBoolConverter')
797797

798-
// Should render a Switch (not a text input) for Optional[bool] (line 410-416)
798+
// An Optional[bool] constructor param surfaces as type_name 'bool', so it renders a Switch (not a text input).
799799
const switchEl = screen.getByTestId('param-flag')
800800
expect(switchEl).toBeInTheDocument()
801801
})
@@ -808,7 +808,7 @@ describe('ConverterPanel edge cases', () => {
808808
supported_input_types: ['text'],
809809
supported_output_types: ['text'],
810810
parameters: [
811-
{ name: 'config_file_path', type_name: 'str', required: true, default_value: null, choices: null, description: 'Config file path.' },
811+
{ name: 'config_file_path', type_name: 'str', required: true, default: null, choices: null, description: 'Config file path.' },
812812
],
813813
is_llm_based: true,
814814
description: 'Required file param test.',

frontend/src/components/Chat/ConverterPanel/ConverterPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ export default function ConverterPanel({ onClose, previewText = '', attachmentDa
166166
const newConverter = converters.find((c) => c.converter_type === type)
167167
const defaults: Record<string, string> = {}
168168
for (const p of newConverter?.parameters ?? []) {
169-
if (p.default_value != null) {
170-
defaults[p.name] = p.default_value
169+
if (p.default != null) {
170+
defaults[p.name] = p.default
171171
}
172172
}
173173
setParamValues(defaults)

frontend/src/components/Chat/ConverterPanel/ConverterParams.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Button, Input, Select, Switch, Text, Tooltip } from '@fluentui/react-components'
22
import { ChevronDownRegular, ChevronRightRegular, InfoRegular } from '@fluentui/react-icons'
3-
import type { ConverterCatalogEntry, ConverterParameterSchema } from '../../../types'
3+
import type { ConverterCatalogEntry, Parameter } from '../../../types'
44
import { useConverterPanelStyles } from './ConverterPanel.styles'
55

66
interface ParamInputProps {
7-
param: ConverterParameterSchema
7+
param: Parameter
88
value: string
99
isMissing: boolean
1010
onChange: (name: string, value: string) => void
@@ -13,7 +13,7 @@ interface ParamInputProps {
1313
function ConverterParameterChoiceViewer({ param, value, onChange }: ParamInputProps) {
1414
return (
1515
<Select
16-
value={value ?? param.default_value ?? ''}
16+
value={value ?? param.default ?? ''}
1717
onChange={(_, data) => onChange(param.name, data.value)}
1818
data-testid={`param-${param.name}`}
1919
>
@@ -33,7 +33,7 @@ function ParameterFileViewer({ param, value, isMissing, onChange, onBrowse }: Pa
3333
<div className={styles.filePickerRow}>
3434
<Input
3535
value={value ?? ''}
36-
placeholder={param.default_value ?? 'Select a file...'}
36+
placeholder={param.default ?? 'Select a file...'}
3737
onChange={(_, data) => onChange(param.name, data.value)}
3838
className={isMissing ? styles.paramInputError : undefined}
3939
data-testid={`param-${param.name}`}
@@ -56,7 +56,7 @@ function ConverterParameterViewer({ param, value, isMissing, onChange }: ParamIn
5656
return (
5757
<Input
5858
value={value ?? ''}
59-
placeholder={param.default_value ?? undefined}
59+
placeholder={param.default ?? undefined}
6060
onChange={(_, data) => onChange(param.name, data.value)}
6161
className={isMissing ? styles.paramInputError : undefined}
6262
data-testid={`param-${param.name}`}
@@ -103,11 +103,11 @@ export default function ConverterParams({ converter, paramValues, paramsExpanded
103103
</Tooltip>
104104
)}
105105
</span>
106-
{param.type_name === 'bool' || param.type_name === 'Optional[bool]' ? (
106+
{param.type_name === 'bool' ? (
107107
<Switch
108-
checked={(paramValues[param.name] ?? param.default_value ?? 'false').toLowerCase() === 'true'}
108+
checked={(paramValues[param.name] ?? param.default ?? 'false').toLowerCase() === 'true'}
109109
onChange={(_, data) => onParamChange(param.name, data.checked ? 'true' : 'false')}
110-
label={(paramValues[param.name] ?? param.default_value ?? 'false').toLowerCase() === 'true' ? 'True' : 'False'}
110+
label={(paramValues[param.name] ?? param.default ?? 'false').toLowerCase() === 'true' ? 'True' : 'False'}
111111
data-testid={`param-${param.name}`}
112112
/>
113113
) : param.choices ? (
@@ -120,8 +120,8 @@ export default function ConverterParams({ converter, paramValues, paramsExpanded
120120
{isMissing && (
121121
<Text size={100} className={styles.paramErrorText}>Required</Text>
122122
)}
123-
{param.type_name !== 'bool' && param.type_name !== 'Optional[bool]' && !param.choices && (
124-
<Text size={100} className={styles.hintText}>{param.type_name.replace(/^Optional\[(.+)\]$/, '$1')}</Text>
123+
{param.type_name !== 'bool' && !param.choices && (
124+
<Text size={100} className={styles.hintText}>{param.type_name}</Text>
125125
)}
126126
</div>
127127
)

frontend/src/types/index.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,41 @@ export interface CreateTargetRequest {
100100

101101
// --- Converters ---
102102

103+
export interface ConverterIdentifier {
104+
class_name: string
105+
class_module: string
106+
hash: string
107+
pyrit_version: string
108+
supported_input_types?: string[] | null
109+
supported_output_types?: string[] | null
110+
// Converter-specific constructor params are inlined at the top level.
111+
[key: string]: unknown
112+
}
113+
103114
export interface ConverterInstance {
104115
converter_id: string
105-
converter_type: string
106-
display_name?: string | null
107-
supported_input_types: string[]
108-
supported_output_types: string[]
109-
converter_specific_params?: Record<string, unknown> | null
110-
sub_converter_ids?: string[] | null
116+
identifier: ConverterIdentifier
111117
}
112118

113119
export interface ConverterListResponse {
114120
items: ConverterInstance[]
115121
}
116122

117-
export interface ConverterParameterSchema {
123+
export interface Parameter {
118124
name: string
119125
type_name: string
120126
required: boolean
121-
default_value?: string | null
127+
default?: string | null
122128
choices?: string[] | null
129+
is_list?: boolean
123130
description?: string | null
124131
}
125132

126133
export interface ConverterCatalogEntry {
127134
converter_type: string
128135
supported_input_types: string[]
129136
supported_output_types: string[]
130-
parameters: ConverterParameterSchema[]
137+
parameters: Parameter[]
131138
is_llm_based: boolean
132139
description?: string | null
133140
}

pyrit/backend/mappers/converter_mappers.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,33 @@
44
"""
55
Converter mappers – domain → DTO translation for converter-related models.
66
7-
Identity vs. presentation:
8-
``ConverterIdentifier`` is the typed, lossless
9-
*identity* projection of a converter's ``ComponentIdentifier``;
10-
``ConverterInstance`` is the backend *presentation* view (adds ``converter_id``
11-
binding, ``display_name``, and ``sub_converter_ids``).
7+
``ConverterInstance`` pairs the registry instance id with the converter's
8+
``ConverterIdentifier`` — the typed, lossless identity projection of the
9+
converter's ``ComponentIdentifier`` — which is the single source of truth on the
10+
wire for the converter's class, supported data types, and constructor params.
1211
"""
1312

1413
from pyrit.backend.models import ConverterInstance
1514
from pyrit.models import ConverterIdentifier
1615
from pyrit.prompt_converter import PromptConverter
1716

1817

19-
def converter_object_to_instance(
20-
converter_id: str,
21-
converter_obj: PromptConverter,
22-
*,
23-
sub_converter_ids: list[str] | None = None,
24-
) -> ConverterInstance:
18+
def converter_object_to_instance(converter_id: str, converter_obj: PromptConverter) -> ConverterInstance:
2519
"""
2620
Build a ConverterInstance DTO from a registry converter object.
2721
28-
Extracts only the frontend-relevant fields from the internal identifier,
29-
avoiding leakage of internal PyRIT core structures.
22+
Pairs the registry instance id with the converter's ``ConverterIdentifier``,
23+
the typed identity/configuration projection used as the single source of truth
24+
on the wire.
3025
3126
Args:
3227
converter_id: The unique converter instance identifier.
3328
converter_obj: The domain PromptConverter object from the registry.
34-
sub_converter_ids: Optional list of registered converter IDs for sub-converters.
3529
3630
Returns:
37-
ConverterInstance DTO with metadata derived from the object.
31+
ConverterInstance DTO wrapping the converter's identifier.
3832
"""
39-
converter_identifier = ConverterIdentifier.from_component_identifier(converter_obj.get_identifier())
40-
41-
supported_input = converter_identifier.supported_input_types
42-
supported_output = converter_identifier.supported_output_types
43-
44-
# supported_input/output_types are promoted to typed fields and mirrored into
45-
# params; strip them so only converter-specific params remain.
46-
promoted_param_names = set(ConverterIdentifier._promoted_param_fields())
47-
converter_specific = {k: v for k, v in converter_identifier.params.items() if k not in promoted_param_names} or None
48-
4933
return ConverterInstance(
5034
converter_id=converter_id,
51-
converter_type=converter_identifier.class_name,
52-
display_name=None,
53-
supported_input_types=list(supported_input) if supported_input else [],
54-
supported_output_types=list(supported_output) if supported_output else [],
55-
converter_specific_params=converter_specific,
56-
sub_converter_ids=sub_converter_ids,
35+
identifier=ConverterIdentifier.from_component_identifier(converter_obj.get_identifier()),
5736
)

pyrit/backend/models/converters.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111

1212
from pydantic import BaseModel, Field
1313

14-
from pyrit.models import PromptDataType
14+
from pyrit.models import ConverterIdentifier, Parameter, PromptDataType
1515

1616
__all__ = [
1717
"ConverterCatalogEntry",
1818
"ConverterCatalogResponse",
1919
"ConverterInstance",
2020
"ConverterInstanceListResponse",
21-
"ConverterParameterSchema",
2221
"CreateConverterRequest",
2322
"CreateConverterResponse",
2423
"ConverterPreviewRequest",
@@ -32,17 +31,6 @@
3231
# ============================================================================
3332

3433

35-
class ConverterParameterSchema(BaseModel):
36-
"""Schema for a single converter constructor parameter."""
37-
38-
name: str = Field(..., description="Parameter name")
39-
type_name: str = Field(..., description="Human-readable type (e.g. 'str', 'int', 'Literal[...]')")
40-
required: bool = Field(..., description="Whether the parameter must be provided")
41-
default_value: str | None = Field(None, description="String representation of default value, if any")
42-
choices: list[str] | None = Field(None, description="Allowed values for Literal types")
43-
description: str | None = Field(None, description="Parameter description from docstring")
44-
45-
4634
class ConverterCatalogEntry(BaseModel):
4735
"""A converter type available from the backend registry."""
4836

@@ -53,7 +41,7 @@ class ConverterCatalogEntry(BaseModel):
5341
supported_output_types: list[str] = Field(
5442
default_factory=list, description="Output data types produced by this converter type"
5543
)
56-
parameters: list[ConverterParameterSchema] = Field(
44+
parameters: list[Parameter] = Field(
5745
default_factory=list, description="Constructor parameters for dynamic form generation"
5846
)
5947
is_llm_based: bool = Field(False, description="Whether this converter requires an LLM target")
@@ -72,23 +60,16 @@ class ConverterCatalogResponse(BaseModel):
7260

7361

7462
class ConverterInstance(BaseModel):
75-
"""A registered converter instance."""
63+
"""
64+
A registered converter instance.
65+
66+
Pairs the registry instance id with the converter's ``ConverterIdentifier`` —
67+
the typed identity/configuration projection that is the single source of truth
68+
for the converter's class, supported data types, and constructor params.
69+
"""
7670

7771
converter_id: str = Field(..., description="Unique converter instance identifier")
78-
converter_type: str = Field(..., description="Converter class name (e.g., 'Base64Converter')")
79-
display_name: str | None = Field(None, description="Human-readable display name")
80-
supported_input_types: list[str] = Field(
81-
default_factory=list, description="Input data types supported by this converter"
82-
)
83-
supported_output_types: list[str] = Field(
84-
default_factory=list, description="Output data types produced by this converter"
85-
)
86-
converter_specific_params: dict[str, Any] | None = Field(
87-
None, description="Additional converter-specific parameters"
88-
)
89-
sub_converter_ids: list[str] | None = Field(
90-
None, description="Converter IDs of sub-converters (for pipelines/composites)"
91-
)
72+
identifier: ConverterIdentifier = Field(..., description="The converter's identity/configuration projection")
9273

9374

9475
class ConverterInstanceListResponse(BaseModel):

pyrit/backend/models/initializers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"""
55
REST envelopes for the initializer endpoints.
66
7-
Canonical initializer catalog types (``RegisteredInitializer``,
8-
``InitializerParameterSummary``) live in ``pyrit.models.catalog.initializer``
9-
and should be imported from there directly.
7+
Canonical initializer catalog types (``RegisteredInitializer``) live in
8+
``pyrit.models.catalog.initializer`` and should be imported from there directly.
9+
Initializer parameters are described by the shared ``pyrit.models.Parameter``.
1010
"""
1111

1212
from pydantic import BaseModel, Field

pyrit/backend/models/scenarios.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
REST envelopes for the scenario endpoints.
66
77
Canonical scenario catalog/run types (``RegisteredScenario``,
8-
``ScenarioParameterSummary``, ``ScenarioRunSummary``, ``RunScenarioRequest``)
9-
live in ``pyrit.models.catalog.scenario`` and should be imported from there
10-
directly.
8+
``ScenarioRunSummary``, ``RunScenarioRequest``) live in
9+
``pyrit.models.catalog.scenario`` and should be imported from there directly.
10+
Scenario parameters are described by the shared ``pyrit.models.Parameter``.
1111
"""
1212

1313
from pydantic import BaseModel, Field

0 commit comments

Comments
 (0)