From 47aa35e946407b4dc9c456ca757facd5b5c4edcd Mon Sep 17 00:00:00 2001 From: davegilligan Date: Mon, 18 May 2026 22:38:29 +0200 Subject: [PATCH] Add dbn-legal-agent targeted check step to BVJ Analyzer (Step 6b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probe testing revealed the fine-tune loops when asked to check a brief directly (tool-planning architecture conflict) but answers focused legal Q&A reliably in ~55s. New step 6b asks one targeted question per document type (akuttvedtak → § 4-25 klar nødvendighet, adopsjon → Strand Lobben, undersøkelse → fvl § 17/§ 41) and merges the finding into procedural_red_flags with check_model provenance. Silent on timeout/error. Co-Authored-By: Claude Sonnet 4.6 --- includes/BvjAnalyzerAgent.php | 19 ++++++ includes/bootstrap.php | 122 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/includes/BvjAnalyzerAgent.php b/includes/BvjAnalyzerAgent.php index c73643e..45a437f 100644 --- a/includes/BvjAnalyzerAgent.php +++ b/includes/BvjAnalyzerAgent.php @@ -953,6 +953,25 @@ PROMPT; ]; } + // Step 6b: dbn-legal-agent targeted legal Q&A check (azure engines only; silent on failure) + // Asks one focused question about the document's statutory basis to surface domain knowledge + // that Azure reliably misses (klar nødvendighet threshold, Strand Lobben, fvl §17/§41). + if (in_array($engine, ['azure_mini', 'azure_full'], true)) { + $checkFindings = dbnToolsRunLegalCheck( + (string)($json['advocacy_brief'] ?? ''), + $docType + ); + if (!empty($checkFindings)) { + if (!is_array($json['procedural_red_flags'] ?? null)) { + $json['procedural_red_flags'] = []; + } + foreach ($checkFindings as $cf) { + $json['procedural_red_flags'][] = $cf; + } + $json['check_model'] = 'dbn-legal-agent'; + } + } + return ['json' => $json, 'deploy_label' => $deployLabel]; } diff --git a/includes/bootstrap.php b/includes/bootstrap.php index 91596c1..9353176 100644 --- a/includes/bootstrap.php +++ b/includes/bootstrap.php @@ -845,3 +845,125 @@ function dbnToolsLiteLLMEmbedBatch(array $texts, string $model = 'nomic-embed-te usort($data, fn($a, $b) => ($a['index'] ?? 0) <=> ($b['index'] ?? 0)); return array_map(fn($d) => $d['embedding'], $data); } + +// ── dbn-legal-agent targeted check step ────────────────────────────────────── +// +// dbn-legal-agent is a QLoRA fine-tune trained for single-turn Q&A with a baked-in +// "Kilder:" citation scaffold. It loops when asked to check a full brief (no trained +// behavior for that task) but answers focused legal questions reliably in ~55s. +// +// Strategy: ask ONE targeted question per document type, cap tokens at 350 to cut +// off before the tool-planning loop kicks in, parse the answer for legal findings. + +function dbnToolsRunLegalCheck(string $brief, string $docType): array +{ + $question = dbnToolsSelectCheckQuestion($docType, $brief); + if ($question === null) { + return []; + } + + $opts = [ + 'model' => 'dbn-legal-agent', + 'temperature' => 0.1, + 'max_tokens' => 350, + 'timeout' => 120, + // No 'json' key — plain narrative, no response_format flag + ]; + + try { + $response = dbnToolsCallGpuLlm( + [['role' => 'user', 'content' => $question]], + $opts + ); + $text = trim((string)($response['choices'][0]['message']['content'] ?? '')); + } catch (Throwable $e) { + return []; + } + + if (empty($text) || str_word_count($text) < 15) { + return []; + } + + $clean = dbnToolsExtractCleanAnswer($text); + if (mb_strlen($clean) < 40) { + return []; + } + + return [[ + 'description' => mb_substr($clean, 0, 280), + 'severity' => dbnToolsInferCheckSeverity($clean), + 'legal_basis' => dbnToolsExtractCheckLegalBasis($clean), + 'source_refs' => [], + 'what_to_check'=> 'Verifiser med norsk familieretsadvokat', + 'check_model' => 'dbn-legal-agent', + ]]; +} + +function dbnToolsSelectCheckQuestion(string $docType, string $brief): ?string +{ + $t = mb_strtolower($docType); + $b = mb_strtolower($brief); + + if (str_contains($t, 'akutt') || str_contains($t, 'emergency')) { + return 'Hva er den korrekte rettslige terskelen for midlertidig plassering utenfor hjemmet ' + . 'etter barnevernsloven § 4-25 (2021-loven)? Er det forskjell mellom § 4-6 (1992-loven) ' + . 'og § 4-25 (2021-loven), og er «klar nødvendighet» det riktige kravet?'; + } + + if (str_contains($t, 'adopsjon') || str_contains($t, 'adoption') + || str_contains($b, '§ 5-8') || str_contains($b, '§ 4-20')) { + return 'Hva krever Høyesterett og EMD (særlig Strand Lobben mot Norge, saksnr. 37283/13, 2019) ' + . 'for at fratakelse av foreldreansvar og adopsjon uten samtykke skal være lovlig? ' + . 'Hvilken rolle spiller dokumentert rehabiliteringsplan og biologiske familiebånd?'; + } + + if (str_contains($t, 'undersøkelse') || str_contains($t, 'investigation') + || (str_contains($b, 'varsel') && str_contains($b, 'hjemmebesøk'))) { + return 'Hva er konsekvensene etter forvaltningsloven § 17 (kontradiksjonsprinsippet) og ' + . '§ 41 (ugyldighet) dersom barnevernstjenesten gjennomfører hjemmebesøk uten å ha ' + . 'sendt varsel om undersøkelse i en ikke-akutt situasjon?'; + } + + // Generic procedural check for other document types + return 'Hva er de viktigste prosessuelle kravene barnevernstjenesten må oppfylle ved ' + . 'inngripende vedtak etter barnevernsloven 2021, særlig knyttet til varsel, ' + . 'begrunnelse, forholdsmessighet og EMD-forpliktelser?'; +} + +function dbnToolsExtractCleanAnswer(string $text): string +{ + // Strip loop artifacts: tool-planning stubs and "Final:" repetition blocks + $cutPatterns = ['Tool calls:', 'Tool_calls:', '"tool_calls"', 'Final:', 'Final answer:']; + foreach ($cutPatterns as $marker) { + $pos = mb_stripos($text, $marker); + if ($pos !== false && $pos > 40) { + $text = mb_substr($text, 0, $pos); + } + } + // Preserve Kilder: section for legal citations but cap its length + $srcPos = mb_strpos($text, 'Kilder:'); + if ($srcPos !== false && $srcPos > 60) { + $text = mb_substr($text, 0, $srcPos + 180); + } + return trim($text); +} + +function dbnToolsInferCheckSeverity(string $text): string +{ + if (preg_match('/ugyldig|§\s*41|kontradiksjon|klar nødvendighet|strand lobben|biologiske bånd/i', $text)) { + return 'high'; + } + if (preg_match('/prosessuell|varsel|terskel|paragrafnummer|forholdsmessig|rehabiliter/i', $text)) { + return 'medium'; + } + return 'low'; +} + +function dbnToolsExtractCheckLegalBasis(string $text): string +{ + preg_match_all('/§\s*\d[\d\-a-zA-Z]*/u', $text, $m); + if (!empty($m[0])) { + return implode(', ', array_unique(array_slice($m[0], 0, 3))); + } + return ''; +}