feat(tools): add letter length + summary depth controls; harden korrespond §-discipline
- Summarize: new depth param (brief/standard/detailed) with depth-aware prompt instructions and coverage mandate; wired through API + JS - Korrespond: new letter length param (concise/standard/detailed) injected as Lengde: instruction in draft pass; wired through API + JS - Korrespond draft prompt: add §-discipline rule (cite only directly relevant §§) plus Opphevet guard (aligned with dobetterlegal-tools) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -171,6 +171,8 @@ try {
|
||||
if (in_array($inputEngine, ['azure_mini', 'claude_sonnet'], true)) {
|
||||
$engine = $inputEngine;
|
||||
}
|
||||
$length = in_array($input['length'] ?? '', ['concise', 'standard', 'detailed'], true)
|
||||
? (string)$input['length'] : 'standard';
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond');
|
||||
$creditDeducted = true;
|
||||
|
||||
@@ -179,7 +181,7 @@ try {
|
||||
: null;
|
||||
|
||||
// ── Pass 2: retrieve law → draft → self-check → translate ──────────────────
|
||||
$result = $agent->generate($intake, $classify, $emit, $engine, $personaSlug);
|
||||
$result = $agent->generate($intake, $classify, $emit, $engine, $personaSlug, $length);
|
||||
$result['ok'] = true;
|
||||
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
|
||||
if ($ftRemaining >= 0) {
|
||||
|
||||
+3
-1
@@ -11,6 +11,8 @@ $ftUid = dbnToolsFreeTierCheck('summarize');
|
||||
$input = dbnToolsJsonInput(400000);
|
||||
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$depth = in_array($input['depth'] ?? '', ['brief', 'standard', 'detailed'], true)
|
||||
? (string)$input['depth'] : 'standard';
|
||||
$slices = is_array($input['slices'] ?? null) ? array_values(array_filter($input['slices'])) : [];
|
||||
|
||||
// Streaming headers — flush each NDJSON line as it's written
|
||||
@@ -68,7 +70,7 @@ try {
|
||||
'detail' => 'Generating summary…',
|
||||
]);
|
||||
|
||||
$result = (new DbnLegalToolsService())->summarizeWithContext($text, $language, $engine, $corpusContext);
|
||||
$result = (new DbnLegalToolsService())->summarizeWithContext($text, $language, $engine, $corpusContext, $depth);
|
||||
|
||||
if ($ftUid > 0) {
|
||||
$balance = dbnToolsFreeTierDeduct($ftUid, 'summarize');
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
force_draft: !!forceDraft,
|
||||
use_my_case: (typeof window.dbnGetUseMyCase === 'function') ? window.dbnGetUseMyCase() : false,
|
||||
engine: (document.querySelector('[name="korrEngine"]:checked')?.value ?? 'azure_mini'),
|
||||
length: (document.querySelector('[name="korrLength"]:checked')?.value ?? 'standard'),
|
||||
};
|
||||
if (korrDocIds.length) payload.doc_ids = korrDocIds;
|
||||
if (persona) payload.profile = persona;
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
text: combined,
|
||||
language: _currentLang,
|
||||
engine: engine,
|
||||
depth: (document.querySelector('input[name="sumDepth"]:checked') || {}).value || 'standard',
|
||||
slices: slices,
|
||||
};
|
||||
if (docIds.length) payload.doc_ids = docIds;
|
||||
|
||||
@@ -243,7 +243,7 @@ PROMPT;
|
||||
*
|
||||
* @return array Final result payload (matches NDJSON 'final' event shape).
|
||||
*/
|
||||
public function generate(array $intake, array $classify, ?callable $emit = null, string $engine = 'azure_mini', ?string $persona = null): array
|
||||
public function generate(array $intake, array $classify, ?callable $emit = null, string $engine = 'azure_mini', ?string $persona = null, string $length = 'standard'): array
|
||||
{
|
||||
$draftDeployment = ($this->azure instanceof DbnBedrockGateway)
|
||||
? (($engine === 'claude_sonnet' || $engine === 'azure_full')
|
||||
@@ -270,7 +270,7 @@ PROMPT;
|
||||
// ── Draft in Norwegian bokmål ───────────────────────────────────────────
|
||||
if ($emit) { $emit('progress', ['detail' => self::L('drafting_no', $userLang)]); }
|
||||
$draftNo = $this->draftNorwegian(
|
||||
$intake, $classify, $retrieval['sources'], $bodyLabel, $outputType, $tone, $goal, $draftDeployment
|
||||
$intake, $classify, $retrieval['sources'], $bodyLabel, $outputType, $tone, $goal, $draftDeployment, $length
|
||||
);
|
||||
|
||||
// ── Self-check: verify citations, deadline, goal, tone ──────────────────
|
||||
@@ -550,7 +550,8 @@ PROMPT;
|
||||
|
||||
private function draftNorwegian(
|
||||
array $intake, array $classify, array $sources, string $bodyLabel,
|
||||
string $outputType, string $tone, string $goal, string $draftDeployment = self::DRAFT_DEPLOYMENT
|
||||
string $outputType, string $tone, string $goal, string $draftDeployment = self::DRAFT_DEPLOYMENT,
|
||||
string $length = 'standard'
|
||||
): string {
|
||||
$context = $this->buildContextBlob($intake);
|
||||
$toneLabel = $this->toneLabelNorsk($tone);
|
||||
@@ -574,9 +575,16 @@ PROMPT;
|
||||
|
||||
$goalLine = $goal !== '' ? ('Brukerens mål: ' . $goal) : 'Brukerens mål: ikke spesifisert — utled fra konteksten.';
|
||||
|
||||
$lengthLabel = match($length) {
|
||||
'concise' => 'Kort og poengtert — maks 2-3 avsnitt. Kom raskt til poenget.',
|
||||
'detailed' => 'Utfyllende — inkluder full bakgrunn, alle argumenter og rettslig begrunnelse.',
|
||||
default => 'Standard lengde — 4-6 avsnitt.',
|
||||
};
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
Du skriver utkast til korrespondanse til {$bodyLabel}. Skriv på norsk bokmål.
|
||||
Tone: {$toneLabel}.
|
||||
Lengde: {$lengthLabel}
|
||||
|
||||
{$goalLine}
|
||||
|
||||
@@ -588,6 +596,8 @@ REGLER FOR LOVHENVISNINGER (kritisk):
|
||||
fra passasjelisten. Eksempel: "etter forvaltningsloven § 17 [CITE:2]".
|
||||
- Hvis du ikke finner dekning i passasjene, skriv UTEN §-henvisning.
|
||||
- IKKE finn på §-nummer eller artikkelnumre.
|
||||
- IKKE siter §-er som er markert "(Opphevet)" eller "Opphevet ved lov" i passasjene — de gjelder ikke lenger.
|
||||
- Siter kun §-er som er DIREKTE RELEVANTE for den juridiske saken i brevet. Ikke siter §-er fra lovteksten som ikke brukes i argumentasjonen.
|
||||
|
||||
{$sourcesBlock}
|
||||
|
||||
|
||||
+23
-4
@@ -1854,7 +1854,8 @@ PROMPT;
|
||||
string $text,
|
||||
string $language = 'en',
|
||||
string $engine = 'azure_mini',
|
||||
string $corpusContext = ''
|
||||
string $corpusContext = '',
|
||||
string $depth = 'standard'
|
||||
): array {
|
||||
$text = $this->requirePasteText($text);
|
||||
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true) ? $engine : 'azure_mini';
|
||||
@@ -1870,16 +1871,34 @@ PROMPT;
|
||||
. $text;
|
||||
}
|
||||
|
||||
$depthInstructions = match($depth) {
|
||||
'brief' => [
|
||||
'what_we_found' => '1-2 sentence executive summary covering the main outcome',
|
||||
'key_facts' => 'up to 3 most important facts',
|
||||
'coverage' => '',
|
||||
],
|
||||
'detailed' => [
|
||||
'what_we_found' => 'comprehensive 6-10 sentence summary covering every fact, decision, party, date, and legal implication',
|
||||
'key_facts' => 'every fact, decision, and legal reference mentioned in the document',
|
||||
'coverage' => "\nCover ALL parties, ALL key decisions, ALL dates, and ALL legal references present in the document. Do not omit facts because they seem minor — err on the side of inclusion.",
|
||||
],
|
||||
default => [
|
||||
'what_we_found' => '3-5 sentence summary covering all key outcomes, parties, and decisions',
|
||||
'key_facts' => 'all key facts',
|
||||
'coverage' => "\nCover ALL parties, ALL key decisions, ALL dates, and ALL legal references present in the document.",
|
||||
],
|
||||
};
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
Summarise the following document in {$locale}. Do not invent facts not present in the text.
|
||||
Return JSON only — no extra text before or after the JSON object.
|
||||
Return JSON only — no extra text before or after the JSON object.{$depthInstructions['coverage']}
|
||||
|
||||
{$enriched}
|
||||
|
||||
Return this JSON structure:
|
||||
{
|
||||
"what_we_found": "plain-language summary (2-4 sentences)",
|
||||
"key_facts": ["fact 1", "fact 2"],
|
||||
"what_we_found": "{$depthInstructions['what_we_found']}",
|
||||
"key_facts": ["{$depthInstructions['key_facts']}"],
|
||||
"dates": ["date or event phrase"],
|
||||
"parties": ["party or role"],
|
||||
"legal_references_detected": ["statute, article, or case name"],
|
||||
|
||||
@@ -70,6 +70,15 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
<label><input type="radio" name="korrTone" value="warm"> Conciliatory-warm</label>
|
||||
</div>
|
||||
|
||||
<!-- Letter length -->
|
||||
<div class="control-row" id="korrLengthControl">
|
||||
<span class="control-label">Letter length</span>
|
||||
<label><input type="radio" name="korrLength" value="concise"> Concise</label>
|
||||
<label><input type="radio" name="korrLength" value="standard" checked> Standard ★</label>
|
||||
<label><input type="radio" name="korrLength" value="detailed"> Detailed</label>
|
||||
</div>
|
||||
<p class="upload-hint">Concise: short and to-the-point (2-3 paragraphs). Standard: balanced correspondence. Detailed: full background, all arguments, and complete legal reasoning.</p>
|
||||
|
||||
<!-- Engine -->
|
||||
<div class="control-row" id="korrEngineControl">
|
||||
<span class="control-label">Engine</span>
|
||||
|
||||
@@ -24,6 +24,14 @@ require_once __DIR__ . '/includes/layout.php';
|
||||
</div>
|
||||
<p class="upload-hint">Azure engines use your BNL Azure credits. GPU runs the local LiteLLM proxy on the GPU server.</p>
|
||||
|
||||
<div class="control-row" id="sumDepthControl">
|
||||
<span class="control-label">Summary depth</span>
|
||||
<label><input type="radio" name="sumDepth" value="brief"> Brief</label>
|
||||
<label><input type="radio" name="sumDepth" value="standard" checked> Standard ★</label>
|
||||
<label><input type="radio" name="sumDepth" value="detailed"> Detailed</label>
|
||||
</div>
|
||||
<p class="upload-hint">Brief: 1-2 sentence executive summary. Standard: balanced summary with all key facts. Detailed: comprehensive — covers every fact, party, date, and legal reference.</p>
|
||||
|
||||
<details class="advanced-panel" id="sumSlicesPanel">
|
||||
<summary class="advanced-toggle">Legal corpus enrichment <small class="control-hint">(optional)</small></summary>
|
||||
<p class="upload-hint">When one or more slices are enabled, the tool searches the Do Better Norge legal corpus for relevant passages and prepends them to the prompt. All slices are off by default — enable only what applies to your document.</p>
|
||||
|
||||
Reference in New Issue
Block a user