feat(tools): persona-driven multi-domain corpus + model routing

Generalize the family-locked legal tools into caveauAI persona profiles
(client 57 chat profiles, resolved in-process via the chat_profiles bridge).
Each tool accepts an optional `profile` slug that scopes the corpus package(s),
search method, system prompt and synthesis model; omitting it falls back to the
family-legal package so existing behaviour is unchanged.

- dbnToolsResolvePersona / dbnToolsListPersonas / dbnToolsBootChatProfiles in
  bootstrap.php; new api/personas.php + dbn.list_personas MCP tool.
- LegalTools search/ask/corpusContextForSummarize and the BvjAnalyzer /
  LegalAnalysis / translate paths take the persona's packages + prompt + model.
- Persona <select> on ask/search/summarize (populated from api/personas.php).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:49:58 +02:00
parent 5a0ef89dca
commit 662fbf7d6d
16 changed files with 404 additions and 58 deletions
+4 -1
View File
@@ -16,5 +16,8 @@ dbnToolsWithChargedTelemetry('ask', $language, $ftUid, function () use ($input,
if (mb_strlen(trim($question), 'UTF-8') < 5) {
dbnToolsAbort('Enter a question or select a document before running.', 422, 'empty_text');
}
return (new DbnLegalToolsService())->ask($question, $language, $engine);
$persona = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
return (new DbnLegalToolsService())->ask($question, $language, $engine, $persona);
});
+7 -1
View File
@@ -122,7 +122,13 @@ try {
}
}
$result = (new DbnBvjAnalyzerAgent())->run(
$personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
$result = (new DbnBvjAnalyzerAgent())
->withPersona($personaSlug)
->run(
$uploadedFiles,
$advocateRole,
$engine,
+4 -1
View File
@@ -13,6 +13,9 @@ $mode = in_array($rawMode, ['hybrid', 'bm25', 'vector', 'azure'], true) ? $r
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
$limit = max(1, min(20, (int)($input['limit'] ?? 8)));
$category = isset($input['category']) && $input['category'] !== '' ? trim((string)$input['category']) : null;
$persona = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
const EXCLUDED_DOMAIN = 'dobetternorge.no';
@@ -23,7 +26,7 @@ if (mb_strlen($query, 'UTF-8') < 3) {
try {
// ── HYBRID: delegate to the existing RAG pipeline ──────────────────────
if ($mode === 'hybrid') {
$result = (new DbnLegalToolsService())->search($query, $language, $limit, 'disabled', null);
$result = (new DbnLegalToolsService())->search($query, $language, $limit, 'disabled', null, 'both', $persona);
$hits = array_map(fn($h) => [
'title' => $h['title'] ?? '',
'category' => $h['category'] ?? '',
+13 -3
View File
@@ -55,7 +55,16 @@ try {
'chars' => mb_strlen($text, 'UTF-8'),
]);
$agent = new DbnLegalAnalysisAgent();
// Resolve the persona (default = child-welfare for this barnevern tool) so
// the targeted legal-check step uses the persona's system prompt + model.
$client = dbnToolsRequireClient();
$personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: 'child-welfare';
$personaResolved = dbnToolsResolvePersona((int)$client['id'], $personaSlug);
$agent = (new DbnLegalAnalysisAgent())
->withPersonaPrompt($personaResolved['system_prompt'] ?? null);
// Pass 1 — extract issues (Azure, fast); deduct credit AFTER this succeeds
$emit('progress', ['step' => 'extracting_issues', 'detail' => 'Identifying distinct legal issues…']);
@@ -101,7 +110,7 @@ try {
'issue_id' => $issue['id'],
]);
$corpusQuery = $issue['question'] . "\n" . $issue['brief_context'];
$corpusContext = $svc->corpusContextForSummarize($corpusQuery, 3);
$corpusContext = $svc->corpusContextForSummarize($corpusQuery, 3, $personaResolved['slug'] ?? null);
$emit('progress', [
'step' => 'issue_answering',
@@ -122,7 +131,8 @@ try {
try {
$legalCheck = dbnToolsRunLegalCheck(
mb_strimwidth((string)($synth['overall_assessment'] ?? ''), 0, 800),
$docType
$docType,
$personaResolved['system_prompt'] ?? null
);
} catch (Throwable) {}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../includes/LegalTools.php';
dbnToolsRequireMethod('GET');
dbnToolsRequireAuth();
$client = dbnToolsRequireClient();
$personas = dbnToolsListPersonas((int)$client['id']);
dbnToolsRespond([
'ok' => true,
'personas' => $personas,
'default_persona' => dbnToolsDefaultPersonaSlug(),
]);
+4 -1
View File
@@ -20,5 +20,8 @@ dbnToolsWithTelemetry('search', $language, function () use ($input, $language):
$scope = in_array($input['corpus_scope'] ?? '', ['shared', 'private', 'both'], true)
? $input['corpus_scope']
: 'both';
return (new DbnLegalToolsService())->search($query, $language, $limit, $temporalMode, $asOfDate, $scope);
$persona = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
return (new DbnLegalToolsService())->search($query, $language, $limit, $temporalMode, $asOfDate, $scope, $persona);
});
+4 -1
View File
@@ -51,7 +51,10 @@ try {
]);
$svc = new DbnLegalToolsService();
$query = mb_substr(trim($text), 0, 600, 'UTF-8');
$corpusContext = $svc->corpusContextForSummarize($query, 8);
$persona = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
$corpusContext = $svc->corpusContextForSummarize($query, 8, $persona);
$emit('progress', [
'step' => 'corpus_done',
'detail' => $corpusContext !== ''
+12 -2
View File
@@ -70,12 +70,22 @@ try {
$sourceName = dbnToolsLanguageName($sourceLang);
$targetName = dbnToolsLanguageName($targetLang);
// Persona-aware domain framing (default = Norwegian family law for back-compat).
$client = dbnToolsRequireClient();
$personaSlug = (isset($input['profile']) && is_string($input['profile']) && trim($input['profile']) !== '')
? trim($input['profile'])
: null;
$persona = dbnToolsResolvePersona((int)$client['id'], $personaSlug);
$domainLabel = (isset($persona['name']) && is_string($persona['name']) && trim($persona['name']) !== '' && ($persona['source'] ?? '') === 'chat_profile')
? trim($persona['name'])
: 'Norwegian family law, ECHR, and child-welfare proceedings';
$docTypeHint = $docType !== 'auto'
? "The document is of type: {$docType}. Apply appropriate Norwegian family-law terminology for this context."
? "The document is of type: {$docType}. Apply appropriate {$domainLabel} terminology for this context."
: '';
$systemPrompt = <<<PROMPT
You are a professional legal translator specialising in Norwegian family law, ECHR, and child-welfare proceedings.
You are a professional legal translator specialising in {$domainLabel}.
Task: Translate the provided text from {$sourceName} into {$targetName}.