$history * @param array $context */ public function chat(string $message, array $history, array $context): string { if (! $this->enabled()) { throw new RuntimeException('Afia is not configured.'); } $provider = (string) config('afia.provider', 'openai'); $model = (string) config('afia.model', 'gpt-4o-mini'); $apiKey = (string) config('afia.api_key'); $messages = [['role' => 'system', 'content' => $this->systemPrompt($context)]]; foreach (array_slice($history, -8) as $turn) { $role = ($turn['role'] ?? 'user') === 'assistant' ? 'assistant' : 'user'; $text = trim((string) ($turn['text'] ?? '')); if ($text !== '') { $messages[] = ['role' => $role, 'content' => $text]; } } $messages[] = ['role' => 'user', 'content' => $message]; return $provider === 'anthropic' ? $this->viaAnthropic($model, $apiKey, $messages) : $this->viaOpenAi($model, $apiKey, $messages); } private function viaOpenAi(string $model, string $apiKey, array $messages): string { $res = Http::withToken($apiKey)->acceptJson()->timeout(45) ->post('https://api.openai.com/v1/chat/completions', [ 'model' => $model, 'temperature' => 0.3, 'max_tokens' => 600, 'messages' => $messages, ]); if ($res->failed()) { throw new RuntimeException('OpenAI request failed: '.$res->status()); } return trim((string) $res->json('choices.0.message.content', '')); } private function viaAnthropic(string $model, string $apiKey, array $messages): string { $system = $messages[0]['content']; $turns = array_values(array_filter($messages, fn ($m) => $m['role'] !== 'system')); $res = Http::withHeaders([ 'x-api-key' => $apiKey, 'anthropic-version' => '2023-06-01', ])->acceptJson()->timeout(45)->post('https://api.anthropic.com/v1/messages', [ 'model' => $model, 'max_tokens' => 600, 'system' => $system, 'messages' => array_map(fn ($m) => ['role' => $m['role'], 'content' => $m['content']], $turns), ]); if ($res->failed()) { throw new RuntimeException('Anthropic request failed: '.$res->status()); } return trim((string) $res->json('content.0.text', '')); } /** @param array $context */ private function systemPrompt(array $context): string { $ctx = collect($context)->map(fn ($v, $k) => "- {$k}: {$v}")->implode("\n"); return match ((string) config('afia.product', 'qr')) { 'qr' => $this->qrSystemPrompt($ctx), default => $this->qrSystemPrompt($ctx), }; } private function qrSystemPrompt(string $ctx): string { return <<