Code Monkey home page Code Monkey logo

model.nvim's Introduction

๐Ÿ—ฟ model.nvim

Use AI models in Neovim for completions or chat. Build prompts programatically with lua. Designed for those who want to customize their prompts, experiment with multiple providers or use local models.

model_nvim.mp4

Features

  • ๐ŸŽช Provider agnostic. Comes with:
    • hosted
      • OpenAI ChatGPT (and compatible API's)
      • Google PaLM, together, huggingface
    • local
      • llama.cpp
      • ollama
    • easy to add your own
  • ๐ŸŽจ Programmatic prompts in lua
    • customize everything
    • async and multistep prompts
    • starter examples
  • ๐ŸŒ  Streaming completions
    • directly in buffer
    • transform/extract text
    • append/replace/insert modes
  • ๐Ÿฆœ Chat in mchat filetype buffer
    • edit settings or messages at any point
    • take conversations to different models
    • treesitter highlights and folds

Contents

If you have any questions feel free to ask in discussions


Setup

Requirements

  • Nvim 0.9.0 or higher
  • curl
require('lazy').setup({
  {
    'gsuuon/model.nvim',

    -- Don't need these if lazy = false
    cmd = { 'M', 'Model', 'Mchat' },
    init = function()
      vim.filetype.add({
        extension = {
          mchat = 'mchat',
        }
      })
    end,
    ft = 'mchat',

    keys = {
      {'<C-m>d', ':Mdelete<cr>', mode = 'n'},
      {'<C-m>s', ':Mselect<cr>', mode = 'n'},
      {'<C-m><space>', ':Mchat<cr>', mode = 'n' }
    },

    -- To override defaults add a config field and call setup()

    -- config = function()
    --   require('model').setup({
    --     prompts = {..},
    --     chats = {..},
    --     ..
    --   })
    --
    --   require('model.providers.llamacpp').setup({
    --     binary = '~/path/to/server/binary',
    --     models = '~/path/to/models/directory'
    --   })
    --end
  }
})

Treesitter

To get treesitter highlighting of chat buffers with markdown injections, use :TSInstall mchat after model.nvim has been loaded (if you're using Lazy run :Lazy load model.nvim first). The grammar repo is at gsuuon/tree-sitter-mchat.

image

Secrets

If you prefer to keep keys out of your environment, they can also be set programmatically using :help vim.env or using the secrets field of the model.nvim setup table. config.secrets takes a table of functions which return a string - the function is called when the key is used and the result is cached for subsequent calls:

require('model').setup({
  secrets = {
    PROVIDER_API_KEY = function()
      return 'some key'
    end
  }
})

Usage

model_mixtral.mp4

model.nvim comes with some starter prompts and makes it easy to build your own prompt library. For an example of a more complex agent-like multi-step prompt where we curl for openapi schema, ask gpt for relevant endpoint, then include that in a final prompt look at the openapi starter prompt.

Prompts can have 5 different modes which determine what happens to the response: append, insert, replace, buffer, insert_or_replace. The default is to append, and with no visual selection the default input is the entire buffer, so your response will be at the end of the file. Modes are configured on a per-prompt basis.

Commands

Run prompts

Run a completion prompt

  • :Model [name] or :M [name] โ€” Start a completion request of either the visual selection or the current buffer. Uses the default prompt if no prompt name is provided. Completions typically edit the current buffer.

Start a new chat

  • :Mchat [name] [instruction] โ€” Start a new chat buffer with the name ChatPrompt for multi-turn conversation. Provide an optional instruction override. If you're currently in an mchat buffer you can use - to import the buffer's instruction to the new chat, e.g. :Mchat openai -, otherwise it will be the system instruction of the new chat prompt.

Run a chat buffer

  • :Mchat โ€” Request the assistant response in a chat buffer. You can save an mchat buffer as my_conversation.mchat, reload it later and run :Mchat with your next message to continue where you left off. You'll need to have the same ChatPrompt configured in setup.
Telescope extension

If you use telescope, mchat buffers can be browsed with :Telescope model mchat.

Manage responses

Responses are inserted with extmarks, so once the buffer is closed the responses become normal text and won't work with the following commands.

Select response
llm_select.mp4
  • :Mselect โ€” Select the response under the cursor.
Delete response
llmdelete.mp4
  • :Mdelete โ€” Delete the response under the cursor. If prompt.mode == 'replace' then replace with the original text.
Cancel response
llmcancel.mp4
  • :Mcancel โ€” Cancel the active response under the cursor.
Show response
llmshow.mp4
  • :Mshow โ€” Flash the response under the cursor if there is one.

Manage context

There are some basic context management helpers which use the quickfix list:

  • :MCadd โ€” Add the current file
  • :MCremove โ€” Remove the current file
  • :MCclear โ€” Remove all entries
  • :MCpaste โ€” Paste the contents of the quickfix list files with filepaths and code fences
    • an example:
      File: `src/index.tsx`
      ```typescriptreact
      import App from "./App";
    
      render( () => <App />), document.getElementById("root")!);
      ```

File content of the quickfix list (what :MCpaste inserts) can be accessed programmatically via require('model.util.qflist').get_text(), for example:

local qflist = require('model.util.qflist')
local starters = require('model.prompts.chats')

config.chats = {
  ['codellama:qfix'] = vim.tbl_deep_extend('force', starters['together:codellama'], {
    system = 'You are an intelligent programming assistant',
    create = function()
      return qflist.get_text()
    end
  }),
}

๐Ÿšง WIP - Local vector store

Setup and usage

Requirements

  • Python 3.10+
  • pip install numpy openai tiktoken

Usage

Check the module functions exposed in store. This uses the OpenAI embeddings api to generate vectors and queries them by cosine similarity.

To add items call into the model.store lua module functions, e.g.

  • :lua require('model.store').add_lua_functions()
  • :lua require('model.store').add_files('.')

Look at store.add_lua_functions for an example of how to use treesitter to parse files to nodes and add them to the local store.

To get query results call store.prompt.query_store with your input text, desired count and similarity cutoff threshold (0.75 seems to be decent). It returns a list of {id: string, content: string}:

builder = function(input, context)
  ---@type {id: string, content: string}[]
  local store_results = require('model.store').prompt.query_store(input, 2, 0.75)

  -- add store_results to your messages
end
  • :Mstore [command]
    • :Mstore init โ€” initialize a store.json file at the closest git root directory
    • :Mstore query <query text> โ€” query a store.json

Configuration

All setup options are optional. Add new prompts to options.prompts.[name] and chat prompts to options.chats.[name].

require('model').setup({
  default_prompt = {},
  prompts = {...},
  chats = {...},
  hl_group = 'Comment',
  join_undo = true,
})

Prompts

Prompts go in the prompts field of the setup table and are ran by the command :Model [prompt name] or :M [prompt name]. The commands tab-complete with the available prompts.

With lazy.nvim:

{
  'gsuuon/model.nvim',
  config = function()
    require('model').setup({
      prompts = {
        instruct = { ... },
        code = { ... },
        ask = { ... }
      }
    })
  end
}

A prompt entry defines how to handle a completion request - it takes in the editor input (either an entire file or a visual selection) and some context, and produces the api request data merging with any defaults. It also defines how to handle the API response - for example it can replace the selection (or file) with the response or insert it at the cursor positon.

Check out the starter prompts to see how to create prompts. Check out the reference for the type definitions.

Chat prompts

model_reason_siblings.mp4

Chat prompts go in the chats field of the setup table.

{
  'gsuuon/model.nvim',
  config = function()
    require('model').setup({
      prompts = { ... },
      chats = {
        gpt4 = { ... },
        mixtral = { ... }
        starling = { ... }
      }
    })
  end
}

Use :Mchat [name] to create a new mchat buffer with that chat prompt. The command will tab complete with available chat prompts. You can prefix the command with :horizontal Mchat [name] or :tab Mchat [name] to create the buffer in a horizontal split or new tab.

A brand new mchat buffer might look like this:

openai
---
{
  params = {
    model = "gpt-4-1106-preview"
  }
}
---
> You are a helpful assistant

Count to three

Run :Mchat in the new buffer (with no name argument) to get the assistant response. You can edit any of the messages, params, options or system instruction (the first line, if it starts with > ) as necessary throughout the conversation. You can also copy/paste to a new buffer, :set ft=mchat and run :Mchat.

You can save the buffer with an .mchat extension to continue the chat later using the same settings shown in the header. mchat comes with some syntax highlighting and folds to show the various chat parts - name of the chatprompt runner, options and params in the header, and a system message.

Check out the starter chat prompts to see how to add your own. Check out the reference for the type definitions.

Library autoload

You can use require('util').module.autoload instead of a naked require to always re-require a module on use. This makes the feedback loop for developing prompts faster:

require('model').setup({
-  prompts = require('prompt_library')
+  prompts = require('model.util').module.autoload('prompt_library')
})

I recommend setting this only during active prompt development, and switching to a normal require otherwise.

Providers

The available providers are in ./lua/model/providers.

OpenAI ChatGPT

(default)

Set the OPENAI_API_KEY environment variable to your api key.

openai parameters

Parameters are documented here. You can override the default parameters for this provider by calling initialize:

    config = function()
      require('model.providers.openai').initialize({
        model = 'gpt-4-1106-preview'
      })
    end

openai prompt options

OpenAI prompts can take an additional option field to talk to compatible API's.

  compat = vim.tbl_extend('force', openai.default_prompt, {
    options = {
      url = 'http://127.0.0.1:8000/v1/'
    }
  })
  • url?: string - (Optional) Custom URL to use for API requests. Defaults to 'https://api.openai.com/v1/'. If url is provided then the environment key will not be sent, you'll need to include authorization.
  • endpoint?: string - (Optional) Endpoint to use in the request URL. Defaults to 'chat/completions'.
  • authorization?: string - (Optional) Authorization header to include in the request. Overrides any authorization given through the environment key.

For example, to configure it for Mistral AI "La plateforme":

  {
      "gsuuon/model.nvim",
      cmd = { "Model", "Mchat" },
      init = function()
          vim.filetype.add({ extension = { mchat = "mchat" } })
      end,
      ft = "mchat",
      keys = { { "<leader>h", ":Model<cr>", mode = "v" } },
      config = function()
          local mistral = require("model.providers.openai")
          local util = require("model.util")
          require("model").setup({
              hl_group = "Substitute",
              prompts = util.module.autoload("prompt_library"),
              default_prompt = {
                  provider = mistral,
                  options = {
                      url = "https://api.mistral.ai/v1/",
                      authorization = "Bearer YOUR_MISTRAL_API_KEY",
                  },
                  builder = function(input)
                      return {
                          model = "mistral-medium",
                          temperature = 0.3,
                          max_tokens = 400,
                          messages = {
                              {
                                  role = "system",
                                  content = "You are helpful assistant.",
                              },
                              { role = "user", content = input },
                          },
                      }
                  end,
              },
          })
      end,
  },

LlamaCpp

This provider uses the llama.cpp server.

You can start the server manually or have it autostart when you run a llamacpp prompt. To autostart the server call require('model.providers.llamacpp').setup({}) in your config function and set a model in the prompt options (see below). Leave model empty to not autostart. The server restarts if the prompt model or args change.

Setup

  1. Build llama.cpp
  2. Download the model you want to use, e.g. Zephyr 7b beta
  3. Setup the llamacpp provider if you plan to use autostart:
    config = function()
      require('model').setup({ .. })
    
      require('model.providers.llamacpp').setup({
        binary = '~/path/to/server/binary',
        models = '~/path/to/models/directory'
      })
    end
  4. Use the llamacpp provider in a prompt:
    local llamacpp = require('model.providers.llamacpp')
    
    require('model').setup({
      prompts = {
        zephyr = {
          provider = llamacpp,
          options = {
            model = 'zephyr-7b-beta.Q5_K_M.gguf',
            args = {
              '-c', 8192,
              '-ngl', 35
            }
          },
          builder = function(input, context)
            return {
              prompt =
                '<|system|>'
                .. (context.args or 'You are a helpful assistant')
                .. '\n</s>\n<|user|>\n'
                .. input
                .. '</s>\n<|assistant|>',
              stops = { '</s>' }
            }
          end
        }
      }
    })

LlamaCpp setup options

Setup require('model.providers.llamacpp').setup({})

  • binary: string - path to the llamacpp server binary executable
  • models: string - path to the parent directory of the models (joined with prompt.model)

LlamaCpp prompt options

  • model?: string - (optional) The path to the model file to use with server autostart. If not specified, the server will not be started.
  • args?: string[] - (optional) An array of additional arguments to pass to the server at startup. Use this to specify things like context size -c or gpu layers -ngl that are specific to the model.
  • url?: string - (optional) Override the default server url. This can be useful for connecting to a remote server or a customized local one.

Ollama

This uses the ollama REST server's /api/generate endpoint. raw defaults to true, and stream is always true.

Example prompt with starling:

  ['ollama:starling'] = {
    provider = ollama,
    params = {
      model = 'starling-lm'
    },
    builder = function(input)
      return {
        prompt = 'GPT4 Correct User: ' .. input .. '<|end_of_turn|>GPT4 Correct Assistant: '
      }
    end
  },

Google PaLM

Set the PALM_API_KEY environment variable to your api key.

The PaLM provider defaults to the text model (text-bison-001). The builder's return params can include model = 'chat-bison-001' to use the chat model instead.

Params should be either a generateText body by default, or a generateMessage body if using model = 'chat-bison-001'.

palm = {
  provider = palm,
  builder = function(input, context)
    return {
      model = 'text-bison-001',
      prompt = {
        text = input
      },
      temperature = 0.2
    }
  end
}

Together

Set the TOGETHER_API_KEY environment variable to your api key. Talks to the together inference endpoint.

  ['together:phind/codellama34b_v2'] = {
    provider = together,
    params = {
      model = 'Phind/Phind-CodeLlama-34B-v2',
      max_tokens = 1024
    },
    builder = function(input)
      return {
        prompt = '### System Prompt\nYou are an intelligent programming assistant\n\n### User Message\n' .. input  ..'\n\n### Assistant\n'
      }
    end
  },

Huggingface API

Set the HUGGINGFACE_API_KEY environment variable to your api key.

Set the model field on the params returned by the builder (or the static params in prompt.params). Set params.stream = false for models which don't support it (e.g. gpt2). Check huggingface api docs for per-task request body types.

  ['hf:starcoder'] = {
    provider = huggingface,
    options = {
      model = 'bigcode/starcoder'
    },
    builder = function(input)
      return { inputs = input }
    end
  },

Kobold

For older models that don't work with llama.cpp, koboldcpp might still support them. Check their repo for setup info.

Langserve

Set the output_parser to correctly parse the contents returned from the /stream endpoint and use the builder to construct the input query. The below uses the example langserve application to make a joke about the input text.

  ['langserve:make-a-joke'] = {
    provider = langserve,
    options = {
      base_url = 'https://langserve-launch-example-vz4y4ooboq-uc.a.run.app/',
      output_parser = langserve.generation_chunk_parser,
    },
    builder = function(input, context)
      return {
        topic = input,
      }
    end
  },

Adding your own

Providers implement a simple interface so it's easy to add your own. Just set your provider as the provider field in a prompt. Your provider needs to kick off the request and call the handlers as data streams in, finishes, or errors. Check the hf provider for a simpler example supporting server-sent events streaming. If you don't need streaming, just make a request and call handler.on_finish with the result.

Basic provider example:

local test_provider = {
  request_completion = function(handlers, params, options)
    vim.notify(vim.inspect({params=params, options=options}))
    handlers.on_partial('a response')
    handlers.on_finish()
  end
}

require('model').setup({
  prompts = {
    test_prompt = {
      provider = test_provider,
      builder = function(input, context)
        return {
          input = input,
          context = context
        }
      end
    }
  }
})

Reference

The following are types and the fields they contain:

SetupOptions

Setup require('model').setup(SetupOptions)

  • default_prompt?: string - The default prompt to use with :Model or :M. Default is the openai starter.
  • prompts?: {string: Prompt} - A table of custom prompts to use with :M [name]. Keys are the names of the prompts. Default are the starters.
  • chats?: {string: ChatPrompt} - A table of chat prompts to use with :Mchat [name]. Keys are the names of the chats.
  • hl_group?: string - The default highlight group for in-progress responses. Default is 'Comment'.
  • join_undo?: boolean - Whether to join streaming response text as a single undo command. When true, unrelated edits during streaming will also be undone. Default is true.

Prompt

params are generally data that go directly into the request sent by the provider (e.g. content, temperature). options are used by the provider to know how to handle the request (e.g. server url or model name if a local LLM).

Setup require('model').setup({prompts = { [prompt name] = Prompt, .. }})
Run :Model [prompt name] or :M [prompt name]

  • provider: Provider - The provider for this prompt, responsible for requesting and returning completion suggestions.
  • builder: ParamsBuilder - Converts input (either the visual selection or entire buffer text) and context to request parameters. Returns either a table of params or a function that takes a callback with the params.
  • transform?: fun(string): string - Optional function that transforms completed response text after on_finish, e.g. to extract code.
  • mode?: SegmentMode | StreamHandlers - Response handling mode. Defaults to 'append'. Can be one of 'append', 'replace', 'buffer', 'insert', or 'insert_or_replace'. Can be a table of StreamHandlers to manually handle the provider response.
  • hl_group?: string - Highlight group of active response.
  • params?: table - Static request parameters for this prompt.
  • options?: table - Optional options for the provider.

Provider

  • request_completion: fun(handler: StreamHandlers, params?: table, options?: table): function - Requests a completion stream from the provider and returns a cancel callback. Feeds completion parts back to the prompt runner using handler methods and calls on_finish after completion is done.
  • default_prompt? : Prompt - Default prompt for this provider (optional).
  • adapt?: fun(prompt: StandardPrompt): table - Adapts a standard prompt to params for this provider (optional).

ParamsBuilder

(function)

  • fun(input: string, context: Context): table | fun(resolve: fun(params: table)) - Converts input (either the visual selection or entire buffer text) and context to request parameters. Returns either a table of params or a function that takes a callback with the params.

SegmentMode

(enum)

Exported as local mode = require('model').mode

  • APPEND = 'append' - Append to the end of input.
  • REPLACE = 'replace' - Replace input.
  • BUFFER = 'buffer' - Create a new buffer and insert.
  • INSERT = 'insert' - Insert at the cursor position.
  • INSERT_OR_REPLACE = 'insert_or_replace' - Insert at the cursor position if no selection, or replace the selection.

StreamHandlers

  • on_partial: fun(partial_text: string): nil - Called by the provider to pass partial incremental text completions during a completion request.
  • on_finish: fun(complete_text?: string, finish_reason?: string): nil - Called by the provider when the completion is done. Takes an optional argument for the completed text (complete_text) and an optional argument for the finish reason (finish_reason).
  • on_error: fun(data: any, label?: string): nil - Called by the provider to pass error data and an optional label during a completion request.

ChatPrompt

params are generally data that go directly into the request sent by the provider (e.g. content, temperature). options are used by the provider to know how to handle the request (e.g. server url or model name if a local LLM).

Setup require('model').setup({chats = { [chat name] = ChatPrompt, .. }})
Run :Mchat [chat name]

  • provider: Provider - The provider for this chat prompt.
  • create: fun(input: string, context: Context): string | ChatContents - Converts input and context into the first message text or ChatContents, which are written into the new chat buffer.
  • run: fun(messages: ChatMessage[], config: ChatConfig): table | fun(resolve: fun(params: table): nil ) - Converts chat messages and configuration into completion request params. This function returns a table containing the required params for generating completions, or it can return a function that takes a callback to resolve the params.
  • system?: string - Optional system instruction used to provide specific instructions for the provider.
  • params?: table - Static request parameters that are provided to the provider during completion generation.
  • options?: table - Provider options, which can be customized by the user to modify the chat prompt behavior.

ChatMessage

  • role: 'user' | 'assistant' - Indicates whether this message was generated by the user or the assistant.
  • content: string - The actual content of the message.

ChatConfig

  • system?: string - Optional system instruction used to provide context or specific instructions for the provider.
  • params?: table - Static request parameters that are provided to the provider during completion generation.
  • options?: table - Provider options, which can be customized by the user to modify the chat prompt behavior.

ChatContents

  • config: ChatConfig - Configuration for this chat buffer, used by chatprompt.run. This includes information such as the system instruction, static request parameters, and provider options.
  • messages: ChatMessage[] - Messages in the chat buffer.

Context

  • before: string - The text present before the selection or cursor.
  • after: string - The text present after the selection or cursor.
  • filename: string - The filename of the buffer containing the selected text.
  • args: string - Any additional command arguments provided to the plugin.
  • selection?: Selection - An optional Selection object representing the selected text, if available.

Selection

  • start: Position - The starting position of the selection within the buffer.
  • stop: Position - The ending position of the selection within the buffer.

Position

  • row: number - The 0-indexed row of the position within the buffer.
  • col: number or vim.v.maxcol - The 0-indexed column of the position within the line. If vim.v.maxcol is provided, it indicates the end of the line.

Examples

Prompts

require('model').setup({
  prompts = {
    ['prompt name'] = ...
  }
})
Ask for additional user instruction
prompt_replace.mp4
  ask = {
    provider = openai,
    params = {
      temperature = 0.3,
      max_tokens = 1500
    },
    builder = function(input)
      local messages = {
        {
          role = 'user',
          content = input
        }
      }

      return util.builder.user_prompt(function(user_input)
        if #user_input > 0 then
          table.insert(messages, {
            role = 'user',
            content = user_input
          })
        end

        return {
          messages = messages
        }
      end, input)
    end,
  }
Create a commit message based on `git diff --staged`
commit-message-example.mp4
  ['commit message'] = {
    provider = openai,
    mode = mode.INSERT,
    builder = function()
      local git_diff = vim.fn.system {'git', 'diff', '--staged'}
      return {
        messages = {
          {
            role = 'system',
            content = 'Write a short commit message according to the Conventional Commits specification for the following git diff: ```\n' .. git_diff .. '\n```'
          }
        }
      }
    end,
  }
Modify input to append messages
modify-input-example.mp4

lua/prompt_library.lua

--- Looks for `<llm:` at the end and splits into before and after
--- returns all text if no directive
local function match_llm_directive(text)
  local before, _, after = text:match("(.-)(<llm:)%s?(.*)$")
  if not before and not after then
    before, after = text, ""
  elseif not before then
    before = ""
  elseif not after then
    after = ""
  end
  return before, after
end

local instruct_code = 'You are a highly competent programmer. Include only valid code in your response.'

return {
  ['to code'] = {
    provider = openai,
    builder = function(input)
      local text, directive = match_llm_directive(input)

      local msgs ={
        {
          role = 'system',
          content = instruct_code,
        },
        {
          role = 'user',
          content = text,
        }
      }

      if directive then
        table.insert(msgs, { role = 'user', content = directive })
      end

      return {
        messages = msgs
      }
    end,
    mode = segment.mode.REPLACE
  },
  code = {
    provider = openai,
    builder = function(input)
      return {
        messages = {
          {
            role = 'system',
            content = instruct_code,
          },
          {
            role = 'user',
            content = input,
          }
        }
      }
    end,
  },
}
Replace text with Spanish
local openai = require('model.providers.openai')
local segment = require('model.util.segment')

require('model').setup({
  prompts = {
    ['to spanish'] =
      {
        provider = openai,
        hl_group = 'SpecialComment',
        builder = function(input)
          return {
            messages = {
              {
                role = 'system',
                content = 'Translate to Spanish',
              },
              {
                role = 'user',
                content = input,
              }
            }
          }
        end,
        mode = segment.mode.REPLACE
      }
  }
})
Notifies each stream part and the complete response
local openai = require('model.providers.openai')

require('model').setup({
  prompts = {
    ['show parts'] = {
      provider = openai,
      builder = openai.default_builder,
      mode = {
        on_finish = function (final)
          vim.notify('final: ' .. final)
        end,
        on_partial = function (partial)
          vim.notify(partial)
        end,
        on_error = function (msg)
          vim.notify('error: ' .. msg)
        end
      }
    },
  }
})

Configuration

You can move prompts into their own file and use util.module.autoload to quickly iterate on prompt development.

Setup

config = function()

local openai = require('model.providers.openai')

-- configure default model params here for the provider
openai.initialize({
  model = 'gpt-3.5-turbo-0301',
  max_tokens = 400,
  temperature = 0.2,
})

local util = require('model.util')

require('model').setup({
  hl_group = 'Substitute',
  prompts = util.module.autoload('prompt_library'),
  default_prompt = {
    provider = openai,
    builder = function(input)
      return {
        temperature = 0.3,
        max_tokens = 120,
        messages = {
          {
            role = 'system',
            content = 'You are helpful assistant.',
          },
          {
            role = 'user',
            content = input,
          }
        }
      }
    end
  }
})
Prompt library

lua/prompt_library.lua

local openai = require('model.providers.openai')
local segment = require('model.util.segment')

return {
  code = {
    provider = openai,
    builder = function(input)
      return {
        messages = {
          {
            role = 'system',
            content = 'You are a 10x super elite programmer. Continue only with code. Do not write tests, examples, or output of code unless explicitly asked for.',
          },
          {
            role = 'user',
            content = input,
          }
        }
      }
    end,
  },
  ['to spanish'] = {
    provider = openai,
    hl_group = 'SpecialComment',
    builder = function(input)
      return {
        messages = {
          {
            role = 'system',
            content = 'Translate to Spanish',
          },
          {
            role = 'user',
            content = input,
          }
        }
      }
    end,
    mode = segment.mode.REPLACE
  },
  ['to javascript'] = {
    provider = openai,
    builder = function(input, ctx)
      return {
        messages = {
          {
            role = 'system',
            content = 'Convert the code to javascript'
          },
          {
            role = 'user',
            content = input
          }
        }
      }
    end,
  },
  ['to rap'] = {
    provider = openai,
    hl_group = 'Title',
    builder = function(input)
      return {
        messages = {
          {
            role = 'system',
            content = "Explain the code in 90's era rap lyrics"
          },
          {
            role = 'user',
            content = input
          }
        }
      }
    end,
  }
}

Contributing

New starter prompts, providers and bug fixes are welcome! If you've figured out some useful prompts and want to share, check out the discussions.

Roadmap

I'm hoping to eventually add the following features - I'd appreciate help with any of these.

Local retrieval augmented generation

The basics are here - a simple json vectorstore based on the git repo, querying, cosine similarity comparison. It just needs a couple more features to improve the DX of using from prompts.

Enhanced context

Make treesitter and LSP info available in prompt context.

model.nvim's People

Contributors

gordon-quad avatar gsuuon avatar ibash avatar joseconseco avatar randoentity avatar somnam avatar tgy avatar tyberiusprime avatar wesl-ee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

model.nvim's Issues

[PaLM]: filters.reason = "OTHER"

Error Message

{
  filters = { {
      reason = "OTHER"
    } },
  messages = { {
      author = "0",
      content = 'enum App {\n    Browser,       // JavaScript\n    Discord,       // Go\n    HackerNews,    // Rust\n    IRC,           // C\n    Lynx,          // C\n    Mail,          // Rust\n    MusicPlayer,
   // C++\n    Reddit,        // Python\n    StackOverflow, // Rust\n    WhatsApp,      // Go\n    WorldMap,      // TypeScript\n}\n\n#[derive(Debug)]\nstruct Options {\n    apps: App,\n    quite: bool,\n    ver
bose: bool,\n}\n\nstruct Installer {\n    options: Options,\n}\n\npub fn main() {\n    println!("Hello, world!");\n}\n\n\n// Hello!'
    } }
}

Config

  {
    "gsuuon/llm.nvim",

    cmd = "Llm", -- Others cmds are ignored for now

    keys = {
      {
        "<leader>al",
        "<cmd> Llm<CR>",
        mode = { "n", "v" },
        desc = "LLM Generate",
      },
    },

    config = function(_, opts)
      require("llm").setup(opts)
    end,

    opts = function()
      return {
        default_prompt = require("llm.prompts.starters").palm,
        hl_group = "",
        -- prompts = {},
      }
    end,
    -- opts = {
    --   default = {
    --   },
    -- },
  }

how to add codeium?

is there a way to add Codeium? I'm a big fan of it, but you don't really get the chat kind of experience like this with it in nvim. you get some completion stuff, with codeium.nvim. is it similar enough to do?

Proposal: Use tree-sitter for highlighting in chat sessions

If we were to use tree-sitter for highlighting mchat buffers, we would be able to leverage injections to highlight code snippets correctly. tree-sitter-markdown already implements this, and chatbots usually respond with valid markdown, so this wouldn't be too much of a lift. We would probably have to figure out how to deliver the tree-sitter parser to users, whether by putting it in the nvim-treesitter repo or some other way. I'm willing to look into this and create a PR--is this something you'd be interested in merging?

llamacpp provider change to use local server instead of local binary breaks existing prompts using llamacpp

The llamacpp provider was changed to to use a local llama.cpp server instead of the llama.cpp binary (main).
This is breaking all prompts using the llamacpp provider.
Is there any special reason for replacing the current llamacpp with the new one?
From my point of view this change results in a way less smooth user experience as I now always have start the server manually.
Ideally we would have two providers:

  • llamacpp (the old one)
  • llamacpp-server (as the new one)

Error executing luv callback: Error in system on_stdout handler

Screenshot 2023-09-14 at 18 35 56
Error executing luv callback:                                                           
Error in system on_stdout handler                                                       
stack traceback:                                                                        
        vim/_editor.lua: in function 'notify'                                           
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/util.lua:16: in function 'sho
w'                                                                                      
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/util.lua:31: in function 'esh
ow'                                                                                     
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/provider.lua:263: in function 'on
_error'                                                                                 
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/providers/openai.lua:81: in function 'fn'
                                                                                        
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/providers/util.lua:11: in function 'ite
r_sse_items'                                                                            
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/providers/openai.lua:65: in function 'on_
stdout'                                                                                 
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/curl.lua:73: in function <...
/.local/share/nvim/lazy/llm.nvim/lua/llm/curl.lua:71>                            
        [C]: in function 'xpcall'                                                       
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/util/system.lua:48: in function <...
/.local/share/nvim/lazy/llm.nvim/lua/llm/util/system.lua:44>                            
stack traceback:                                                                        
        [C]: in function 'error'                                                        
        .../.local/share/nvim/lazy/llm.nvim/lua/llm/util/system.lua:57: in function <...
/.local/share/nvim/lazy/llm.nvim/lua/llm/util/system.lua:44>

Version Neovim: v0.9.2
Version plugin: master
Package manager: lazy.nvim

Minimal init file:

-- options
-- keybinds
-- statusline
-- init lazy

require("lazy").setup({
    { "gsuuon/llm.nvim" },
})

Setting up llamacpp

Hi @gsuuon thanks for sharing this plugin for neovim! I'm new to the ecosystem, so apologies in advance if this questions is obvious. I'm trying to get your plugin running with a local llamacpp server. The llamacpp server is running fine and I can send requests to it. However, when I try to set it up with llm.nvim with the following configuration:

require('lazy').setup({
  'gsuuon/llm.nvim'
})

require('llm').setup({
  default_prompt = require('llm.providers.llamacpp').default_prompt
})

I get the following error when I start nvim:

Error detected while processing /Users/rhsimplex/.config/nvim/init.lua:
E5113: Error while calling lua chunk: vim/shared.lua:0: stack overflow
stack traceback:
        vim/shared.lua: in function ''
        vim/shared.lua: in function 'validate'
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        ...
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function ''
        vim/shared.lua: in function 'tbl_deep_extend'
        ...nderson/.local/share/nvim/lazy/llm.nvim/lua/llm/init.lua:198: in function 'setup'
        /Users/rhsimplex/.config/nvim/init.lua:18: in main chunk

Many thanks if you have any ideas!

Curl to llama.cpp server fails to parse json if accessed over the network

Using following config

llamacpp_url = 'http://10.1.1.23:8123'
require('model').setup({
    chats = {
        zephyr = {
            provider = llamacpp,
            options = {
                url = llamacpp_url
                },
            system = 'You are a helpful assistant',
            create = input_if_selection,
            run = zephyr_fmt.chat
        },
    },
})

Open Mchat buffer and make model produce long enough text:

zephyr
---
{
  options = {
    url = "http://10.1.1.23:8123"
  }
}
---
> You are a helpful assistant
Tell me something I don't know, produce a very long response. I need it to test something.

======
Certainly! Here's a lengthy response to test your capabilities:

Did you know that the world's largest living organism is actually a fungus, and it covers an area of over 2,000 acres in Oregon's Malheur National Forest? Known as the Armillaria ostoyae, this particular species of fungus has been estimated to be at least 2,400 years old. The fungus spreads through a network of underground mycelia that connects neighboring trees and can grow at a rate of up to three feet per day. In fact, the collective weight of this single organism is estimated to be more than 6,000 tons. This fungus has been dubbed the "Humongous Fungus" and has been a source of fascination for scientists and nature enthusiasts alike. It's also a testament to the incredible complexity and interconnectedness that exists in our natural world, reminding us of the importance of preserving these delicate ecosystems for future generations.
======

Keep talking.

After such prompt model.nvim fails to parse returned json stating the reason it is incomplete. If I call curl directly by hand it produces correct i.e. non-cropped json. However if I replace curl here https://github.com/gsuuon/model.nvim/blob/main/lua/model/util/curl.lua#L86 with custom script containing

#!/bin/bash
curl "$@"
sleep 1

it succeeds every time.
My suspicion is that neovim's system function calls on_exit prior to calling on_stdout on last part of the curl's output, but I am not so familiar to neovim's lua programming to find out how to test it quickly.

Neovim version:

NVIM v0.9.5
Build type: Release
LuaJIT 2.1.0-beta3
Compilation: /usr/bin/x86_64-pc-linux-gnu-gcc  -Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion -Wvla -Wdouble-promotion -Wmissing-noreturn -Wmissing-format-attribute -Wmissing-prototypes -fno-common -Wno-unused-result -Wimplicit-fallthrough -fdiagnostics-color=always -fstack-protector-strong -DUNIT_TESTING -DINCLUDE_GENERATED_DECLARATIONS -D_GNU_SOURCE -I/usr/include/luajit-2.1 -I/usr/include -I/usr/include -I/var/tmp/portage/app-editors/neovim-0.9.5/work/neovim-0.9.5_build/src/nvim/auto -I/var/tmp/portage/app-editors/neovim-0.9.5/work/neovim-0.9.5_build/include -I/var/tmp/portage/app-editors/neovim-0.9.5/work/neovim-0.9.5_build/cmake.config -I/var/tmp/portage/app-editors/neovim-0.9.5/work/neovim-0.9.5/src -I/usr/include -I/usr/include -I/usr/include -I/usr/include -I/usr/include -I/usr/include -I/usr/include

   system vimrc file: "/etc/vim/sysinit.vim"
  fall-back for $VIM: "/usr/share/nvim"

[Feature request] proxy support for curl?

Hello there. Hope you're doing well. Thank you for creating this llm plugin. This plugin of yours suits my workflow better, so thank you very much!

Description

I'm using proton-privoxy because I need to use a proxy through a VPN to access api.openai.com .

I'm writing to see if you're willing to add support for proxy?

Expected behavior

Currently I can use below:

curl --proxy http://127.0.0.1:8888 ifconfig.co/json | jq

Could you please add some kind of configuration to allow users to use curl through a proxy?

Thank you again!

llama.cpp without neovim nightly build? (0.10)

Is there anyway to use neovim 0.9+ for the llama.cpp feature?

Neovim 0.10 does not seem to be released yet. (currently in nightly). https://github.com/neovim/neovim/releases

As much as it might be cool getting a nightly neovim working, its not worth messing with my whole setup for a single plugin.

I could just wait for neovim 0.10 update, but seeing how the whole plugin already supports 0.8 this becomes a surprise roadblock.

Is vim.system critical for getting llama.cpp to work?

Use markdown for mchat filetype

With the params as YAML front matter, the system prompt as a quoted block and the messages separated by >>> the syntax highlighting comes free with tree-sitter.

Also, nested code blocks with syntax highlighting are possible!

Here is an example how the mchat file could look like:

image

Failing to connect/start the llama.cpp server

Thank you for creating this neovim plugin and publishing it online!

I tried the plugin to access a local llama.cpp server. If I start the server manually, everything works fine. However, if I let llama.cpp start the server (as described here), I get the following error message in neovim:

curl: (7) Failed to connect to 127.0.0.1 port 8080 after 6 ms: Couldn't connect to server

I tried to set some curl arguments to increase the timeout time, but had still no luck.

My configuration looks like that:

require('llm').setup({
    llamacpp = {
        provider = llamacpp,
        options = {
            server_start = {
                command = "~/Documents/src/llama.cpp/server",
                args = {
                    "-m", "~/Documents/src/llama.cpp/models/open-llama-7b-v2/ggml-model-q4_0.gguf",
                    "-c", 2048,
                    --"-c", 4096,
                    --"-ngl", 22
                }
            },
        },
        builder = function(input, context)
            return {
                prompt = llamacpp.llama_2_user_prompt({
                    user = context.args or '',
                    message = input
                })
            }
        end,
    },
})

Do I something wrong here? I'm sorry, if this is explained somehow, but I couldn't find more information on this problem. Thank you for your help and time!

llama.cpp usage

Hey,

First of all, thanks for working on this!
I was trying to get the local llamacpp provider working, without much success.
I tried to debug what was going wrong, but my lack of lua knowledge makes this slow and difficult.

What I did to try and get it working was the following:

  • Clone llama.cpp
  • Download 7B huggingface llama model
  • Convert model
  • point LLAMACPP_DIR to the root llama.cpp folder
  • Install llm.nvim
  • Use the following config based on the starter
local llamacpp = require("llm.providers.llamacpp")
require("llm").setup({
	prompts = {
		llamacpp = {
			provider = llamacpp,
			params = {
				model = "<converted_model_path>",
				["n-gpu-layers"] = 32,
				threads = 6,
				["repeat-penalty"] = 1.2,
				temp = 0.2,
				["ctx-size"] = 4096,
				["n-predict"] = -1,
			},
			builder = function(input)
				return {
					prompt = llamacpp.llama_2_format({
						messages = {
							input,
						},
					}),
				}
			end,
			options = {
				path = "<llama.cpp root folder>",
				main_dir = "build/bin/Release/", 
			},
		},
	},
})

Current environment is nvim v0.9.0, on a MacOS M1. Are there any steps I'm missing?

Improve README

  • give a full setup example with options using lazy.nvim
  • better document how to author prompts (input, context)
  • guide to adding providers (sse utils, calling handlers)

`extract_markdown` transform fails on vim.notify

When I try to use following config

require('model').setup({
    prompts = {
            provider = llamacpp,
            options = {
                url = llamacpp_url
            },
            builder = function(input)
                local text, directive = match_llm_directive(input)

                local prompt = '<|system|>'
                    .. instruct_code
                    .. '\n</s>\n'

                prompt = prompt .. '<|user|>\n'
                    .. text
                    .. '\n</s>\n'

                if directive then
                    prompt = prompt .. '<|user|>\n'
                        .. directive
                        .. '\n</s>\n'
                end

                return {
                    prompt = prompt,
                    stops = { '</s>' }
                }
            end,
            mode = 'replace',
            transform = extract.markdown_code,
        }
    }
})

