new config PromptBuilderConfig.php
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Agent;
|
namespace App\Agent;
|
||||||
|
|
||||||
use App\Commerce\Dto\ShopProductResult;
|
use App\Commerce\Dto\ShopProductResult;
|
||||||
|
use App\Config\PromptBuilderConfig;
|
||||||
use App\Context\ContextService;
|
use App\Context\ContextService;
|
||||||
use App\Repository\SystemPromptRepository;
|
use App\Repository\SystemPromptRepository;
|
||||||
use App\Service\ModelGenerationConfigProvider;
|
use App\Service\ModelGenerationConfigProvider;
|
||||||
@@ -13,95 +14,6 @@ use RuntimeException;
|
|||||||
|
|
||||||
final readonly class PromptBuilder
|
final readonly class PromptBuilder
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Approximate character-to-token ratio for conservative prompt budgeting.
|
|
||||||
*/
|
|
||||||
private const CHARS_PER_TOKEN = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keep a small gap so history does not consume the last available prompt space.
|
|
||||||
*/
|
|
||||||
private const HISTORY_PADDING_CHARS = 400;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserve some space for the model output.
|
|
||||||
*/
|
|
||||||
private const OUTPUT_RESERVE_RATIO = 0.25;
|
|
||||||
private const OUTPUT_RESERVE_MIN_TOKENS = 768;
|
|
||||||
private const OUTPUT_RESERVE_MAX_TOKENS = 6000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserve a small safety buffer to avoid hitting the context limit too tightly.
|
|
||||||
*/
|
|
||||||
private const SAFETY_RESERVE_RATIO = 0.05;
|
|
||||||
private const SAFETY_RESERVE_MIN_TOKENS = 256;
|
|
||||||
private const SAFETY_RESERVE_MAX_TOKENS = 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure the prompt budget never collapses completely on smaller models.
|
|
||||||
*/
|
|
||||||
private const MIN_PROMPT_BUDGET_TOKENS = 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limit how many ranked shop results are passed into the final prompt.
|
|
||||||
* The shop search may return many candidates, but the LLM should only see
|
|
||||||
* the most relevant top subset after local reranking.
|
|
||||||
*/
|
|
||||||
private const MAX_SHOP_RESULTS_IN_PROMPT = 8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Technical product prompts should be answered like documentation,
|
|
||||||
* not like sales copy.
|
|
||||||
*/
|
|
||||||
private const TECHNICAL_PRODUCT_KEYWORDS = [
|
|
||||||
'technisch',
|
|
||||||
'technical',
|
|
||||||
'produkt',
|
|
||||||
'product',
|
|
||||||
'gerät',
|
|
||||||
'device',
|
|
||||||
'modell',
|
|
||||||
'model',
|
|
||||||
'messprinzip',
|
|
||||||
'measurement principle',
|
|
||||||
'schnittstelle',
|
|
||||||
'interface',
|
|
||||||
'relais',
|
|
||||||
'relay',
|
|
||||||
'indikator',
|
|
||||||
'indicator',
|
|
||||||
'spannung',
|
|
||||||
'voltage',
|
|
||||||
'strom',
|
|
||||||
'current',
|
|
||||||
'druck',
|
|
||||||
'pressure',
|
|
||||||
'temperatur',
|
|
||||||
'temperature',
|
|
||||||
'schutzart',
|
|
||||||
'ip',
|
|
||||||
'fehlercode',
|
|
||||||
'error code',
|
|
||||||
'wasserhärte',
|
|
||||||
'hardness',
|
|
||||||
'testomat',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const ACCESSORY_REQUEST_KEYWORDS = [
|
|
||||||
'passend',
|
|
||||||
'passende',
|
|
||||||
'passendes',
|
|
||||||
'zubehör',
|
|
||||||
'zubehor',
|
|
||||||
'dazu',
|
|
||||||
'indikator',
|
|
||||||
'reagenz',
|
|
||||||
'kit',
|
|
||||||
'set',
|
|
||||||
'zusatz',
|
|
||||||
'ergänzung',
|
|
||||||
'ergaenzung',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ContextService $contextService,
|
private ContextService $contextService,
|
||||||
@@ -257,7 +169,7 @@ final readonly class PromptBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
$totalCount = count($normalizedShopResults);
|
$totalCount = count($normalizedShopResults);
|
||||||
$limitedShopResults = array_slice($normalizedShopResults, 0, self::MAX_SHOP_RESULTS_IN_PROMPT);
|
$limitedShopResults = array_slice($normalizedShopResults, 0, PromptBuilderConfig::MAX_SHOP_RESULTS_IN_PROMPT);
|
||||||
$isDetailed = count($limitedShopResults) <= 5;
|
$isDetailed = count($limitedShopResults) <= 5;
|
||||||
$lines = [];
|
$lines = [];
|
||||||
|
|
||||||
@@ -440,27 +352,27 @@ final readonly class PromptBuilder
|
|||||||
$numCtx = $this->modelGenerationConfigProvider->getActiveNumCtx();
|
$numCtx = $this->modelGenerationConfigProvider->getActiveNumCtx();
|
||||||
|
|
||||||
$outputReserveTokens = $this->clamp(
|
$outputReserveTokens = $this->clamp(
|
||||||
(int) floor($numCtx * self::OUTPUT_RESERVE_RATIO),
|
(int) floor($numCtx * PromptBuilderConfig::OUTPUT_RESERVE_RATIO),
|
||||||
self::OUTPUT_RESERVE_MIN_TOKENS,
|
PromptBuilderConfig::OUTPUT_RESERVE_MIN_TOKENS,
|
||||||
self::OUTPUT_RESERVE_MAX_TOKENS
|
PromptBuilderConfig::OUTPUT_RESERVE_MAX_TOKENS
|
||||||
);
|
);
|
||||||
|
|
||||||
$safetyReserveTokens = $this->clamp(
|
$safetyReserveTokens = $this->clamp(
|
||||||
(int) floor($numCtx * self::SAFETY_RESERVE_RATIO),
|
(int) floor($numCtx * PromptBuilderConfig::SAFETY_RESERVE_RATIO),
|
||||||
self::SAFETY_RESERVE_MIN_TOKENS,
|
PromptBuilderConfig::SAFETY_RESERVE_MIN_TOKENS,
|
||||||
self::SAFETY_RESERVE_MAX_TOKENS
|
PromptBuilderConfig::SAFETY_RESERVE_MAX_TOKENS
|
||||||
);
|
);
|
||||||
|
|
||||||
$promptBudgetTokens = max(
|
$promptBudgetTokens = max(
|
||||||
self::MIN_PROMPT_BUDGET_TOKENS,
|
PromptBuilderConfig::MIN_PROMPT_BUDGET_TOKENS,
|
||||||
$numCtx - $outputReserveTokens - $safetyReserveTokens
|
$numCtx - $outputReserveTokens - $safetyReserveTokens
|
||||||
);
|
);
|
||||||
|
|
||||||
$promptBudgetChars = $promptBudgetTokens * self::CHARS_PER_TOKEN;
|
$promptBudgetChars = $promptBudgetTokens * PromptBuilderConfig::CHARS_PER_TOKEN;
|
||||||
|
|
||||||
$remaining = $promptBudgetChars
|
$remaining = $promptBudgetChars
|
||||||
- mb_strlen($fixedPrompt)
|
- mb_strlen($fixedPrompt)
|
||||||
- self::HISTORY_PADDING_CHARS;
|
- PromptBuilderConfig::HISTORY_PADDING_CHARS;
|
||||||
|
|
||||||
return max(0, $remaining);
|
return max(0, $remaining);
|
||||||
}
|
}
|
||||||
@@ -577,7 +489,7 @@ final readonly class PromptBuilder
|
|||||||
|
|
||||||
$matches = 0;
|
$matches = 0;
|
||||||
|
|
||||||
foreach (self::TECHNICAL_PRODUCT_KEYWORDS as $keyword) {
|
foreach (PromptBuilderConfig::TECHNICAL_PRODUCT_KEYWORDS as $keyword) {
|
||||||
if (str_contains($normalized, $keyword)) {
|
if (str_contains($normalized, $keyword)) {
|
||||||
$matches++;
|
$matches++;
|
||||||
}
|
}
|
||||||
@@ -594,7 +506,7 @@ final readonly class PromptBuilder
|
|||||||
{
|
{
|
||||||
$normalized = mb_strtolower($prompt, 'UTF-8');
|
$normalized = mb_strtolower($prompt, 'UTF-8');
|
||||||
|
|
||||||
foreach (self::ACCESSORY_REQUEST_KEYWORDS as $keyword) {
|
foreach (PromptBuilderConfig::ACCESSORY_REQUEST_KEYWORDS as $keyword) {
|
||||||
if (str_contains($normalized, $keyword)) {
|
if (str_contains($normalized, $keyword)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ final readonly class ShopSearchService
|
|||||||
): array {
|
): array {
|
||||||
$criteria = $this->criteriaBuilder->build($query, $this->maxResults);
|
$criteria = $this->criteriaBuilder->build($query, $this->maxResults);
|
||||||
|
|
||||||
$response = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = $this->storeApiClient->searchProducts($criteria);
|
$response = $this->storeApiClient->searchProducts($criteria);
|
||||||
} catch (
|
} catch (
|
||||||
|
|||||||
96
src/Config/PromptBuilderConfig.php
Normal file
96
src/Config/PromptBuilderConfig.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Config;
|
||||||
|
|
||||||
|
class PromptBuilderConfig{
|
||||||
|
/**
|
||||||
|
* Approximate character-to-token ratio for conservative prompt budgeting.
|
||||||
|
*/
|
||||||
|
public const CHARS_PER_TOKEN = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep a small gap so history does not consume the last available prompt space.
|
||||||
|
*/
|
||||||
|
public const HISTORY_PADDING_CHARS = 400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve some space for the model output.
|
||||||
|
*/
|
||||||
|
public const OUTPUT_RESERVE_RATIO = 0.25;
|
||||||
|
public const OUTPUT_RESERVE_MIN_TOKENS = 768;
|
||||||
|
public const OUTPUT_RESERVE_MAX_TOKENS = 6000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve a small safety buffer to avoid hitting the context limit too tightly.
|
||||||
|
*/
|
||||||
|
public const SAFETY_RESERVE_RATIO = 0.05;
|
||||||
|
public const SAFETY_RESERVE_MIN_TOKENS = 256;
|
||||||
|
public const SAFETY_RESERVE_MAX_TOKENS = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the prompt budget never collapses completely on smaller models.
|
||||||
|
*/
|
||||||
|
public const MIN_PROMPT_BUDGET_TOKENS = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit how many ranked shop results are passed into the final prompt.
|
||||||
|
* The shop search may return many candidates, but the LLM should only see
|
||||||
|
* the most relevant top subset after local reranking.
|
||||||
|
*/
|
||||||
|
public const MAX_SHOP_RESULTS_IN_PROMPT = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Technical product prompts should be answered like documentation,
|
||||||
|
* not like sales copy.
|
||||||
|
*/
|
||||||
|
public const TECHNICAL_PRODUCT_KEYWORDS = [
|
||||||
|
'technisch',
|
||||||
|
'technical',
|
||||||
|
'produkt',
|
||||||
|
'product',
|
||||||
|
'gerät',
|
||||||
|
'device',
|
||||||
|
'modell',
|
||||||
|
'model',
|
||||||
|
'messprinzip',
|
||||||
|
'measurement principle',
|
||||||
|
'schnittstelle',
|
||||||
|
'interface',
|
||||||
|
'relais',
|
||||||
|
'relay',
|
||||||
|
'indikator',
|
||||||
|
'indicator',
|
||||||
|
'spannung',
|
||||||
|
'voltage',
|
||||||
|
'strom',
|
||||||
|
'current',
|
||||||
|
'druck',
|
||||||
|
'pressure',
|
||||||
|
'temperatur',
|
||||||
|
'temperature',
|
||||||
|
'schutzart',
|
||||||
|
'ip',
|
||||||
|
'fehlercode',
|
||||||
|
'error code',
|
||||||
|
'wasserhärte',
|
||||||
|
'hardness',
|
||||||
|
'testomat',
|
||||||
|
];
|
||||||
|
|
||||||
|
public const ACCESSORY_REQUEST_KEYWORDS = [
|
||||||
|
'passend',
|
||||||
|
'passende',
|
||||||
|
'passendes',
|
||||||
|
'zubehör',
|
||||||
|
'zubehor',
|
||||||
|
'dazu',
|
||||||
|
'indikator',
|
||||||
|
'reagenz',
|
||||||
|
'kit',
|
||||||
|
'set',
|
||||||
|
'zusatz',
|
||||||
|
'ergänzung',
|
||||||
|
'ergaenzung',
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user