最近用 LLM 挺多的,尝试用英文写 prompt,但有时我不知道怎么写(英文菜)就想着翻译一下,当然翻译的工具很多。
这个函数只是分享一下 gptel-request 的一种实现。(本来是想玩玩 gptel-tool,但还没玩明白)
逻辑都是 LLM 写的,大体的逻辑是:
- 找到当前选中区域,或者基于当前 point 获取对应的行/段落,作为要翻译的内容
- 绑定一些本地变量,如 model,backend,因为翻译只需要一个快速便宜的模型
- 调用
gptel-request
发起请求,得到响应后覆盖第 1 步选中的内容
spike-leung/translate-region-to-english
(defun spike-leung/translate-region-to-english ()
"Translate the selected region to English using gptel and replace it in the buffer.
If no region is active, try to guess the sentence or paragraph at point."
(interactive)
(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))
(model 'openai/gpt-4.1-nano)
(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)))
(if (string-blank-p text)
(user-error "No text to translate")
(let ((gptel-backend backend)
(gptel-model model)
(gptel-use-tools nil)
(gptel-use-context nil))
(gptel-request
(format "Translate the following text to English:
%s" text)
:callback (lambda (response _)
(when response
(save-excursion
(delete-region start end)
(goto-char start)
(insert response)))))))))
定义一个方法包装 prompt 和 gptel-request,感觉可以利用 LLM 解决很多重复的小任务。
(顺便水了篇博客 https://taxodium.ink/use-gptel-request-to-translate.html )
更新:
- 支持了自定义 prompt,把我常用的一些简单功能写成 prompt 选项,便于使用,也可以自定义
- 设置了 1 个 fallback 模型,万一一个挂了,还有一个顶着
感觉就是一个内置了一些 prompt 的 gptel-rewrite
。(话说 gptel-rewrite
能设置一些默认 prompt 吗?)
spike-leung/gptel-rewrite
(defcustom spike-leung/custom-rewrite-prompts
'(("Translate" . "Translate the following text to English:")
("Translate to Chinese" . "将以下文本翻译成中文:")
("Format quotes" .
"按照以下要求,格式化内容:
1.移除英文
2.在每个中文句号(。)换行,行与行之间需要添加一行空行
3.如果存在中英文混合,中文和英文/数字之间需要保留一个空格
4.如果涉及到人名,使用英文的名字
5.如果涉及到缩写,需要在中文附近补充英文缩写和完整的英文,如最低合格读者 (MQR, Minimum Qualified Reader)
"))
"Alist of translation prompt options for `spike-leung/gptel-rewrite'.
Each entry is (DISPLAY . PROMPT).The first entry is the default."
:type '(alist :key-type string :value-type string)
:group 'spike-leung)
(defun spike-leung/gpt-rewrite (&optional prompt)
"Rewrite the selected region using a customizable PROMPT.
Always prompt the user to select or enter a prompt."
(interactive
(list
(let* ((choices (append (mapcar #'car spike-leung/custom-rewrite-prompts)
'("Custom...")))
(choice (completing-read "Choose rewrite prompt: " choices nil t)))
(if (string-equal choice "Custom...")
(read-string "Custom rewrite prompt: ")
(cdr (assoc choice spike-leung/custom-rewrite-prompts))))))
(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 prompt))
(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.5-flash-preview))
(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
%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))))))