Skip to content

dceluis/kznllm.nvim

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

351 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kznllm.nvim

A Neovim plugin for integrating Large Language Models (LLMs) into your coding workflow.

kznllm_demo_v2.mp4

The only main command is leader + k, it does nothing more than fill in some LLM completion into the text buffer. It has two main behaviors:

  1. If you made a visual selection, it will attempt to replace your selection with a valid code fragment.
  2. If you make no visual selection, it can yap freely (or do something else specified by a good template).

Note

project-mode is also available when you have a directory named .kzn. It will use the folder closest to your current working directory and traverse backwards until it finds a .kzn directory or reaches your home directory and exits.

It's easy to hack on and implement customize behaviors without understanding much about nvim plugins. Try the default preset configuration provided below, but I recommend you fork the repo and using the preset as a reference for implementing your own features.

  • close-to-natty coding experience
  • add custom prompt templates
  • pipe any context into template engine
  • extend with custom features/modes

Installation

Note

This plugin depends on minijinja-cli - way easier to compose prompts.

  1. Install minijinja-cli (required for prompt templating):
   cargo install minijinja-cli

2.1 Add the plugin to your Neovim configuration using Lazy.nvim:

   {
     'chottolabs/kznllm.nvim',
     dependencies = { 'nvim-lua/plenary.nvim' },
     config = function()
       -- Add your configuration here (see Configuration section below)
     end
   }

2.2 Or, add the plugin to your Neovim configuration using plug.vim:

   Plug 'nvim-lua/plenary.nvim'
   Plug 'chottolabs/kznllm.nvim'

Then, in your init.vim or init.lua, add the following configuration:

   require('kznllm').setup({
     -- Add your configuration here (see Configuration section below)
   })

Configuration

Make your API keys available via environment variables

export LAMBDA_API_KEY=secret_...
export ANTHROPIC_API_KEY=sk-...
export OPENAI_API_KEY=sk-proj-...
export GROQ_API_KEY=gsk_...
export DEEPSEEK_API_KEY=vllm_...
export VLLM_API_KEY=vllm_...

Full config with a preset switcher mechanism and optional debugging:

{
  'chottolabs/kznllm.nvim',
  -- dev = true,
  -- dir = /path/to/your/fork,
  dependencies = {
    { 'nvim-lua/plenary.nvim' }
  },
  config = function(self)
    local presets = require 'kznllm.presets'

    -- bind a key to the preset switcher
    vim.keymap.set({ 'n', 'v' }, '<leader>m', presets.switch_presets, { desc = 'switch between presets' })

    local function llm_fill()
      local selected_preset = presets.load()

      presets.invoke_llm(selected_preset)
    end

    vim.keymap.set({ 'n', 'v' }, '<leader>k', llm_fill, { desc = 'Send current selection to LLM llm_fill' })

    -- optional for debugging purposes
    local function debug()
      local selected_preset = presets.load()

      presets.invoke_llm(selected_preset, { debug = true })
    end

    vim.keymap.set({ 'n', 'v' }, '<leader>d', debug, { desc = 'Send current selection to LLM debug' })

    vim.api.nvim_set_keymap('n', '<Esc>', '', {
      noremap = true,
      silent = true,
      callback = function()
        vim.api.nvim_exec_autocmds('User', { pattern = 'LLM_Escape' })
      end,
    })
  end
},

Additional Notes

Originally based on dingllm.nvim - but diverged quite a bit

  • prompts user for additional context before filling
  • structured to make the inherent coupling between neovim logic, LLM streaming spec, and model-specific templates more explicit
  • uses jinja as templating engine for ensuring correctness in more complex prompts
  • preset defaults + simple approach for overriding them
  • free cursor movement during generation
  • avoids "undojoin after undo" error

Alternative Configurations

Preset switcher with added presets

local extra_presets = {
    {
        id = 'r1-qwen-32B',
        provider = 'huggingface',
        spec = 'openai',
        opts = {
            model = 'deepseek-ai/DeepSeek-R1-Distill-Qwen-32B',
            data_params = {
                max_tokens = 8192,
                temperature = 0.3,
            },
            api_key_name = 'HUGGINGFACE_API_KEY',
            base_url = 'https://api-inference.huggingface.co',
            endpoint = '/v1/chat/completions',
        },
    },
}

local presets = require 'kznllm.presets'
local kznllm = require 'kznllm'

presets.register_presets(extra_presets)

vim.keymap.set({ 'n', 'v' }, '<leader>m', presets.switch_presets, { desc = 'switch between presets' })

local function llm_fill()
    local selected_preset = presets.load(all_presets)
    presets.invoke_llm(selected_preset)
end

vim.keymap.set({ 'n', 'v' }, '\\', llm_fill, { desc = 'Send current selection to LLM llm_fill' })

local function debug()
    local selected_preset = presets.load(all_presets)
    presets.invoke_llm(selected_preset, { debug = true })
end

vim.keymap.set({ 'n', 'v' }, '<leader>\\', debug, { desc = 'Send current selection to LLM debug' })

Minimal configuration with no preset switcher and a custom template directory

local Path = require 'plenary.path'
local TEMPLATE_DIRECTORY = Path:new(vim.fn.expand('~') .. '/templates')

local function llm_fill()
    presets.invoke_llm({
        id = 'r1-llama-70B-ln-or',
        -- prompt = 'ask claude' -- optional. set an alternative input prompt
        spec = 'openai', -- required. 'openai' | 'anthropic' | 'lndiff/openai' | 'lndiff/anthropic'
        opts = {
            model = 'deepseek/deepseek-r1-distill-llama-70b',
            data_params = {
                max_tokens = 8192,
                temperature = 0.7,
            },
            api_key_name = 'OPENROUTER_API_KEY', -- optional
            base_url = 'https://openrouter.ai/api', -- optional
            -- endpoint = '/v1/chat/completions', -- optional
            -- template_directory = TEMPLATE_DIRECTORY, -- optional. set an alternative template directory
            -- template_scope = 'openrouter', -- optional. set an alternative template scope (template will be searched in `template_directory/template_scope/..` )
        }
    })
end

vim.keymap.set({ 'n', 'v' }, '<leader>f', llm_fill, { desc = 'Send current selection to LLM llm_fill' })

Minimal VLLM configuration with no preset switcher

local function llm_fill()
    presets.invoke_llm({
        id = 'qwen-2.5-1.5b-vllm',
        spec = 'vllm',
        opts = {
            model = 'Qwen/Qwen2.5-1.5B-Instruct',
            data_params = {
                max_tokens = 512,
                temperature = 0.7,
            },
            api_key_name = 'VLLM_API_KEY',
            base_url = 'http://localhost:8000/v1'
        }
    })
end

vim.keymap.set({ 'n', 'v' }, '<leader>f', llm_fill, { desc = 'Send current selection to VLLM' })

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Lua 86.2%
  • Jinja 13.8%