add history to shop search

This commit is contained in:
team 1
2026-04-17 12:59:21 +02:00
parent bab3682975
commit ae2b52ad18
8 changed files with 630 additions and 172 deletions

View File

@@ -72,12 +72,69 @@ final class ContextService
return '';
}
$maxLines = $full ? ContextServiceConfig::MAX_FULL_LINES : ContextServiceConfig::MAX_VISIBLE_REGULAR_LINES;
$maxLines = $full
? ContextServiceConfig::MAX_FULL_LINES
: ContextServiceConfig::MAX_VISIBLE_REGULAR_LINES;
$selected = array_slice($lines, -$maxLines);
return implode("\n", $selected);
}
/**
* Returns as much recent history as possible within a character budget.
*
* Strategy:
* - Preserve complete conversation turns whenever possible
* - Prioritize the newest turns
* - Only truncate when even the newest single turn is larger than the budget
*
* A turn starts with "Question: " and includes the corresponding assistant answer.
*/
public function buildUserContextWithinBudget(string $userId, int $maxChars): string
{
if ($maxChars <= 0) {
return '';
}
$history = $this->readHistoryFile($userId);
if ($history === '') {
return '';
}
$turns = $this->splitHistoryIntoTurns($history);
if ($turns === []) {
return $this->truncateTurnToBudget($history, $maxChars);
}
$selected = [];
$currentLength = 0;
for ($i = count($turns) - 1; $i >= 0; $i--) {
$turn = $turns[$i];
$turnLength = mb_strlen($turn);
$separatorLength = $selected === [] ? 0 : 2; // "\n\n"
if (($currentLength + $separatorLength + $turnLength) <= $maxChars) {
array_unshift($selected, $turn);
$currentLength += $separatorLength + $turnLength;
continue;
}
// If nothing fits yet, keep at least the newest turn in truncated form.
if ($selected === []) {
$truncatedTurn = $this->truncateTurnToBudget($turn, $maxChars);
if ($truncatedTurn !== '') {
$selected[] = $truncatedTurn;
}
}
break;
}
return implode("\n\n", $selected);
}
/**
* Appends a completed interaction to the user's history.
*
@@ -105,6 +162,82 @@ final class ContextService
}
}
/**
* Reads the raw history file contents for a user.
*/
private function readHistoryFile(string $userId): string
{
$path = $this->getHistoryPath($userId);
if (!is_file($path)) {
return '';
}
$content = file_get_contents($path);
if ($content === false) {
return '';
}
return trim($content);
}
/**
* Splits append-only history into complete turns.
*
* Each turn starts with "Question: ".
*/
private function splitHistoryIntoTurns(string $history): array
{
$history = trim($history);
if ($history === '') {
return [];
}
$parts = preg_split('/(?=^Question:\s)/m', $history);
if ($parts === false) {
return [];
}
$turns = [];
foreach ($parts as $part) {
$part = trim($part);
if ($part === '') {
continue;
}
$turns[] = $part;
}
return $turns;
}
/**
* Truncates a single turn to fit into the available budget.
*
* The start of the turn is preserved so the "Question: ..." marker remains intact.
*/
private function truncateTurnToBudget(string $turn, int $maxChars): string
{
$turn = trim($turn);
if ($turn === '' || $maxChars <= 0) {
return '';
}
if (mb_strlen($turn) <= $maxChars) {
return $turn;
}
if ($maxChars <= 3) {
return mb_substr($turn, 0, $maxChars);
}
return rtrim(mb_substr($turn, 0, $maxChars - 3)) . '...';
}
/**
* Resolves the absolute history file path for a user.
*/
@@ -114,4 +247,4 @@ final class ContextService
return $this->historyDir . '/' . $safeUserId . '.txt';
}
}
}