systemMsgOn = true; } public function run(string $prompt, string $userId, ?bool $includeFullContext = false): Generator { $prompt = trim($prompt); $swagFullOutPut = ''; $firstThinkLoop = true; $shopResults = []; if ($prompt === '') { yield '❌ Empty prompt.'; return; } $this->agentLogger->info('Agent run started', [ 'userId' => $userId, ]); try { // --------------------------------------------------------- // 1) Context strategy // --------------------------------------------------------- if ($includeFullContext) { //Coming soon } yield $this->systemMsg("Ich analysiere deine Anfrage...", "think"); // --------------------------------------------------------- // 2) Extract URL content (if present) // --------------------------------------------------------- yield $this->systemMsg("Ich prüfe auf Internet Quellen...", "think"); $urlContent = $this->urlAnalyzer->extractContentFromPrompt($prompt); // --------------------------------------------------------- // 3) Retrieve RAG knowledge // --------------------------------------------------------- yield $this->systemMsg("Ich hole relevante Daten aus meinem RAG Wissen...", "think"); $knowledgeChunks = $this->retriever->retrieve($prompt); // --------------------------------------------------------- // 4) commerce/shop search // --------------------------------------------------------- $commerceMeta = $this->commerceIntentLite->detect($prompt); $commerceIntent = (string)($commerceMeta['intent'] ?? CommerceIntentLite::NONE); if ($commerceIntent === CommerceIntentLite::PRODUCT_SEARCH || $commerceIntent === CommerceIntentLite::ADVISORY_PRODUCT_SEARCH) { //PreOptimize swag search query $promptSwagSearch = $this->agentRunnerConfig->getShopPrompt($prompt); //Reset thinkSuppressor $this->thinkSuppressor->reset(); yield $this->systemMsg("Ich optimere die Recherche...", "think"); //Call ai for optimized swag query foreach ($this->ollamaClient->stream($promptSwagSearch) as $swagToken) { if (!is_string($swagToken)) { continue; } $swagCleanToken = $this->thinkSuppressor->filter($swagToken); if ($swagCleanToken === '') { continue; } $swagFullOutPut .= $swagCleanToken; } yield $this->systemMsg("Ich rufe Recherchedaten ab (type: " . $commerceIntent . ")", "think"); //Search in swag by ai optimized query try { $shopResults = $swagFullOutPut !== '' ? $this->shopSearchService->search($swagFullOutPut, $commerceIntent) : []; } catch (Throwable $e) { $this->agentLogger->warning('Shop search failed, continuing without shop results', [ 'userId' => $userId, 'exception' => $e, ]); $shopResults = []; yield $this->systemMsg('Shopdaten konnten nicht geladen werden, ich antworte mit Wissensbasis weiter...', 'think'); } } if ($commerceIntent === CommerceIntentLite::PRODUCT_SEARCH) { $knowledgeChunks = array_slice($knowledgeChunks, 0, 2); } elseif ($commerceIntent === CommerceIntentLite::ADVISORY_PRODUCT_SEARCH) { $knowledgeChunks = array_slice($knowledgeChunks, 0, 3); } yield $this->systemMsg("Ich analysiere alle Informationen...", "think"); // --------------------------------------------------------- // 5) Build final prompt // --------------------------------------------------------- $finalPrompt = $this->promptBuilder->build( prompt: $prompt, userId: $userId, urlContent: $urlContent, knowledgeChunks: $knowledgeChunks, shopResults: $shopResults, fullContext: $includeFullContext, swagFullOutPut: $swagFullOutPut ); if ($this->debug && $this->logPrompt) { $this->agentLogger->debug($finalPrompt); } if ($this->debug && $this->logContext) { $this->agentLogger->debug('Conversation context snapshot', [ 'context' => $this->contextService->buildUserContext( $userId, $includeFullContext ), ]); } // --------------------------------------------------------- // 6) Stream tokens from the LLM backend (chunked streaming) // --------------------------------------------------------- $fullOutput = ''; $chunker = new StreamChunker(); $chunker->flush(); $this->thinkSuppressor->reset(); foreach ($this->ollamaClient->stream($finalPrompt) as $token) { if (!is_string($token)) { continue; } $cleanToken = $this->thinkSuppressor->filter((string)$token); if ($cleanToken === '') { if ($firstThinkLoop) { yield $this->systemMsg("Denke nach...", "think"); $firstThinkLoop = false; } continue; } // Vollständige Antwort weiter sammeln (für History) $fullOutput .= $cleanToken; // ⬇️ Token in Chunker geben $chunk = $chunker->push($cleanToken); if ($chunk !== null) { yield $this->systemMsg($chunk, 'answer'); } } // ⬇️ Rest flushen $finalChunk = $chunker->flush(); if ($finalChunk !== null) { yield $this->systemMsg($finalChunk, 'answer'); } elseif ($fullOutput === '') { yield $this->systemMsg('❌ Es wurden keine Daten vom LLM empfangen.', 'err'); } // --------------------------------------------------------- // 7) Persist conversation history // --------------------------------------------------------- $this->contextService->appendHistory( $userId, $prompt, $fullOutput ); $this->agentLogger->info('Agent run finished', [ 'userId' => $userId, 'outputLength' => mb_strlen($fullOutput), 'contextMode' => 'recent', 'commerceIntent' => $commerceIntent, 'shopResultsCount' => count($shopResults), ]); } catch (Throwable $e) { $this->agentLogger->error('Agent run failed', [ 'userId' => $userId, 'exception' => $e, ]); yield $this->systemMsg("\n❌ An internal error occurred while processing the request. \nError: " . $e->getMessage(), 'err'); } } private function systemMsg(string $msg, string $type = ''): string { if (!$this->systemMsgOn) { return ''; } return match ($type) { 'answer' => '' . $msg, 'err' => '' . $msg . "\n