feat(mcp): expose corpus_search, korrespond_refine, extract_text tools
Restores the 3 tools (manifest + invoke arms + invokeExtract helper), the citation-atom RAG lever in LegalTools/corpus-search, and the catalog icons. These were live on prod via rsync but uncommitted, so a git-pull deploy reverted the manifest from 22 to 19 tools. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,46 @@ try {
|
||||
$catClause = $category !== null ? ' AND d.category = ?' : '';
|
||||
$excludeLike = '%' . EXCLUDED_DOMAIN . '%';
|
||||
|
||||
// Exact-identifier routing: the FULLTEXT tokenizer drops "§ 4-12" / "Art. 8"
|
||||
// to stopword fragments, so a citation query never matches. Route those to a
|
||||
// verbatim LIKE lookup and pin the hits ahead of the fuzzy BM25 results.
|
||||
$exactHits = [];
|
||||
$exactChunkIds = [];
|
||||
$citationAtoms = DbnLegalToolsService::citationAtoms($query);
|
||||
if (!empty($citationAtoms)) {
|
||||
$atomClauses = [];
|
||||
$atomParams = [1];
|
||||
foreach ($citationAtoms as $atom) {
|
||||
$like = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $atom) . '%';
|
||||
$atomClauses[] = '(c.content LIKE ? OR c.section_title LIKE ?)';
|
||||
$atomParams[] = $like;
|
||||
$atomParams[] = $like;
|
||||
}
|
||||
$atomParams[] = $excludeLike;
|
||||
$exactSql = "SELECT d.id AS document_id, d.title, d.category,
|
||||
d.source_url, c.id AS chunk_id, c.content AS excerpt,
|
||||
c.section_title AS section, d.language, 1.0 AS score
|
||||
FROM chunks c
|
||||
JOIN documents d ON c.document_id = d.id
|
||||
WHERE d.corpus_id = ? AND d.status = 'ready'
|
||||
AND (" . implode(' OR ', $atomClauses) . ")
|
||||
AND d.source_url NOT LIKE ?
|
||||
$catClause
|
||||
LIMIT $limit";
|
||||
$exactParams = $atomParams;
|
||||
if ($category !== null) $exactParams[] = $category;
|
||||
try {
|
||||
$stmt = $ragDb->prepare($exactSql);
|
||||
$stmt->execute($exactParams);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$exactHits[] = $r;
|
||||
if (isset($r['chunk_id'])) $exactChunkIds[(int)$r['chunk_id']] = true;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// Non-fatal — fall through to fuzzy BM25.
|
||||
}
|
||||
}
|
||||
|
||||
// Try FULLTEXT index first
|
||||
try {
|
||||
$sql = "SELECT d.id AS document_id, d.title, d.category,
|
||||
@@ -89,6 +129,12 @@ try {
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// Exact identifier hits lead; drop fuzzy rows that duplicate them.
|
||||
if (!empty($exactChunkIds)) {
|
||||
$rows = array_values(array_filter($rows, fn($r) => empty($exactChunkIds[(int)($r['chunk_id'] ?? 0)])));
|
||||
}
|
||||
$rows = array_slice(array_merge($exactHits, $rows), 0, $limit);
|
||||
|
||||
$hits = array_map(fn($r) => [
|
||||
'title' => $r['title'] ?? '',
|
||||
'category' => $r['category'] ?? '',
|
||||
@@ -100,6 +146,7 @@ try {
|
||||
'chunk_id' => isset($r['chunk_id']) ? (int)$r['chunk_id'] : null,
|
||||
'source_url' => $r['source_url'] ?? null,
|
||||
'language' => $r['language'] ?? null,
|
||||
'exact_match' => !empty($exactChunkIds[(int)($r['chunk_id'] ?? 0)]),
|
||||
], $rows);
|
||||
dbnToolsRespond(['ok' => true, 'hits' => $hits, 'mode' => 'bm25', 'query' => $query]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user