refactor: kb & station & terminal

This commit is contained in:
2026-03-23 20:17:17 +08:00
parent 63ea2686e1
commit b74ba1a3f8
81 changed files with 1016 additions and 2492 deletions

View File

@@ -2,80 +2,34 @@
namespace App\Services;
use App\Models\Document;
use App\Models\KnowledgeBase;
use App\Models\Terminal;
use Illuminate\Support\Facades\Log;
class KnowledgeContextService
{
private const MAX_CONTEXT_LENGTH = 2000;
private const TOP_K = 5;
public function __construct(
private DocumentSearchService $searchService,
) {}
/**
* 搜索所有知识库中的文档
*
* @param string $query
* @return array{context: string, sources: array}
* 搜索知识库中的文档,返回 RAG 上下文
*/
public function search(string $query): array
public function search(string $query, ?Terminal $terminal = null): array
{
$knowledgeBaseIds = KnowledgeBase::where('status', 'active')->pluck('id')->toArray();
$stationIds = $terminal?->station_id ? [$terminal->station_id] : [];
if (empty($knowledgeBaseIds)) {
return [
'context' => '',
'sources' => [],
];
$results = $this->searchService->search($query, $stationIds);
if ($results->isEmpty()) {
return ['context' => '', 'sources' => []];
}
try {
// 使用 Scout/Meilisearch 搜索并获取排名分数
$rawResults = Document::search($query, function ($meilisearch, $query, $options) use ($knowledgeBaseIds) {
$options['showRankingScore'] = true;
$filter = collect($knowledgeBaseIds)
->map(fn($id) => "knowledge_base_id = {$id}")
->implode(' OR ');
$options['filter'] = $filter;
$options['limit'] = self::TOP_K;
return $meilisearch->search($query, $options);
})->raw();
$hits = $rawResults['hits'] ?? [];
} catch (\Exception $e) {
Log::warning('Knowledge search failed', [
'query' => $query,
'error' => $e->getMessage(),
]);
return [
'context' => '',
'sources' => [],
];
}
if (empty($hits)) {
return [
'context' => '',
'sources' => [],
];
}
// 取出文档 ID 并加载 Eloquent 模型
$hitIds = collect($hits)->pluck('id')->toArray();
$documents = Document::whereIn('id', $hitIds)->get()->keyBy('id');
// 构建排名分数映射
$rankingScores = collect($hits)->pluck('_rankingScore', 'id');
$context = '';
$sources = [];
foreach ($hits as $hit) {
$document = $documents->get($hit['id']);
if (!$document) {
continue;
}
foreach ($results as $document) {
$snippet = $this->extractSnippet($document);
if (mb_strlen($context) + mb_strlen($snippet) > self::MAX_CONTEXT_LENGTH) {
@@ -86,7 +40,6 @@ class KnowledgeContextService
$sources[] = [
'title' => $document->title,
'id' => 'kb-doc-' . str_pad($document->id, 3, '0', STR_PAD_LEFT),
'relevance' => round($rankingScores->get($document->id, 0), 2),
];
}
@@ -96,12 +49,9 @@ class KnowledgeContextService
];
}
/**
* 从文档中提取摘要片段
*/
private function extractSnippet($document): string
{
$content = $document->markdown_preview ?? $document->description ?? '';
$content = $document->getMarkdownContent() ?? $document->description ?? '';
if (mb_strlen($content) <= 500) {
return "{$document->title}\n{$content}";