Protokoll #3693
| ID | 3693 |
|---|---|
| Zeitstempel | 2025-12-22 08:24:17.817231 |
| Client | root |
| 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 |
| Modell | claude-sonnet-4-20250514 |
| Status | completed |
| Tokens | 3,855 (Input: 0, Output: 0) |
| Dauer | 100 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": "Edit",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Docs\/HybridSearchService.php",
"old_string": "use Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\n\/**\n * Hybrid Search Service combining vector search with SQL filtering.\n *\n * Implements a two-stage search:\n * 1. Semantic search via Qdrant (vector similarity)\n * 2. Structured filtering via MariaDB (taxonomy, entities, keywords)\n *\n * This approach achieves 70-85% precision vs 30-40% for pure vector search.\n *\/\nfinal class HybridSearchService\n{\n use JsonDecodeTrait;\n\n private const string QDRANT_HOST = 'http:\/\/localhost:6333';\n private const string COLLECTION = 'dokumentation_chunks';\n private const int TIMEOUT = 30;\n\n private PDO $pdo;\n private OllamaService $ollama;\n\n public function __construct()\n {\n $this->ollama = new OllamaService();\n $this->pdo = $this->createConnection();\n }",
"new_string": "use Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Config\\CredentialService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\n\/**\n * Hybrid Search Service combining vector search with SQL filtering.\n *\n * Implements a two-stage search:\n * 1. Semantic search via Qdrant (vector similarity)\n * 2. Structured filtering via MariaDB (taxonomy, entities, keywords)\n *\n * This approach achieves 70-85% precision vs 30-40% for pure vector search.\n *\/\nfinal class HybridSearchService\n{\n use JsonDecodeTrait;\n\n private const string COLLECTION = 'dokumentation_chunks';\n private const int TIMEOUT = 30;\n\n private PDO $pdo;\n private OllamaService $ollama;\n private string $qdrantHost;\n\n public function __construct()\n {\n $this->ollama = new OllamaService();\n $this->pdo = $this->createConnection();\n $this->qdrantHost = CredentialService::getQdrantHost();\n }"
}
}
Response
{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Docs\/HybridSearchService.php",
"oldString": "use Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\n\/**\n * Hybrid Search Service combining vector search with SQL filtering.\n *\n * Implements a two-stage search:\n * 1. Semantic search via Qdrant (vector similarity)\n * 2. Structured filtering via MariaDB (taxonomy, entities, keywords)\n *\n * This approach achieves 70-85% precision vs 30-40% for pure vector search.\n *\/\nfinal class HybridSearchService\n{\n use JsonDecodeTrait;\n\n private const string QDRANT_HOST = 'http:\/\/localhost:6333';\n private const string COLLECTION = 'dokumentation_chunks';\n private const int TIMEOUT = 30;\n\n private PDO $pdo;\n private OllamaService $ollama;\n\n public function __construct()\n {\n $this->ollama = new OllamaService();\n $this->pdo = $this->createConnection();\n }",
"newString": "use Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Config\\CredentialService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\n\/**\n * Hybrid Search Service combining vector search with SQL filtering.\n *\n * Implements a two-stage search:\n * 1. Semantic search via Qdrant (vector similarity)\n * 2. Structured filtering via MariaDB (taxonomy, entities, keywords)\n *\n * This approach achieves 70-85% precision vs 30-40% for pure vector search.\n *\/\nfinal class HybridSearchService\n{\n use JsonDecodeTrait;\n\n private const string COLLECTION = 'dokumentation_chunks';\n private const int TIMEOUT = 30;\n\n private PDO $pdo;\n private OllamaService $ollama;\n private string $qdrantHost;\n\n public function __construct()\n {\n $this->ollama = new OllamaService();\n $this->pdo = $this->createConnection();\n $this->qdrantHost = CredentialService::getQdrantHost();\n }",
"originalFile": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\Docs;\n\nuse Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\n\/**\n * Hybrid Search Service combining vector search with SQL filtering.\n *\n * Implements a two-stage search:\n * 1. Semantic search via Qdrant (vector similarity)\n * 2. Structured filtering via MariaDB (taxonomy, entities, keywords)\n *\n * This approach achieves 70-85% precision vs 30-40% for pure vector search.\n *\/\nfinal class HybridSearchService\n{\n use JsonDecodeTrait;\n\n private const string QDRANT_HOST = 'http:\/\/localhost:6333';\n private const string COLLECTION = 'dokumentation_chunks';\n private const int TIMEOUT = 30;\n\n private PDO $pdo;\n private OllamaService $ollama;\n\n public function __construct()\n {\n $this->ollama = new OllamaService();\n $this->pdo = $this->createConnection();\n }\n\n \/**\n * Performs a hybrid search combining semantic and structured filtering.\n *\n * @param string $query The search query\n * @param array{\n * taxonomy_category?: string,\n * taxonomy_path?: array<string>,\n * entity_type?: string,\n * entity_name?: string,\n * keyword?: string,\n * min_score?: float\n * } $filters Optional structured filters\n * @param int $limit Maximum results\n * @return array<array{\n * chunk_id: int,\n * doc_id: int,\n * path: string,\n * title: string,\n * content: string,\n * heading_path: array<string>,\n * taxonomy: array<string>,\n * entities: array<mixed>,\n * keywords: array<string>,\n * score: float,\n * relevance_score: float\n * }>\n *\/\n public function search(string $query, array $filters = [], int $limit = 10): array\n {\n \/\/ Stage 1: Semantic search in Qdrant\n $vectorResults = $this->semanticSearch($query, $filters, $limit * 3);\n\n if (empty($vectorResults)) {\n return [];\n }\n\n \/\/ Stage 2: Enrich with SQL data and apply filters\n $enrichedResults = $this->enrichAndFilter($vectorResults, $filters);\n\n \/\/ Stage 3: Re-rank based on combined score\n $rankedResults = $this->rerank($enrichedResults, $query);\n\n return array_slice($rankedResults, 0, $limit);\n }\n\n \/**\n * Searches within a specific taxonomy category.\n *\n * @return array<array<string, mixed>>\n *\/\n public function searchByCategory(string $query, string $category, int $limit = 10): array\n {\n return $this->search($query, ['taxonomy_category' => $category], $limit);\n }\n\n \/**\n * Searches for chunks containing a specific entity.\n *\n * @return array<array<string, mixed>>\n *\/\n public function searchByEntity(string $query, string $entityName, int $limit = 10): array\n {\n return $this->search($query, ['entity_name' => $entityName], $limit);\n }\n\n \/**\n * Gets all available taxonomy categories.\n *\n * @return array<array{category: string, count: int}>\n *\/\n public function getTaxonomyCategories(): array\n {\n $stmt = $this->pdo->query('\n SELECT taxonomy_category as category, COUNT(*) as count\n FROM dokumentation_chunks\n WHERE taxonomy_category IS NOT NULL\n GROUP BY taxonomy_category\n ORDER BY count DESC\n ');\n\n return $stmt->fetchAll(PDO::FETCH_ASSOC);\n }\n\n \/**\n * Gets all entities grouped by type.\n *\n * @return array<string, array<string>>\n *\/\n public function getEntitiesByType(): array\n {\n $stmt = $this->pdo->query(\"\n SELECT entities FROM dokumentation_chunks\n WHERE entities IS NOT NULL AND entities != '[]'\n \");\n\n $byType = [];\n\n foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {\n $entities = $this->decodeJsonArray($row['entities'] ?? null);\n foreach ($entities as $entity) {\n if (isset($entity['name'], $entity['type'])) {\n $type = $entity['type'];\n if (!isset($byType[$type])) {\n $byType[$type] = [];\n }\n if (!in_array($entity['name'], $byType[$type], true)) {\n $byType[$type][] = $entity['name'];\n }\n }\n }\n }\n\n return $byType;\n }\n\n \/**\n * Suggests related searches based on current results.\n *\n * @param array<array<string, mixed>> $results\n * @return array<string>\n *\/\n public function suggestRelatedSearches(array $results): array\n {\n $suggestions = [];\n\n foreach ($results as $result) {\n \/\/ Add keywords from results\n foreach ($result['keywords'] ?? [] as $keyword) {\n if (!in_array($keyword, $suggestions, true)) {\n $suggestions[] = $keyword;\n }\n }\n\n \/\/ Add entity names\n foreach ($result['entities'] ?? [] as $entity) {\n if (isset($entity['name']) && !in_array($entity['name'], $suggestions, true)) {\n $suggestions[] = $entity['name'];\n }\n }\n }\n\n return array_slice($suggestions, 0, 5);\n }\n\n \/**\n * Performs semantic search in Qdrant.\n *\n * @param array<string, mixed> $filters\n * @return array<array{id: string, score: float, payload: array<string, mixed>}>\n *\/\n private function semanticSearch(string $query, array $filters, int $limit): array\n {\n $embedding = $this->ollama->getEmbedding($query);\n\n $url = sprintf('%s\/collections\/%s\/points\/search', self::QDRANT_HOST, self::COLLECTION);\n\n $payload = [\n 'vector' => array_values($embedding),\n 'limit' => $limit,\n 'with_payload' => true,\n ];\n\n \/\/ Add Qdrant filter if taxonomy category specified\n if (isset($filters['taxonomy_category'])) {\n $payload['filter'] = [\n 'must' => [\n [\n 'key' => 'taxonomy_category',\n 'match' => ['value' => $filters['taxonomy_category']],\n ],\n ],\n ];\n }\n\n try {\n $response = $this->makeRequest($url, $payload, 'POST');\n\n if (!isset($response['result']) || !is_array($response['result'])) {\n return [];\n }\n\n return array_map(static function (array $item): array {\n return [\n 'id' => (string) $item['id'],\n 'score' => (float) ($item['score'] ?? 0),\n 'payload' => is_array($item['payload'] ?? null) ? $item['payload'] : [],\n ];\n }, $response['result']);\n } catch (RuntimeException) {\n return [];\n }\n }\n\n \/**\n * Enriches vector results with SQL data and applies additional filters.\n *\n * @param array<array{id: string, score: float, payload: array<string, mixed>}> $vectorResults\n * @param array<string, mixed> $filters\n * @return array<array<string, mixed>>\n *\/\n private function enrichAndFilter(array $vectorResults, array $filters): array\n {\n $results = [];\n $minScore = $filters['min_score'] ?? 0.3;\n\n foreach ($vectorResults as $vr) {\n \/\/ Apply minimum score filter\n if ($vr['score'] < $minScore) {\n continue;\n }\n\n $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n if ($chunkId === 0) {\n continue;\n }\n\n \/\/ Get full chunk data from DB\n $chunk = $this->getChunkWithDocument($chunkId);\n if ($chunk === null) {\n continue;\n }\n\n \/\/ Apply entity filter\n if (isset($filters['entity_name'])) {\n $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n $found = false;\n foreach ($entities as $entity) {\n if (isset($entity['name']) && stripos($entity['name'], $filters['entity_name']) !== false) {\n $found = true;\n break;\n }\n }\n if (!$found) {\n continue;\n }\n }\n\n \/\/ Apply entity type filter\n if (isset($filters['entity_type'])) {\n $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n $found = false;\n foreach ($entities as $entity) {\n if (isset($entity['type']) && strtoupper($entity['type']) === strtoupper($filters['entity_type'])) {\n $found = true;\n break;\n }\n }\n if (!$found) {\n continue;\n }\n }\n\n \/\/ Apply keyword filter\n if (isset($filters['keyword'])) {\n $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n $found = false;\n foreach ($keywords as $kw) {\n if (stripos($kw, $filters['keyword']) !== false) {\n $found = true;\n break;\n }\n }\n if (!$found) {\n continue;\n }\n }\n\n $results[] = [\n 'chunk_id' => $chunkId,\n 'doc_id' => (int) $chunk['dokumentation_id'],\n 'path' => $chunk['doc_path'] ?? '',\n 'title' => $chunk['doc_title'] ?? '',\n 'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n 'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n 't... [TRUNCATED-f9c912c97b7e9391]",
"structuredPatch": [
{
"oldStart": 5,
"oldLines": 6,
"newStart": 5,
"newLines": 7,
"lines": [
" namespace Infrastructure\\Docs;",
" ",
" use Infrastructure\\AI\\OllamaService;",
"+use Infrastructure\\Config\\CredentialService;",
" use Infrastructure\\Traits\\JsonDecodeTrait;",
" use PDO;",
" use RuntimeException;"
]
},
{
"oldStart": 22,
"oldLines": 17,
"newStart": 23,
"newLines": 18,
"lines": [
" {",
" use JsonDecodeTrait;",
" ",
"- private const string QDRANT_HOST = 'http:\/\/localhost:6333';",
" private const string COLLECTION = 'dokumentation_chunks';",
" private const int TIMEOUT = 30;",
" ",
" private PDO $pdo;",
" private OllamaService $ollama;",
"+ private string $qdrantHost;",
" ",
" public function __construct()",
" {",
" $this->ollama = new OllamaService();",
" $this->pdo = $this->createConnection();",
"+ $this->qdrantHost = CredentialService::getQdrantHost();",
" }",
" ",
" \/**"
]
}
],
"userModified": false,
"replaceAll": false
}
}