This commit is contained in:
team 1
2026-05-09 12:04:06 +02:00
parent 7f25335c44
commit dabbc33f07
5 changed files with 171 additions and 18 deletions

View File

@@ -700,6 +700,17 @@ final readonly class AgentRunner
'meta'
);
$followUpActionsMessage = $this->buildFollowUpActionsMessage(
isCommerceIntent: $this->isCommerceIntent($commerceIntent),
hasShopResults: $shopResults !== [],
hasKnowledge: $this->isDirectKnowledgeEvidence($knowledgeEvidenceState),
shopSearchHadSystemFailure: $primaryShopSearchHadSystemFailure
);
if ($followUpActionsMessage !== '') {
yield $this->systemMsg($followUpActionsMessage, 'meta');
}
/* if ($sources !== []) {
yield $this->emitSources(
$sources,
@@ -4850,20 +4861,27 @@ final readonly class AgentRunner
return $this->agentRunnerConfig->getProductionUiTemplate('relevance_default');
}
private function buildFollowUpActionsMessage(bool $isCommerceIntent, bool $hasShopResults, bool $hasKnowledge): string
{
if (!$isCommerceIntent && !$hasShopResults && !$hasKnowledge) {
private function buildFollowUpActionsMessage(
bool $isCommerceIntent,
bool $hasShopResults,
bool $hasKnowledge,
bool $shopSearchHadSystemFailure
): string {
if (!$this->agentRunnerConfig->isProductionUiFollowUpActionsEnabled()) {
return '';
}
$actions = [];
$seenActionKeys = [];
if ($isCommerceIntent || $hasShopResults) {
$actions = array_merge($actions, $this->agentRunnerConfig->getProductionUiFollowUpActions('commerce'));
if ($hasShopResults) {
$this->appendFollowUpActions($actions, $seenActionKeys, $this->agentRunnerConfig->getProductionUiFollowUpActions('shop_results'));
} elseif ($isCommerceIntent && !$shopSearchHadSystemFailure) {
$this->appendFollowUpActions($actions, $seenActionKeys, $this->agentRunnerConfig->getProductionUiFollowUpActions('commerce'));
}
if ($hasKnowledge || $hasShopResults) {
$actions = array_merge($actions, $this->agentRunnerConfig->getProductionUiFollowUpActions('knowledge'));
if ($hasKnowledge) {
$this->appendFollowUpActions($actions, $seenActionKeys, $this->agentRunnerConfig->getProductionUiFollowUpActions('knowledge'));
}
if ($actions === []) {
@@ -4876,15 +4894,10 @@ final readonly class AgentRunner
. '<div class="retriex-action-chip-row">';
foreach ($actions as $action) {
$label = (string) ($action['label'] ?? '');
$actionPrompt = (string) ($action['prompt'] ?? '');
if ($label === '' || $actionPrompt === '') {
continue;
}
$html .= '<button type="button" class="retriex-action-chip" data-retriex-action-prompt="'
. htmlspecialchars($actionPrompt, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. htmlspecialchars($action['prompt'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '">'
. htmlspecialchars($label, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. htmlspecialchars($action['label'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '</button>';
}
@@ -4893,6 +4906,34 @@ final readonly class AgentRunner
return $html;
}
/**
* @param array<int, array{label:string, prompt:string}> $actions
* @param array<string, bool> $seenActionKeys
* @param array<int, array{label:string, prompt:string}> $items
*/
private function appendFollowUpActions(array &$actions, array &$seenActionKeys, array $items): void
{
foreach ($items as $item) {
$label = trim((string) ($item['label'] ?? ''));
$actionPrompt = trim((string) ($item['prompt'] ?? ''));
if ($label === '' || $actionPrompt === '') {
continue;
}
$key = mb_strtolower($label . "\n" . $actionPrompt, 'UTF-8');
if (isset($seenActionKeys[$key])) {
continue;
}
$seenActionKeys[$key] = true;
$actions[] = [
'label' => $label,
'prompt' => $actionPrompt,
];
}
}
private function buildShopSearchMetaMessage(
string $query,
string $commerceIntent,

View File

@@ -979,12 +979,23 @@ final class AgentRunnerConfig
return $this->getRequiredInt('production_ui.shop_results.max_cards');
}
public function isProductionUiFollowUpActionsEnabled(): bool
{
if ($this->chatMessages !== null) {
return $this->chatMessages->getBool('agent.production_ui.follow_up_actions.enabled');
}
return $this->getOptionalBool('production_ui.follow_up_actions.enabled', false);
}
/**
* @return array<int, array{label:string, prompt:string}>
*/
public function getProductionUiFollowUpActions(string $group): array
{
return $this->getChatActionList('agent.production_ui.follow_up_actions.' . $group, 'production_ui.follow_up_actions.' . $group);
$legacyGroup = $group === 'shop_results' ? 'commerce' : $group;
return $this->getChatActionList('agent.production_ui.follow_up_actions.' . $group, 'production_ui.follow_up_actions.' . $legacyGroup);
}
public function getNoLlmProductField(string $key): string

View File

@@ -172,6 +172,11 @@ final class ChatMessagesConfig
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be a string.', $path));
}
public function getBool(string $path): bool
{
return $this->bool($path);
}
/**
* @return array<int, array{label:string, prompt:string}>
*/
@@ -211,6 +216,14 @@ final class ChatMessagesConfig
}
}
foreach ($this->requiredBoolPaths() as $path) {
try {
$this->bool($path);
} catch (\InvalidArgumentException $e) {
$errors[] = $e->getMessage();
}
}
foreach ($this->requiredActionListPaths() as $path) {
try {
$this->actionList($path);
@@ -443,10 +456,21 @@ final class ChatMessagesConfig
{
return [
'agent.production_ui.follow_up_actions.commerce',
'agent.production_ui.follow_up_actions.shop_results',
'agent.production_ui.follow_up_actions.knowledge',
];
}
/**
* @return list<string>
*/
private function requiredBoolPaths(): array
{
return [
'agent.production_ui.follow_up_actions.enabled',
];
}
/**
* @return list<string>
*/
@@ -566,6 +590,29 @@ final class ChatMessagesConfig
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be a non-empty string.', $path));
}
private function bool(string $path): bool
{
$value = $this->value($path);
if (is_bool($value)) {
return $value;
}
if (is_scalar($value)) {
$normalized = strtolower(trim((string) $value));
if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
return true;
}
if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
return false;
}
}
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be boolean.', $path));
}
private function value(string $path): mixed
{
$current = $this->config;