extract.markdown_code fails with following error:

E5560: nvim_echo must not be called in a lua loop callback

at line https://github.com/gsuuon/model.nvim/blob/main/lua/model/prompts/extract.lua#L56

Quick fix seems to solve the issue, however I'm not sure if that is 100% correct way to solve it

diff --git a/lua/model/prompts/extract.lua b/lua/model/prompts/extract.lua
index 6e8e715..2dc3fed 100644
--- a/lua/model/prompts/extract.lua
+++ b/lua/model/prompts/extract.lua
@@ -53,7 +53,9 @@ function M.markdown_code(md_text)

     local code_blocks = vim.tbl_filter(function(block)
       if block.text ~= nil then
-        vim.notify(block.text)
+        vim.schedule(function()
+            vim.notify(block.text)
+        end)
       end
       return block.code ~= nil
     end, blocks)

switch default openai to gpt4

For most of my use cases I'm okay with the "starter" prompts (and in fact just the default prompt); however I'd love it if I could switch the default gpt-3.5-turbo to a gpt4 model - is there an easy way to just override this setting?

`:Mcancel` does not work for chats

When aborting a completion with Mcancel the request continues in the background, so that on Mchat the old request will be inserted.

  1. Prompt: Give me 100 names.
  2. Mcancel -> Now the highlight changed and no text is inserted
  3. Mchat and prompt anything -> The first prompt is inserted

LLM error ['stop']

Across multiple models (for example WizardLM/WizardCoder-1B-V1.0), both inference, using the model name and with my own depoyment I get the error:

[LLM] The following 'model_kwargs' are not used by the model: ['stop'] (note typos in the generate argument will also show up in this list)

I can't work out what to do to fix this....

Add option to insert streamed responses using :undojoin

Issue:
Currently, the plugin does not properly support the undo command when working with streamed responses.

Expected Behavior: After receiving a streamed response, I should be able to hit u a single time to remove the entire response

Actual Behavior: After receiving a streamed response, I have to hit u once per streamed "chunk" to remove the entire response.

Suggestion: Use :undojoin to join all of the streamed chunks into a single undoable action.

Thanks for a great plugin!

Buffer mode options

Is there a way to not pollute the buffer with the input and just write there the response?
Is it possible to open the buffer in a split as a default?
Is there any chance of reutilising the properties of the original file, like filetype and syntax?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.