fix(advisor): generic — drop trailing source list in answer + de-duplicate source card
Two structural fixes (not query-specific): - Proxy prompt: forbid ANY trailing "Quellen:"/"Quellen im RAG-System" list and make it the LAST instruction so it overrides the soul file's answer-structure + example that teach a closing sources section. Applies to every answer. - KnowledgeUnitCard: render the label only when it differs from regulation.short, so a source whose label == short name no longer prints twice. Applies to every source. Answer text is still never parsed in the FE (sources live in the pane). + card test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,10 @@ const FORMAT_GUIDANCE = `\n\n## Antwortformat (WICHTIG)
|
|||||||
nummerierte Schritte und **Fettung** fuer Schluesselbegriffe. Halte Absaetze kurz.
|
nummerierte Schritte und **Fettung** fuer Schluesselbegriffe. Halte Absaetze kurz.
|
||||||
- Nenne Fundstellen/Quellen NICHT im Fliesstext (kein "(Art. 30 DSGVO)", keine "[Quelle 1]").
|
- Nenne Fundstellen/Quellen NICHT im Fliesstext (kein "(Art. 30 DSGVO)", keine "[Quelle 1]").
|
||||||
Die Quellen werden dem Nutzer in einem EIGENEN Bereich neben der Antwort angezeigt.
|
Die Quellen werden dem Nutzer in einem EIGENEN Bereich neben der Antwort angezeigt.
|
||||||
|
- Beende die Antwort NIEMALS mit einer Quellen-/Fundstellen-Liste (kein "Quellen:", kein
|
||||||
|
"--- Quellen im RAG-System: ...", kein "Quellen im RAG-System"). KEINE Quellenaufzaehlung im
|
||||||
|
Antworttext. Dies UEBERSCHREIBT jede anderslautende Struktur-/Beispielvorgabe weiter oben im
|
||||||
|
System-Prompt (auch eine dort gezeigte "Quellen:"-Abschlusssektion gilt hier NICHT).
|
||||||
- Schreibe so, dass die Antwort auch ohne eingebettete Zitate vollstaendig verstaendlich ist.`
|
- Schreibe so, dass die Antwort auch ohne eingebettete Zitate vollstaendig verstaendlich ist.`
|
||||||
|
|
||||||
const COUNTRY_LABELS: Record<Country, string> = {
|
const COUNTRY_LABELS: Record<Country, string> = {
|
||||||
@@ -124,8 +128,8 @@ export async function POST(request: NextRequest) {
|
|||||||
systemContent += `\n\n## Relevanter Kontext aus dem RAG-System (deine EINZIGEN Rechtsquellen)\n\nDies sind deine einzigen zulaessigen Rechtsquellen. Triff keine konkrete Rechtsaussage (Zahl, Frist, Schwelle, Pflicht, Fundstelle), die nicht hier oder im Controls-Block belegt ist — sonst sage offen, dass du sie aus deinen Quellen nicht belegen kannst.\n\n${evidence.contextText}`
|
systemContent += `\n\n## Relevanter Kontext aus dem RAG-System (deine EINZIGEN Rechtsquellen)\n\nDies sind deine einzigen zulaessigen Rechtsquellen. Triff keine konkrete Rechtsaussage (Zahl, Frist, Schwelle, Pflicht, Fundstelle), die nicht hier oder im Controls-Block belegt ist — sonst sage offen, dass du sie aus deinen Quellen nicht belegen kannst.\n\n${evidence.contextText}`
|
||||||
}
|
}
|
||||||
if (controlsContext) systemContent += `\n\n${controlsContext}`
|
if (controlsContext) systemContent += `\n\n${controlsContext}`
|
||||||
systemContent += FORMAT_GUIDANCE
|
|
||||||
systemContent += `\n\n## Aktueller SDK-Schritt\nDer Nutzer befindet sich im SDK-Schritt: ${currentStep}`
|
systemContent += `\n\n## Aktueller SDK-Schritt\nDer Nutzer befindet sich im SDK-Schritt: ${currentStep}`
|
||||||
|
systemContent += FORMAT_GUIDANCE // LAST instruction: overrides the soul's trailing "Quellen" structure/example
|
||||||
|
|
||||||
// 4. Nachrichten (History auf die letzten 6 begrenzen)
|
// 4. Nachrichten (History auf die letzten 6 begrenzen)
|
||||||
const messages: ChatMessage[] = [
|
const messages: ChatMessage[] = [
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { KnowledgeUnitCard } from './KnowledgeUnitCard'
|
||||||
|
import type { KnowledgeUnit } from '@/lib/sdk/advisor/evidence'
|
||||||
|
|
||||||
|
const base: KnowledgeUnit = { id: 's1', regulation: { code: 'dsk', short: 'DSK Sdm B51' } }
|
||||||
|
|
||||||
|
describe('KnowledgeUnitCard', () => {
|
||||||
|
it('does not duplicate the regulation when label equals the short name', () => {
|
||||||
|
const { container } = render(<KnowledgeUnitCard unit={{ ...base, label: 'DSK Sdm B51' }} />)
|
||||||
|
const occurrences = (container.textContent?.match(/DSK Sdm B51/g) || []).length
|
||||||
|
expect(occurrences).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the label when it differs from the short name (no breadcrumb)', () => {
|
||||||
|
const { container } = render(<KnowledgeUnitCard unit={{ ...base, label: 'Art. 30 DSGVO' }} />)
|
||||||
|
expect(container.textContent).toContain('DSK Sdm B51')
|
||||||
|
expect(container.textContent).toContain('Art. 30 DSGVO')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the section/paragraph breadcrumb when present', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<KnowledgeUnitCard unit={{ ...base, section: 'Art. 5', paragraph: 'Abs. 2' }} />,
|
||||||
|
)
|
||||||
|
expect(container.textContent).toContain('Art. 5')
|
||||||
|
expect(container.textContent).toContain('Abs. 2')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -30,7 +30,10 @@ export function KnowledgeUnitCard({ unit }: { unit: KnowledgeUnit }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
unit.label && <div className="mt-0.5 text-[11px] text-gray-500">{unit.label}</div>
|
unit.label &&
|
||||||
|
unit.label !== unit.regulation.short && (
|
||||||
|
<div className="mt-0.5 text-[11px] text-gray-500">{unit.label}</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{canOpen && (
|
{canOpen && (
|
||||||
|
|||||||
Reference in New Issue
Block a user