Backup #2081

ID2081
Dateipfad/var/www/dev.campus.systemische-tools.de/src/UseCases/Chat/SendChatMessageUseCase.php
Version24
Typ modified
Größe11.5 KB
Hashc4d73405a9eb65a8c6c18f890007e0058d4b83a65256785b82c7c8930661d477
Datum2025-12-29 00:08:32
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

declare(strict_types=1);

namespace UseCases\Chat;

// @responsibility: Orchestriert Chat-Nachrichten (Validierung, AI-Antwort, Speicherung)

use Domain\Repository\ChatMessageRepositoryInterface;
use Domain\Repository\ChatSessionRepositoryInterface;
use Infrastructure\AI\ChatService;
use Infrastructure\AI\ContentQualityValidator;
use Infrastructure\Persistence\ContentConfigRepository;

class SendChatMessageUseCase
{
    public function __construct(
        private ChatService $chatService,
        private ChatSessionRepositoryInterface $sessionRepo,
        private ChatMessageRepositoryInterface $messageRepo,
        private ContentConfigRepository $configRepo,
        private ContentQualityValidator $qualityValidator
    ) {
    }

    /**
     * Execute the use case
     *
     * @param string $sessionUuid Session UUID
     * @param string $message User message text
     * @param string $model AI model identifier
     * @param array<string> $collections Qdrant collections to search
     * @param int $contextLimit Number of context chunks
     * @param int $authorProfileId Author profile ID (0 = none)
     * @param int $systemPromptId System prompt config ID
     * @param float $temperature AI temperature setting
     * @param int $maxTokens Max tokens for response
     * @param int $structureId Output structure ID (0 = frei)
     * @param bool $qualityCheck Run quality validation on response
     * @return ChatResponse
     */
    public function execute(
        string $sessionUuid,
        string $message,
        string $model,
        array $collections = ['documents'],
        int $contextLimit = 5,
        int $authorProfileId = 0,
        int $systemPromptId = 1,
        float $temperature = 0.7,
        int $maxTokens = 4096,
        int $structureId = 0,
        bool $qualityCheck = false
    ): ChatResponse {
        // 1. Validate session
        $session = $this->sessionRepo->findByUuid($sessionUuid);
        if ($session === null) {
            return ChatResponse::error('Session nicht gefunden.');
        }
        $sessionId = $session->getId() ?? 0;

        // 2. Validate message
        $message = trim($message);
        if ($message === '') {
            return ChatResponse::error('Bitte gib eine Frage ein.');
        }

        // 3. Save user message
        $this->messageRepo->save(
            sessionId: $sessionId,
            role: 'user',
            content: $message,
            model: $model
        );

        // 4. Auto-set title from first message
        if ($session->getTitle() === null) {
            $title = mb_substr($message, 0, 50) . (mb_strlen($message) > 50 ? '...' : '');
            $this->sessionRepo->updateTitle($sessionId, $title);
        }

        // 5. Get style prompt from author profile
        $stylePrompt = $this->getStylePromptFromProfile($authorProfileId);
        // 6. Get system prompt
        $systemPrompt = $this->getSystemPromptById($systemPromptId);
        // 6b. Add structure formatting if selected
        $structurePrompt = $this->getStructurePrompt($structureId);
        if ($structurePrompt !== null) {
            $systemPrompt = ($systemPrompt ?? '') . "\n\n" . $structurePrompt;
        }

        // 7. Track timing and get AI response
        $startTime = microtime(true);

        try {
            $result = $this->chatService->chat(
                question: $message,
                model: $model,
                collections: $collections,
                limit: $contextLimit,
                stylePrompt: $stylePrompt,
                customSystemPrompt: $systemPrompt,
                temperature: $temperature,
                maxTokens: $maxTokens
            );
        } catch (\Exception $e) {
            return ChatResponse::error('Chat-Service Fehler: ' . $e->getMessage());
        }

        $endTime = microtime(true);

        // 8. Prepare sources as JSON strings for storage
        /** @var array<int, non-empty-string> $sourcesForStorage */
        $sourcesForStorage = array_map(
            static fn (array $source): string => json_encode($source, JSON_THROW_ON_ERROR),
            $result['sources']
        );

        // 9. Save assistant message with tracking
        $collectionsJson = json_encode($collections);
        $this->messageRepo->save(
            sessionId: $sessionId,
            role: 'assistant',
            content: $result['answer'],
            model: $model,
            tokensInput: $result['usage']['input_tokens'] ?? null,
            tokensOutput: $result['usage']['output_tokens'] ?? null,
            sources: $sourcesForStorage,
            startMicrotime: $startTime,
            endMicrotime: $endTime,
            authorProfileId: $authorProfileId > 0 ? $authorProfileId : null,
            systemPromptId: $systemPromptId > 0 ? $systemPromptId : null,
            collectionsJson: $collectionsJson,
            contextLimit: $contextLimit
        );

        // 10. Create response
        $response = ChatResponse::fromServiceResponse($result, $endTime - $startTime);
        // 11. Run quality validation if enabled
        if ($qualityCheck) {
            $structureName = $structureId > 0 ? $this->getStructureName($structureId) : null;
            $validation = $this->qualityValidator->validate(
                question: $message,
                answer: $result['answer'],
                sources: $result['sources'],
                structureName: $structureName
            );
            $response = $response->withQualityValidation($validation);
        }
        // 12. Return response
        return $response;
    }

