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,
|
'language' => $language,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
|
||||||
|
? trim($input['profile'])
|
||||||
|
: null;
|
||||||
|
|
||||||
$agent = new DbnKorrespondAgent();
|
$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');
|
$ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'korrespond_refine');
|
||||||
$result['ok'] = true;
|
$result['ok'] = true;
|
||||||
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
|
$result['latency_ms'] = (int)round((microtime(true) - $startTime) * 1000);
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ try {
|
|||||||
$personaResolved = dbnToolsResolvePersona((int)$client['id'], $personaSlug);
|
$personaResolved = dbnToolsResolvePersona((int)$client['id'], $personaSlug);
|
||||||
|
|
||||||
$agent = (new DbnLegalAnalysisAgent())
|
$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
|
// Pass 1 — extract issues (Azure, fast); deduct credit AFTER this succeeds
|
||||||
$emit('progress', ['step' => 'extracting_issues', 'detail' => 'Identifying distinct legal issues…']);
|
$emit('progress', ['step' => 'extracting_issues', 'detail' => 'Identifying distinct legal issues…']);
|
||||||
@@ -132,7 +133,8 @@ try {
|
|||||||
$legalCheck = dbnToolsRunLegalCheck(
|
$legalCheck = dbnToolsRunLegalCheck(
|
||||||
mb_strimwidth((string)($synth['overall_assessment'] ?? ''), 0, 800),
|
mb_strimwidth((string)($synth['overall_assessment'] ?? ''), 0, 800),
|
||||||
$docType,
|
$docType,
|
||||||
$personaResolved['system_prompt'] ?? null
|
$personaResolved['system_prompt'] ?? null,
|
||||||
|
$personaResolved['slug'] ?? $personaSlug
|
||||||
);
|
);
|
||||||
} catch (Throwable) {}
|
} catch (Throwable) {}
|
||||||
|
|
||||||
|
|||||||
@@ -847,9 +847,10 @@ PROMPT;
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
$docExcerpt = mb_substr($docText, 0, 8000, 'UTF-8');
|
$docExcerpt = mb_substr($docText, 0, 8000, 'UTF-8');
|
||||||
|
$product = dbnToolsProductName();
|
||||||
|
|
||||||
$prompt = <<<PROMPT
|
$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:
|
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.
|
- 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(
|
$checkFindings = dbnToolsRunLegalCheck(
|
||||||
(string)($json['advocacy_brief'] ?? ''),
|
(string)($json['advocacy_brief'] ?? ''),
|
||||||
$docType,
|
$docType,
|
||||||
$this->resolvedPersonaPrompt
|
$this->resolvedPersonaPrompt,
|
||||||
|
$this->personaSlug
|
||||||
);
|
);
|
||||||
if (!empty($checkFindings)) {
|
if (!empty($checkFindings)) {
|
||||||
if (!is_array($json['procedural_red_flags'] ?? null)) {
|
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');
|
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||||
}
|
}
|
||||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
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;
|
return $package;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ final class DbnMcpRuntime
|
|||||||
'mode' => ['type' => 'string', 'enum' => ['standard', 'strict']],
|
'mode' => ['type' => 'string', 'enum' => ['standard', 'strict']],
|
||||||
'output_format' => ['type' => 'string', 'enum' => ['contextual', 'generic', 'pseudonym']],
|
'output_format' => ['type' => 'string', 'enum' => ['contextual', 'generic', 'pseudonym']],
|
||||||
], ['text']),
|
], ['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,
|
'text' => $text,
|
||||||
'source_lang' => $lang,
|
'source_lang' => $lang,
|
||||||
'target_lang' => $lang,
|
'target_lang' => $lang,
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ final class DbnDeepResearchAgent
|
|||||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||||
}
|
}
|
||||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
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;
|
return $package;
|
||||||
}
|
}
|
||||||
@@ -487,8 +487,9 @@ final class DbnDeepResearchAgent
|
|||||||
$priorContextBlock = implode("\n", $parts) . "\n\nNow investigate this branch:\n";
|
$priorContextBlock = implode("\n", $parts) . "\n\nNow investigate this branch:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$product = dbnToolsProductName();
|
||||||
$prompt = <<<PROMPT
|
$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:
|
Input:
|
||||||
{$seedDescription}
|
{$seedDescription}
|
||||||
@@ -580,8 +581,9 @@ Rules:
|
|||||||
- Write the questions in {$locale}.
|
- Write the questions in {$locale}.
|
||||||
PROMPT;
|
PROMPT;
|
||||||
} else {
|
} else {
|
||||||
|
$product = dbnToolsProductName();
|
||||||
$prompt = <<<PROMPT
|
$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:
|
Research brief:
|
||||||
{$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"
|
? "\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 !== '') {
|
if ($advocateRole !== '') {
|
||||||
$prompt = <<<PROMPT
|
$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}
|
Your client: {$advocateRole}
|
||||||
{$priorContextSection}
|
{$priorContextSection}
|
||||||
User input:
|
User input:
|
||||||
@@ -1106,7 +1109,7 @@ Return JSON:
|
|||||||
PROMPT;
|
PROMPT;
|
||||||
} else {
|
} else {
|
||||||
$prompt = <<<PROMPT
|
$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}
|
{$priorContextSection}
|
||||||
User input:
|
User input:
|
||||||
{$seedDescription}
|
{$seedDescription}
|
||||||
|
|||||||
@@ -803,9 +803,10 @@ PROMPT;
|
|||||||
$docTypeB = $metaB['doc_type'] ?? $nameB;
|
$docTypeB = $metaB['doc_type'] ?? $nameB;
|
||||||
$docDateB = $metaB['doc_date'] ?? '?';
|
$docDateB = $metaB['doc_date'] ?? '?';
|
||||||
$authority = $metaA['issuing_authority'] ?? $metaB['issuing_authority'] ?? 'the authority';
|
$authority = $metaA['issuing_authority'] ?? $metaB['issuing_authority'] ?? 'the authority';
|
||||||
|
$product = dbnToolsProductName();
|
||||||
|
|
||||||
$prompt = <<<PROMPT
|
$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:
|
HALLUCINATION RULES:
|
||||||
- Only cite statute sections (§), ECHR articles, and case names that appear verbatim in the corpus sources below.
|
- 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');
|
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||||
}
|
}
|
||||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
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;
|
return $package;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ PROMPT;
|
|||||||
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
||||||
$legalCheck = [];
|
$legalCheck = [];
|
||||||
try {
|
try {
|
||||||
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body);
|
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
|
||||||
} catch (Throwable $e) { /* silent — non-critical */ }
|
} catch (Throwable $e) { /* silent — non-critical */ }
|
||||||
|
|
||||||
// ── Translate to user language (if not Norwegian) ───────────────────────
|
// ── Translate to user language (if not Norwegian) ───────────────────────
|
||||||
@@ -777,7 +777,8 @@ EOT,
|
|||||||
array $classify,
|
array $classify,
|
||||||
string $originalDraftNo,
|
string $originalDraftNo,
|
||||||
string $jurisdiction,
|
string $jurisdiction,
|
||||||
?callable $emit = null
|
?callable $emit = null,
|
||||||
|
?string $persona = null
|
||||||
): array {
|
): array {
|
||||||
$body = $intake['recipient_body'] ?? 'other';
|
$body = $intake['recipient_body'] ?? 'other';
|
||||||
$outputType = $intake['output_type'] ?? 'email';
|
$outputType = $intake['output_type'] ?? 'email';
|
||||||
@@ -808,7 +809,7 @@ EOT,
|
|||||||
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
|
||||||
$legalCheckRefine = [];
|
$legalCheckRefine = [];
|
||||||
try {
|
try {
|
||||||
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body);
|
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
|
||||||
} catch (Throwable $e) { /* silent — non-critical */ }
|
} catch (Throwable $e) { /* silent — non-critical */ }
|
||||||
|
|
||||||
$draftUser = $checked['draft'];
|
$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. */
|
/** Persona system prompt (from the resolved chat profile); null ⇒ use the built-in barnevern prompt. */
|
||||||
private ?string $personaPrompt = null;
|
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
|
public function withPersonaPrompt(?string $prompt): self
|
||||||
{
|
{
|
||||||
@@ -36,6 +38,15 @@ final class DbnLegalAnalysisAgent
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withPersonaSlug(?string $slug): self
|
||||||
|
{
|
||||||
|
$slug = is_string($slug) ? trim($slug) : '';
|
||||||
|
if ($slug !== '') {
|
||||||
|
$this->personaSlug = $slug;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// On Azure: gpt-4o-mini for extraction/synthesis. On Bedrock: factory picks Haiku/Sonnet.
|
// 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'];
|
$userMsg .= "\n\n" . $ctxLabel . ': ' . $issue['brief_context'];
|
||||||
}
|
}
|
||||||
if ($corpusContext !== '') {
|
if ($corpusContext !== '') {
|
||||||
|
$product = dbnToolsProductName();
|
||||||
$srcLabel = match ($language) {
|
$srcLabel = match ($language) {
|
||||||
'no' => 'Relevante kilder fra Do Better Norge-korpuset',
|
'no' => 'Relevante kilder fra ' . $product . '-korpuset',
|
||||||
'pl' => 'Istotne źródła z korpusu Do Better Norge',
|
'pl' => 'Istotne źródła z korpusu ' . $product,
|
||||||
'uk' => 'Релевантні джерела з корпусу Do Better Norge',
|
'uk' => 'Релевантні джерела з корпусу ' . $product,
|
||||||
default => 'Relevant sources from the Do Better Norge corpus',
|
default => 'Relevant sources from the ' . $product . ' corpus',
|
||||||
};
|
};
|
||||||
$userMsg .= "\n\n" . $srcLabel . ":\n" . $corpusContext;
|
$userMsg .= "\n\n" . $srcLabel . ":\n" . $corpusContext;
|
||||||
}
|
}
|
||||||
@@ -613,7 +625,9 @@ PROMPT;
|
|||||||
try {
|
try {
|
||||||
$legalCheck = dbnToolsRunLegalCheck(
|
$legalCheck = dbnToolsRunLegalCheck(
|
||||||
mb_strimwidth($synth['overall_assessment'], 0, 800),
|
mb_strimwidth($synth['overall_assessment'], 0, 800),
|
||||||
$docType
|
$docType,
|
||||||
|
$this->personaPrompt,
|
||||||
|
$this->personaSlug
|
||||||
);
|
);
|
||||||
} catch (Throwable) {}
|
} catch (Throwable) {}
|
||||||
|
|
||||||
|
|||||||
+33
-21
@@ -39,8 +39,9 @@ final class DbnLegalToolsService
|
|||||||
'shared' => 'Legal Library only',
|
'shared' => 'Legal Library only',
|
||||||
default => 'Legal Library + personal corpus',
|
default => 'Legal Library + personal corpus',
|
||||||
};
|
};
|
||||||
|
$product = dbnToolsProductName();
|
||||||
$trace = [
|
$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'),
|
$this->trace('Search tools used', 'ClientRagPipeline::searchAll with keyword mode.', 'running'),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -252,12 +253,9 @@ Return JSON only with these keys:
|
|||||||
}
|
}
|
||||||
PROMPT;
|
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).
|
// structured-output contract while applying the persona's legal framing).
|
||||||
$system = $this->legalJsonSystemPrompt($language);
|
$system = $this->legalJsonSystemPrompt($language, $personaResolved['system_prompt'] ?? null);
|
||||||
if (!empty($personaResolved['system_prompt'])) {
|
|
||||||
$system = $personaResolved['system_prompt'] . "\n\n" . $system;
|
|
||||||
}
|
|
||||||
$askDeployment = $personaModel;
|
$askDeployment = $personaModel;
|
||||||
$raw = $gateway->withDeployment($askDeployment)->chatText([
|
$raw = $gateway->withDeployment($askDeployment)->chatText([
|
||||||
['role' => 'system', 'content' => $system],
|
['role' => 'system', 'content' => $system],
|
||||||
@@ -1166,7 +1164,7 @@ PROMPT;
|
|||||||
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||||
}
|
}
|
||||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
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;
|
return $package;
|
||||||
}
|
}
|
||||||
@@ -1191,30 +1189,44 @@ PROMPT;
|
|||||||
return [$this->azure, ($engine === 'azure_full') ? 'gpt-4o' : 'gpt-4o-mini'];
|
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([
|
// With a persona, route to its pinned engine (Track-1 → tuned Qwen, Track-2 → gpt-4o)
|
||||||
['role' => 'system', 'content' => $this->legalJsonSystemPrompt($language)],
|
// 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],
|
['role' => 'user', 'content' => $prompt],
|
||||||
], [
|
], [
|
||||||
'json' => true,
|
'json' => true,
|
||||||
'temperature' => 0.1,
|
'temperature' => 0.1,
|
||||||
'max_tokens' => $maxTokens,
|
'max_tokens' => $maxTokens,
|
||||||
]);
|
]);
|
||||||
$json = $this->azure->decodeJsonObject($raw);
|
$json = $gateway->decodeJsonObject($raw);
|
||||||
if (!$json) {
|
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;
|
return $json;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function legalJsonSystemPrompt(string $language): string
|
private function legalJsonSystemPrompt(string $language, ?string $personaPrompt = null): string
|
||||||
{
|
{
|
||||||
$locale = dbnToolsLanguageName($language);
|
$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
|
return <<<PROMPT
|
||||||
You are Do Better Norge Legal Tools — a source-grounded Norwegian legal preparation assistant.
|
You are {$product} Tools — a source-grounded Norwegian legal preparation assistant covering all areas of Norwegian law.
|
||||||
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).
|
{$personaBlock}Legal guardrails:
|
||||||
Use the DBN legal guardrails:
|
|
||||||
- Answer only from provided source excerpts or pasted text.
|
- Answer only from provided source excerpts or pasted text.
|
||||||
- Treat your role as legal information and issue-spotting, not final legal advice.
|
- 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.
|
- Never invent statutes, paragraph numbers, case names, citations, parties, dates, or sources.
|
||||||
@@ -1260,7 +1272,7 @@ PROMPT;
|
|||||||
'title' => $title,
|
'title' => $title,
|
||||||
'excerpt' => $docSummary ?? $rawExcerpt,
|
'excerpt' => $docSummary ?? $rawExcerpt,
|
||||||
'chunk_text' => $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,
|
'score' => $score,
|
||||||
'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null,
|
'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null,
|
||||||
'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null,
|
'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null,
|
||||||
@@ -1354,7 +1366,7 @@ PROMPT;
|
|||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
foreach ($rows as &$row) {
|
foreach ($rows as &$row) {
|
||||||
$row['similarity'] = 0.25;
|
$row['similarity'] = 0.25;
|
||||||
$row['source_name'] = 'Do Better Norge private corpus';
|
$row['source_name'] = dbnToolsProductName() . ' private corpus';
|
||||||
$row['source_type'] = 'private';
|
$row['source_type'] = 'private';
|
||||||
}
|
}
|
||||||
return $rows;
|
return $rows;
|
||||||
@@ -1819,7 +1831,7 @@ PROMPT;
|
|||||||
$enriched = $text;
|
$enriched = $text;
|
||||||
$corpusUsed = $corpusContext !== '';
|
$corpusUsed = $corpusContext !== '';
|
||||||
if ($corpusUsed) {
|
if ($corpusUsed) {
|
||||||
$enriched = "[Relevant legal context from Do Better Norge corpus]\n"
|
$enriched = '[Relevant legal context from ' . dbnToolsProductName() . " corpus]\n"
|
||||||
. $corpusContext
|
. $corpusContext
|
||||||
. "\n\n---\n\nDocument to summarise:\n"
|
. "\n\n---\n\nDocument to summarise:\n"
|
||||||
. $text;
|
. $text;
|
||||||
@@ -1874,7 +1886,7 @@ PROMPT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
$corpusNote = $corpusUsed
|
$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.';
|
: 'No corpus search performed; summarised from document text only.';
|
||||||
|
|
||||||
$trace = [
|
$trace = [
|
||||||
|
|||||||
+59
-12
@@ -479,6 +479,39 @@ function dbnToolsDefaultPersonaSlug(): string
|
|||||||
return $v !== '' ? $v : 'family';
|
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
|
* Resolve a DBN persona (a caveauAI chat profile for client 57) into a normalized
|
||||||
* runtime bundle the tools can act on:
|
* 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');
|
dbnToolsAbort('No persona profile and the family-legal corpus package is not active.', 503, 'package_unavailable');
|
||||||
}
|
}
|
||||||
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
|
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 = [
|
$resolved = [
|
||||||
'slug' => 'family',
|
'slug' => 'family',
|
||||||
@@ -938,7 +971,7 @@ function dbnToolsRequireClient(): array
|
|||||||
{
|
{
|
||||||
$client = dbnToolsFetchClient();
|
$client = dbnToolsFetchClient();
|
||||||
if (!$client || empty($client['is_active'])) {
|
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;
|
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
|
// 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.
|
// 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) {
|
if ($question === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$opts = [
|
$opts = [
|
||||||
'model' => 'dbn-legal-agent-v3',
|
'model' => $model,
|
||||||
'temperature' => 0.1,
|
'temperature' => 0.1,
|
||||||
'max_tokens' => 350,
|
'max_tokens' => 350,
|
||||||
'timeout' => 45,
|
'timeout' => 45,
|
||||||
// No 'json' key — plain narrative, no response_format flag
|
// 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) : '';
|
$personaPrompt = is_string($personaPrompt) ? trim($personaPrompt) : '';
|
||||||
$sysMsg = $personaPrompt !== ''
|
if ($personaPrompt !== '') {
|
||||||
? $personaPrompt
|
$sysMsg = $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.';
|
} 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,
|
// 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.
|
// 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),
|
'severity' => dbnToolsInferCheckSeverity($clean),
|
||||||
'legal_basis' => dbnToolsExtractCheckLegalBasis($clean),
|
'legal_basis' => dbnToolsExtractCheckLegalBasis($clean),
|
||||||
'source_refs' => [],
|
'source_refs' => [],
|
||||||
'what_to_check'=> 'Verifiser med norsk familieretsadvokat',
|
'what_to_check'=> $track === 'family' ? 'Verifiser med norsk familieretsadvokat' : 'Verifiser med norsk advokat',
|
||||||
'check_model' => 'dbn-legal-agent-v3',
|
'check_model' => $model,
|
||||||
]];
|
]];
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbnToolsSelectCheckQuestion(string $docType, string $brief): ?string
|
function dbnToolsSelectCheckQuestion(string $docType, string $brief, string $track = 'family'): ?string
|
||||||
{
|
{
|
||||||
$t = mb_strtolower($docType);
|
$t = mb_strtolower($docType);
|
||||||
$b = mb_strtolower($brief);
|
$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')) {
|
if (str_contains($t, 'akutt') || str_contains($t, 'emergency')) {
|
||||||
return 'Hva er den korrekte rettslige terskelen for midlertidig plassering utenfor hjemmet '
|
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) '
|
. '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',
|
'doc_picker_modal_title' => 'Select from My Docs',
|
||||||
|
|
||||||
// MCP setup page + tool detail pages
|
// MCP setup page + tool detail pages
|
||||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||||
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all 19 DBN legal preparation tools via MCP.',
|
'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_badge' => '✦ Plus & Pro',
|
||||||
'mcp_hero_h1' => 'Use DBN tools from Claude, Cursor & Copilot',
|
'mcp_hero_h1' => 'Use Do Better Legal 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_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_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_p' => 'Sign in to create your personal MCP token. Available to Plus and Pro members.',
|
||||||
'mcp_gate_guest_btn' => 'Sign in',
|
'mcp_gate_guest_btn' => 'Sign in',
|
||||||
@@ -928,11 +928,11 @@ function dbnToolsTranslations(): array
|
|||||||
'doc_picker_modal_title' => 'Velg fra Mine dokumenter',
|
'doc_picker_modal_title' => 'Velg fra Mine dokumenter',
|
||||||
|
|
||||||
// MCP setup page + tool detail pages
|
// MCP setup page + tool detail pages
|
||||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||||
'mcp_meta_desc' => 'Koble Claude, Cursor og andre AI-verktøy til alle 19 DBN juridiske forberedelsesverktøy via MCP.',
|
'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_badge' => '✦ Plus & Pro',
|
||||||
'mcp_hero_h1' => 'Bruk DBN-verktøy fra Claude, Cursor & Copilot',
|
'mcp_hero_h1' => 'Bruk Do Better Legal-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_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_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_p' => 'Logg inn for å opprette din personlige MCP-token. Tilgjengelig for Plus- og Pro-medlemmer.',
|
||||||
'mcp_gate_guest_btn' => 'Logg inn',
|
'mcp_gate_guest_btn' => 'Logg inn',
|
||||||
@@ -1386,11 +1386,11 @@ function dbnToolsTranslations(): array
|
|||||||
'doc_picker_modal_title' => 'Вибрати з Моїх документів',
|
'doc_picker_modal_title' => 'Вибрати з Моїх документів',
|
||||||
|
|
||||||
// MCP setup page + tool detail pages
|
// MCP setup page + tool detail pages
|
||||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||||
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх 19 юридичних підготовчих інструментів DBN через MCP.',
|
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх інструментів Do Better Legal для підготовки за нормами норвезького права через MCP.',
|
||||||
'mcp_hero_badge' => '✦ Плюс та Профі',
|
'mcp_hero_badge' => '✦ Плюс та Профі',
|
||||||
'mcp_hero_h1' => 'Використовуйте інструменти DBN від Claude, Cursor та Copilot',
|
'mcp_hero_h1' => 'Використовуйте інструменти Do Better Legal від Claude, Cursor та Copilot',
|
||||||
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до всіх 19 інструментів Do Better Norge — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
|
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до повного набору інструментів Do Better Legal — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
|
||||||
'mcp_token_section_title' => 'Ваш токен MCP',
|
'mcp_token_section_title' => 'Ваш токен MCP',
|
||||||
'mcp_gate_guest_p' => 'Увійдіть, щоб створити свій особистий токен MCP. Доступно для учасників Плюс та Профі.',
|
'mcp_gate_guest_p' => 'Увійдіть, щоб створити свій особистий токен MCP. Доступно для учасників Плюс та Профі.',
|
||||||
'mcp_gate_guest_btn' => 'Увійти',
|
'mcp_gate_guest_btn' => 'Увійти',
|
||||||
@@ -1844,11 +1844,11 @@ function dbnToolsTranslations(): array
|
|||||||
'doc_picker_modal_title' => 'Wybierz z Moich dokumentów',
|
'doc_picker_modal_title' => 'Wybierz z Moich dokumentów',
|
||||||
|
|
||||||
// MCP setup page + tool detail pages
|
// MCP setup page + tool detail pages
|
||||||
'mcp_page_title' => 'MCP — Do Better Norge',
|
'mcp_page_title' => 'MCP — Do Better Legal',
|
||||||
'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_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_badge' => '✦ Plus & Pro',
|
||||||
'mcp_hero_h1' => 'Użyj narzędzi DBN z Claude, Cursor i Copilot',
|
'mcp_hero_h1' => 'Użyj narzędzi Do Better Legal 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_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_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_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ę',
|
'mcp_gate_guest_btn' => 'Zaloguj się',
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ export DBN_MCP_TOKEN=dbn_user_mcp_...</code></pre>
|
|||||||
{
|
{
|
||||||
"type": "promptString",
|
"type": "promptString",
|
||||||
"id": "dbn-token",
|
"id": "dbn-token",
|
||||||
"description": "Do Better Norge MCP token",
|
"description": "Do Better Legal MCP token",
|
||||||
"password": true
|
"password": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user