aiConfiguration->makeDriver($companyId); if ($driver === null) { throw new AiException('AI is not enabled for this company', 'ai_disabled'); } $resolved = $this->aiConfiguration->resolveForCompany($companyId); if (empty($resolved['text_generation_enabled'])) { throw new AiException('Text generation is not enabled for this company', 'text_generation_disabled'); } $model = (string) ($resolved['ai_text_generation_model'] ?? ''); if ($model === '') { throw new AiException('No text generation model configured', 'missing_model'); } $fullPrompt = $this->buildPrompt($prompt, $context); return trim($driver->textCompletion($fullPrompt, $model)); } /** * Compose the final prompt sent to the model. * * 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 = PromptLoader::load('text-generation'); if ($context !== null && trim($context) !== '') { return $system."\n\n" ."Context (current content the user is working with):\n" .trim($context) ."\n\n" ."Instruction: {$prompt}"; } return $system."\n\nInstruction: {$prompt}"; } }