Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b786120
FEAT: Add request-local temperature draft
nina-msft Oct 24, 2026
9cba1d4
Refine runtime temperature capabilities and restore send decorators
nina-msft Oct 27, 2026
e75bf5e
Merge latest main and resolve runtime temperature conflicts
nina-msft Oct 27, 2026
6fffa57
Restructure runtime generation configuration and evidence
nina-msft Oct 27, 2026
b74203e
Generalize request generation plumbing and refresh capability support
nina-msft Oct 28, 2026
028d419
Clarify runtime temperature usage documentation
nina-msft Oct 30, 2026
8472426
Centralize OpenAI model-family sampling rules
nina-msft Oct 31, 2026
f52bb47
Move generation controls to composer and remove legacy override fields
nina-msft Oct 31, 2026
69aaee6
Merge latest main into runtime generation configuration
nina-msft Oct 31, 2026
1c1b7a7
Keep OpenAI sampling rules private to the Chat adapter
nina-msft Nov 2, 2026
fbd9105
Fix runtime generation lifecycle review findings
nina-msft Nov 2, 2026
6454f3d
Merge latest main and resolve chat generation imports
nina-msft Nov 2, 2026
f042c88
Fix Ruff formatting in runtime generation documentation
nina-msft Nov 2, 2026
f6d0bb9
Merge latest main with batched manual message writes
nina-msft Nov 2, 2026
f257bce
Fix HTTP target tests to use real message models
nina-msft Nov 2, 2026
abdcd11
Address backend runtime generation review feedback
nina-msft Nov 3, 2026
99c5d82
Merge latest main and preserve runtime generation settings
nina-msft Nov 3, 2026
424badc
Merge latest main before publishing backend review fixes
nina-msft Nov 3, 2026
13a862d
DOC: Remove GPT-3.5 from runtime temperature table
nina-msft Nov 4, 2026
52606b1
Merge main and preserve generation settings with media drops
nina-msft Nov 4, 2026
53f9f06
Merge branch 'main' into richlundeen-temporary-component-settings
nina-msft Nov 4, 2026
e230da3
FEAT: Place compact temperature control below Send
nina-msft Nov 4, 2026
b712ea0
Merge main and preserve runtime temperature behavior
nina-msft Nov 4, 2026
03e07cf
FIX: Keep fallback temperature resolution local to each target
nina-msft Nov 4, 2026
f5a5357
DOC: Shorten GUI temperature guidance
nina-msft Nov 4, 2026
260b573
FIX: Align temperature tests and capability docs
nina-msft Nov 4, 2026
8252347
FIX: Protect saved generation evidence and capability reporting
nina-msft Nov 5, 2026
a3fb481
Merge branch 'main' into richlundeen-temporary-component-settings
nina-msft Nov 5, 2026
5c3234b
Merge main into runtime temperature work
nina-msft Nov 5, 2026
47f7ed3
FEAT: Share runtime temperature across OpenAI text targets
nina-msft Nov 5, 2026
f18140a
Merge latest main and retain runtime generation identity handling
nina-msft Nov 5, 2026
00bc52f
Merge concurrent PR main update
nina-msft Nov 5, 2026
ab8cad9
Fix temperature delegation, model bounds, and evidence visibility
nina-msft Nov 5, 2026
cab6396
Fix runtime temperature review feedback
nina-msft Nov 6, 2026
01626bb
Clarify and condense runtime temperature documentation
nina-msft Nov 6, 2026
eb030d8
Merge main into runtime temperature branch
nina-msft Nov 6, 2026
47f7098
Narrow runtime temperature fixture type for CI
nina-msft Nov 6, 2026
216c66a
Move runtime temperature bounds out of capabilities
nina-msft Nov 6, 2026
9dbbf15
Simplify temperature documentation and share target mock setup
nina-msft Nov 6, 2026
c8c2775
Trim runtime temperature overview and GUI documentation
nina-msft Nov 6, 2026
e2fcdd7
Merge main and resolve runtime temperature documentation conflict
nina-msft Nov 6, 2026
57d571e
Merge branch 'main' into richlundeen-temporary-component-settings
nina-msft Nov 7, 2026
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: 6 additions & 4 deletions doc/code/registry/0_registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ reconstruction:
}
```

Use `source.params` for overrides; do not also supply `params`. The backend
Use `source.params` to override constructor defaults; do not also supply `params`. The backend
keeps the source's other constructor inputs, including authentication, on the
server. It does not change the source. Missing, changed, or ambiguous sources
produce an error. If a source was renamed, a unique matching hash can resolve it.
An optional `effective_hash` checks the reconstructed object's identity.
Registered descriptors report `reconstructable` and, for targets,
`supports_temperature_override`. Unsupported inputs can also report
`reconstruction_error`.
Converter descriptors expose `reconstructable` and `reconstruction_error`;
target runtime support is in `capabilities`, with bounds in the separate
`temperature_range` field, independent of reconstruction. Use
[runtime generation configuration](../targets/0_prompt_targets.md#runtime-generation-configuration)
for per-request overrides instead of rebuilding a target.
Constructor inputs that are not in the registry's build contract are not
discarded silently: reconstruction is rejected. Such sources can still be used
as registered objects.
Expand Down
29 changes: 29 additions & 0 deletions doc/code/targets/0_prompt_targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ Target-side rate limiting remains independent and continues to use `limit_reques
`_send_prompt_to_target_async(*, normalized_conversation: list[Message]) -> list[Message]` instead of
overriding `send_prompt_async`.

