Add AI persona profiles for prompt injection
Profiles allow users to inject system instructions into prompts, customizing the AI's behavior and persona for tool execution. Features: - Profile dataclass with name, description, system_prompt - 8 built-in profiles: None, Comedian, Technical Writer, Teacher, Concise, Creative, Code Reviewer, Analyst - Custom profile creation and storage in ~/.cmdforge/profiles/ - Profile selector in Prompt Step dialog - Profile injection during tool execution - Profiles page in GUI (Ctrl+4) for viewing and managing profiles Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9d56f703cd
commit
9588c3f8b0
|
|
@ -5,9 +5,11 @@ from PySide6.QtWidgets import (
|
|||
QComboBox, QPushButton, QHBoxLayout, QLabel,
|
||||
QPlainTextEdit
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from ...tool import PromptStep, CodeStep
|
||||
from ...providers import load_providers
|
||||
from ...profiles import list_profiles
|
||||
|
||||
|
||||
class PromptStepDialog(QDialog):
|
||||
|
|
@ -43,6 +45,16 @@ class PromptStepDialog(QDialog):
|
|||
self.provider_combo.addItem(default)
|
||||
form.addRow("Provider:", self.provider_combo)
|
||||
|
||||
# Profile selection
|
||||
self.profile_combo = QComboBox()
|
||||
profiles = list_profiles()
|
||||
for profile in profiles:
|
||||
self.profile_combo.addItem(profile.name)
|
||||
# Set tooltip with description
|
||||
idx = self.profile_combo.count() - 1
|
||||
self.profile_combo.setItemData(idx, profile.description, Qt.ToolTipRole)
|
||||
form.addRow("Profile:", self.profile_combo)
|
||||
|
||||
# Output variable
|
||||
self.output_input = QLineEdit()
|
||||
self.output_input.setPlaceholderText("response")
|
||||
|
|
@ -87,6 +99,12 @@ class PromptStepDialog(QDialog):
|
|||
else:
|
||||
self.provider_combo.setCurrentText(step.provider)
|
||||
|
||||
# Load profile
|
||||
if step.profile:
|
||||
idx = self.profile_combo.findText(step.profile)
|
||||
if idx >= 0:
|
||||
self.profile_combo.setCurrentIndex(idx)
|
||||
|
||||
self.output_input.setText(step.output_var)
|
||||
self.prompt_input.setPlainText(step.prompt)
|
||||
|
||||
|
|
@ -106,10 +124,15 @@ class PromptStepDialog(QDialog):
|
|||
|
||||
def get_step(self) -> PromptStep:
|
||||
"""Get the step from form data."""
|
||||
profile = self.profile_combo.currentText()
|
||||
# Don't store "None" profile
|
||||
if profile == "None":
|
||||
profile = None
|
||||
return PromptStep(
|
||||
prompt=self.prompt_input.toPlainText(),
|
||||
provider=self.provider_combo.currentText(),
|
||||
output_var=self.output_input.text().strip()
|
||||
output_var=self.output_input.text().strip(),
|
||||
profile=profile
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ class MainWindow(QMainWindow):
|
|||
("Tools", "Manage your tools"),
|
||||
("Registry", "Browse and install tools"),
|
||||
("Providers", "Configure AI providers"),
|
||||
("Profiles", "AI persona profiles"),
|
||||
]
|
||||
|
||||
font = QFont()
|
||||
|
|
@ -92,14 +93,17 @@ class MainWindow(QMainWindow):
|
|||
from .pages.tools_page import ToolsPage
|
||||
from .pages.registry_page import RegistryPage
|
||||
from .pages.providers_page import ProvidersPage
|
||||
from .pages.profiles_page import ProfilesPage
|
||||
|
||||
self.tools_page = ToolsPage(self)
|
||||
self.registry_page = RegistryPage(self)
|
||||
self.providers_page = ProvidersPage(self)
|
||||
self.profiles_page = ProfilesPage(self)
|
||||
|
||||
self.pages.addWidget(self.tools_page)
|
||||
self.pages.addWidget(self.registry_page)
|
||||
self.pages.addWidget(self.providers_page)
|
||||
self.pages.addWidget(self.profiles_page)
|
||||
|
||||
def _on_page_changed(self, index: int):
|
||||
"""Handle page changes."""
|
||||
|
|
@ -120,6 +124,7 @@ class MainWindow(QMainWindow):
|
|||
"tools": 0,
|
||||
"registry": 1,
|
||||
"providers": 2,
|
||||
"profiles": 3,
|
||||
}
|
||||
if page_name.lower() in page_map:
|
||||
self.sidebar.setCurrentRow(page_map[page_name.lower()])
|
||||
|
|
@ -164,7 +169,7 @@ class MainWindow(QMainWindow):
|
|||
shortcut_escape = QShortcut(QKeySequence("Escape"), self)
|
||||
shortcut_escape.activated.connect(self._shortcut_escape)
|
||||
|
||||
# Ctrl+1/2/3: Navigate pages
|
||||
# Ctrl+1/2/3/4: Navigate pages
|
||||
shortcut_page1 = QShortcut(QKeySequence("Ctrl+1"), self)
|
||||
shortcut_page1.activated.connect(lambda: self.sidebar.setCurrentRow(0))
|
||||
|
||||
|
|
@ -174,6 +179,9 @@ class MainWindow(QMainWindow):
|
|||
shortcut_page3 = QShortcut(QKeySequence("Ctrl+3"), self)
|
||||
shortcut_page3.activated.connect(lambda: self.sidebar.setCurrentRow(2))
|
||||
|
||||
shortcut_page4 = QShortcut(QKeySequence("Ctrl+4"), self)
|
||||
shortcut_page4.activated.connect(lambda: self.sidebar.setCurrentRow(3))
|
||||
|
||||
# Ctrl+R: Refresh current page
|
||||
shortcut_refresh = QShortcut(QKeySequence("Ctrl+R"), self)
|
||||
shortcut_refresh.activated.connect(self._shortcut_refresh)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
"""Profiles page - manage AI persona profiles."""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem,
|
||||
QPushButton, QLabel, QGroupBox, QTextEdit, QLineEdit,
|
||||
QFormLayout, QMessageBox, QSplitter
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from ...profiles import list_profiles, save_profile, delete_profile, Profile
|
||||
|
||||
|
||||
class ProfilesPage(QWidget):
|
||||
"""Profiles management page."""
|
||||
|
||||
def __init__(self, main_window):
|
||||
super().__init__()
|
||||
self.main_window = main_window
|
||||
self._setup_ui()
|
||||
self.refresh()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Set up the UI."""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(24, 24, 24, 24)
|
||||
layout.setSpacing(16)
|
||||
|
||||
# Header
|
||||
header = QHBoxLayout()
|
||||
title = QLabel("Profiles")
|
||||
title.setObjectName("heading")
|
||||
header.addWidget(title)
|
||||
|
||||
header.addStretch()
|
||||
|
||||
self.btn_new = QPushButton("New Profile")
|
||||
self.btn_new.clicked.connect(self._new_profile)
|
||||
header.addWidget(self.btn_new)
|
||||
|
||||
layout.addLayout(header)
|
||||
|
||||
# Description
|
||||
desc = QLabel(
|
||||
"AI persona profiles inject system instructions into prompts. "
|
||||
"Select a profile when creating a prompt step to customize the AI's behavior."
|
||||
)
|
||||
desc.setWordWrap(True)
|
||||
desc.setStyleSheet("color: #718096;")
|
||||
layout.addWidget(desc)
|
||||
|
||||
# Main content splitter
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# Left: Profile list
|
||||
left = QWidget()
|
||||
left_layout = QVBoxLayout(left)
|
||||
left_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.profile_list = QListWidget()
|
||||
self.profile_list.currentItemChanged.connect(self._on_profile_selected)
|
||||
left_layout.addWidget(self.profile_list)
|
||||
|
||||
splitter.addWidget(left)
|
||||
|
||||
# Right: Profile details/editor
|
||||
right = QWidget()
|
||||
right_layout = QVBoxLayout(right)
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Details group
|
||||
details_box = QGroupBox("Profile Details")
|
||||
details_layout = QFormLayout(details_box)
|
||||
details_layout.setSpacing(12)
|
||||
|
||||
self.name_input = QLineEdit()
|
||||
self.name_input.setPlaceholderText("Profile name")
|
||||
details_layout.addRow("Name:", self.name_input)
|
||||
|
||||
self.desc_input = QLineEdit()
|
||||
self.desc_input.setPlaceholderText("Brief description")
|
||||
details_layout.addRow("Description:", self.desc_input)
|
||||
|
||||
right_layout.addWidget(details_box)
|
||||
|
||||
# System prompt group
|
||||
prompt_box = QGroupBox("System Prompt")
|
||||
prompt_layout = QVBoxLayout(prompt_box)
|
||||
|
||||
self.prompt_input = QTextEdit()
|
||||
self.prompt_input.setPlaceholderText(
|
||||
"Enter the system instructions that will be injected into prompts.\n\n"
|
||||
"Example: You are a helpful assistant who explains things clearly..."
|
||||
)
|
||||
prompt_layout.addWidget(self.prompt_input)
|
||||
|
||||
right_layout.addWidget(prompt_box, 1)
|
||||
|
||||
# Action buttons
|
||||
actions = QHBoxLayout()
|
||||
|
||||
self.btn_save = QPushButton("Save")
|
||||
self.btn_save.clicked.connect(self._save_profile)
|
||||
self.btn_save.setEnabled(False)
|
||||
actions.addWidget(self.btn_save)
|
||||
|
||||
self.btn_delete = QPushButton("Delete")
|
||||
self.btn_delete.setObjectName("danger")
|
||||
self.btn_delete.clicked.connect(self._delete_profile)
|
||||
self.btn_delete.setEnabled(False)
|
||||
actions.addWidget(self.btn_delete)
|
||||
|
||||
actions.addStretch()
|
||||
|
||||
right_layout.addLayout(actions)
|
||||
|
||||
splitter.addWidget(right)
|
||||
splitter.setSizes([300, 500])
|
||||
|
||||
layout.addWidget(splitter, 1)
|
||||
|
||||
# Status label
|
||||
self.status_label = QLabel("")
|
||||
self.status_label.setStyleSheet("color: #718096; font-style: italic;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the profile list."""
|
||||
self.profile_list.clear()
|
||||
profiles = list_profiles()
|
||||
|
||||
for profile in profiles:
|
||||
item = QListWidgetItem()
|
||||
if profile.builtin:
|
||||
item.setText(f"{profile.name} (built-in)")
|
||||
else:
|
||||
item.setText(profile.name)
|
||||
item.setData(Qt.UserRole, profile)
|
||||
self.profile_list.addItem(item)
|
||||
|
||||
self._clear_form()
|
||||
self.status_label.setText(f"{len(profiles)} profiles available")
|
||||
|
||||
def _on_profile_selected(self, current, previous):
|
||||
"""Handle profile selection."""
|
||||
if not current:
|
||||
self._clear_form()
|
||||
return
|
||||
|
||||
profile = current.data(Qt.UserRole)
|
||||
self._load_profile(profile)
|
||||
|
||||
def _load_profile(self, profile: Profile):
|
||||
"""Load profile into form."""
|
||||
self.name_input.setText(profile.name)
|
||||
self.desc_input.setText(profile.description)
|
||||
self.prompt_input.setPlainText(profile.system_prompt)
|
||||
|
||||
# Built-in profiles can't be edited
|
||||
is_builtin = profile.builtin
|
||||
self.name_input.setEnabled(not is_builtin)
|
||||
self.desc_input.setEnabled(not is_builtin)
|
||||
self.prompt_input.setEnabled(not is_builtin)
|
||||
self.btn_save.setEnabled(not is_builtin)
|
||||
self.btn_delete.setEnabled(not is_builtin and profile.name != "None")
|
||||
|
||||
if is_builtin:
|
||||
self.status_label.setText("Built-in profile (read-only)")
|
||||
else:
|
||||
self.status_label.setText("Custom profile")
|
||||
|
||||
def _clear_form(self):
|
||||
"""Clear the form."""
|
||||
self.name_input.clear()
|
||||
self.name_input.setEnabled(True)
|
||||
self.desc_input.clear()
|
||||
self.desc_input.setEnabled(True)
|
||||
self.prompt_input.clear()
|
||||
self.prompt_input.setEnabled(True)
|
||||
self.btn_save.setEnabled(False)
|
||||
self.btn_delete.setEnabled(False)
|
||||
|
||||
def _new_profile(self):
|
||||
"""Start creating a new profile."""
|
||||
self.profile_list.clearSelection()
|
||||
self._clear_form()
|
||||
self.name_input.setFocus()
|
||||
self.btn_save.setEnabled(True)
|
||||
self.status_label.setText("Creating new profile...")
|
||||
|
||||
def _save_profile(self):
|
||||
"""Save the current profile."""
|
||||
name = self.name_input.text().strip()
|
||||
if not name:
|
||||
QMessageBox.warning(self, "Validation", "Profile name is required")
|
||||
return
|
||||
|
||||
if name == "None":
|
||||
QMessageBox.warning(self, "Validation", "'None' is a reserved profile name")
|
||||
return
|
||||
|
||||
description = self.desc_input.text().strip()
|
||||
system_prompt = self.prompt_input.toPlainText().strip()
|
||||
|
||||
if not system_prompt:
|
||||
QMessageBox.warning(self, "Validation", "System prompt is required")
|
||||
return
|
||||
|
||||
profile = Profile(
|
||||
name=name,
|
||||
description=description,
|
||||
system_prompt=system_prompt
|
||||
)
|
||||
|
||||
try:
|
||||
save_profile(profile)
|
||||
self.main_window.show_status(f"Saved profile '{name}'")
|
||||
self.refresh()
|
||||
# Select the saved profile
|
||||
for i in range(self.profile_list.count()):
|
||||
item = self.profile_list.item(i)
|
||||
if item.data(Qt.UserRole).name == name:
|
||||
self.profile_list.setCurrentItem(item)
|
||||
break
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to save profile:\n{e}")
|
||||
|
||||
def _delete_profile(self):
|
||||
"""Delete the selected profile."""
|
||||
current = self.profile_list.currentItem()
|
||||
if not current:
|
||||
return
|
||||
|
||||
profile = current.data(Qt.UserRole)
|
||||
if profile.builtin:
|
||||
QMessageBox.warning(self, "Error", "Cannot delete built-in profiles")
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self, "Confirm Delete",
|
||||
f"Delete profile '{profile.name}'?\n\nThis cannot be undone.",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
if delete_profile(profile.name):
|
||||
self.main_window.show_status(f"Deleted profile '{profile.name}'")
|
||||
self.refresh()
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", "Failed to delete profile")
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
"""AI persona profiles for prompt injection."""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, List
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from .config import get_config_dir
|
||||
|
||||
|
||||
@dataclass
|
||||
class Profile:
|
||||
"""An AI persona profile with system instructions."""
|
||||
name: str
|
||||
description: str = ""
|
||||
system_prompt: str = ""
|
||||
tags: List[str] = field(default_factory=list)
|
||||
builtin: bool = False # True for default profiles
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"system_prompt": self.system_prompt,
|
||||
}
|
||||
if self.tags:
|
||||
d["tags"] = self.tags
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict, builtin: bool = False) -> "Profile":
|
||||
return cls(
|
||||
name=data.get("name", ""),
|
||||
description=data.get("description", ""),
|
||||
system_prompt=data.get("system_prompt", ""),
|
||||
tags=data.get("tags", []),
|
||||
builtin=builtin
|
||||
)
|
||||
|
||||
|
||||
# Built-in default profiles
|
||||
DEFAULT_PROFILES = [
|
||||
Profile(
|
||||
name="None",
|
||||
description="No profile - use prompt as-is",
|
||||
system_prompt="",
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Comedian",
|
||||
description="Witty and humorous, but stays informative",
|
||||
system_prompt="""You are a witty and humorous assistant who uses humor to make information more engaging and memorable. You're able to be serious when it matters, but you naturally inject levity and clever observations into your responses. You are an expert at explaining things clearly while keeping the reader entertained.""",
|
||||
tags=["creative", "engaging"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Technical Writer",
|
||||
description="Precise, structured, uses proper terminology",
|
||||
system_prompt="""You are a professional technical writer who prioritizes clarity, precision, and proper structure. You use correct terminology, organize information logically with headers and bullet points when appropriate, and ensure accuracy in all technical details. You write documentation that is both comprehensive and accessible.""",
|
||||
tags=["technical", "documentation"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Teacher",
|
||||
description="Patient, explains step-by-step, uses analogies",
|
||||
system_prompt="""You are a patient and experienced teacher who excels at breaking down complex topics into understandable parts. You explain concepts step-by-step, use relatable analogies and examples, and check for understanding. You adapt your explanations to the learner's level and never make them feel bad for not knowing something.""",
|
||||
tags=["educational", "patient"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Concise",
|
||||
description="Brief responses, bullet points, no fluff",
|
||||
system_prompt="""You are a direct and efficient communicator who values brevity. You get straight to the point, use bullet points and short sentences, and avoid unnecessary filler words or lengthy explanations. Every word you write serves a purpose. You provide complete information in the most compact form possible.""",
|
||||
tags=["brief", "efficient"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Creative",
|
||||
description="Imaginative, thinks outside the box",
|
||||
system_prompt="""You are a highly creative thinker who approaches problems from unexpected angles. You generate novel ideas, make unique connections between concepts, and aren't afraid to suggest unconventional solutions. You balance creativity with practicality, ensuring your ideas are both innovative and actionable.""",
|
||||
tags=["creative", "innovative"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Code Reviewer",
|
||||
description="Critical eye, suggests improvements, follows best practices",
|
||||
system_prompt="""You are an experienced code reviewer with a keen eye for quality. You identify potential bugs, security issues, and performance problems. You suggest improvements based on best practices and design patterns. You explain the reasoning behind your suggestions and prioritize the most impactful changes. You're constructive, not harsh.""",
|
||||
tags=["developer", "code"],
|
||||
builtin=True
|
||||
),
|
||||
Profile(
|
||||
name="Analyst",
|
||||
description="Data-driven, objective, thorough analysis",
|
||||
system_prompt="""You are a meticulous analyst who examines information thoroughly and objectively. You consider multiple perspectives, identify patterns and trends, and support your conclusions with evidence. You present findings in a clear, structured manner and acknowledge uncertainties or limitations in your analysis.""",
|
||||
tags=["analytical", "objective"],
|
||||
builtin=True
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def get_profiles_dir() -> Path:
|
||||
"""Get the profiles directory path."""
|
||||
profiles_dir = Path(get_config_dir()) / "profiles"
|
||||
profiles_dir.mkdir(parents=True, exist_ok=True)
|
||||
return profiles_dir
|
||||
|
||||
|
||||
def load_profile(name: str) -> Optional[Profile]:
|
||||
"""Load a profile by name."""
|
||||
# Check built-in profiles first
|
||||
for profile in DEFAULT_PROFILES:
|
||||
if profile.name.lower() == name.lower():
|
||||
return profile
|
||||
|
||||
# Check user profiles
|
||||
profile_path = get_profiles_dir() / f"{name}.yaml"
|
||||
if profile_path.exists():
|
||||
try:
|
||||
with open(profile_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
return Profile.from_dict(data)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def save_profile(profile: Profile) -> None:
|
||||
"""Save a user profile."""
|
||||
if profile.builtin:
|
||||
raise ValueError("Cannot save built-in profiles")
|
||||
|
||||
profile_path = get_profiles_dir() / f"{profile.name}.yaml"
|
||||
with open(profile_path, "w") as f:
|
||||
yaml.safe_dump(profile.to_dict(), f, default_flow_style=False)
|
||||
|
||||
|
||||
def delete_profile(name: str) -> bool:
|
||||
"""Delete a user profile."""
|
||||
# Can't delete built-in profiles
|
||||
for profile in DEFAULT_PROFILES:
|
||||
if profile.name.lower() == name.lower():
|
||||
return False
|
||||
|
||||
profile_path = get_profiles_dir() / f"{name}.yaml"
|
||||
if profile_path.exists():
|
||||
profile_path.unlink()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def list_profiles() -> List[Profile]:
|
||||
"""List all available profiles (built-in + user)."""
|
||||
profiles = list(DEFAULT_PROFILES)
|
||||
|
||||
# Load user profiles
|
||||
profiles_dir = get_profiles_dir()
|
||||
if profiles_dir.exists():
|
||||
for path in profiles_dir.glob("*.yaml"):
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
profile = Profile.from_dict(data)
|
||||
# Don't add duplicates of built-in names
|
||||
if not any(p.name.lower() == profile.name.lower() for p in profiles):
|
||||
profiles.append(profile)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def get_profile_names() -> List[str]:
|
||||
"""Get list of all profile names."""
|
||||
return [p.name for p in list_profiles()]
|
||||
|
|
@ -9,6 +9,7 @@ from .tool import Tool, PromptStep, CodeStep, ToolStep
|
|||
from .providers import call_provider, mock_provider
|
||||
from .resolver import resolve_tool, ToolNotFoundError, ToolSpec
|
||||
from .manifest import load_manifest
|
||||
from .profiles import load_profile
|
||||
|
||||
# Maximum recursion depth for nested tool calls
|
||||
MAX_TOOL_DEPTH = 10
|
||||
|
|
@ -114,6 +115,13 @@ def execute_prompt_step(step: PromptStep, variables: dict, provider_override: st
|
|||
# Build prompt with variable substitution
|
||||
prompt = substitute_variables(step.prompt, variables)
|
||||
|
||||
# Inject profile system prompt if specified
|
||||
if step.profile:
|
||||
profile = load_profile(step.profile)
|
||||
if profile and profile.system_prompt:
|
||||
# Prepend system prompt to user prompt
|
||||
prompt = f"{profile.system_prompt}\n\n---\n\n{prompt}"
|
||||
|
||||
# Call provider
|
||||
provider = provider_override or step.provider
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class PromptStep:
|
|||
provider: str # Provider name
|
||||
output_var: str # Variable to store output
|
||||
prompt_file: Optional[str] = None # Optional filename for external prompt
|
||||
profile: Optional[str] = None # Optional AI persona profile name
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {
|
||||
|
|
@ -59,6 +60,8 @@ class PromptStep:
|
|||
}
|
||||
if self.prompt_file:
|
||||
d["prompt_file"] = self.prompt_file
|
||||
if self.profile:
|
||||
d["profile"] = self.profile
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
|
|
@ -67,7 +70,8 @@ class PromptStep:
|
|||
prompt=data["prompt"],
|
||||
provider=data["provider"],
|
||||
output_var=data["output_var"],
|
||||
prompt_file=data.get("prompt_file")
|
||||
prompt_file=data.get("prompt_file"),
|
||||
profile=data.get("profile")
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue