p64
This commit is contained in:
@@ -30,3 +30,46 @@ parameters:
|
||||
storage:
|
||||
directory_create_failed: 'Stream job directory could not be created.'
|
||||
write_failed: 'Stream job could not be written.'
|
||||
|
||||
frontend:
|
||||
document:
|
||||
title: 'AI Agent'
|
||||
ui:
|
||||
header_title: 'KI-Agent'
|
||||
footer_disclaimer: 'powered by mitho® | RetrieX kann fehlerhafte Ausgaben machen. RetrieX verwendet alle Daten zum Trainieren seiner Modelle.'
|
||||
buttons:
|
||||
clear: 'Diesen Chat löschen'
|
||||
send: 'Send'
|
||||
abort: 'Abbrechen'
|
||||
options:
|
||||
aria_label: 'Chat-Anzeigeoptionen'
|
||||
status_info: 'Statusinfo anzeigen'
|
||||
input:
|
||||
prompt_placeholder: 'Stelle eine Frage'
|
||||
assistant:
|
||||
loader: 'Antwort wird vorbereitet…'
|
||||
aborted: '[aborted]'
|
||||
history_cleared: 'History cleared.'
|
||||
source_chips:
|
||||
live_shop_data: 'Live-Shopdaten'
|
||||
run_meta:
|
||||
completed_title: 'Abgeschlossen'
|
||||
interrupted_title: 'Antwort wurde unterbrochen'
|
||||
completed_status: 'Status: abgeschlossen'
|
||||
interrupted_status: 'Status: unterbrochen'
|
||||
completed_empty_source: 'keine belastbare Datenbasis'
|
||||
interrupted_empty_source: 'nicht vollständig geprüft'
|
||||
pending_source_marker: 'wird geprüft'
|
||||
stream:
|
||||
incomplete: 'Der Antwort-Stream wurde beendet, bevor die Antwort abgeschlossen werden konnte.'
|
||||
job_not_found_retry: 'Der Antwort-Job wurde nicht mehr gefunden. Bitte sende die Anfrage erneut.'
|
||||
failed_retry: 'Der Antwort-Stream ist fehlgeschlagen. Bitte sende die Anfrage erneut.'
|
||||
interrupted_retry: 'Der Antwort-Stream wurde durch einen Verbindungsabbruch unterbrochen. Bitte sende die Anfrage erneut, falls die Antwort unvollständig ist.'
|
||||
missing_retry: 'Der Antwort-Job wurde nicht gefunden. Bitte sende die Anfrage erneut.'
|
||||
stale_retry: 'Der Antwort-Job liefert seit längerer Zeit keine neuen Daten. Der Stream wurde beendet.'
|
||||
connection_interrupted_retry: 'Die Verbindung zum Antwort-Stream wurde unterbrochen. Bitte sende die Anfrage erneut, falls die Antwort unvollständig ist.'
|
||||
guards:
|
||||
no_concrete_shop_response_markers:
|
||||
- 'keine konkrete shop-suchanfrage erkannt'
|
||||
- 'shop-suche noch nicht belastbar auflösen'
|
||||
- 'shop-suche noch nicht belastbar aufloesen'
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# RetrieX Patch 64 - Frontend Chat Messages Config
|
||||
|
||||
## Ziel
|
||||
|
||||
p64 erweitert den mit p63 eingeführten `chat-messages.yaml`-Schnitt auf die sichtbaren Frontend-Chattexte in `public/index.html` und `public/assets/js/base.js`.
|
||||
|
||||
Ziel ist, dass die im Browser sichtbaren Chat-UI-, Loader-, Status- und Stream-Fallback-Texte nicht mehr hart in HTML/JavaScript gepflegt werden müssen, sondern zentral über `config/retriex/chat-messages.yaml` konfigurierbar sind.
|
||||
|
||||
## Änderungen
|
||||
|
||||
- `config/retriex/chat-messages.yaml`
|
||||
- neuer Bereich `frontend`
|
||||
- UI-Texte für Titel, Header, Buttons, Placeholder, Footer und Optionen
|
||||
- Assistant-Hinweise wie Loader, Abbruch- und History-Cleared-Meldung
|
||||
- Frontend-Stream-Fallback-Meldungen
|
||||
- Run-Meta-Statuslabels
|
||||
- Live-Shopdaten-Chip-Label
|
||||
- konfigurierbare Marker für No-Concrete-Shop-Response-Erkennung
|
||||
|
||||
- `src/Config/ChatMessagesConfig.php`
|
||||
- `getFrontendMessages()` ergänzt
|
||||
- Validation um Frontend-Message-Pfade erweitert
|
||||
- List-Validation für konfigurierbare Guard-Marker ergänzt
|
||||
|
||||
- `src/Controller/ChatMessagesController.php`
|
||||
- neuer JSON-Endpunkt `/chat-messages/frontend`
|
||||
- liefert den `frontend`-Teil aus `chat-messages.yaml` an das Browser-Frontend
|
||||
- no-store/no-cache Header gesetzt
|
||||
|
||||
- `public/assets/js/base.js`
|
||||
- lädt Chat-Frontend-Messages beim Start aus `/chat-messages/frontend`
|
||||
- wendet UI-Texte über `data-chat-message-*` Attribute an
|
||||
- ersetzt sichtbare Loader-/Stream-/Status-/Abbruch-/History-Texte durch YAML-Werte
|
||||
- No-Concrete-Shop-Response-Marker kommen aus YAML statt aus JS-Stringliteralen
|
||||
|
||||
- `public/index.html`
|
||||
- sichtbare harte UI-Texte entfernt
|
||||
- stattdessen `data-chat-message-text`, `data-chat-message-placeholder` und `data-chat-message-aria-label`
|
||||
- Dokumenttitel wird durch `base.js` aus YAML gesetzt
|
||||
|
||||
## Bewusste Nicht-Ziele
|
||||
|
||||
- p64 verschiebt die bereits YAML-basierten Agent-/Production-UI-Blöcke aus `agent.yaml` noch nicht nach `chat-messages.yaml`.
|
||||
- p64 ändert keine RAG-, Shop-, Prompt-, Retrieval- oder Scoring-Logik.
|
||||
- Technische Protokollwerte wie SSE-Eventnamen, Job-Statuswerte, Storage Keys, CSS-Klassen und Debug-/Console-Meldungen bleiben im Code.
|
||||
|
||||
## Checks
|
||||
|
||||
Lokal geprüft:
|
||||
|
||||
```bash
|
||||
php -l src/Config/ChatMessagesConfig.php
|
||||
php -l src/Controller/ChatMessagesController.php
|
||||
php -l src/Controller/AskSseController.php
|
||||
node --check public/assets/js/base.js
|
||||
python3 - <<'PY'
|
||||
import yaml
|
||||
for path in ['config/retriex/chat-messages.yaml', 'config/services.yaml']:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
yaml.safe_load(f)
|
||||
print(f'YAML OK: {path}')
|
||||
PY
|
||||
```
|
||||
|
||||
Nicht ausführbar im entpackten ZIP ohne `vendor/`:
|
||||
|
||||
```bash
|
||||
php bin/console mto:agent:config:validate
|
||||
```
|
||||
|
||||
Fehler:
|
||||
|
||||
```text
|
||||
Dependencies are missing. Try running "composer install".
|
||||
```
|
||||
|
||||
## Empfohlene manuelle Regression
|
||||
|
||||
Nach Einspielen im echten Projekt mit Vendor-Abhängigkeiten:
|
||||
|
||||
```bash
|
||||
bin/console mto:agent:config:validate
|
||||
bin/console mto:agent:regression:test
|
||||
bin/console mto:agent:config:audit-source --details
|
||||
bin/console mto:agent:config:audit-patterns --details
|
||||
```
|
||||
|
||||
Browser-Check:
|
||||
|
||||
1. Startseite öffnen.
|
||||
2. Prüfen, ob Titel, Header, Buttons, Placeholder und Footer korrekt erscheinen.
|
||||
3. Eine normale Anfrage senden und Loader/Statuskarten prüfen.
|
||||
4. Stream abbrechen und Abbruchmeldung prüfen.
|
||||
5. Chat löschen und History-Cleared-Meldung prüfen.
|
||||
6. Einen Stream-/Reconnect-Fehlerfall prüfen, falls reproduzierbar.
|
||||
|
||||
## Folgearbeit
|
||||
|
||||
Falls wirklich alle chat-sichtbaren Texte in exakt einer Datei liegen sollen, sollte ein späterer Patch die bereits YAML-konfigurierten Agent-UI-Bereiche aus `agent.yaml` nach `chat-messages.yaml` verschieben und die Getter in `AgentRunnerConfig` sauber delegieren oder auf eine Message-spezifische Config-Fassade umstellen.
|
||||
@@ -1,4 +1,4 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const chatEl = document.getElementById('chat');
|
||||
const promptEl = document.getElementById('prompt');
|
||||
const sendBtn = document.getElementById('send');
|
||||
@@ -12,6 +12,106 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const JOB_COMPLETION_CATCHUP_GRACE_MS = 10000;
|
||||
const JOB_CLIENT_STALE_GRACE_MS = 150000;
|
||||
|
||||
let chatMessages = {};
|
||||
|
||||
function configuredMessage(path, parameters = {}) {
|
||||
const segments = String(path || '').split('.').filter(Boolean);
|
||||
let current = chatMessages;
|
||||
|
||||
for (const segment of segments) {
|
||||
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, segment)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
current = current[segment];
|
||||
}
|
||||
|
||||
if (typeof current !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return current.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(parameters, key)) {
|
||||
return match;
|
||||
}
|
||||
|
||||
return String(parameters[key] ?? '').replace(/\s+/g, ' ').trim();
|
||||
});
|
||||
}
|
||||
|
||||
function configuredMessageList(path) {
|
||||
const segments = String(path || '').split('.').filter(Boolean);
|
||||
let current = chatMessages;
|
||||
|
||||
for (const segment of segments) {
|
||||
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, segment)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
current = current[segment];
|
||||
}
|
||||
|
||||
return Array.isArray(current) ? current.filter((item) => typeof item === 'string' && item.trim() !== '') : [];
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function configuredEmphasis(path) {
|
||||
const text = configuredMessage(path);
|
||||
|
||||
return text === '' ? '' : `<em>${escapeHtml(text)}</em>`;
|
||||
}
|
||||
|
||||
async function loadConfiguredChatMessages() {
|
||||
try {
|
||||
const response = await fetch('/chat-messages/frontend', {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: {'Accept': 'application/json'},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Chat messages request failed with status:', response.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
chatMessages = payload && typeof payload === 'object' ? payload : {};
|
||||
} catch (err) {
|
||||
console.error('Chat messages load failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function applyConfiguredUiMessages(root = document) {
|
||||
const documentTitle = configuredMessage('document.title');
|
||||
|
||||
if (documentTitle !== '') {
|
||||
document.title = documentTitle;
|
||||
}
|
||||
|
||||
root.querySelectorAll('[data-chat-message-text]').forEach((element) => {
|
||||
element.textContent = configuredMessage(element.getAttribute('data-chat-message-text') || '');
|
||||
});
|
||||
|
||||
root.querySelectorAll('[data-chat-message-placeholder]').forEach((element) => {
|
||||
element.setAttribute('placeholder', configuredMessage(element.getAttribute('data-chat-message-placeholder') || ''));
|
||||
});
|
||||
|
||||
root.querySelectorAll('[data-chat-message-aria-label]').forEach((element) => {
|
||||
element.setAttribute('aria-label', configuredMessage(element.getAttribute('data-chat-message-aria-label') || ''));
|
||||
});
|
||||
}
|
||||
|
||||
await loadConfiguredChatMessages();
|
||||
applyConfiguredUiMessages();
|
||||
|
||||
const state = {
|
||||
abortRequested: false,
|
||||
isStreaming: false,
|
||||
@@ -118,9 +218,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function isNoConcreteShopResponse(value) {
|
||||
const normalized = normalizeContextHintText(value).toLowerCase();
|
||||
|
||||
return normalized.includes('keine konkrete shop-suchanfrage erkannt')
|
||||
|| normalized.includes('shop-suche noch nicht belastbar auflösen')
|
||||
|| normalized.includes('shop-suche noch nicht belastbar aufloesen');
|
||||
return configuredMessageList('guards.no_concrete_shop_response_markers')
|
||||
.some((marker) => normalized.includes(marker.toLowerCase()));
|
||||
}
|
||||
|
||||
function rememberCompletedTurn(userPrompt, assistantText) {
|
||||
@@ -278,7 +377,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function addLoader() {
|
||||
return addMessage('assistant', 'Antwort wird vorbereitet…', 'loader');
|
||||
return addMessage('assistant', escapeHtml(configuredMessage('assistant.loader')), 'loader');
|
||||
}
|
||||
|
||||
function hasMeaningfulChildContent(element) {
|
||||
@@ -504,7 +603,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let key = canonicalRetriexSourceChipKey(chip.textContent);
|
||||
|
||||
if (key === 'shopsystem' || key === 'liveshopdaten') {
|
||||
chip.textContent = 'Live-Shopdaten';
|
||||
chip.textContent = configuredMessage('source_chips.live_shop_data');
|
||||
key = 'liveshopdaten';
|
||||
}
|
||||
|
||||
@@ -643,9 +742,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
const isError = options.state === 'error';
|
||||
const titleText = isError ? 'Antwort wurde unterbrochen' : 'Abgeschlossen';
|
||||
const statusText = isError ? 'Status: unterbrochen' : 'Status: abgeschlossen';
|
||||
const emptySourceText = isError ? 'nicht vollständig geprüft' : 'keine belastbare Datenbasis';
|
||||
const titleText = isError ? configuredMessage('run_meta.interrupted_title') : configuredMessage('run_meta.completed_title');
|
||||
const statusText = isError ? configuredMessage('run_meta.interrupted_status') : configuredMessage('run_meta.completed_status');
|
||||
const emptySourceText = isError ? configuredMessage('run_meta.interrupted_empty_source') : configuredMessage('run_meta.completed_empty_source');
|
||||
|
||||
container.querySelectorAll('.retriex-run-meta[data-retriex-meta-id="run-status"]').forEach((card) => {
|
||||
card.setAttribute('data-retriex-meta-state', isError ? 'error' : 'completed');
|
||||
@@ -664,7 +763,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const emptySource = card.querySelector('.retriex-source-overview__empty');
|
||||
|
||||
if (emptySource && emptySource.textContent.trim() === 'wird geprüft') {
|
||||
if (emptySource && emptySource.textContent.trim() === configuredMessage('run_meta.pending_source_marker')) {
|
||||
emptySource.textContent = emptySourceText;
|
||||
}
|
||||
});
|
||||
@@ -919,7 +1018,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
};
|
||||
|
||||
const completeWithJobError = (message) => {
|
||||
appendError(message || 'Der Antwort-Stream wurde beendet, bevor die Antwort abgeschlossen werden konnte.');
|
||||
appendError(message || configuredMessage('stream.incomplete'));
|
||||
complete();
|
||||
};
|
||||
|
||||
@@ -937,7 +1036,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404 && Date.now() - lastClientProgressAt > JOB_CLIENT_STALE_GRACE_MS) {
|
||||
completeWithJobError('Der Antwort-Job wurde nicht mehr gefunden. Bitte sende die Anfrage erneut.');
|
||||
completeWithJobError(configuredMessage('stream.job_not_found_retry'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -952,17 +1051,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
if (status === 'failed') {
|
||||
completeWithJobError(message || 'Der Antwort-Stream ist fehlgeschlagen. Bitte sende die Anfrage erneut.');
|
||||
completeWithJobError(message || configuredMessage('stream.failed_retry'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'interrupted') {
|
||||
completeWithJobError(message || 'Der Antwort-Stream wurde durch einen Verbindungsabbruch unterbrochen. Bitte sende die Anfrage erneut, falls die Antwort unvollständig ist.');
|
||||
completeWithJobError(message || configuredMessage('stream.interrupted_retry'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'missing') {
|
||||
completeWithJobError(message || 'Der Antwort-Job wurde nicht gefunden. Bitte sende die Anfrage erneut.');
|
||||
completeWithJobError(message || configuredMessage('stream.missing_retry'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -972,7 +1071,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const staleAfterSeconds = parsePositiveInteger(statusPayload?.runningStaleAfterSeconds);
|
||||
|
||||
if (updatedAt > 0 && serverTime > 0 && staleAfterSeconds > 0 && serverTime - updatedAt > staleAfterSeconds + 15) {
|
||||
completeWithJobError(message || 'Der Antwort-Job liefert seit längerer Zeit keine neuen Daten. Der Stream wurde beendet.');
|
||||
completeWithJobError(message || configuredMessage('stream.stale_retry'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1065,7 +1164,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
bubble.classList.remove('loader');
|
||||
|
||||
const userMessage = 'Die Verbindung zum Antwort-Stream wurde unterbrochen. Bitte sende die Anfrage erneut, falls die Antwort unvollständig ist.';
|
||||
const userMessage = configuredMessage('stream.connection_interrupted_retry');
|
||||
|
||||
if (raw.trim() !== '') {
|
||||
const formattedMessage = `<em>${userMessage}</em>`;
|
||||
@@ -1094,7 +1193,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
state.completeStream?.();
|
||||
await releaseStreamResources();
|
||||
setBusyUi(false);
|
||||
addMessage('assistant', '<em>[aborted]</em>');
|
||||
addMessage('assistant', configuredEmphasis('assistant.aborted'));
|
||||
});
|
||||
|
||||
clearBtn.addEventListener('click', async () => {
|
||||
@@ -1116,6 +1215,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.debug('Could not clear last completed turn:', err);
|
||||
}
|
||||
chatEl.innerHTML = '';
|
||||
addMessage('assistant', '<em>History cleared.</em>');
|
||||
addMessage('assistant', configuredEmphasis('assistant.history_cleared'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>AI Agent</title>
|
||||
<title></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Markdown + Sanitizer -->
|
||||
@@ -25,29 +25,29 @@
|
||||
<div class="d-flex">
|
||||
<img src="/assets/img/logo.png" style="max-height: 20px;">
|
||||
</div>
|
||||
<div class="text-info fw-bold" style="font-size: 12px">KI-Agent</div>
|
||||
<div class="text-info fw-bold" style="font-size: 12px" data-chat-message-text="ui.header_title"></div>
|
||||
</div>
|
||||
<img src="/assets/img/logo.svg" style="max-height: 20px;">
|
||||
<div class="spacer"></div>
|
||||
|
||||
<button id="clear" class="btn btn-trans">Diesen Chat löschen</button>
|
||||
<button id="clear" class="btn btn-trans" data-chat-message-text="ui.buttons.clear"></button>
|
||||
</div>
|
||||
<div id="ai-cloud" class="ai-cloud d-none"></div>
|
||||
<div id="chat" class="chat"></div>
|
||||
|
||||
<div id="retriex-chat-options" class="retriex-chat-options p-2" aria-label="Chat-Anzeigeoptionen">
|
||||
<div id="retriex-chat-options" class="retriex-chat-options p-2" data-chat-message-aria-label="ui.options.aria_label">
|
||||
<label class="retriex-option-toggle" for="toggle-retriex-cards">
|
||||
<input id="toggle-retriex-cards" type="checkbox">
|
||||
<span>Statusinfo anzeigen</span>
|
||||
<span data-chat-message-text="ui.options.status_info"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<textarea id="prompt" class="form-control bg-dark" placeholder="Stelle eine Frage"></textarea>
|
||||
<button id="send" class="btn btn-trans">Send</button>
|
||||
<button id="abort" class="btn btn-trans" disabled>Abbrechen</button>
|
||||
<textarea id="prompt" class="form-control bg-dark" data-chat-message-placeholder="ui.input.prompt_placeholder"></textarea>
|
||||
<button id="send" class="btn btn-trans" data-chat-message-text="ui.buttons.send"></button>
|
||||
<button id="abort" class="btn btn-trans" disabled data-chat-message-text="ui.buttons.abort"></button>
|
||||
</div>
|
||||
<div class="small mt-2 text-center text-secondary">powered by mitho® | RetrieX kann fehlerhafte Ausgaben machen. RetrieX verwendet alle Daten zum Trainieren seiner Modelle.</div>
|
||||
<div class="small mt-2 text-center text-secondary" data-chat-message-text="ui.footer_disclaimer"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
44
qwen3-8b-system-pormpt.txt
Normal file
44
qwen3-8b-system-pormpt.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
# ==========================================
|
||||
# QWEN 3 8B - RAG OPTIMIZED CONFIGURATION
|
||||
# ==========================================
|
||||
|
||||
# Basis-Modell festlegen
|
||||
FROM qwen3:8b
|
||||
|
||||
# --- TECHNISCHE PARAMETER (Sampling & Performance) ---
|
||||
# Schaltet Kreativität aus, um Halluzinationen in RAG zu vermeiden
|
||||
PARAMETER temperature 0.0
|
||||
# Sorgt für deterministische (gleichbleibende) Antworten
|
||||
PARAMETER seed 42
|
||||
# Erweitert den Arbeitsspeicher für Dokumente (16k Token)
|
||||
PARAMETER num_ctx 16384
|
||||
# Verhindert Wort-Wiederholungen aus den Quelltexten
|
||||
PARAMETER repeat_penalty 1.1
|
||||
# Definiert, wie viele Tokens gleichzeitig verarbeitet werden
|
||||
PARAMETER num_predict 2048
|
||||
|
||||
# --- SYSTEM PROMPT (Verhalten & Regeln) ---
|
||||
SYSTEM """
|
||||
Du bist ein spezialisierter RAG-Analyst für Qwen 3. Deine einzige Aufgabe ist die präzise Extraktion von Informationen aus dem bereitgestellten KONTEXT.
|
||||
|
||||
### STRIKTE ARBEITSANWEISUNGEN:
|
||||
1. QUELLEN-TREUE: Antworte AUSSCHLIESSLICH basierend auf den übergebenen Dokumenten.
|
||||
2. UNWISSENHEIT: Wenn die Information nicht im Kontext steht, antworte exakt mit: "Information nicht in den Dokumenten vorhanden."
|
||||
3. KEINE HALLUZINATIONEN: Erfinde keine Fakten und ergänze kein externes Wissen.
|
||||
4. ZITIERPFLICHT: Füge hinter jede Fakten-Aussage die Quelle in eckigen Klammern an, z.B. [Dokument Name, Seite X].
|
||||
5. FORMATIERUNG: Nutze Markdown-Tabellen für Datenvergleiche und Bulletpoints für Listen.
|
||||
|
||||
### SPRACHE & TON:
|
||||
- Sprache: Deutsch (Sachlich, professionell).
|
||||
- Keine Höflichkeitsfloskeln am Anfang oder Ende.
|
||||
- Direkt auf den Punkt kommen.
|
||||
"""
|
||||
|
||||
# --- TEMPLATE (ChatML Struktur) ---
|
||||
TEMPLATE """{{ if .System }}<|im_start|>system
|
||||
{{ .System }}<|im_end|>
|
||||
{{ end }}{{ if .Prompt }}<|im_start|>user
|
||||
{{ .Prompt }}<|im_end|>
|
||||
{{ end }}<|im_start|>assistant
|
||||
{{ .Response }}<|im_end|>"""
|
||||
|
||||
@@ -142,6 +142,20 @@ final class ChatMessagesConfig
|
||||
return $this->string('sse.storage.write_failed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getFrontendMessages(): array
|
||||
{
|
||||
$messages = $this->value('frontend');
|
||||
|
||||
if (is_array($messages)) {
|
||||
return $messages;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('RetrieX chat messages config key "frontend" must be an array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -165,6 +179,14 @@ final class ChatMessagesConfig
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->requiredStringListPaths() as $path) {
|
||||
try {
|
||||
$this->stringList($path);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $errors === [] ? 'OK' : 'ERROR',
|
||||
'errors' => $errors,
|
||||
@@ -202,6 +224,43 @@ final class ChatMessagesConfig
|
||||
'sse.claim.missing',
|
||||
'sse.storage.directory_create_failed',
|
||||
'sse.storage.write_failed',
|
||||
'frontend.document.title',
|
||||
'frontend.ui.header_title',
|
||||
'frontend.ui.footer_disclaimer',
|
||||
'frontend.ui.buttons.clear',
|
||||
'frontend.ui.buttons.send',
|
||||
'frontend.ui.buttons.abort',
|
||||
'frontend.ui.options.aria_label',
|
||||
'frontend.ui.options.status_info',
|
||||
'frontend.ui.input.prompt_placeholder',
|
||||
'frontend.assistant.loader',
|
||||
'frontend.assistant.aborted',
|
||||
'frontend.assistant.history_cleared',
|
||||
'frontend.source_chips.live_shop_data',
|
||||
'frontend.run_meta.completed_title',
|
||||
'frontend.run_meta.interrupted_title',
|
||||
'frontend.run_meta.completed_status',
|
||||
'frontend.run_meta.interrupted_status',
|
||||
'frontend.run_meta.completed_empty_source',
|
||||
'frontend.run_meta.interrupted_empty_source',
|
||||
'frontend.run_meta.pending_source_marker',
|
||||
'frontend.stream.incomplete',
|
||||
'frontend.stream.job_not_found_retry',
|
||||
'frontend.stream.failed_retry',
|
||||
'frontend.stream.interrupted_retry',
|
||||
'frontend.stream.missing_retry',
|
||||
'frontend.stream.stale_retry',
|
||||
'frontend.stream.connection_interrupted_retry',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function requiredStringListPaths(): array
|
||||
{
|
||||
return [
|
||||
'frontend.guards.no_concrete_shop_response_markers',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -237,6 +296,34 @@ final class ChatMessagesConfig
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function stringList(string $path): array
|
||||
{
|
||||
$value = $this->value($path);
|
||||
|
||||
if (!is_array($value)) {
|
||||
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must be a list of non-empty strings.', $path));
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($value as $item) {
|
||||
if (!is_string($item) || trim($item) === '') {
|
||||
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must contain only non-empty strings.', $path));
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
if ($items === []) {
|
||||
throw new \InvalidArgumentException(sprintf('RetrieX chat messages config key "%s" must not be empty.', $path));
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function string(string $path): string
|
||||
{
|
||||
$value = $this->value($path);
|
||||
|
||||
27
src/Controller/ChatMessagesController.php
Normal file
27
src/Controller/ChatMessagesController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Config\ChatMessagesConfig;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
final readonly class ChatMessagesController
|
||||
{
|
||||
public function __construct(private ChatMessagesConfig $chatMessages)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/chat-messages/frontend', name: 'chat_messages_frontend', methods: ['GET'])]
|
||||
public function frontend(): JsonResponse
|
||||
{
|
||||
$response = new JsonResponse($this->chatMessages->getFrontendMessages());
|
||||
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
||||
$response->headers->set('Pragma', 'no-cache');
|
||||
$response->headers->set('Expires', '0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user