## Runtime generation configuration

Use `RequestGenerationConfig` in a current request's `MessagePiece.prompt_metadata`
to override temperature without changing shared target defaults:

```python
from pyrit.models import MessagePiece, RequestGenerationConfig

piece = MessagePiece(
role="user",
original_value="Describe this scene.",
prompt_metadata=RequestGenerationConfig(temperature=0).to_metadata(),
)
```

Check `target.capabilities.supports_temperature` and `target.temperature_range`
before choosing a value. Bounds are inclusive; `None` means unspecified bounds,
not lack of support. Missing or null temperature preserves existing defaults.
Invalid, conflicting, or unsupported overrides fail before provider I/O.

Read saved request and response evidence with
`RequestGenerationInfo.from_metadata(metadata=piece.prompt_metadata)` to inspect the
requested temperature, resolved value, and source. Unknown provider defaults remain
`None`. See [Chat temperature](../../gui/0_gui.md#temperature) for the UI.

## Chat-style targets vs general targets

A `PromptTarget` is a generic place to send a prompt. With PyRIT, the idea is that it will eventually be consumed by an AI application, but that doesn't have to be immediate. For example, you could have a SharePoint target. Everything you send a prompt to is a `PromptTarget`. Many attacks work generically with any `PromptTarget` including `RedTeamingAttack` and `PromptSendingAttack`.
Expand Down Expand Up @@ -249,12 +274,16 @@ A `TargetConfiguration` composes three concerns:
| `supports_system_prompt` | The target natively supports a system-role message. |
| `supports_json_output` | The target supports a "json" response format that guarantees valid JSON output. |
| `supports_json_schema` | The target supports constraining output to a caller-provided JSON schema. |
| `supports_temperature` | The adapter and configured model mode support per-request temperature overrides. |
| `input_modalities` | The set of input modality combinations the target accepts (e.g. `{text}`, `{text, image_path}`). |
| `output_modalities` | The set of output modality combinations the target produces. |

Each target class defines defaults; instances can override individual capabilities when they depend on deployment configuration (e.g. `HTTPTarget`, `PlaywrightTarget`).

For well-known underlying models, you can look up a profile with `get_known_capabilities(underlying_model="gpt-4o")` from `pyrit.prompt_target`.
Profiles alone do not determine runtime temperature support; check the instance's
capabilities and separate `temperature_range` property as described under
[Runtime generation configuration](#runtime-generation-configuration).

Tool-call history uses the existing `function_call` and `function_call_output` input
modalities. These describe acceptance of prior calls and results, not tool execution.
Expand Down
6 changes: 5 additions & 1 deletion doc/code/targets/6_1_target_capabilities.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@
"`get_default_configuration(underlying_model=...)` returns a richer profile from\n",
"`get_known_capabilities` — for example, `gpt-5` gains `supports_json_schema=True`\n",
"and other models pick up the right modality combinations automatically. Unknown models fall back to\n",
"the class default."
"the class default.\n",
"\n",
"Runtime temperature support depends on the configured target instance, not just its model profile.\n",
"Check `target.capabilities.supports_temperature`; see\n",
"[Runtime generation configuration](./0_prompt_targets.md#runtime-generation-configuration) for usage and bounds."
]
},
{
Expand Down
4 changes: 4 additions & 0 deletions doc/code/targets/6_1_target_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
# `get_known_capabilities` — for example, `gpt-5` gains `supports_json_schema=True`
# and other models pick up the right modality combinations automatically. Unknown models fall back to
# the class default.
#
# Runtime temperature support depends on the configured target instance, not just its model profile.
# Check `target.capabilities.supports_temperature`; see
# [Runtime generation configuration](./0_prompt_targets.md#runtime-generation-configuration) for usage and bounds.

# %%
class_default = OpenAIChatTarget._DEFAULT_CONFIGURATION.capabilities
Expand Down
13 changes: 11 additions & 2 deletions doc/gui/0_gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ corresponding Chat route while retaining query parameters and fragments.

![Chat workspace with a prompt and target response](images/chat.png)

### Temperature

On supported targets, use the thermometer below **Send** to adjust temperature
for subsequent messages. **Use target default** clears the override; shared
defaults and saved messages are unchanged. Select a message's temperature footer
to inspect its saved settings.

See [Runtime generation configuration](../code/targets/0_prompt_targets.md#runtime-generation-configuration)
for SDK usage.

## Scanner

Scanner has **Launch** (`/scanner/launch`) and **History** (`/scanner/history`) tabs.
Expand Down Expand Up @@ -232,8 +242,7 @@ the original launch configuration is available. It skips completed objectives
with non-error outcomes, including `failure`. Retrying an unsuccessful technique
is a separate setting in `technique.execute`.

Compound-attack hierarchy browsing is a separate GUI follow-up. The
[backend hierarchy API](../../pyrit/backend/README.md#scenario-result-hierarchy)
The [backend hierarchy API](../../pyrit/backend/README.md#scenario-result-hierarchy)
provides paged roots and ordered child expansion without clipping run statistics.
Conversation reads expose main and pruned conversations only; adversarial and
preparation contexts remain internal. Transcript bodies load only for a selected
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/App.labels.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ describe('Shared new run labels', () => {
it('hosts Chat controls below the labels and above the objective, and removes them when navigating away', async () => {
const user = userEvent.setup()
jest.mocked(targetsApi.listTargets).mockResolvedValue({
items: [{ ...TARGET, supports_temperature_override: true }],
items: [{ ...TARGET, capabilities: { ...TARGET.capabilities, supports_temperature: true } }],
pagination: { limit: 200, has_more: false },
})
renderApp('/attack/chat')
Expand All @@ -366,9 +366,13 @@ describe('Shared new run labels', () => {
await waitFor(() => expect(targetPicker).toBeEnabled())
await user.selectOptions(targetPicker, 'test_target')
expect(targetPicker).toHaveValue('test_target')
const temperature = within(settings).getByRole('spinbutton', { name: 'Temperature' })
expect(within(settings).queryByRole('spinbutton', { name: 'Temperature' })).not.toBeInTheDocument()
const generation = screen.getByRole('button', { name: /^Adjust temperature/ })
await user.click(generation)
const temperature = screen.getByRole('textbox', { name: 'Temperature' })
await user.type(temperature, '0.7')
expect(temperature).toHaveValue(0.7)
expect(temperature).toHaveValue('0.7')
await user.click(generation)
expect(within(settings).getByRole('button', { name: 'Edit Conversation' })).toBeEnabled()
expect(within(screen.getByTestId('chat-area')).queryByRole('group', { name: 'Chat controls' }))
.not.toBeInTheDocument()
Expand All @@ -382,12 +386,15 @@ describe('Shared new run labels', () => {

await user.click(screen.getByRole('button', { name: 'Home', exact: true }))
expect(screen.queryByRole('group', { name: 'Chat controls' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: /^Adjust temperature/ })).not.toBeInTheDocument()
expect(screen.getAllByTestId('labels-bar')).toHaveLength(1)
await user.click(screen.getByRole('button', { name: 'Scanner', exact: true }))
expect(screen.queryByRole('group', { name: 'Chat controls' })).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Attacks', exact: true }))
expect(screen.getAllByRole('group', { name: 'Chat controls' })).toHaveLength(1)
expect(within(screen.getByRole('group', { name: 'Conversation actions' })).getByRole('switch')).toBeChecked()
await user.click(screen.getByRole('button', { name: /^Adjust temperature/ }))
expect(await screen.findByRole('textbox', { name: 'Temperature' })).toHaveValue('0.7')
})

it('preserves an edit made before the backend defaults arrive', async () => {
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/components/Chat/ChatInputArea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ describe("ChatInputArea", () => {
expect(onToggleConverterPanel).toHaveBeenCalledTimes(1);
});

it("should place the compact temperature control immediately after Send without losing converter or Undo actions", async () => {
const user = userEvent.setup();
const onUndo = jest.fn();
render(<TestWrapper><ChatInputArea {...defaultProps} initialText="Hello"
activeTarget={makeTarget({ target_registry_name: "temperature-target", capabilities: buildCapabilities({ supports_temperature: true }) })}
generationDraft={{ temperature: "0" }} onGenerationDraftChange={jest.fn()}
undoLastSend={{ isUndoing: false, onUndo }} convertedValue="Converted"
/></TestWrapper>);
const prompt = screen.getByTestId("prompt-editor");
const send = within(prompt).getByRole("button", { name: "Send message" });
const temperature = within(prompt).getByRole("button", { name: "Adjust temperature: 0" });
const clearConversion = within(prompt).getByRole("button", { name: "Clear conversion" });
expect(send.compareDocumentPosition(temperature) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(temperature.compareDocumentPosition(clearConversion) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
act(() => send.focus());
await user.tab();
expect(temperature).toHaveFocus();
expect(temperature).toHaveTextContent("0");
await user.click(within(prompt).getByRole("button", { name: "Undo last send" }));
expect(onUndo).toHaveBeenCalledTimes(1);
await user.click(within(prompt).getByRole("button", { name: "Toggle converter panel" }));
expect(defaultProps.onToggleConverterPanel).toHaveBeenCalledTimes(1);
});

it("should place Undo directly after the converter button inside the prompt box without opening the panel", async () => {
const user = userEvent.setup();
const onUndo = jest.fn();
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/components/Chat/ChatInputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { useState, useCallback, useEffect, useLayoutEffect, useRef, forwardRef,
import { Button, Caption1, Popover, PopoverSurface, PopoverTrigger, Tooltip, Text } from '@fluentui/react-components'
import { SendRegular, DismissRegular, InfoRegular, CopyRegular, WarningRegular, SettingsRegular, ArrowSyncRegular } from '@fluentui/react-icons'

import type { AttackTargetResolutionStatus, ChatSendOutcome, ConvertedFileChip, MessageAttachment, MultiSendOptions, PieceConversion, TargetInstance, UndoLastSendAction } from '@/types'
import type { AttackTargetResolutionStatus, ChatSendOutcome, ConvertedFileChip, MessageAttachment, MultiSendOptions, PieceConversion, RequestGenerationDraft, TargetInstance, UndoLastSendAction } from '@/types'
import { isTargetResolutionBlocking } from '@/utils/targetIdentity'

import { usePromptEditorStyles } from './PromptEditor.styles'
import PromptEditor from './PromptEditor'
import SystemPromptSetup from './SystemPromptSetup'
import MultiSendSettings from './MultiSendSettings'
import GenerationSettings from './GenerationSettings'
import { attachmentsFromFiles, PIECE_TYPE_TO_DATA_TYPE, withDraftIdentity } from './converterTypes'

const DISABLED_WARNING_HOVER_DELAY_MS = 700
Expand Down Expand Up @@ -107,6 +108,8 @@ export interface ChatInputAreaHandle {
}

interface ChatInputAreaProps {
generationDraft?: RequestGenerationDraft
onGenerationDraftChange?: (value: RequestGenerationDraft) => void
undoLastSend?: UndoLastSendAction
repeatDisabledReason?: string
onSend: (
Expand Down Expand Up @@ -160,7 +163,7 @@ const ChatInputArea = forwardRef<ChatInputAreaHandle, ChatInputAreaProps>(functi
convertedValue, onClearConversion, onClearAllConversions, onConvertedValueChange,
converterOutputDataTypes = [], mediaConversions = [], onClearMediaConversion, convertedFileChip,
onClearConvertedFileChip, showSystemPrompt = false, supportsSystemPrompt = false, systemPrompt = '', onSystemPromptChange,
repeatDisabledReason, undoLastSend,
repeatDisabledReason, undoLastSend, generationDraft = {}, onGenerationDraftChange,
initialText = '', initialAttachments = [], initialTextMetadata,
}, ref) {
const styles = usePromptEditorStyles()
Expand Down Expand Up @@ -325,6 +328,8 @@ const ChatInputArea = forwardRef<ChatInputAreaHandle, ChatInputAreaProps>(functi
<Button className={styles.sendButton} appearance="primary" icon={<SendRegular />}
onClick={() => { void handleSend() }} disabledFocusable={!canSend} aria-label="Send message" data-testid="send-message-btn" />
</Tooltip>
{onGenerationDraftChange && <GenerationSettings value={generationDraft}
onChange={onGenerationDraftChange} target={activeTarget} disabled={disabled} />}
{convertedValue != null && <Tooltip content="Clear conversion" relationship="label">
<Button appearance="subtle" className={styles.clearConversionButton} icon={<DismissRegular />}
onClick={onClearConversion} data-testid="clear-conversion-btn" />
Expand Down
10 changes: 0 additions & 10 deletions frontend/src/components/Chat/ChatWindow.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,6 @@ export const useChatWindowStyles = makeStyles({
alignItems: 'center',
gap: tokens.spacingHorizontalXS,
},
temperatureField: {
gridTemplateColumns: 'max-content 5rem',
columnGap: tokens.spacingHorizontalNone,
alignItems: 'center',
flexShrink: 0,
},
temperatureInput: {
width: '5rem',
minWidth: 0,
},
sharedTarget: {
maxWidth: '240px',
[NARROW_VIEWPORT_QUERY]: {
Expand Down
Loading
Loading