使用 gptel-request 封装一个翻译方法

demo.gif
图1  一个 GIF 图,演示了选中中文,然后调用 spike-leung/translate-region-to-english 翻译成英文。

最近用 LLM 挺多的,尝试用英文写 prompt,但有时我不知道怎么写(英文比较菜)就想着翻译一下。

当然 Emacs 中的翻译的工具很多 1,写这个函数只是分享一下 gptel-request 的一种实现2

逻辑都是 LLM 写的,大体的逻辑是:3 , 4

  1. 找到当前选中区域,或者基于当前 point 获取对应的行/段落,作为要翻译的内容。
  2. 绑定一些本地变量,如 model,backend,因为翻译只需要一个快速便宜的模型。
  3. 调用 gptel-request ,写一个翻译的 prompt,发起请求,得到响应后覆盖第 1 步选中的内容。

更新:

(defun spike-leung/translate-region (prompt)
  "Translate the selected region (default to English) replace it.
If no region is active, try to guess the sentence or paragraph at point.
With prefix argument, PROMPT is used as the translation prompt."
  (interactive
   (list (when current-prefix-arg
           (read-string "Translation prompt: " "Translate the following text to English:"))))
  (require 'gptel)
  (let* ((has-region (use-region-p))
         (bounds
          (cond
           (has-region
            (cons (region-beginning) (region-end)))
           ((use-region-p)
            (cons (region-beginning) (region-end)))
           ((and (fboundp 'bounds-of-thing-at-point))
            (or (bounds-of-thing-at-point 'sentence)
                (bounds-of-thing-at-point 'paragraph)
                (bounds-of-thing-at-point 'line)
                (cons (point) (point))))
           (t (cons (point) (point)))))
         (start (car bounds))
         (end (cdr bounds))
         (text (buffer-substring-no-properties start end))
         (prompt-text (or prompt "Translate the following text to English:")))
    (if (string-blank-p text)
        (user-error "No text to translate")
      (let ((openrouter-backend (gptel-make-openai "OpenRouter"
                                  :host "openrouter.ai"
                                  :endpoint "/api/v1/chat/completions"
                                  :stream t
                                  :key (spike-leung/get-openrouter-api-key)
                                  :models spike-leung/openrouter-models))
            (primary-model 'openai/gpt-4.1-nano)
            (fallback-model 'google/gemini-2.0-flash-001))
        (cl-labels
            ((do-translate
               (model)
               (let ((gptel-backend openrouter-backend)
                     (gptel-model model)
                     (gptel-use-tools nil)
                     (gptel-use-context nil))
                 (gptel-request
                     (format "%s\n\n%s" prompt-text text)
                   :callback
                   (lambda (response _)
                     (if (and response (not (string-blank-p response)))
                         (save-excursion
                           (delete-region start end)
                           (goto-char start)
                           (insert response))
                       (if (eq model primary-model)
                           (progn
                             (message "Primary model failed, retrying with fallback model...")
                             (do-translate fallback-model))
                         (message "Translation failed with both models."))))))))
          (do-translate primary-model))))))

利用 gptel-request ,封装一些 prompt,可以方便地解决平时一些重复的、可以使用 LLM 解决的任务。

脚注:

1

Manatee LazyCat 的英文翻译 里还有更多的工具介绍。

2

Emacs China 的讨论 中,其他人还有别的实现方法。Emacs 中同一件事情可以有很多实现,也是折腾 Emacs 的乐趣之一吧。

3

源码可以看 init-gptel

4

Folo 感觉有个 bug,设置里开启了行内样式渲染,代码块会使用 style 的颜色,但是我默认是浅色背景,Folo 的代码块默认是黑色背景,就会导致我的代码块很刺眼。目前先都统一成 dark mode 了。

Author: Spike Leung

Date: 2025-04-15 Tue 00:00

Last Modified: 2025-04-16 Wed 14:03

License: CC BY-NC 4.0