refactor(ai): move chat + text-gen prompts into resources/ai/prompts/

Extracts the two inline LLM prompts (AiAssistantService's chat system
prompt and AiTextGenerationService's writing preamble) out of PHP
heredocs and into plain-markdown template files under resources/ai/
prompts/. Each file can now be edited without opening a service
class, without wrestling with PHP string interpolation, and with
proper markdown syntax highlighting in editors.

A tiny PromptLoader helper at app/Support/Ai/PromptLoader.php reads
the file and does {{placeholder}} substitution via strtr() — no
Blade, because Blade's {{ $var }} HTML-escapes ampersands and quotes,
which is wrong for LLM prompts (a company called "Smith & Co" would
be sent as "Smith & Co"). Missing templates throw RuntimeException
so they fail loud during development.

Pure refactor: no prompt wording changes. Existing AI feature tests
(AiChatFlowTest, AiGenerationTest) pass unchanged — they assert on
message structure via ScriptedAiDriver, not on prompt content. Three
new unit tests in PromptLoaderTest lock the helper's contract:
placeholder substitution, no-var loading, missing-file error.
This commit is contained in:
Darko Gjorgjijoski
2026-04-11 21:08:43 +02:00
parent f66d0f80b8
commit 5b53a7c283
6 changed files with 122 additions and 21 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Services\Ai;
use App\Services\AiConfigurationService;
use App\Support\Ai\AiException;
use App\Support\Ai\PromptLoader;
/**
* Stateless one-shot text generation for the WYSIWYG popup.
@@ -67,13 +68,14 @@ class AiTextGenerationService
* Keep the framing terse — text-generation output should not be padded
* with "Here is the text you requested:" preambles. The instruction is
* always placed last so the model gives it the most weight.
*
* The static preamble lives at resources/ai/prompts/text-generation.md.
* The conditional context + instruction appending stays here because it
* has different structure depending on whether `context` is present.
*/
protected function buildPrompt(string $prompt, ?string $context): string
{
$system = "You are a helpful writing assistant embedded in an invoicing application. Generate text based on the user's instruction. Rules:\n"
.'- Return only the requested text. No preamble, no explanation, no markdown code fences.'."\n"
.'- Match the tone and language implied by the instruction.'."\n"
.'- Be concise unless explicitly asked for length.';
$system = PromptLoader::load('text-generation');
if ($context !== null && trim($context) !== '') {
return $system."\n\n"