feat: Enhance Support Reply Service with tone instructions and article details
- Added tone instruction retrieval to SupportReplyService. - Improved user feedback when no relevant article is found. - Included article URL and tone instruction in LLM prompt. - Updated response format to include source information. - Enhanced article management UI with search functionality and editing capabilities. - Introduced a new API endpoint for nearest articles based on vector search. - Added confidence badge component to display article confidence levels. - Implemented tests for article searching, editing, and nearest article API. - Removed obsolete .htaccess file.
This commit is contained in:
81
app/Http/Controllers/Api/NearestArticleController.php
Normal file
81
app/Http/Controllers/Api/NearestArticleController.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exceptions\OllamaUnavailableException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Repositories\Contracts\ArticleRepositoryInterface;
|
||||
use App\Services\EmbeddingService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NearestArticleController extends Controller
|
||||
{
|
||||
public function __invoke(
|
||||
Request $request,
|
||||
EmbeddingService $embeddingService,
|
||||
ArticleRepositoryInterface $articleRepository
|
||||
): JsonResponse {
|
||||
$validated = $request->validate([
|
||||
'query' => ['required', 'string', 'min:2', 'max:1000'],
|
||||
'limit' => ['sometimes', 'integer', 'min:1', 'max:20'],
|
||||
'min_similarity' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'include_content' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
|
||||
$query = trim($validated['query']);
|
||||
$limit = (int) ($validated['limit'] ?? 5);
|
||||
$minSimilarity = (float) ($validated['min_similarity'] ?? 0);
|
||||
$includeContent = $request->boolean('include_content', false);
|
||||
|
||||
try {
|
||||
$embedding = $embeddingService->embed($query);
|
||||
} catch (OllamaUnavailableException $exception) {
|
||||
return response()->json([
|
||||
'message' => 'Embedding provider is unavailable.',
|
||||
'error' => $exception->getMessage(),
|
||||
], 503);
|
||||
}
|
||||
|
||||
$embeddingContext = $embeddingService->context();
|
||||
$candidates = $articleRepository->findSimilarByEmbedding(
|
||||
embedding: $embedding,
|
||||
limit: $limit,
|
||||
embeddingContext: $embeddingContext,
|
||||
filters: ['published_only' => true]
|
||||
);
|
||||
|
||||
$results = collect($candidates)
|
||||
->map(function ($candidate) use ($includeContent) {
|
||||
$similarity = max(0, min(1, 1 - $candidate->distance));
|
||||
$content = trim($candidate->content);
|
||||
|
||||
return [
|
||||
'article_id' => $candidate->articleId,
|
||||
'title' => $candidate->title,
|
||||
'similarity' => round($similarity, 4),
|
||||
'distance' => round($candidate->distance, 4),
|
||||
'snippet' => str($content)->limit(220)->toString(),
|
||||
'content' => $includeContent ? $content : null,
|
||||
'source_url' => $candidate->sourceUrl,
|
||||
'source_article_id' => $candidate->sourceArticleId,
|
||||
'note' => $candidate->note,
|
||||
'allowed_actions' => $candidate->allowedActions,
|
||||
];
|
||||
})
|
||||
->filter(fn (array $result) => $result['similarity'] >= $minSimilarity)
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'data' => $results,
|
||||
'meta' => [
|
||||
'query' => $query,
|
||||
'limit' => $limit,
|
||||
'min_similarity' => $minSimilarity,
|
||||
'published_only' => true,
|
||||
'embedding_provider_instance_id' => $embeddingContext['provider_instance_id'] ?? null,
|
||||
'embedding_model' => $embeddingContext['embedding_model'] ?? null,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user