    /**
     * Get structure name by ID
     */
    private function getStructureName(int $structureId): ?string
    {
        $structure = $this->configRepo->findByIdAndType($structureId, 'structure');
        return $structure['name'] ?? null;
    }

    /**
     * Create default ChatService from credentials
     */
    private function createDefaultChatService(): ChatService
    {
        $config = \Infrastructure\AI\AIConfig::fromCredentialsFile();
        return $config->createChatService();
    }

    /**
     * Get style prompt from author profile
     */
    private function getStylePromptFromProfile(int $profileId): ?string
    {
        if ($profileId === 0) {
            return null;
        }
        $profile = $this->configRepo->findByIdAndType($profileId, 'author_profile');
        if ($profile === null) {
            return null;
        }
        $config = json_decode($profile['content'] ?? '{}', true);
        if ($config === null) {
            return null;
        }
        $parts = [];

        if (isset($config['stimme']['ton'])) {
            $parts[] = 'Ton: ' . $config['stimme']['ton'];
        }
        if (isset($config['stimme']['perspektive'])) {
            $parts[] = 'Perspektive: ' . $config['stimme']['perspektive'];
        }
        if (isset($config['stil']['fachsprache']) && $config['stil']['fachsprache']) {
            $parts[] = 'Verwende Fachsprache';
        }
        if (isset($config['stil']['beispiele']) && $config['stil']['beispiele'] === 'häufig') {
            $parts[] = 'Nutze häufig Beispiele';
        }
        if (isset($config['stil']['listen']) && $config['stil']['listen'] === 'bevorzugt') {
            $parts[] = 'Bevorzuge Listen und Bullet-Points';
        }
        if (isset($config['tabus']) && is_array($config['tabus'])) {
            $parts[] = 'Vermeide: ' . implode(', ', $config['tabus']);
        }

        if ($parts === []) {
            return null;
        }

        return 'Schreibstil (' . ($profile['name'] ?? 'Profil') . '): ' . implode('. ', $parts) . '.';
    }

    /**
     * Get system prompt by ID
     */
    private function getSystemPromptById(int $promptId): ?string
    {
        if ($promptId === 0) {
            return null;
        }

        $prompt = $this->configRepo->findByIdAndType($promptId, 'system_prompt');
        if ($prompt === null) {
            return null;
        }

        $content = json_decode($prompt['content'] ?? '{}', true);

        return $content['prompt'] ?? null;
    }

