refactor: make prompt templates DB-only system prompts
Remove all hardcoded defaults from PromptTemplateService — the DB record is now mandatory and render() throws if a key is missing. Admin UI disables new/delete and makes the key field read-only so system prompts cannot be renamed or removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
974bd239a5
commit
32da9bb48f
2 changed files with 11 additions and 76 deletions
|
|
@ -8,68 +8,6 @@ use App\Domain\AI\Repository\PromptTemplateRepositoryInterface;
|
|||
|
||||
final class PromptTemplateService
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private const array DEFAULTS = [
|
||||
'specs_research' => <<<'PROMPT'
|
||||
You are a hardware specifications expert. Extract the technical specifications for the {{articleType}}: "{{subject}}".
|
||||
|
||||
Web search results:
|
||||
{{searchResults}}
|
||||
|
||||
Based on the search results above, list all technical specifications including:
|
||||
processor, RAM, storage variants, display size and resolution, GPU, battery capacity,
|
||||
ports, connectivity, weight, dimensions, OS, and any other relevant specs.
|
||||
Be specific and accurate. If a spec is not found in the search results, omit it rather than guessing.
|
||||
PROMPT,
|
||||
|
||||
'ebay_title' => <<<'PROMPT'
|
||||
Create a concise eBay listing title (max 80 characters) for this {{typeName}}.
|
||||
Device: {{deviceLabel}}
|
||||
Use the most important specifications. Include condition if not "new".
|
||||
Condition: {{condition}}
|
||||
{{specsSection}}
|
||||
Return ONLY the title text, no quotes, no explanation.
|
||||
PROMPT,
|
||||
|
||||
'ebay_description' => <<<'PROMPT'
|
||||
Create a professional eBay listing description in German for this {{typeName}}.
|
||||
Device: {{deviceLabel}}
|
||||
Include all available specifications in a clear, structured format.
|
||||
Mention the condition: {{condition}}.
|
||||
{{conditionNotes}}
|
||||
{{specsSection}}
|
||||
Use HTML formatting (ul, li, strong tags). Max 2000 characters.
|
||||
PROMPT,
|
||||
|
||||
'vision_analyze' => <<<'PROMPT'
|
||||
Look at this nameplate/label photo of IT hardware.
|
||||
Extract the manufacturer, any model identifier (name or number), and serial number visible on the label.
|
||||
If the label shows both a product name (e.g. "ThinkPad T490s") and a separate part/product code (e.g. "20NXS0BA00"), put the product name in MODEL_NAME and the code in MODEL_NUMBER.
|
||||
If only one model field is visible, put it in MODEL_NAME and leave MODEL_NUMBER completely empty.
|
||||
MODEL_NUMBER must never contain the serial number.
|
||||
Do not guess or add information not visible on the label.
|
||||
Respond in exactly this format:
|
||||
MANUFACTURER: Lenovo
|
||||
MODEL_NAME: ThinkBook 14 G6 IRL
|
||||
MODEL_NUMBER:
|
||||
SERIAL: PNV09SJZ
|
||||
Use empty string (nothing after the colon) when a field is not visible.
|
||||
PROMPT,
|
||||
|
||||
'json_coding' => <<<'PROMPT'
|
||||
Convert the following hardware specifications to a JSON object.
|
||||
The JSON must use these exact keys (UUIDs) and follow the indicated value formats:
|
||||
|
||||
{{schema}}
|
||||
{{missingHint}}
|
||||
Specifications text:
|
||||
{{specsText}}
|
||||
|
||||
Return ONLY valid JSON. No explanation. No markdown. No extra text.
|
||||
JSON:
|
||||
PROMPT,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly PromptTemplateRepositoryInterface $repository,
|
||||
) {
|
||||
|
|
@ -83,9 +21,11 @@ PROMPT,
|
|||
public function render(string $key, array $variables = []): string
|
||||
{
|
||||
$template = $this->repository->findByKey($key);
|
||||
$body = $template?->getBody() ?? self::DEFAULTS[$key]
|
||||
?? throw new \InvalidArgumentException("Unknown prompt template key: {$key}");
|
||||
if (null === $template) {
|
||||
throw new \RuntimeException("System prompt '{$key}' not found in database.");
|
||||
}
|
||||
|
||||
$body = $template->getBody();
|
||||
$search = array_map(static fn (string $k) => '{{'.$k.'}}', array_keys($variables));
|
||||
|
||||
return str_replace($search, array_values($variables), $body);
|
||||
|
|
@ -106,10 +46,4 @@ PROMPT,
|
|||
'json_coding' => ['schema', 'missingHint', 'specsText'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public static function defaultFor(string $key): string
|
||||
{
|
||||
return self::DEFAULTS[$key] ?? '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ namespace App\Infrastructure\Http\Controller\Admin;
|
|||
|
||||
use App\Domain\AI\PromptTemplate;
|
||||
use App\Infrastructure\AI\PromptTemplateService;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
|
||||
/** @extends AbstractCrudController<PromptTemplate> */
|
||||
final class PromptTemplateCrudController extends AbstractCrudController
|
||||
|
|
@ -30,17 +32,17 @@ final class PromptTemplateCrudController extends AbstractCrudController
|
|||
->setDefaultSort(['key' => 'ASC']);
|
||||
}
|
||||
|
||||
public function createEntity(string $entityFqcn): PromptTemplate
|
||||
public function configureActions(Actions $actions): Actions
|
||||
{
|
||||
return new PromptTemplate('', '');
|
||||
return $actions->disable(Action::NEW, Action::DELETE);
|
||||
}
|
||||
|
||||
public function configureFields(string $pageName): iterable
|
||||
{
|
||||
yield IdField::new('id')->hideOnForm()->hideOnIndex();
|
||||
yield TextField::new('key', new TranslatableMessage('field.prompt_key', [], 'admin'))
|
||||
->setHelp(new TranslatableMessage('field.prompt_key_help', [], 'admin'))
|
||||
->setColumns(4);
|
||||
->setColumns(4)
|
||||
->setFormTypeOption('disabled', true);
|
||||
yield TextareaField::new('body', new TranslatableMessage('field.prompt_body', [], 'admin'))
|
||||
->setHelp($this->buildVariableHelp())
|
||||
->setNumOfRows(18)
|
||||
|
|
@ -64,7 +66,6 @@ final class PromptTemplateCrudController extends AbstractCrudController
|
|||
$lines[] = "<li><strong>{$key}</strong>: {$varList}</li>";
|
||||
}
|
||||
$lines[] = '</ul>';
|
||||
$lines[] = 'If no DB entry exists for a key, the built-in default is used automatically.';
|
||||
|
||||
return implode('', $lines);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue