Skip to content

Latest commit

 

History

History
801 lines (662 loc) · 30.2 KB

README_CN.md

File metadata and controls

801 lines (662 loc) · 30.2 KB

llm.nvim

English | 简体中文


Important

免费的大语言模型插件,让你在Neovim中与大模型交互

  1. 支持任意一款大模型,比如gpt,glm,kimi、deepseek或者本地运行的大模型(比如ollama)

  2. 支持定义属于你自己的AI工具,且不同工具可以使用不同的模型

  3. 最重要的一点,你可以使用任何平台提供的免费模型(比如CloudflareGithub modelssiliconflowopenrouter或者其他的平台)

Note

不同大模型的配置(比如ollama, deepseek)、 界面的配置、以及AI工具的配置(包括代码补全) 请先查阅 examples. 这里有你想知道的大部分内容。

目录

截图

聊天界面

llm-chat

代码补全

  • 虚拟文本

completion-virtual-text

  • blink.cmp 或 nvim-cmp

completion-blink-cmp

快速翻译

llm-translate

解释代码

llm-explain-code

随机提问

一次性,不保留历史记录

llm-ask

给对话附加上下文

llm-attach

优化代码

  • 并排展示

llm-optimize-code

  • 以diff的形式展示

llm-optimize-compare-action

生成测试用例

test-case

AI翻译

llm-trans

生成git commit信息

llm-git-commit-msg

生成docstring

llm-docstring

⬆ 返回目录

安装

依赖

  • curl: 请自行安装

准备工作

  1. 在官网注册并获取你的API Key (Cloudflare 需要额外获取你的 account).

  2. 在你的zshrc或者bashrc中设置LLM_KEY环境变量(Cloudflare 需要额外设置 ACCOUNT)

export LLM_KEY=<Your API_KEY>
export ACCOUNT=<Your ACCOUNT> # just for cloudflare

不同AI平台的官网

平台 获取API Key的链接 备注
Cloudflare https://dash.cloudflare.com/ 你可以在这里看到cloudflare的所有模型, 其中标注beta的是免费模型.
ChatGLM(智谱清言) https://open.bigmodel.cn/
Kimi(月之暗面) Moonshot AI 开放平台
Github Models Github Token
siliconflow (硅基流动) siliconflow 你可以在这里看到硅基流动上所有的模型,选择只看免费可以看到所有的免费模型
Deepseek https://platform.deepseek.com/api_keys
Openrouter https://openrouter.ai/
Chatanywhere https://api.chatanywhere.org/v1/oauth/free/render 每天200次免费调用GPT-4o-mini.

对于本地大模型, 在zshrc or bashrc中设置 LLM_KEYNONE.

⬆ 返回目录

最小安装示例

  • lazy.nvim
  {
    "Kurama622/llm.nvim",
    dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim"},
    cmd = { "LLMSessionToggle", "LLMSelectedTextHandler", "LLMAppHandler" },
    keys = {
      { "<leader>ac", mode = "n", "<cmd>LLMSessionToggle<cr>" },
    },
  }

配置

基本配置

一些你应该知道的命令

  • LLMSessionToggle: 打开/隐藏对话界面
  • LLMSelectedTextHandler: 对选中的文本进行处理,如何处理取决于你传入什么提示词
  • LLMAppHandler: 调用AI工具

如果url没有被配置,默认使用Cloudflare

示例

对于更多细节和示例,请查看Chat Configuration.

点击查看配置项的含义
  • prompt: 模型的提示词

  • prefix: 对话角色的标志

  • style: 对话窗口的样式(float即浮动窗口,其他均为分割窗口)

  • url: 模型的API地址

  • model: 模型的名称

  • api_type: 模型输出的解析格式: openai, zhipu, ollama, workers-ai. openai的格式可以兼容大部分的模型,但ChatGLM只能用zhipu的格式去解析,cloudflare只能用workers-ai去解析。如果你使用ollama来运行模型,你可以配置ollama

  • fetch_key: 如果你需要同时使用不同平台的模型,可以通过配置fetch_key来保证不同模型使用不同的API Key,用法如下:

    fetch_key = function() return "<your api key>" end
  • max_tokens: 模型的最大输出长度

  • save_session: 是否保存会话历史

  • max_history: 最多保存多少个会话

  • history_path: 会话历史的保存路径

  • temperature: 模型的temperature, 控制模型输出的随机性

  • top_p: 模型的top_p, 控制模型输出的随机性

  • spinner: 模型输出的等待动画 (非流式输出时生效)

  • display

    • diff: diff的显示风格(优化代码并显示diff时生效, 截图中的风格为mini_diff, 需要安装mini.diff
  • keys: 不同窗口的快捷键设置,默认值见默认快捷键

    • 浮动窗口风格下的快捷键
      • 输入窗口
        • Input:Cancel: 取消对话
        • Input:Submit: 提交问题
        • Input:Resend: 重新回答
        • Input:HistoryNext: 切换到下一个会话历史
        • Input:HistoryPrev: 切换到上一个会话历史
      • 整个对话界面
        • Session:Toggle: 打开/隐藏对话界面
        • Session:Close: 关闭对话界面
    • 分割窗口风格下的快捷键
      • 输出窗口
        • Output:Ask: 打开输入窗口
        • Output:Cancel: 取消对话
        • Output:Resend: 重新回答
        • Session:History: 打开会话历史窗口
      • 整个对话界面
        • Session:Toggle: 打开/隐藏对话界面
        • Session:Close: 关闭对话界面

如果你使用本地运行的大模型(但不是用ollama运行的),你可能需要定义streaming_handler(必须),以及parse_handler(非必需,只有个别AI工具会用到),具体见本地运行大模型

⬆ 返回目录

窗口风格配置

如果你想进一步配置对话界面的样式,你可以分别对chat_ui_optspopwin_opts进行配置。

点击查看如何配置窗口风格

它们的配置项都是相同的:

  • relative:

    • editor: 该浮动窗口相对于当前编辑器窗口
    • cursor: 该浮动窗口相对于当前光标位置
    • win : 该浮动窗口相对于当前窗口
  • position: 窗口的位置

  • size: 窗口的大小

  • enter: 窗口是否自动获得焦点

  • focusable: 窗口是否可以获得焦点

  • zindex: 窗口的层级

  • border

    • style: 窗口的边框样式
    • text: 窗口的边框文本
  • win_options: 窗口的选项

  • winblend: 窗口的透明度

  • winhighlight: 窗口的高亮

更多信息可以查阅nui/popup

示例

对于更多细节和示例,请查看UI Configuration.

⬆ 返回目录

AI工具的配置

目前llm.nvim提供了一些AI工具的模板,方便大家去自定义自己的AI工具

所有的AI工具都需要定义在app_handler中,以一对key-value的形式呈现,key为工具名称,value为工具的配置信息

示例

对于更多细节和示例,请查看AI Tools Configuration.

点击查看如何配置AI工具

对于所有的AI工具,它们的配置项都是基本类似的:

  • handler: 使用哪个模板
    • side_by_side_handler: 两个窗口并排展示结果
    • action_handler: 在源文件中以diff的形式展示结果
      • Y/y: 接受LLM建议代码
      • N/n: 拒绝LLM建议代码
      • <ESC>: 直接退出
      • I/i: 输入优化的补充条件
      • <C-r>: 直接重新优化
    • qa_handler: 单轮对话的AI
    • flexi_handler: 结果会展示在弹性窗口中 ( 根据输出文本的内容多少自动计算窗口大小 )
    • 你也可以自定义函数
  • prompt: AI工具的提示词
  • opts
    • spell: 是否有拼写检查
    • number: 是否显示行号
    • wrap: 是否自动换行
    • linebreak: 是否允许从单词中间换行
    • urlmodel: 该AI工具使用哪个大模型
    • api_type: 该AI工具输出的解析类型
    • streaming_handler: 该AI工具使用自定义的流解析函数
    • parse_handler: 该AI工具使用自定义的解析函数
    • border:浮动窗口的边框样式
    • accept
      • mapping: 接受AI输出的按键映射
        • mode: 映射对应的vim模式, 默认为n
        • keys: 你的按键, 默认为Y/y
      • action: 接受AI输出时执行的函数,默认是复制到剪贴板
    • reject
      • mapping: 拒绝AI输出的按键映射
        • mode: 映射对应的vim模式, 默认为n
        • keys: 你的按键, 默认为N/n
      • action: 拒绝AI输出时执行的函数,默认是什么也不做或者关闭AI工具窗口
    • close
      • mapping: 关闭AI工具的按键映射
        • mode: 映射对应的vim模式, 默认为n
        • keys: 你的按键, 默认为<ESC>
      • action: 关闭AI工具,默认是拒绝所有AI输出并关闭AI工具窗口

不同模板还有一些属于自己的专属配置项

  • qa_handleropts中你还可以定义:

    • component_width: 组件的宽度
    • component_height: 组件的高度
    • query
      • title: 组件的标题,会显示在组件上方居中处
      • hl : 标题的高亮
    • input_box_opts: 输入框的窗口选项(size, win_options
    • preview_box_opts: 预览框的窗口选项(size, win_options
  • action_handleropts中你还可以定义:

    • language: 输出结果使用的语言(English/Chinese/Japanese等)
    • input
      • relative: 分割窗口的相对位置(editor/win
      • position: 分割窗口的位置(top/left/right/bottom
      • size: 分割窗口的比例(默认是25%)
      • enter: 是否自动进入窗口
    • output
      • relative: 同input
      • position: 同input
      • size: 同input
      • enter: 同input
  • side_by_side_handleropts中你还可以定义:

    • left 左窗口
      • title: 窗口的标题
      • focusable: 是否允许窗口获得焦点
      • border
      • win_options
    • right 右窗口
      • title: 窗口的标题
      • focusable: 是否允许窗口获得焦点
      • border
      • win_options
  • flexi_handleropts中你还可以定义:

    • exit_on_move: 是否在光标移动时关闭弹性窗口
    • enter_flexible_window: 是否在弹性窗口弹出时自动进入窗口
    • apply_visual_selection: 是否要在prompt后追加选中的文本内容

我的一些AI工具配置:

  {
    "Kurama622/llm.nvim",
    dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim" },
    cmd = { "LLMSessionToggle", "LLMSelectedTextHandler", "LLMAppHandler" },
    config = function()
      local tools = require("llm.common.tools")
      require("llm").setup({
        app_handler = {
          OptimizeCode = {
            handler = tools.side_by_side_handler,
            -- opts = {
            --   streaming_handler = local_llm_streaming_handler,
            -- },
          },
          TestCode = {
            handler = tools.side_by_side_handler,
            prompt = [[ Write some test cases for the following code, only return the test cases.
            Give the code content directly, do not use code blocks or other tags to wrap it. ]],
            opts = {
              right = {
                title = " Test Cases ",
              },
            },
          },
          OptimCompare = {
            handler = tools.action_handler,
            opts = {
              fetch_key = function()
                return vim.env.GITHUB_TOKEN
              end,
              url = "https://models.inference.ai.azure.com/chat/completions",
              model = "gpt-4o",
              api_type = "openai",
            },
          },

          Translate = {
            handler = tools.qa_handler,
            opts = {
              fetch_key = function()
                return vim.env.GLM_KEY
              end,
              url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
              model = "glm-4-flash",
              api_type = "zhipu",

              component_width = "60%",
              component_height = "50%",
              query = {
                title = " 󰊿 Trans ",
                hl = { link = "Define" },
              },
              input_box_opts = {
                size = "15%",
                win_options = {
                  winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
                },
              },
              preview_box_opts = {
                size = "85%",
                win_options = {
                  winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
                },
              },
            },
          },

          -- check siliconflow's balance
          UserInfo = {
            handler = function()
              local key = os.getenv("LLM_KEY")
              local res = tools.curl_request_handler(
                "https://api.siliconflow.cn/v1/user/info",
                { "GET", "-H", string.format("'Authorization: Bearer %s'", key) }
              )
              if res ~= nil then
                print("balance: " .. res.data.balance)
              end
            end,
          },
          WordTranslate = {
            handler = tools.flexi_handler,
            prompt = "Translate the following text to Chinese, please only return the translation",
            opts = {
              fetch_key = function()
                return vim.env.GLM_KEY
              end,
              url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
              model = "glm-4-flash",
              api_type = "zhipu",
              args = [[return {url, "-N", "-X", "POST", "-H", "Content-Type: application/json", "-H", authorization, "-d", vim.fn.json_encode(body)}]],
              exit_on_move = true,
              enter_flexible_window = false,
            },
          },
          CodeExplain = {
            handler = tools.flexi_handler,
            prompt = "Explain the following code, please only return the explanation, and answer in Chinese",
            opts = {
              fetch_key = function()
                return vim.env.GLM_KEY
              end,
              url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
              model = "glm-4-flash",
              api_type = "zhipu",
              enter_flexible_window = true,
            },
          },
          CommitMsg = {
            handler = tools.flexi_handler,
            prompt = function()
              -- Source: https://andrewian.dev/blog/ai-git-commits
              return string.format([[You are an expert at following the Conventional Commit specification. Given the git diff listed below, please generate a commit message for me:

1. First line: conventional commit format (type: concise description) (remember to use semantic types like feat, fix, docs, style, refactor, perf, test, chore, etc.)
2. Optional bullet points if more context helps:
   - Keep the second line blank
   - Keep them short and direct
   - Focus on what changed
   - Always be terse
   - Don't overly explain
   - Drop any fluffy or formal language

Return ONLY the commit message - no introduction, no explanation, no quotes around it.

Examples:
feat: add user auth system

- Add JWT tokens for API auth
- Handle token refresh for long sessions

fix: resolve memory leak in worker pool

- Clean up idle connections
- Add timeout for stale workers

Simple change example:
fix: typo in README.md

Very important: Do not respond with any of the examples. Your message must be based off the diff that is about to be provided, with a little bit of styling informed by the recent commits you're about to see.

Based on this format, generate appropriate commit messages. Respond with message only. DO NOT format the message in Markdown code blocks, DO NOT use backticks:

```diff
%s
```
]],
                vim.fn.system("git diff --no-ext-diff --staged")
              )
            end,
            opts = {
              fetch_key = function()
                return vim.env.GLM_KEY
              end,
              url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
              model = "glm-4-flash",
              api_type = "zhipu",
              enter_flexible_window = true,
              apply_visual_selection = false,
            },
          },
        },
    })
    end,
    keys = {
      { "<leader>ac", mode = "n", "<cmd>LLMSessionToggle<cr>" },
      { "<leader>ts", mode = "x", "<cmd>LLMAppHandler WordTranslate<cr>" },
      { "<leader>ae", mode = "v", "<cmd>LLMAppHandler CodeExplain<cr>" },
      { "<leader>at", mode = "n", "<cmd>LLMAppHandler Translate<cr>" },
      { "<leader>tc", mode = "x", "<cmd>LLMAppHandler TestCode<cr>" },
      { "<leader>ao", mode = "x", "<cmd>LLMAppHandler OptimCompare<cr>" },
      { "<leader>au", mode = "n", "<cmd>LLMAppHandler UserInfo<cr>" },
      { "<leader>ag", mode = "n", "<cmd>LLMAppHandler CommitMsg<cr>" },
      -- { "<leader>ao", mode = "x", "<cmd>LLMAppHandler OptimizeCode<cr>" },
    },
  },

⬆ 返回目录

本地运行大模型

本地大模型需要自定义解析函数,对于流式输出,我们使用自定义的streaming_handler;对于一次性返回输出结果的AI工具,我们使用自定义的parse_handler

下面是ollama运行llama3.2:1b的样例

展开代码
local function local_llm_streaming_handler(chunk, ctx, F)
  if not chunk then
    return ctx.assistant_output
  end
  local tail = chunk:sub(-1, -1)
  if tail:sub(1, 1) ~= "}" then
    ctx.line = ctx.line .. chunk
  else
    ctx.line = ctx.line .. chunk
    local status, data = pcall(vim.fn.json_decode, ctx.line)
    if not status or not data.message.content then
      return ctx.assistant_output
    end
    ctx.assistant_output = ctx.assistant_output .. data.message.content
    F.WriteContent(ctx.bufnr, ctx.winid, data.message.content)
    ctx.line = ""
  end
  return ctx.assistant_output
end

local function local_llm_parse_handler(chunk)
  local assistant_output = chunk.message.content
  return assistant_output
end

return {
  {
    "Kurama622/llm.nvim",
    dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim" },
    cmd = { "LLMSessionToggle", "LLMSelectedTextHandler" },
    config = function()
      require("llm").setup({
        url = "http://localhost:11434/api/chat", -- your url
        model = "llama3.2:1b",

        streaming_handler = local_llm_streaming_handler,
        app_handler = {
          WordTranslate = {
            handler = tools.flexi_handler,
            prompt = "Translate the following text to Chinese, please only return the translation",
            opts = {
              parse_handler = local_llm_parse_handler,
              exit_on_move = true,
              enter_flexible_window = false,
            },
          },
        }
      })
    end,
    keys = {
      { "<leader>ac", mode = "n", "<cmd>LLMSessionToggle<cr>" },
    },
  }
}

⬆ 返回目录

默认快捷键

  • 浮动窗口风格下的快捷键
窗口 按键 模式 描述
Input ctrl+g i 提交你的问题
Input ctrl+c i 取消本轮对话
Input ctrl+r i 重新发起本轮对话
Input ctrl+j i 切换到下一个会话历史
Input ctrl+k i 切换到上一个会话历史
Output+Input <leader>ac n 打开/隐藏对话界面
Output+Input <esc> n 关闭对话界面

窗口切换

你可以使用 <C-w><C-w> 来切换窗口,如果你觉得这种方式不方便,你可以设置你自己的快捷键来切换窗口 (该特性没有默认快捷键)。

    -- Switch from the output window to the input window.
    ["Focus:Input"]       = { mode = "n", key = {"i", "<C-w>"} },
    -- Switch from the input window to the output window.
    ["Focus:Output"]      = { mode = { "n", "i" }, key = "<C-w>" },
  • 分割窗口风格下的快捷键
窗口 按键 模式 描述
Input <cr> n 提交你的问题
Output i n 打开输入窗口
Output ctrl+c n 取消本轮对话
Output ctrl+r n 重新发起本轮对话
Output ctrl+h n 打开会话历史窗口
Output+Input <leader>ac n 打开/隐藏对话界面
Output+Input <esc> n 关闭对话界面
History j n 预览下一个会话历史
History k n 预览上一个会话历史
History <cr> n 进入选择的会话
History <esc> n 关闭会话历史窗口

⬆ 返回目录

作者的配置文件

plugins/llm

致谢

以下开源项目为llm.nvim提供了宝贵的灵感和参考:

特别鸣谢

常见问题

windows的curl使用格式与linux不一样,llm.nvim默认的请求格式,windows下会有问题

使用自定义请求格式

  • 基础对话功能以及部分AI工具(使用流式输出)自定义请求格式

    定义args参数,与prompt同层级

    --[[ custom request args ]]
    args = [[return {url, "-N", "-X", "POST", "-H", "Content-Type: application/json", "-H", authorization, "-d", vim.fn.json_encode(body)}]],
  • AI工具(使用非流式输出)自定义请求格式

    opts中定义args

      WordTranslate = {
        handler = tools.flexi_handler,
        prompt = "Translate the following text to Chinese, please only return the translation",
        opts = {
          fetch_key = function()
            return vim.env.GLM_KEY
          end,
          url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
          model = "glm-4-flash",
          api_type = "zhipu",
          args = [[return {url, "-N", "-X", "POST", "-H", "Content-Type: application/json", "-H", authorization, "-d", vim.fn.json_encode(body)}]],
          exit_on_move = true,
          enter_flexible_window = false,
        },
      },

Note

需要根据你的实际情况去修改args

⬆ 返回目录

多个大模型切换,频繁更改LLM_KEY的值很麻烦,而且我不想在Neovim的配置文件中暴露我的Key

  • 创建一个.env文件,专门保存你的各种Key。注意:此文件不要上传Github
export GITHUB_TOKEN=xxxxxxx
export DEEPSEEK_TOKEN=xxxxxxx
export SILICONFLOW_TOKEN=xxxxxxx
  • 在zshrc或者bashrc中加载.env

    source ~/.config/zsh/.env
    
    # 默认使用Github Models
    export LLM_KEY=$GITHUB_TOKEN
  • 最后在llm.nvim配置文件中,通过fetch_key完成Key的切换

      fetch_key = function()
        return vim.env.DEEPSEEK_TOKEN
      end,

⬆ 返回目录

不同解析函数的优先级

AI工具配置的streaming_handler或者parse_handler > AI工具配置的api_type > 主配置的streaming_handler或者parse_handler > 主配置的api_type

⬆ 返回目录

AI生成git commit信息的功能如何与lazygit集成在一起?

  {
    "kdheepak/lazygit.nvim",
    lazy = true,
    cmd = {
      "LazyGit",
      "LazyGitConfig",
      "LazyGitCurrentFile",
      "LazyGitFilter",
      "LazyGitFilterCurrentFile",
    },
    -- optional for floating window border decoration
    dependencies = {
      "nvim-lua/plenary.nvim",
    },
    config = function()
      vim.keymap.set("t", "<C-c>", function()
        vim.api.nvim_win_close(vim.api.nvim_get_current_win(), true)
        vim.api.nvim_command("LLMAppHandler CommitMsg")
      end, { desc = "AI Commit Msg" })
    end,
  }

⬆ 返回目录