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
|
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(
|
public function __construct(
|
||||||
private readonly PromptTemplateRepositoryInterface $repository,
|
private readonly PromptTemplateRepositoryInterface $repository,
|
||||||
) {
|
) {
|
||||||
|
|
@ -83,9 +21,11 @@ PROMPT,
|
||||||
public function render(string $key, array $variables = []): string
|
public function render(string $key, array $variables = []): string
|
||||||
{
|
{
|
||||||
$template = $this->repository->findByKey($key);
|
$template = $this->repository->findByKey($key);
|
||||||
$body = $template?->getBody() ?? self::DEFAULTS[$key]
|
if (null === $template) {
|
||||||
?? throw new \InvalidArgumentException("Unknown prompt template key: {$key}");
|
throw new \RuntimeException("System prompt '{$key}' not found in database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $template->getBody();
|
||||||
$search = array_map(static fn (string $k) => '{{'.$k.'}}', array_keys($variables));
|
$search = array_map(static fn (string $k) => '{{'.$k.'}}', array_keys($variables));
|
||||||
|
|
||||||
return str_replace($search, array_values($variables), $body);
|
return str_replace($search, array_values($variables), $body);
|
||||||
|
|
@ -106,10 +46,4 @@ PROMPT,
|
||||||
'json_coding' => ['schema', 'missingHint', 'specsText'],
|
'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\Domain\AI\PromptTemplate;
|
||||||
use App\Infrastructure\AI\PromptTemplateService;
|
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\Config\Crud;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||||
|
use Symfony\Component\Translation\TranslatableMessage;
|
||||||
|
|
||||||
/** @extends AbstractCrudController<PromptTemplate> */
|
/** @extends AbstractCrudController<PromptTemplate> */
|
||||||
final class PromptTemplateCrudController extends AbstractCrudController
|
final class PromptTemplateCrudController extends AbstractCrudController
|
||||||
|
|
@ -30,17 +32,17 @@ final class PromptTemplateCrudController extends AbstractCrudController
|
||||||
->setDefaultSort(['key' => 'ASC']);
|
->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
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield IdField::new('id')->hideOnForm()->hideOnIndex();
|
yield IdField::new('id')->hideOnForm()->hideOnIndex();
|
||||||
yield TextField::new('key', new TranslatableMessage('field.prompt_key', [], 'admin'))
|
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'))
|
yield TextareaField::new('body', new TranslatableMessage('field.prompt_body', [], 'admin'))
|
||||||
->setHelp($this->buildVariableHelp())
|
->setHelp($this->buildVariableHelp())
|
||||||
->setNumOfRows(18)
|
->setNumOfRows(18)
|
||||||
|
|
@ -64,7 +66,6 @@ final class PromptTemplateCrudController extends AbstractCrudController
|
||||||
$lines[] = "<li><strong>{$key}</strong>: {$varList}</li>";
|
$lines[] = "<li><strong>{$key}</strong>: {$varList}</li>";
|
||||||
}
|
}
|
||||||
$lines[] = '</ul>';
|
$lines[] = '</ul>';
|
||||||
$lines[] = 'If no DB entry exists for a key, the built-in default is used automatically.';
|
|
||||||
|
|
||||||
return implode('', $lines);
|
return implode('', $lines);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue