Implement structured output for prompt steps (Phase 1)
Add schema validation and retry logic for AI responses:
- Add output_schema, max_retries, plain_text fields to PromptStep
- Add DEFAULT_OUTPUT_SCHEMA with output/reasoning fields
- Implement append_schema_instructions() for prompt augmentation
- Implement validate_schema() using jsonschema library
- Add retry logic with error feedback on validation failure
- Enhance substitute_variables() for nested field access ({var.field})
- Add jsonschema to dependencies
- Update GUI dialog with plain_text checkbox and max_retries spinner
- Add comprehensive tests for structured output and nested access
Tools using plain_text=True bypass validation (current behavior).
Tools without plain_text get structured JSON output with schema enforcement.
Migration Phase 1 complete. Next: Apply plain_text=True to all 72 tools.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -6,7 +6,7 @@ from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||||
QComboBox, QPushButton, QHBoxLayout, QLabel,
|
||||
QPlainTextEdit, QSplitter, QGroupBox, QTextEdit, QMessageBox,
|
||||
QCheckBox
|
||||
QCheckBox, QSpinBox
|
||||
)
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
|
||||
@@ -73,6 +73,31 @@ class PromptStepDialog(QDialog):
|
||||
self.strip_fences_check = QCheckBox("Strip markdown code fences from output")
|
||||
form.addRow("", self.strip_fences_check)
|
||||
|
||||
# Structured output options
|
||||
form.addRow(QLabel("")) # Spacer
|
||||
structured_label = QLabel("<b>Structured Output</b>")
|
||||
form.addRow(structured_label)
|
||||
|
||||
# Plain text checkbox (bypass structured output)
|
||||
self.plain_text_check = QCheckBox("Plain text mode (bypass JSON validation)")
|
||||
self.plain_text_check.setToolTip(
|
||||
"When checked, output is returned as-is without JSON parsing.\n"
|
||||
"When unchecked, output must be valid JSON matching a schema."
|
||||
)
|
||||
self.plain_text_check.stateChanged.connect(self._on_plain_text_changed)
|
||||
form.addRow("", self.plain_text_check)
|
||||
|
||||
# Max retries (only visible when structured output is enabled)
|
||||
self.retries_spin = QSpinBox()
|
||||
self.retries_spin.setRange(0, 5)
|
||||
self.retries_spin.setValue(1)
|
||||
self.retries_spin.setToolTip(
|
||||
"Number of retries if JSON validation fails.\n"
|
||||
"The AI will receive error feedback and try again."
|
||||
)
|
||||
self.retries_label = QLabel("Max retries:")
|
||||
form.addRow(self.retries_label, self.retries_spin)
|
||||
|
||||
layout.addLayout(form)
|
||||
|
||||
# Prompt text
|
||||
@@ -103,6 +128,12 @@ class PromptStepDialog(QDialog):
|
||||
|
||||
layout.addLayout(buttons)
|
||||
|
||||
def _on_plain_text_changed(self, state):
|
||||
"""Show/hide structured output options based on plain_text checkbox."""
|
||||
is_plain = bool(state)
|
||||
self.retries_label.setVisible(not is_plain)
|
||||
self.retries_spin.setVisible(not is_plain)
|
||||
|
||||
def _load_step(self, step: PromptStep):
|
||||
"""Load step data into form."""
|
||||
# Load name
|
||||
@@ -125,6 +156,11 @@ class PromptStepDialog(QDialog):
|
||||
self.prompt_input.setPlainText(step.prompt)
|
||||
self.strip_fences_check.setChecked(step.strip_fences)
|
||||
|
||||
# Structured output fields
|
||||
self.plain_text_check.setChecked(step.plain_text)
|
||||
self.retries_spin.setValue(step.max_retries)
|
||||
self._on_plain_text_changed(step.plain_text)
|
||||
|
||||
def _validate_and_accept(self):
|
||||
"""Validate and accept."""
|
||||
prompt = self.prompt_input.toPlainText().strip()
|
||||
@@ -153,7 +189,9 @@ class PromptStepDialog(QDialog):
|
||||
output_var=self.output_input.text().strip(),
|
||||
profile=profile,
|
||||
name=name,
|
||||
strip_fences=self.strip_fences_check.isChecked()
|
||||
strip_fences=self.strip_fences_check.isChecked(),
|
||||
plain_text=self.plain_text_check.isChecked(),
|
||||
max_retries=self.retries_spin.value()
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user