完全にプライバシー保護されたAIカウンセラーとしてEmacsのdoctorを進化させる

しむどん 2025-08-29

LMStudioを設定しAPIサーバを起動する

LMStudioにモデルをダウンロードする。モデルはJan-v1-4B-GGUFを使用する。

https://pub.symdon.info/ef/aa/efaabf4f64ef6db3bed6f1e52a8051855a59bfe2faa1f25bfd34f99f5375c0bf.png

ダウンロードしたモデルを使ってAPIサーバーを起動する。

https://pub.symdon.info/8d/ab/8dab5a3dffadce8f73193a1609372427d2006dca0b879c5ef68e2e435d699a43.png

ここで提供されるWeb APIのリクエストとレスポンスの形式は、OpenAI APIと、とてもよく似ているため、OpenAI APIに書いたコードがそのまま使える事も多い。

Emacs Lispを修正する

以前作成したdoctor-quack.eldoctor-quack-send-curl-request を少しだけ(というかAPIのオリジンだけ)書き換えて呼び出せるようにした。APIは /v1/chat/completions を使用する。

(defun doctor-quack-send-curl-request ()
  (interactive)
  (message "ペイロード用ファイルを作成")
  (let ((payload
         (with-current-buffer (get-buffer-create "*LLM: context*")
           (goto-char (point-min))
           (let ((contexts (json-read-array)))
             (let ((body (json-read-from-string doctor-quack-request-body-template)))
               (setcdr (assoc 'messages body) contexts)
               body)))))
    (make-directory "~/.cache/openai" t)
    (setq doctor-quack-openai-api-request-body-file
          (make-temp-file (expand-file-name "~/.cache/openai/chat-completions-") nil ".json"
                          (json-encode payload))))
  (message "Succeed to create body file: %s" doctor-quack-openai-api-request-body-file)

  (message "出力バッファを初期化する")
  (with-current-buffer (get-buffer-create doctor-quack-openai-api-request-stdout-buffer-name)
    (erase-buffer)
    (doctor-quack-update-curl-stdout-end-position))

  (message "curlプロセスを起動する")
  (setq doctor-quack-openai-api-curl-process
        (make-process :name "*doctor: curl: OpenAI*"
                      :buffer doctor-quack-openai-api-request-stdout-buffer-name
                      :stderr "*doctor: curl: OpenAI: stderr*"
                      :command `(,doctor-quack-curl-executable
                                 "--data" ,(format "@%s" doctor-quack-openai-api-request-body-file)
                                 "-K-")
                      :filter (lambda (process output)
                                (with-current-buffer (process-buffer process)
                                  (goto-char (point-max))
                                  (insert output)
                                  (doctor-quack-register-process-output)
                                  ))
                      :sentinel (lambda (process event)
                                  (doctor-quack-register-process-output))))

  (message "リクエストを送信する")
  (with-current-buffer (get-buffer-create doctor-quack-openai-api-request-temp-buffer-name)
    (erase-buffer)
    (insert (format "
url \"%s/v1/chat/completions\"
request \"POST\"
header \"Content-Type: application/json\"
header \"Authorization: Bearer %s\"
" "http://127.0.0.1:1234" "DUMMY"))
    (process-send-region doctor-quack-openai-api-curl-process (point-min) (point-max))
    (process-send-eof doctor-quack-openai-api-curl-process))
  (message "doctor: send request"))

これによって、バックエンドにローカルLLMを使用するdoctorを使えるようになった。