    /**
     * Get structure formatting prompt
     */
    private function getStructurePrompt(int $structureId): ?string
    {
        if ($structureId === 0) {
            return null;
        }

        $structure = $this->configRepo->findByIdAndType($structureId, 'structure');
        if ($structure === null) {
            return null;
        }

        $name = $structure['name'] ?? 'Struktur';
        $config = json_decode($structure['content'] ?? '{}', true);

        if ($config === null) {
            return null;
        }

        // Complex structure (Blog-Beitrag, LinkedIn-Beitrag etc.)
        if (isset($config['ausgabe']) || isset($config['aufbau']) || isset($config['gesamtaufbau'])) {
            return $this->buildComplexStructurePrompt($name, $config);
        }

        // Simple structure (legacy format)
        $parts = ["Formatiere deine Antwort als: {$name}"];

        if (isset($config['sections']) && is_array($config['sections'])) {
            $parts[] = 'Struktur: ' . implode(' → ', $config['sections']);
        }

        if (isset($config['max_chars'])) {
            $parts[] = 'Maximale Länge: ' . $config['max_chars'] . ' Zeichen';
        }

        if (isset($config['min_words'])) {
            $parts[] = 'Mindestens ' . $config['min_words'] . ' Wörter';
        }

        if (isset($config['max_words'])) {
            $parts[] = 'Maximal ' . $config['max_words'] . ' Wörter';
        }

        if (isset($config['format']) && $config['format'] === 'qa') {
            $min = $config['min_questions'] ?? 3;
            $parts[] = "Formatiere als FAQ mit mindestens {$min} Frage-Antwort-Paaren";
        }

        if (isset($config['hashtags']) && $config['hashtags']) {
            $parts[] = 'Füge passende Hashtags hinzu';
        }

        if (isset($config['cta']) && $config['cta']) {
            $parts[] = 'Schließe mit einem Call-to-Action ab';
        }

        return implode('. ', $parts) . '.';
    }

    /**
     * Build prompt from complex structure config
     */
    private function buildComplexStructurePrompt(string $name, array $config): string
    {
        $prompt = "## Ausgabeformat: {$name}\n\n";
        $prompt .= "Befolge diese Struktur-Vorgaben exakt:\n\n";
        $prompt .= $this->configToYaml($config, 0);

        return $prompt;
    }

    /**
     * Convert config array to readable YAML-like format
     */
    private function configToYaml(array $config, int $indent): string
    {
        $result = '';
        $prefix = str_repeat('  ', $indent);

        foreach ($config as $key => $value) {
            if (is_array($value)) {
                if ($this->isIndexedArray($value)) {
                    $result .= "{$prefix}{$key}:\n";
                    foreach ($value as $item) {
                        if (is_array($item)) {
                            $result .= "{$prefix}- " . trim($this->configToYaml($item, 0)) . "\n";
                        } else {
                            $result .= "{$prefix}- {$item}\n";
                        }
                    }
                } else {
                    $result .= "{$prefix}{$key}:\n";
                    $result .= $this->configToYaml($value, $indent + 1);
                }
            } else {
                $result .= "{$prefix}{$key}: {$value}\n";
            }
        }

        return $result;
    }

    /**
     * Check if array is indexed (list) vs associative
     */
    private function isIndexedArray(array $arr): bool
    {
        if ($arr === []) {
            return true;
        }

        return array_keys($arr) === range(0, count($arr) - 1);
    }
}

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
2084 27 modified 11.4 KB 2025-12-29 00:09
2083 26 modified 11.4 KB 2025-12-29 00:09
2082 25 modified 11.4 KB 2025-12-29 00:08
2081 24 modified 11.5 KB 2025-12-29 00:08
1947 23 modified 11.5 KB 2025-12-28 02:21
1946 22 modified 11.5 KB 2025-12-28 02:21
1945 21 modified 11.5 KB 2025-12-28 02:21
1944 20 modified 11.5 KB 2025-12-28 02:21
1943 19 modified 11.5 KB 2025-12-28 02:21
1942 18 modified 11.5 KB 2025-12-28 02:20
1941 17 modified 11.5 KB 2025-12-28 02:20
1614 16 modified 9.7 KB 2025-12-27 01:34
1131 15 modified 9.7 KB 2025-12-25 09:41
1130 14 modified 9.6 KB 2025-12-25 09:41
842 13 modified 10.2 KB 2025-12-23 08:17
708 12 modified 10.4 KB 2025-12-23 07:54
568 11 modified 10.4 KB 2025-12-23 03:37
563 10 modified 9.6 KB 2025-12-23 03:35
562 9 modified 9.4 KB 2025-12-23 03:35
561 8 modified 9.4 KB 2025-12-23 03:35
555 7 modified 9.3 KB 2025-12-23 03:33
554 6 modified 9.2 KB 2025-12-23 03:33
549 5 modified 7.6 KB 2025-12-23 02:38
548 4 modified 7.3 KB 2025-12-23 02:38
547 3 modified 7.2 KB 2025-12-23 02:38
471 2 modified 7.2 KB 2025-12-22 14:43
396 1 modified 7.1 KB 2025-12-22 08:47

← Zurück zur Übersicht