new config PromptBuilderConfig.php

This commit is contained in:
team 1
2026-04-19 14:49:51 +02:00
parent 4d944a5113
commit 39849d22cb
3 changed files with 109 additions and 103 deletions

View File

@@ -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;
} }

View File

@@ -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 (

View 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',
];
}