fix stream error handling
This commit is contained in:
@@ -84,6 +84,17 @@ final readonly class SearchRepairService
|
||||
foreach ($repairQueries as $repairQuery) {
|
||||
$results = $this->shopSearchService->search($repairQuery, $commerceIntent, '');
|
||||
|
||||
if ($this->shopSearchService->hadLastSearchSystemFailure()) {
|
||||
$this->logger->warning('Shop repair stopped after Store API system failure', [
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'primaryQuery' => $primaryQuery,
|
||||
'failedRepairQuery' => $repairQuery,
|
||||
'failureReason' => $this->shopSearchService->getLastSearchFailureReason(),
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($results === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -10,30 +10,44 @@ use App\Commerce\Dto\ShopProductResult;
|
||||
use App\Config\ShopServiceConfig;
|
||||
use App\Shopware\ShopwareCriteriaBuilder;
|
||||
use App\Shopware\StoreApiClient;
|
||||
use App\Shopware\StoreApiException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
final readonly class ShopSearchService
|
||||
final class ShopSearchService
|
||||
{
|
||||
private const FOCUS_NEUTRAL = 'neutral';
|
||||
private const FOCUS_DEVICE = 'device';
|
||||
private const FOCUS_ACCESSORY = 'accessory';
|
||||
|
||||
private bool $lastSearchHadSystemFailure = false;
|
||||
private ?string $lastSearchFailureReason = null;
|
||||
|
||||
public function __construct(
|
||||
private CommerceQueryParser $queryParser,
|
||||
private ShopwareCriteriaBuilder $criteriaBuilder,
|
||||
private StoreApiClient $storeApiClient,
|
||||
private ShopServiceConfig $shopConfig,
|
||||
private LoggerInterface $logger,
|
||||
private bool $enabled = true,
|
||||
private int $maxResults = 25,
|
||||
private string $baseUrl = ''
|
||||
private readonly CommerceQueryParser $queryParser,
|
||||
private readonly ShopwareCriteriaBuilder $criteriaBuilder,
|
||||
private readonly StoreApiClient $storeApiClient,
|
||||
private readonly ShopServiceConfig $shopConfig,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly bool $enabled = true,
|
||||
private readonly int $maxResults = 25,
|
||||
private readonly string $baseUrl = ''
|
||||
) {
|
||||
}
|
||||
|
||||
public function hadLastSearchSystemFailure(): bool
|
||||
{
|
||||
return $this->lastSearchHadSystemFailure;
|
||||
}
|
||||
|
||||
public function getLastSearchFailureReason(): ?string
|
||||
{
|
||||
return $this->lastSearchFailureReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShopProductResult[]
|
||||
*/
|
||||
@@ -43,6 +57,8 @@ final readonly class ShopSearchService
|
||||
string $commerceHistoryContext = '',
|
||||
?CommerceReferenceContext $referenceContext = null
|
||||
): array {
|
||||
$this->resetLastSearchFailure();
|
||||
|
||||
if (!$this->enabled) {
|
||||
$this->logger->info('Shop search skipped because commerce search is disabled', [
|
||||
'commerceIntent' => $commerceIntent,
|
||||
@@ -335,30 +351,114 @@ final readonly class ShopSearchService
|
||||
|
||||
try {
|
||||
$response = $this->storeApiClient->searchProducts($criteria);
|
||||
return $this->mapAndLogSearchResponse(
|
||||
response: $response,
|
||||
query: $query,
|
||||
commerceIntent: $commerceIntent,
|
||||
originalPrompt: $originalPrompt,
|
||||
usesHistoryContext: $usesHistoryContext,
|
||||
usedSafeCriteria: false
|
||||
);
|
||||
} catch (StoreApiException $e) {
|
||||
if ($e->isSafeCriteriaRetryRecommended()) {
|
||||
$safeResults = $this->retryWithSafeCriteria(
|
||||
query: $query,
|
||||
commerceIntent: $commerceIntent,
|
||||
originalPrompt: $originalPrompt,
|
||||
usesHistoryContext: $usesHistoryContext,
|
||||
previousException: $e
|
||||
);
|
||||
|
||||
if ($safeResults !== null) {
|
||||
return $safeResults;
|
||||
}
|
||||
}
|
||||
|
||||
$this->recordFailedSearch($e);
|
||||
$this->logShopSearchFailure($query, $commerceIntent, $originalPrompt, $usesHistoryContext, $e);
|
||||
|
||||
return [];
|
||||
} catch (
|
||||
ClientExceptionInterface |
|
||||
RedirectionExceptionInterface |
|
||||
ServerExceptionInterface |
|
||||
TransportExceptionInterface |
|
||||
\RuntimeException $e
|
||||
ClientExceptionInterface |
|
||||
RedirectionExceptionInterface |
|
||||
ServerExceptionInterface |
|
||||
TransportExceptionInterface |
|
||||
\RuntimeException $e
|
||||
) {
|
||||
$this->logger->warning('Shop search request failed', [
|
||||
$this->recordFailedSearch($e);
|
||||
$this->logShopSearchFailure($query, $commerceIntent, $originalPrompt, $usesHistoryContext, $e);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShopProductResult[]|null
|
||||
*/
|
||||
private function retryWithSafeCriteria(
|
||||
CommerceSearchQuery $query,
|
||||
string $commerceIntent,
|
||||
string $originalPrompt,
|
||||
bool $usesHistoryContext,
|
||||
StoreApiException $previousException
|
||||
): ?array {
|
||||
$this->logger->warning('Shop search retrying with safe criteria', [
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'originalPrompt' => $originalPrompt,
|
||||
'normalizedPrompt' => $query->normalizedPrompt,
|
||||
'searchText' => $query->searchText,
|
||||
'usesHistoryContext' => $usesHistoryContext,
|
||||
'previousStatusCode' => $previousException->getStatusCode(),
|
||||
'previousUtf8Failure' => $previousException->isUtf8Failure(),
|
||||
'previousExceptionMessage' => $previousException->getMessage(),
|
||||
]);
|
||||
|
||||
try {
|
||||
$safeCriteria = $this->criteriaBuilder->buildSafe($query, $this->maxResults);
|
||||
$response = $this->storeApiClient->searchProducts($safeCriteria);
|
||||
|
||||
return $this->mapAndLogSearchResponse(
|
||||
response: $response,
|
||||
query: $query,
|
||||
commerceIntent: $commerceIntent,
|
||||
originalPrompt: $originalPrompt,
|
||||
usesHistoryContext: $usesHistoryContext,
|
||||
usedSafeCriteria: true
|
||||
);
|
||||
} catch (
|
||||
ClientExceptionInterface |
|
||||
RedirectionExceptionInterface |
|
||||
ServerExceptionInterface |
|
||||
TransportExceptionInterface |
|
||||
\RuntimeException $safeException
|
||||
) {
|
||||
$this->recordFailedSearch($safeException);
|
||||
$this->logger->warning('Shop search safe criteria retry failed', [
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'originalPrompt' => $originalPrompt,
|
||||
'normalizedPrompt' => $query->normalizedPrompt,
|
||||
'searchText' => $query->searchText,
|
||||
'brand' => $query->brand,
|
||||
'sizes' => $query->sizes,
|
||||
'priceMin' => $query->priceMin,
|
||||
'priceMax' => $query->priceMax,
|
||||
'usesHistoryContext' => $usesHistoryContext,
|
||||
'exceptionClass' => $e::class,
|
||||
'exceptionMessage' => $e->getMessage(),
|
||||
'exceptionClass' => $safeException::class,
|
||||
'exceptionMessage' => $safeException->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $response
|
||||
* @return ShopProductResult[]
|
||||
*/
|
||||
private function mapAndLogSearchResponse(
|
||||
array $response,
|
||||
CommerceSearchQuery $query,
|
||||
string $commerceIntent,
|
||||
string $originalPrompt,
|
||||
bool $usesHistoryContext,
|
||||
bool $usedSafeCriteria
|
||||
): array {
|
||||
$mappedProducts = $this->mapProducts($response);
|
||||
$rankedProducts = $this->rerankProducts($mappedProducts, $query);
|
||||
|
||||
@@ -372,6 +472,7 @@ final readonly class ShopSearchService
|
||||
'priceMin' => $query->priceMin,
|
||||
'priceMax' => $query->priceMax,
|
||||
'usesHistoryContext' => $usesHistoryContext,
|
||||
'usedSafeCriteria' => $usedSafeCriteria,
|
||||
'rawElementsCount' => is_array($response['elements'] ?? null) ? count($response['elements']) : 0,
|
||||
'mappedProductsCount' => count($mappedProducts),
|
||||
'rankedProductsCount' => count($rankedProducts),
|
||||
@@ -380,6 +481,50 @@ final readonly class ShopSearchService
|
||||
return $rankedProducts;
|
||||
}
|
||||
|
||||
private function logShopSearchFailure(
|
||||
CommerceSearchQuery $query,
|
||||
string $commerceIntent,
|
||||
string $originalPrompt,
|
||||
bool $usesHistoryContext,
|
||||
\Throwable $e
|
||||
): void {
|
||||
$this->logger->warning('Shop search request failed', [
|
||||
'commerceIntent' => $commerceIntent,
|
||||
'originalPrompt' => $originalPrompt,
|
||||
'normalizedPrompt' => $query->normalizedPrompt,
|
||||
'searchText' => $query->searchText,
|
||||
'brand' => $query->brand,
|
||||
'sizes' => $query->sizes,
|
||||
'priceMin' => $query->priceMin,
|
||||
'priceMax' => $query->priceMax,
|
||||
'usesHistoryContext' => $usesHistoryContext,
|
||||
'systemFailure' => $this->lastSearchHadSystemFailure,
|
||||
'failureReason' => $this->lastSearchFailureReason,
|
||||
'exceptionClass' => $e::class,
|
||||
'exceptionMessage' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function resetLastSearchFailure(): void
|
||||
{
|
||||
$this->lastSearchHadSystemFailure = false;
|
||||
$this->lastSearchFailureReason = null;
|
||||
}
|
||||
|
||||
private function recordFailedSearch(\Throwable $e): void
|
||||
{
|
||||
$isSystemFailure = $e instanceof StoreApiException
|
||||
? $e->isSystemFailure()
|
||||
: $e instanceof ServerExceptionInterface || $e instanceof TransportExceptionInterface;
|
||||
|
||||
if (!$isSystemFailure) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lastSearchHadSystemFailure = true;
|
||||
$this->lastSearchFailureReason = $e->getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $referenceProbeResults
|
||||
* @param ShopProductResult[] $rankedProducts
|
||||
|
||||
Reference in New Issue
Block a user