1. Nguyên tắc tích hợp
Drupal nên gọi API trung gian hoặc Ollama từ phía server-side. Không nên để JavaScript trên trình duyệt gọi trực tiếp Ollama.
Mô hình khuyến nghị:
Drupal Form/Controller↓Drupal Service↓API AI trung gian hoặc Ollama localhost↓Kết quả trả về Drupal
2. Cấu trúc module mẫu
Ví dụ module tên:
ai_local_chatCấu trúc:
ai_local_chat/ai_local_chat.info.ymlai_local_chat.routing.ymlai_local_chat.services.ymlsrc/Controller/AiLocalChatController.phpService/OllamaClient.php
3. File ai_local_chat.info.yml
name: 'AI Local Chat'type: moduledescription: 'Tích hợp chatbot AI local với Ollama hoặc API trung gian.'core_version_requirement: ^10 || ^11package: 'Custom'
4. File ai_local_chat.routing.yml
ai_local_chat.form:path: '/ai-local-chat'defaults:_controller: '\Drupal\ai_local_chat\Controller\AiLocalChatController::chatPage'_title: 'AI Local Chat'requirements:_permission: 'access content'ai_local_chat.ask:path: '/ai-local-chat/ask'defaults:_controller: '\Drupal\ai_local_chat\Controller\AiLocalChatController::ask'requirements:_permission: 'access content'methods: [POST]
5. File ai_local_chat.services.yml
services:ai_local_chat.ollama_client:class: Drupal\ai_local_chat\Service\OllamaClientarguments: ['@http_client']
6. Service gọi Ollama: OllamaClient.php
<?phpnamespace Drupal\ai_local_chat\Service;use GuzzleHttp\ClientInterface;class OllamaClient {protected ClientInterface $httpClient;public function __construct(ClientInterface $http_client) {$this->httpClient = $http_client;}public function chat(string $question): string {$url = 'http://127.0.0.1:11434/api/chat';$payload = ['model' => 'gemma3:4b','stream' => FALSE,'messages' => [['role' => 'system','content' => 'Bạn là trợ lý AI nội bộ bệnh viện. Không bịa số liệu hoặc căn cứ. Không tư vấn điều trị cá nhân.',],['role' => 'user','content' => $question,],],'options' => ['temperature' => 0.2,],];$response = $this->httpClient->request('POST', $url, ['json' => $payload,'timeout' => 120,]);$data = json_decode((string) $response->getBody(), TRUE);return $data['message']['content'] ?? 'Không nhận được phản hồi hợp lệ từ AI.';}}
7. Controller mẫu: AiLocalChatController.php
<?phpnamespace Drupal\ai_local_chat\Controller;use Drupal\Core\Controller\ControllerBase;use Symfony\Component\DependencyInjection\ContainerInterface;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\JsonResponse;use Drupal\ai_local_chat\Service\OllamaClient;class AiLocalChatController extends ControllerBase {protected OllamaClient $ollamaClient;public function __construct(OllamaClient $ollama_client) {$this->ollamaClient = $ollama_client;}public static function create(ContainerInterface $container): static {return new static($container->get('ai_local_chat.ollama_client'));}public function chatPage(): array {return ['#markup' => '<div id="ai-local-chat"><p><strong>Lưu ý:</strong> Không nhập dữ liệu định danh người bệnh, bệnh án, kết quả xét nghiệm hoặc thông tin nhạy cảm vào chatbot chung.</p><textarea id="ai-question" rows="5" style="width:100%;"></textarea><br><button id="ai-send">Gửi câu hỏi</button><pre id="ai-answer" style="white-space: pre-wrap; margin-top: 20px;"></pre></div><script>document.getElementById("ai-send").addEventListener("click", async function () {const question = document.getElementById("ai-question").value;const answerBox = document.getElementById("ai-answer");answerBox.textContent = "Đang xử lý...";const response = await fetch("/ai-local-chat/ask", {method: "POST",headers: {"Content-Type": "application/json"},body: JSON.stringify({question})});const data = await response.json();answerBox.textContent = data.answer || data.error || "Không có phản hồi.";});</script>','#allowed_tags' => ['div', 'p', 'strong', 'textarea', 'br', 'button', 'pre', 'script'],];}public function ask(Request $request): JsonResponse {$content = json_decode($request->getContent(), TRUE);$question = trim($content['question'] ?? '');if ($question === '') {return new JsonResponse(['error' => 'Câu hỏi không được để trống.'], 400);}try {$answer = $this->ollamaClient->chat($question);return new JsonResponse(['answer' => $answer]);}catch (\Throwable $e) {\Drupal::logger('ai_local_chat')->error($e->getMessage());return new JsonResponse(['error' => 'Có lỗi khi gọi hệ thống AI.'], 500);}}}
8. Lưu ý triển khai thực tế
Trong production, nên bổ sung:
- CSRF token.
- Phân quyền riêng.
- Rate limit.
- API trung gian thay vì gọi trực tiếp Ollama.
- Log user, role, thời gian, tác vụ.
- Kiểm soát dữ liệu đầu vào.
- Không lưu prompt nhạy cảm.
- Hiển thị nguồn nếu dùng RAG.
- Đăng nhập để gửi ý kiến