feat(tools): reposition as Do Better Legal two-track Norwegian-law MCP
De-family-ify shared JSON tools (persona-aware routing + neutral base prompt), make the verification review pick its engine per track (family/child-welfare -> dbn-legal-agent-v3, others -> gpt-4o interim), and route product-name strings through dbnToolsProductName(). Rebrand the MCP/tools surface (mcp.php + i18n mcp_* strings) to Do Better Legal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -75,8 +75,12 @@ try {
|
||||
'language' => $language,
|
||||
]);
|
||||
|
||||
$personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
|
||||
? trim($input['profile'])
|
||||
: null;
|
||||
|
||||
$agent = new DbnKorrespondAgent();
|
||||
$result = $agent->refine($intake, $classify, $originalDraftNo, $jurisdiction, $emit);
|
||||
$result = $agent->refine($intake, $classify, $originalDraftNo, $jurisdiction, $emit, $personaSlug);
|
||||
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond_refine');
|
||||
$result['ok'] = true;
|
||||
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
|
||||
|
||||
@@ -64,7 +64,8 @@ try {
|
||||
$personaResolved = dbnToolsResolvePersona((int)$client['id'], $personaSlug);
|
||||
|
||||
$agent = (new DbnLegalAnalysisAgent())
|
||||
->withPersonaPrompt($personaResolved['system_prompt'] ?? null);
|
||||
->withPersonaPrompt($personaResolved['system_prompt'] ?? null)
|
||||
->withPersonaSlug($personaResolved['slug'] ?? $personaSlug);
|
||||
|
||||
// Pass 1 — extract issues (Azure, fast); deduct credit AFTER this succeeds
|
||||
$emit('progress', ['step' => 'extracting_issues', 'detail' => 'Identifying distinct legal issues…']);
|
||||
@@ -132,7 +133,8 @@ try {
|
||||
$legalCheck = dbnToolsRunLegalCheck(
|
||||
mb_strimwidth((string)($synth['overall_assessment'] ?? ''), 0, 800),
|
||||
$docType,
|
||||
$personaResolved['system_prompt'] ?? null
|
||||
$personaResolved['system_prompt'] ?? null,
|
||||
$personaResolved['slug'] ?? $personaSlug
|
||||
);
|
||||
} catch (Throwable) {}
|
||||
|
||||
|
||||
@@ -847,9 +847,10 @@ PROMPT;
|
||||
: '';
|
||||
|
||||
$docExcerpt = mb_substr($docText, 0, 8000, 'UTF-8');
|
||||
$product = dbnToolsProductName();
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
You are Do Better Norge Legal Tools. Produce a structured Barnevernet case analysis for: {$roleStr}.
|
||||
You are {$product} Tools. Produce a structured Barnevernet case analysis for: {$roleStr}.
|
||||
|
||||
HALLUCINATION RULES — READ FIRST:
|
||||
- You may ONLY cite statute sections (§), ECHR article numbers, ECHR application numbers, case names, and Bufdir/Statsforvalter circular references that appear verbatim in the numbered corpus sources below.
|
||||
@@ -984,7 +985,8 @@ PROMPT;
|
||||
$checkFindings = dbnToolsRunLegalCheck(
|
||||
(string)($json['advocacy_brief'] ?? ''),
|
||||
$docType,
|
||||
$this->resolvedPersonaPrompt
|
||||
$this->resolvedPersonaPrompt,
|
||||
$this->personaSlug
|
||||
);
|
||||
if (!empty($checkFindings)) {
|
||||
if (!is_array($json['procedural_red_flags'] ?? null)) {
|
||||
@@ -1379,7 +1381,7 @@ PROMPT;
|
||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||
}
|
||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
||||
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
}
|
||||
return $package;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ final class DbnMcpRuntime
|
||||
'mode' => ['type' => 'string', 'enum' => ['standard', 'strict']],
|
||||
'output_format' => ['type' => 'string', 'enum' => ['contextual', 'generic', 'pseudonym']],
|
||||
], ['text']),
|
||||
self::tool('dbn.translate', 'Translate legal document', 'Translate Norwegian family-law text with legal terminology annotations.', [
|
||||
self::tool('dbn.translate', 'Translate legal document', 'Translate Norwegian legal text with legal terminology annotations.', [
|
||||
'text' => $text,
|
||||
'source_lang' => $lang,
|
||||
'target_lang' => $lang,
|
||||
|
||||
@@ -440,7 +440,7 @@ final class DbnDeepResearchAgent
|
||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||
}
|
||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
||||
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
}
|
||||
return $package;
|
||||
}
|
||||
@@ -487,8 +487,9 @@ final class DbnDeepResearchAgent
|
||||
$priorContextBlock = implode("\n", $parts) . "\n\nNow investigate this branch:\n";
|
||||
}
|
||||
|
||||
$product = dbnToolsProductName();
|
||||
$prompt = <<<PROMPT
|
||||
{$rolePrefix}{$priorContextBlock}You are reviewing the input below to set up a deep legal research pass against the Do Better Norge family-law corpus.
|
||||
{$rolePrefix}{$priorContextBlock}You are reviewing the input below to set up a deep legal research pass against the {$product} Norwegian legal corpus.
|
||||
|
||||
Input:
|
||||
{$seedDescription}
|
||||
@@ -580,8 +581,9 @@ Rules:
|
||||
- Write the questions in {$locale}.
|
||||
PROMPT;
|
||||
} else {
|
||||
$product = dbnToolsProductName();
|
||||
$prompt = <<<PROMPT
|
||||
You are decomposing a Do Better Norge legal-research request into {$targetCount} focused sub-questions that should each be answered by the legal corpus (Norwegian family law, child welfare, ECHR/Hague).
|
||||
You are decomposing a {$product} legal-research request into {$targetCount} focused sub-questions that should each be answered by the legal corpus (Norwegian law — e.g. family, child welfare, immigration, labour, consumer/tenancy, ECHR/Hague).
|
||||
|
||||
Research brief:
|
||||
{$brief}
|
||||
@@ -1064,9 +1066,10 @@ PROMPT;
|
||||
? "\nKey retrieval signals (statutory/factual terms that drove corpus search — ground your brief in these where sources permit):\n" . implode(', ', $keySignals) . "\n"
|
||||
: '';
|
||||
|
||||
$product = dbnToolsProductName();
|
||||
if ($advocateRole !== '') {
|
||||
$prompt = <<<PROMPT
|
||||
You are Do Better Norge Legal Tools producing a legal preparation brief in {$locale}.
|
||||
You are {$product} Tools producing a legal preparation brief in {$locale}.
|
||||
Your client: {$advocateRole}
|
||||
{$priorContextSection}
|
||||
User input:
|
||||
@@ -1106,7 +1109,7 @@ Return JSON:
|
||||
PROMPT;
|
||||
} else {
|
||||
$prompt = <<<PROMPT
|
||||
You are Do Better Norge Legal Tools running a deep-research synthesis. You MUST ground every claim in the numbered sources below, using inline `[n]` citation markers that map to the source list. Do NOT cite a source you did not use. Do NOT invent statutes, paragraph numbers, case names, dates, or parties.
|
||||
You are {$product} Tools running a deep-research synthesis. You MUST ground every claim in the numbered sources below, using inline `[n]` citation markers that map to the source list. Do NOT cite a source you did not use. Do NOT invent statutes, paragraph numbers, case names, dates, or parties.
|
||||
{$priorContextSection}
|
||||
User input:
|
||||
{$seedDescription}
|
||||
|
||||
@@ -803,9 +803,10 @@ PROMPT;
|
||||
$docTypeB = $metaB['doc_type'] ?? $nameB;
|
||||
$docDateB = $metaB['doc_date'] ?? '?';
|
||||
$authority = $metaA['issuing_authority'] ?? $metaB['issuing_authority'] ?? 'the authority';
|
||||
$product = dbnToolsProductName();
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
You are Do Better Norge Legal Tools evaluating discrepancies between two Barnevernet document versions.
|
||||
You are {$product} Tools evaluating discrepancies between two Barnevernet document versions.
|
||||
|
||||
HALLUCINATION RULES:
|
||||
- Only cite statute sections (§), ECHR articles, and case names that appear verbatim in the corpus sources below.
|
||||
@@ -1040,7 +1041,7 @@ PROMPT;
|
||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||
}
|
||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
||||
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
}
|
||||
return $package;
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ PROMPT;
|
||||
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
||||
$legalCheck = [];
|
||||
try {
|
||||
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body);
|
||||
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
|
||||
} catch (Throwable $e) { /* silent — non-critical */ }
|
||||
|
||||
// ── Translate to user language (if not Norwegian) ───────────────────────
|
||||
@@ -777,7 +777,8 @@ EOT,
|
||||
array $classify,
|
||||
string $originalDraftNo,
|
||||
string $jurisdiction,
|
||||
?callable $emit = null
|
||||
?callable $emit = null,
|
||||
?string $persona = null
|
||||
): array {
|
||||
$body = $intake['recipient_body'] ?? 'other';
|
||||
$outputType = $intake['output_type'] ?? 'email';
|
||||
@@ -808,7 +809,7 @@ EOT,
|
||||
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
||||
$legalCheckRefine = [];
|
||||
try {
|
||||
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body);
|
||||
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
|
||||
} catch (Throwable $e) { /* silent — non-critical */ }
|
||||
|
||||
$draftUser = $checked['draft'];
|
||||
|
||||
@@ -28,6 +28,8 @@ final class DbnLegalAnalysisAgent
|
||||
|
||||
/** Persona system prompt (from the resolved chat profile); null ⇒ use the built-in barnevern prompt. */
|
||||
private ?string $personaPrompt = null;
|
||||
/** Persona slug driving the per-track reviewer model; default = child-welfare (family track). */
|
||||
private string $personaSlug = 'child-welfare';
|
||||
|
||||
public function withPersonaPrompt(?string $prompt): self
|
||||
{
|
||||
@@ -36,6 +38,15 @@ final class DbnLegalAnalysisAgent
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPersonaSlug(?string $slug): self
|
||||
{
|
||||
$slug = is_string($slug) ? trim($slug) : '';
|
||||
if ($slug !== '') {
|
||||
$this->personaSlug = $slug;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// On Azure: gpt-4o-mini for extraction/synthesis. On Bedrock: factory picks Haiku/Sonnet.
|
||||
@@ -408,11 +419,12 @@ PROMPT;
|
||||
$userMsg .= "\n\n" . $ctxLabel . ': ' . $issue['brief_context'];
|
||||
}
|
||||
if ($corpusContext !== '') {
|
||||
$product = dbnToolsProductName();
|
||||
$srcLabel = match ($language) {
|
||||
'no' => 'Relevante kilder fra Do Better Norge-korpuset',
|
||||
'pl' => 'Istotne źródła z korpusu Do Better Norge',
|
||||
'uk' => 'Релевантні джерела з корпусу Do Better Norge',
|
||||
default => 'Relevant sources from the Do Better Norge corpus',
|
||||
'no' => 'Relevante kilder fra ' . $product . '-korpuset',
|
||||
'pl' => 'Istotne źródła z korpusu ' . $product,
|
||||
'uk' => 'Релевантні джерела з корпусу ' . $product,
|
||||
default => 'Relevant sources from the ' . $product . ' corpus',
|
||||
};
|
||||
$userMsg .= "\n\n" . $srcLabel . ":\n" . $corpusContext;
|
||||
}
|
||||
@@ -613,7 +625,9 @@ PROMPT;
|
||||
try {
|
||||
$legalCheck = dbnToolsRunLegalCheck(
|
||||
mb_strimwidth($synth['overall_assessment'], 0, 800),
|
||||
$docType
|
||||
$docType,
|
||||
$this->personaPrompt,
|
||||
$this->personaSlug
|
||||
);
|
||||
} catch (Throwable) {}
|
||||
|
||||
|
||||
+32
-20
@@ -39,8 +39,9 @@ final class DbnLegalToolsService
|
||||
'shared' => 'Legal Library only',
|
||||
default => 'Legal Library + personal corpus',
|
||||
};
|
||||
$product = dbnToolsProductName();
|
||||
$trace = [
|
||||
$this->trace('Query interpretation', "Searching Do Better Norge {$scopeLabel}.", 'complete'),
|
||||
$this->trace('Query interpretation', "Searching {$product} {$scopeLabel}.", 'complete'),
|
||||
$this->trace('Search tools used', 'ClientRagPipeline::searchAll with keyword mode.', 'running'),
|
||||
];
|
||||
|
||||
@@ -252,12 +253,9 @@ Return JSON only with these keys:
|
||||
}
|
||||
PROMPT;
|
||||
|
||||
// Persona voice/domain prepended to the JSON-enforcing scaffold (keeps the
|
||||
// Persona voice/domain folded into the JSON-enforcing scaffold (keeps the
|
||||
// structured-output contract while applying the persona's legal framing).
|
||||
$system = $this->legalJsonSystemPrompt($language);
|
||||
if (!empty($personaResolved['system_prompt'])) {
|
||||
$system = $personaResolved['system_prompt'] . "\n\n" . $system;
|
||||
}
|
||||
$system = $this->legalJsonSystemPrompt($language, $personaResolved['system_prompt'] ?? null);
|
||||
$askDeployment = $personaModel;
|
||||
$raw = $gateway->withDeployment($askDeployment)->chatText([
|
||||
['role' => 'system', 'content' => $system],
|
||||
@@ -1166,7 +1164,7 @@ PROMPT;
|
||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||
}
|
||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
||||
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
|
||||
}
|
||||
return $package;
|
||||
}
|
||||
@@ -1191,30 +1189,44 @@ PROMPT;
|
||||
return [$this->azure, ($engine === 'azure_full') ? 'gpt-4o' : 'gpt-4o-mini'];
|
||||
}
|
||||
|
||||
private function runJsonTool(string $prompt, string $language, int $maxTokens): array
|
||||
private function runJsonTool(string $prompt, string $language, int $maxTokens, ?array $persona = null): array
|
||||
{
|
||||
$raw = $this->azure->chatText([
|
||||
['role' => 'system', 'content' => $this->legalJsonSystemPrompt($language)],
|
||||
// With a persona, route to its pinned engine (Track-1 → tuned Qwen, Track-2 → gpt-4o)
|
||||
// and fold its domain framing into the system prompt. Without one (e.g. pasted-text
|
||||
// tools), keep the default Azure routing with the neutral base prompt.
|
||||
$personaPrompt = $persona['system_prompt'] ?? null;
|
||||
if ($persona !== null) {
|
||||
[$gateway, $model] = $this->personaGateway($persona, 'azure_mini');
|
||||
$gateway = $gateway->withDeployment($model);
|
||||
} else {
|
||||
$gateway = $this->azure;
|
||||
}
|
||||
$raw = $gateway->chatText([
|
||||
['role' => 'system', 'content' => $this->legalJsonSystemPrompt($language, $personaPrompt)],
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
], [
|
||||
'json' => true,
|
||||
'temperature' => 0.1,
|
||||
'max_tokens' => $maxTokens,
|
||||
]);
|
||||
$json = $this->azure->decodeJsonObject($raw);
|
||||
$json = $gateway->decodeJsonObject($raw);
|
||||
if (!$json) {
|
||||
dbnToolsAbort('Azure OpenAI did not return valid structured JSON.', 502, 'azure_invalid_json');
|
||||
dbnToolsAbort('The model did not return valid structured JSON.', 502, 'invalid_json');
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function legalJsonSystemPrompt(string $language): string
|
||||
private function legalJsonSystemPrompt(string $language, ?string $personaPrompt = null): string
|
||||
{
|
||||
$locale = dbnToolsLanguageName($language);
|
||||
$product = dbnToolsProductName();
|
||||
$personaPrompt = is_string($personaPrompt) ? trim($personaPrompt) : '';
|
||||
// The persona (family, immigration, labour, …) supplies the domain framing; the
|
||||
// base prompt stays domain-neutral so non-family tracks are not cast as child-welfare.
|
||||
$personaBlock = $personaPrompt !== '' ? ($personaPrompt . "\n") : '';
|
||||
return <<<PROMPT
|
||||
You are Do Better Norge Legal Tools — a source-grounded Norwegian legal preparation assistant.
|
||||
Norwegian legal context: CPS cases follow the chain Barnevernstjenesten → Fylkesnemnda → Statsforvalteren → Tingrett → Lagmannsrett → Høyesterett. Key order types: akuttvedtak (emergency removal), omsorgsvedtak (care order), samværsvedtak (contact order). Relevant bodies: BUP (child psychiatry), PPT (educational psychology), NAV (welfare).
|
||||
Use the DBN legal guardrails:
|
||||
You are {$product} Tools — a source-grounded Norwegian legal preparation assistant covering all areas of Norwegian law.
|
||||
{$personaBlock}Legal guardrails:
|
||||
- Answer only from provided source excerpts or pasted text.
|
||||
- Treat your role as legal information and issue-spotting, not final legal advice.
|
||||
- Never invent statutes, paragraph numbers, case names, citations, parties, dates, or sources.
|
||||
@@ -1260,7 +1272,7 @@ PROMPT;
|
||||
'title' => $title,
|
||||
'excerpt' => $docSummary ?? $rawExcerpt,
|
||||
'chunk_text' => $rawExcerpt,
|
||||
'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? 'Do Better Norge'),
|
||||
'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? dbnToolsProductName()),
|
||||
'score' => $score,
|
||||
'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null,
|
||||
'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null,
|
||||
@@ -1354,7 +1366,7 @@ PROMPT;
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as &$row) {
|
||||
$row['similarity'] = 0.25;
|
||||
$row['source_name'] = 'Do Better Norge private corpus';
|
||||
$row['source_name'] = dbnToolsProductName() . ' private corpus';
|
||||
$row['source_type'] = 'private';
|
||||
}
|
||||
return $rows;
|
||||
@@ -1819,7 +1831,7 @@ PROMPT;
|
||||
$enriched = $text;
|
||||
$corpusUsed = $corpusContext !== '';
|
||||
if ($corpusUsed) {
|
||||
$enriched = "[Relevant legal context from Do Better Norge corpus]\n"
|
||||
$enriched = '[Relevant legal context from ' . dbnToolsProductName() . " corpus]\n"
|
||||
. $corpusContext
|
||||
. "\n\n---\n\nDocument to summarise:\n"
|
||||
. $text;
|
||||
@@ -1874,7 +1886,7 @@ PROMPT;
|
||||
}
|
||||
|
||||
$corpusNote = $corpusUsed
|
||||
? 'Summary enriched with ' . count(array_filter(explode('=== ', $corpusContext))) . ' passage(s) from the Do Better Norge legal corpus.'
|
||||
? 'Summary enriched with ' . count(array_filter(explode('=== ', $corpusContext))) . ' passage(s) from the ' . dbnToolsProductName() . ' legal corpus.'
|
||||
: 'No corpus search performed; summarised from document text only.';
|
||||
|
||||
$trace = [
|
||||
|
||||
+59
-12
@@ -479,6 +479,39 @@ function dbnToolsDefaultPersonaSlug(): string
|
||||
return $v !== '' ? $v : 'family';
|
||||
}
|
||||
|
||||
/** Product display name for tool-facing copy. Override via .env DBN_PRODUCT_NAME. */
|
||||
function dbnToolsProductName(): string
|
||||
{
|
||||
$v = trim((string)(dbnToolsEnv('DBN_PRODUCT_NAME', 'Do Better Legal') ?? 'Do Better Legal'));
|
||||
return $v !== '' ? $v : 'Do Better Legal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine track for a persona slug:
|
||||
* 'family' → family + child-welfare, served/verified by the fine-tuned Qwen.
|
||||
* 'general' → all other Norwegian-law personas, served/verified by the interim engine.
|
||||
*/
|
||||
function dbnToolsPersonaTrack(?string $slug): string
|
||||
{
|
||||
$slug = strtolower(trim((string)$slug));
|
||||
return in_array($slug, ['family', 'child-welfare'], true) ? 'family' : 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reviewer/verification model for a persona's track.
|
||||
* Family track → fine-tuned Qwen (dbn-legal-agent-v3).
|
||||
* General track → interim gpt-4o; swap to the 2nd fine-tune later via .env
|
||||
* DBN_REVIEW_MODEL_GENERAL with no code change.
|
||||
*/
|
||||
function dbnToolsReviewerModel(?string $slug = null): string
|
||||
{
|
||||
if (dbnToolsPersonaTrack($slug) === 'family') {
|
||||
return trim((string)(dbnToolsEnv('DBN_REVIEW_MODEL_FAMILY', 'dbn-legal-agent-v3') ?? 'dbn-legal-agent-v3'))
|
||||
?: 'dbn-legal-agent-v3';
|
||||
}
|
||||
return trim((string)(dbnToolsEnv('DBN_REVIEW_MODEL_GENERAL', 'gpt-4o') ?? 'gpt-4o')) ?: 'gpt-4o';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a DBN persona (a caveauAI chat profile for client 57) into a normalized
|
||||
* runtime bundle the tools can act on:
|
||||
@@ -544,7 +577,7 @@ function dbnToolsResolvePersona(int $clientId, ?string $slug = null): array
|
||||
dbnToolsAbort('No persona profile and the family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||
}
|
||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
||||
dbnToolsAbort('Do Better Norge does not have an active corpus subscription.', 503, 'subscription_missing');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' does not have an active corpus subscription.', 503, 'subscription_missing');
|
||||
}
|
||||
$resolved = [
|
||||
'slug' => 'family',
|
||||
@@ -938,7 +971,7 @@ function dbnToolsRequireClient(): array
|
||||
{
|
||||
$client = dbnToolsFetchClient();
|
||||
if (!$client || empty($client['is_active'])) {
|
||||
dbnToolsAbort('Do Better Norge client tenant is not active or was not found.', 503, 'client_unavailable');
|
||||
dbnToolsAbort(dbnToolsProductName() . ' client tenant is not active or was not found.', 503, 'client_unavailable');
|
||||
}
|
||||
return $client;
|
||||
}
|
||||
@@ -1304,26 +1337,33 @@ function dbnToolsLiteLLMEmbedBatch(array $texts, string $model = 'nomic-embed-te
|
||||
// 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, ?string $personaPrompt = null): array
|
||||
function dbnToolsRunLegalCheck(string $brief, string $docType, ?string $personaPrompt = null, ?string $personaSlug = null): array
|
||||
{
|
||||
$question = dbnToolsSelectCheckQuestion($docType, $brief);
|
||||
$track = dbnToolsPersonaTrack($personaSlug);
|
||||
$model = dbnToolsReviewerModel($personaSlug);
|
||||
|
||||
$question = dbnToolsSelectCheckQuestion($docType, $brief, $track);
|
||||
if ($question === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$opts = [
|
||||
'model' => 'dbn-legal-agent-v3',
|
||||
'model' => $model,
|
||||
'temperature' => 0.1,
|
||||
'max_tokens' => 350,
|
||||
'timeout' => 45,
|
||||
// No 'json' key — plain narrative, no response_format flag
|
||||
];
|
||||
|
||||
// Base prompt from the resolved persona (default = Child-welfare/barnevern).
|
||||
// Base prompt: resolved persona prompt wins; otherwise a track-appropriate default.
|
||||
$personaPrompt = is_string($personaPrompt) ? trim($personaPrompt) : '';
|
||||
$sysMsg = $personaPrompt !== ''
|
||||
? $personaPrompt
|
||||
: 'Du er en ekspert på norsk barnevernsloven og EMD-praksis. Svar alltid på norsk med korrekt juridisk terminologi. Bruk terskler fra barnevernsloven 2021: § 4-25 krever «klar nødvendighet». Strand Lobben mot Norge (37283/13) setter krav om rehabiliteringsplan før adopsjon. Aldri oppfinn paragrafnumre, saksnumre eller dommernavn.';
|
||||
if ($personaPrompt !== '') {
|
||||
$sysMsg = $personaPrompt;
|
||||
} elseif ($track === 'family') {
|
||||
$sysMsg = 'Du er en ekspert på norsk barnevernsloven og EMD-praksis. Svar alltid på norsk med korrekt juridisk terminologi. Bruk terskler fra barnevernsloven 2021: § 4-25 krever «klar nødvendighet». Strand Lobben mot Norge (37283/13) setter krav om rehabiliteringsplan før adopsjon. Aldri oppfinn paragrafnumre, saksnumre eller dommernavn.';
|
||||
} else {
|
||||
$sysMsg = 'Du er en erfaren norsk jurist med bred kompetanse i norsk rett (forvaltningsrett, utlendingsrett, arbeidsrett, husleie- og forbrukerrett). Svar alltid på norsk med korrekt juridisk terminologi. Aldri oppfinn paragrafnumre, saksnumre, dommernavn eller kilder. Hvis du er usikker, si det tydelig.';
|
||||
}
|
||||
|
||||
// Prepend a short snippet of the actual synthesis text so v3 answers in context,
|
||||
// not just as a general law quiz. Strip HTML tags and cap at 350 chars.
|
||||
@@ -1360,16 +1400,23 @@ function dbnToolsRunLegalCheck(string $brief, string $docType, ?string $personaP
|
||||
'severity' => dbnToolsInferCheckSeverity($clean),
|
||||
'legal_basis' => dbnToolsExtractCheckLegalBasis($clean),
|
||||
'source_refs' => [],
|
||||
'what_to_check'=> 'Verifiser med norsk familieretsadvokat',
|
||||
'check_model' => 'dbn-legal-agent-v3',
|
||||
'what_to_check'=> $track === 'family' ? 'Verifiser med norsk familieretsadvokat' : 'Verifiser med norsk advokat',
|
||||
'check_model' => $model,
|
||||
]];
|
||||
}
|
||||
|
||||
function dbnToolsSelectCheckQuestion(string $docType, string $brief): ?string
|
||||
function dbnToolsSelectCheckQuestion(string $docType, string $brief, string $track = 'family'): ?string
|
||||
{
|
||||
$t = mb_strtolower($docType);
|
||||
$b = mb_strtolower($brief);
|
||||
|
||||
// Track 2 (other Norwegian law): neutral verification question, always fires.
|
||||
if ($track !== 'family') {
|
||||
return 'Vurder teksten ovenfor opp mot gjeldende norsk rett: er de rettslige påstandene korrekte, '
|
||||
. 'er det vist til riktige lover og paragrafer, og mangler det viktige rettslige vilkår, frister '
|
||||
. 'eller prosessuelle krav som burde vært nevnt?';
|
||||
}
|
||||
|
||||
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) '
|
||||
|
||||
+16
-16
@@ -470,11 +470,11 @@ function dbnToolsTranslations(): array
|
||||
'doc_picker_modal_title' => 'Select from My Docs',
|
||||
|
||||
// MCP setup page + tool detail pages
|
||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
||||
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all 19 DBN legal preparation tools via MCP.',
|
||||
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all Do Better Legal Norwegian-law preparation tools via MCP.',
|
||||
'mcp_hero_badge' => '✦ Plus & Pro',
|
||||
'mcp_hero_h1' => 'Use DBN tools from Claude, Cursor & Copilot',
|
||||
'mcp_hero_sub' => 'Connect any MCP client to all 19 Do Better Norge tools — transcription, legal analysis, timelines, redaction, and more.',
|
||||
'mcp_hero_h1' => 'Use Do Better Legal tools from Claude, Cursor & Copilot',
|
||||
'mcp_hero_sub' => 'Connect any MCP client to the full Do Better Legal toolset — transcription, legal analysis, timelines, redaction, and more.',
|
||||
'mcp_token_section_title' => 'Your MCP token',
|
||||
'mcp_gate_guest_p' => 'Sign in to create your personal MCP token. Available to Plus and Pro members.',
|
||||
'mcp_gate_guest_btn' => 'Sign in',
|
||||
@@ -928,11 +928,11 @@ function dbnToolsTranslations(): array
|
||||
'doc_picker_modal_title' => 'Velg fra Mine dokumenter',
|
||||
|
||||
// MCP setup page + tool detail pages
|
||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
||||
'mcp_meta_desc' => 'Koble Claude, Cursor og andre AI-verktøy til alle 19 DBN juridiske forberedelsesverktøy via MCP.',
|
||||
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||
'mcp_meta_desc' => 'Koble Claude, Cursor og andre AI-verktøy til alle Do Better Legal-verktøy for norsk juridisk forberedelse via MCP.',
|
||||
'mcp_hero_badge' => '✦ Plus & Pro',
|
||||
'mcp_hero_h1' => 'Bruk DBN-verktøy fra Claude, Cursor & Copilot',
|
||||
'mcp_hero_sub' => 'Koble enhver MCP-klient til alle 19 Do Better Norge-verktøy — transkripsjon, juridisk analyse, tidslinjer, redigering og mer.',
|
||||
'mcp_hero_h1' => 'Bruk Do Better Legal-verktøy fra Claude, Cursor & Copilot',
|
||||
'mcp_hero_sub' => 'Koble enhver MCP-klient til hele Do Better Legal-verktøysettet — transkripsjon, juridisk analyse, tidslinjer, redigering og mer.',
|
||||
'mcp_token_section_title' => 'Din MCP-token',
|
||||
'mcp_gate_guest_p' => 'Logg inn for å opprette din personlige MCP-token. Tilgjengelig for Plus- og Pro-medlemmer.',
|
||||
'mcp_gate_guest_btn' => 'Logg inn',
|
||||
@@ -1386,11 +1386,11 @@ function dbnToolsTranslations(): array
|
||||
'doc_picker_modal_title' => 'Вибрати з Моїх документів',
|
||||
|
||||
// MCP setup page + tool detail pages
|
||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
||||
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх 19 юридичних підготовчих інструментів DBN через MCP.',
|
||||
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх інструментів Do Better Legal для підготовки за нормами норвезького права через MCP.',
|
||||
'mcp_hero_badge' => '✦ Плюс та Профі',
|
||||
'mcp_hero_h1' => 'Використовуйте інструменти DBN від Claude, Cursor та Copilot',
|
||||
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до всіх 19 інструментів Do Better Norge — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
|
||||
'mcp_hero_h1' => 'Використовуйте інструменти Do Better Legal від Claude, Cursor та Copilot',
|
||||
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до повного набору інструментів Do Better Legal — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
|
||||
'mcp_token_section_title' => 'Ваш токен MCP',
|
||||
'mcp_gate_guest_p' => 'Увійдіть, щоб створити свій особистий токен MCP. Доступно для учасників Плюс та Профі.',
|
||||
'mcp_gate_guest_btn' => 'Увійти',
|
||||
@@ -1844,11 +1844,11 @@ function dbnToolsTranslations(): array
|
||||
'doc_picker_modal_title' => 'Wybierz z Moich dokumentów',
|
||||
|
||||
// MCP setup page + tool detail pages
|
||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
||||
'mcp_meta_desc' => 'Połącz Claude, Cursor i inne narzędzia AI z wszystkimi 19 narzędziami przygotowania prawnego DBN za pośrednictwem MCP.',
|
||||
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||
'mcp_meta_desc' => 'Połącz Claude, Cursor i inne narzędzia AI z wszystkimi narzędziami Do Better Legal do przygotowania w zakresie prawa norweskiego za pośrednictwem MCP.',
|
||||
'mcp_hero_badge' => '✦ Plus & Pro',
|
||||
'mcp_hero_h1' => 'Użyj narzędzi DBN z Claude, Cursor i Copilot',
|
||||
'mcp_hero_sub' => 'Połącz dowolnego klienta MCP ze wszystkimi 19 narzędziami Do Better Norge — transkrypcja, analiza prawna, harmonogramy, redakcja i inne.',
|
||||
'mcp_hero_h1' => 'Użyj narzędzi Do Better Legal z Claude, Cursor i Copilot',
|
||||
'mcp_hero_sub' => 'Połącz dowolnego klienta MCP z pełnym zestawem narzędzi Do Better Legal — transkrypcja, analiza prawna, harmonogramy, redakcja i inne.',
|
||||
'mcp_token_section_title' => 'Twój token MCP',
|
||||
'mcp_gate_guest_p' => 'Zaloguj się, aby utworzyć swój osobisty token MCP. Dostępny dla członków Plus i Pro.',
|
||||
'mcp_gate_guest_btn' => 'Zaloguj się',
|
||||
|
||||
Reference in New Issue
Block a user