Protokoll #15661

ID15661
Zeitstempel2025-12-25 16:56:15.159873
Clientroot
IP
Warning: Undefined array key "request_ip" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13

Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens2,677 (Input: 0, Output: 0)
Dauer89 ms
Request-Zeit
Warning: Undefined array key "request_timestamp" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 18
Response-Zeit-

Warning: Undefined array key "error_message" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 22

Request

{
    "event": "PreToolUse",
    "tool_name": "Read",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/AI\/ClaudeService.php"
    }
}

Response

{
    "tool_response": {
        "type": "text",
        "file": {
            "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/AI\/ClaudeService.php",
            "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\AI;\n\n\/\/ @responsibility: Anthropic Claude API für RAG-Antworten\n\nuse Infrastructure\\Config\\CredentialService;\nuse RuntimeException;\n\nfinal readonly class ClaudeService\n{\n    \/**\n     * Default timeout for HTTP requests in seconds (LLM responses can take time).\n     *\/\n    private const int DEFAULT_TIMEOUT = 120;\n\n    \/**\n     * Health check timeout in seconds.\n     *\/\n    private const int HEALTH_CHECK_TIMEOUT = 5;\n\n    \/**\n     * Anthropic API base URL.\n     *\/\n    private const string API_BASE_URL = 'https:\/\/api.anthropic.com\/v1';\n\n    \/**\n     * Anthropic API version header.\n     *\/\n    private const string API_VERSION = '2023-06-01';\n\n    \/**\n     * The Anthropic API key loaded from environment.\n     *\/\n    private readonly string $apiKey;\n\n    \/**\n     * Constructs a new ClaudeService instance.\n     *\n     * Loads the Anthropic API key from environment variables via CredentialService.\n     * This ensures secrets are never hardcoded and follow security best practices.\n     *\n     * @param CredentialService $credentials Service for loading credentials from environment\n     *\/\n    public function __construct(\n        CredentialService $credentials\n    ) {\n        $this->apiKey = $credentials->getAnthropicApiKey();\n    }\n\n    \/**\n     * Sends a prompt to Claude and receives a text response.\n     *\n     * Makes a request to the Anthropic Messages API with the specified prompt,\n     * optional system prompt, model, token limit, and temperature. Returns the\n     * generated text along with token usage statistics.\n     *\n     * @param string      $prompt       The user prompt to send\n     * @param string|null $systemPrompt Optional system prompt for instructions (default: null)\n     * @param string      $model        The Claude model to use (default: claude-opus-4-5-20251101)\n     * @param int         $maxTokens    Maximum tokens in response (default: 4000)\n     * @param float       $temperature  Sampling temperature 0.0-1.0 (default: 0.7)\n     *\n     * @return array{text: string, usage: array{input_tokens: int, output_tokens: int}} Response text and token usage\n     *\n     * @throws RuntimeException If the API request fails or returns invalid data\n     *\n     * @example\n     * $credentials = new CredentialService();\n     * $service = new ClaudeService($credentials);\n     * $result = $service->ask('Explain quantum computing', 'You are a physics teacher.', 'claude-opus-4-5-20251101', 4000, 0.7);\n     * \/\/ Returns: [\n     * \/\/   'text' => 'Quantum computing is...',\n     * \/\/   'usage' => ['input_tokens' => 123, 'output_tokens' => 456]\n     * \/\/ ]\n     *\/\n    public function ask(\n        string $prompt,\n        ?string $systemPrompt = null,\n        string $model = 'claude-opus-4-5-20251101',\n        int $maxTokens = 4000,\n        float $temperature = 0.7\n    ): array {\n        $url = self::API_BASE_URL . '\/messages';\n\n        $payload = [\n            'model' => $model,\n            'max_tokens' => $maxTokens,\n            'temperature' => max(0.0, min(1.0, $temperature)),\n            'messages' => [\n                [\n                    'role' => 'user',\n                    'content' => $prompt,\n                ],\n            ],\n        ];\n\n        if ($systemPrompt !== null) {\n            $payload['system'] = $systemPrompt;\n        }\n\n        $response = $this->makeRequest($url, $payload, self::DEFAULT_TIMEOUT);\n\n        if (!isset($response['content']) || !is_array($response['content'])) {\n            throw new RuntimeException('Invalid response structure from Claude API');\n        }\n\n        if (!isset($response['content'][0]['text'])) {\n            throw new RuntimeException('Missing text content in Claude API response');\n        }\n\n        $text = (string) $response['content'][0]['text'];\n\n        $usage = [\n            'input_tokens' => (int) ($response['usage']['input_tokens'] ?? 0),\n            'output_tokens' => (int) ($response['usage']['output_tokens'] ?? 0),\n        ];\n\n        return [\n            'text' => $text,\n            'usage' => $usage,\n        ];\n    }\n\n    \/**\n     * Builds a RAG-specific user prompt combining question and context.\n     *\n     * Creates a formatted prompt that presents retrieved context to Claude\n     * along with the user's question, suitable for RAG (Retrieval-Augmented Generation).\n     *\n     * @param string $question The user's question\n     * @param string $context  The retrieved context from vector search\n     *\n     * @return string The formatted RAG prompt\n     *\n     * @example\n     * $credentials = new CredentialService();\n     * $service = new ClaudeService($credentials);\n     * $prompt = $service->buildRagPrompt(\n     *     'Was ist systemisches Coaching?',\n     *     'Kontext: Systemisches Coaching betrachtet...'\n     * );\n     * \/\/ Returns: \"Kontext aus den Dokumenten:\\n\\n...\\n\\n---\\n\\nFrage: Was ist systemisches Coaching?\"\n     *\/\n    public function buildRagPrompt(string $question, string $context): string\n    {\n        return sprintf(\n            \"Kontext aus den Dokumenten:\\n\\n%s\\n\\n---\\n\\nFrage: %s\",\n            $context,\n            $question\n        );\n    }\n\n    \/**\n     * Returns the default system prompt for RAG use cases.\n     *\n     * Provides a standard German system prompt instructing Claude to answer\n     * questions based on provided context for teamcoaching and team development topics.\n     *\n     * @return string The default system prompt\n     *\n     * @example\n     * $credentials = new CredentialService();\n     * $service = new ClaudeService($credentials);\n     * $systemPrompt = $service->getDefaultSystemPrompt();\n     * $result = $service->ask($userPrompt, $systemPrompt);\n     *\/\n    public function getDefaultSystemPrompt(): string\n    {\n        return <<<'PROMPT'\n            Du bist ein hilfreicher Assistent für Fragen zu systemischem Teamcoaching und Teamentwicklung.\n\n            Beantworte die Frage des Nutzers basierend auf dem bereitgestellten Kontext.\n            - Antworte auf Deutsch\n            - Sei präzise und hilfreich\n            - Wenn der Kontext die Frage nicht beantwortet, sage das ehrlich\n            - Verweise auf die Quellen wenn passend\n            PROMPT;\n    }\n\n    \/**\n     * Checks if the Claude API is available and responding.\n     *\n     * Performs a minimal health check by attempting to connect to the API.\n     * Note: This is optional as Claude is an external service with its own status page.\n     * Returns false on any error to avoid exposing API key in logs.\n     *\n     * @return bool True if the API appears available, false otherwise\n     *\n     * @example\n     * $credentials = new CredentialService();\n     * $service = new ClaudeService($credentials);\n     * if ($service->isAvailable()) {\n     *     echo \"Claude API is accessible\";\n     * } else {\n     *     echo \"Claude API is not available\";\n     * }\n     *\/\n    public function isAvailable(): bool\n    {\n        try {\n            $ch = curl_init(self::API_BASE_URL . '\/messages');\n\n            if ($ch === false) {\n                return false;\n            }\n\n            curl_setopt_array($ch, [\n                CURLOPT_RETURNTRANSFER => true,\n                CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n                CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT,\n                CURLOPT_CUSTOMREQUEST => 'HEAD',\n                CURLOPT_NOBODY => true,\n            ]);\n\n            $result = curl_exec($ch);\n            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n            curl_close($ch);\n\n            \/\/ API returns 4xx for HEAD without auth, but connectivity is proven\n            return $result !== false && ($httpCode === 400 || $httpCode === 401);\n        } catch (\\Throwable) {\n            return false;\n        }\n    }\n\n    \/**\n     * Makes an HTTP POST request to the Claude API.\n     *\n     * Internal helper method that handles cURL initialization, request execution,\n     * error handling, and response parsing. API key is sent via x-api-key header\n     * and is never logged or included in exception messages.\n     *\n     * @param string               $url     The API endpoint URL\n     * @param array<string, mixed> $payload The JSON payload to send\n     * @param int                  $timeout The request timeout in seconds\n     *\n     * @return array<string, mixed> The decoded JSON response\n     *\n     * @throws RuntimeException If the request fails or returns an error (without exposing API key)\n     *\/\n    private function makeRequest(string $url, array $payload, int $timeout): array\n    {\n        $ch = curl_init($url);\n\n        if ($ch === false) {\n            throw new RuntimeException('Failed to initialize cURL');\n        }\n\n        $jsonPayload = json_encode($payload);\n\n        if ($jsonPayload === false) {\n            curl_close($ch);\n\n            throw new RuntimeException('Failed to encode JSON payload');\n        }\n\n        curl_setopt_array($ch, [\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_POST => true,\n            CURLOPT_POSTFIELDS => $jsonPayload,\n            CURLOPT_TIMEOUT => $timeout,\n            CURLOPT_CONNECTTIMEOUT => 10,\n            CURLOPT_HTTPHEADER => [\n                'Content-Type: application\/json',\n                'x-api-key: ' . $this->apiKey,\n                'anthropic-version: ' . self::API_VERSION,\n            ],\n        ]);\n\n        $result = curl_exec($ch);\n        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n        $curlError = curl_error($ch);\n\n        curl_close($ch);\n\n        if ($result === false) {\n            throw new RuntimeException(\n                sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error')\n            );\n        }\n\n        if ($httpCode !== 200) {\n            \/\/ Never expose API key in error messages - sanitize response\n            $sanitizedResponse = $this->sanitizeErrorResponse((string) $result);\n\n            throw new RuntimeException(\n                sprintf('Claude API r... [TRUNCATED-ea1c8386fb7777c6]",
            "numLines": 325,
            "startLine": 1,
            "totalLines": 325
        }
    }
}
← Vorheriger Zur Liste Nächster →