feat: integrated morph
This commit is contained in:
62
README.md
62
README.md
@@ -931,6 +931,68 @@ return {
|
||||
|
||||
See [highlights.lua](./lua/avante/highlights.lua) for more information
|
||||
|
||||
## Fast Apply
|
||||
|
||||
Fast Apply is a feature that enables instant code edits with high accuracy by leveraging specialized models. It replicates Cursor's instant apply functionality, allowing for seamless code modifications without the typical delays associated with traditional code generation.
|
||||
|
||||
### Purpose and Benefits
|
||||
|
||||
Fast Apply addresses the common pain point of slow code application in AI-assisted development. Instead of waiting for a full language model to process and apply changes, Fast Apply uses a specialized "apply model" that can quickly and accurately merge code edits with 96-98% accuracy at speeds of 2500-4500+ tokens per second.
|
||||
|
||||
Key benefits:
|
||||
- **Instant application**: Code changes are applied immediately without noticeable delays
|
||||
- **High accuracy**: Specialized models achieve 96-98% accuracy for code edits
|
||||
- **Seamless workflow**: Maintains the natural flow of development without interruptions
|
||||
- **Large context support**: Handles up to 16k tokens for both input and output
|
||||
|
||||
### Configuration
|
||||
|
||||
To enable Fast Apply, you need to:
|
||||
|
||||
1. **Enable Fast Apply in your configuration**:
|
||||
```lua
|
||||
require('avante').setup({
|
||||
behaviour = {
|
||||
enable_fastapply = true, -- Enable Fast Apply feature
|
||||
},
|
||||
-- ... other configuration
|
||||
})
|
||||
```
|
||||
|
||||
2. **Get your Morph API key**:
|
||||
Go to [morph.com](https://morphllm.com/api-keys) and create an account and get the API key.
|
||||
|
||||
3. **Set your Morph API key**:
|
||||
```bash
|
||||
export MORPH_API_KEY="your-enterprise-api-key"
|
||||
```
|
||||
|
||||
### Model Options
|
||||
|
||||
Morph provides different models optimized for different use cases:
|
||||
|
||||
| Model | Speed | Accuracy | Context Limit |
|
||||
|-------|-------|----------|---------------|
|
||||
| `morph-v3-fast` | 4500+ tok/sec | 96% | 16k tokens |
|
||||
| `morph-v3-large` | 2500+ tok/sec | 98% | 16k tokens |
|
||||
| `auto` | 2500-4500 tok/sec | 98% | 16k tokens |
|
||||
|
||||
### How It Works
|
||||
|
||||
When Fast Apply is enabled and a Morph provider is configured, avante.nvim will:
|
||||
|
||||
1. Use the `edit_file` tool for code modifications instead of traditional tools
|
||||
2. Send the original code, edit instructions, and update snippet to the Morph API
|
||||
3. Receive the fully merged code back from the specialized apply model
|
||||
4. Apply the changes directly to your files with high accuracy
|
||||
|
||||
The process uses a specialized prompt format that includes:
|
||||
- `<instructions>`: Clear description of what changes to make
|
||||
- `<code>`: The original code content
|
||||
- `<update>`: The specific changes using truncation markers (`// ... existing code ...`)
|
||||
|
||||
This approach ensures that the apply model can quickly and accurately merge your changes without the overhead of full code generation.
|
||||
|
||||
## Ollama
|
||||
|
||||
ollama is a first-class provider for avante.nvim. You can use it by setting `provider = "ollama"` in the configuration, and set the `model` field in `ollama` to the model you want to use. For example:
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
Cursor planning mode
|
||||
====================
|
||||
|
||||
Because avante.nvim has always used Aider’s method for planning applying, but its prompts are very picky with models and require ones like claude-3.5-sonnet or gpt-4o to work properly.
|
||||
|
||||
Therefore, I have adopted Cursor’s method to implement planning applying, which should work on most models. If you encounter issues with your current model, I highly recommend switching to cursor planning mode to resolve them.
|
||||
|
||||
For details on the implementation, please refer to: [🚀 Introducing Fast Apply - Replicate Cursor's Instant Apply model](https://www.reddit.com/r/LocalLLaMA/comments/1ga25gj/introducing_fast_apply_replicate_cursors_instant/)
|
||||
|
||||
~~So you need to first run the `FastApply` model mentioned above:~~
|
||||
|
||||
~~```bash~~
|
||||
ollama pull hf.co/Kortix/FastApply-7B-v1.0_GGUF:Q4_K_M
|
||||
~~```~~
|
||||
|
||||
An interesting fact is that I found the `FastApply` model mentioned above doesn't work well. First, it's too slow, and second, it's not accurate for processing long code file. It often includes `// ... existing code ...` comments in the generated final code, resulting in incorrect code generation.
|
||||
|
||||
The best model I found for applying is `llama-3.3-70b-versatile` on [Groq](https://console.groq.com/playground), it's both fast and accurate, it's perfect!
|
||||
|
||||
Then enable it in avante.nvim:
|
||||
|
||||
```lua
|
||||
{
|
||||
--- ... existing configurations
|
||||
provider = 'claude', -- In this example, use Claude for planning, but you can also use any provider you want.
|
||||
cursor_applying_provider = 'groq', -- In this example, use Groq for applying, but you can also use any provider you want.
|
||||
behaviour = {
|
||||
--- ... existing behaviours
|
||||
enable_cursor_planning_mode = true, -- enable cursor planning mode!
|
||||
},
|
||||
providers = {
|
||||
--- ... existing providers
|
||||
groq = { -- define groq provider
|
||||
__inherited_from = 'openai',
|
||||
api_key_name = 'GROQ_API_KEY',
|
||||
endpoint = 'https://api.groq.com/openai/v1/',
|
||||
model = 'llama-3.3-70b-versatile',
|
||||
max_completion_tokens = 32768, -- remember to increase this value, otherwise it will stop generating halfway
|
||||
},
|
||||
},
|
||||
--- ... existing configurations
|
||||
}
|
||||
```
|
||||
@@ -395,6 +395,12 @@ M._defaults = {
|
||||
max_tokens = 4096,
|
||||
},
|
||||
},
|
||||
morph = {
|
||||
__inherited_from = "openai",
|
||||
endpoint = "https://api.morphllm.com/v1",
|
||||
model = "auto",
|
||||
api_key_name = "MORPH_API_KEY",
|
||||
},
|
||||
},
|
||||
---Specify the special dual_boost mode
|
||||
---1. enabled: Whether to enable dual_boost mode. Default to false.
|
||||
@@ -440,6 +446,7 @@ M._defaults = {
|
||||
---@type boolean | string[] -- true: auto-approve all tools, false: normal prompts, string[]: auto-approve specific tools by name
|
||||
auto_approve_tool_permissions = false, -- Default: show permission prompts for all tools
|
||||
auto_check_diagnostics = true,
|
||||
enable_fastapply = false,
|
||||
},
|
||||
prompt_logger = { -- logs prompts to disk (timestamped, for replay/debugging)
|
||||
enabled = true, -- toggle logging entirely
|
||||
|
||||
@@ -10,7 +10,9 @@ M.name = "create"
|
||||
|
||||
M.description = "The create tool allows you to create a new file with specified content."
|
||||
|
||||
function M.enabled() return require("avante.config").mode == "agentic" end
|
||||
function M.enabled()
|
||||
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
---@type AvanteLLMToolParam
|
||||
M.param = {
|
||||
|
||||
174
lua/avante/llm_tools/edit_file.lua
Normal file
174
lua/avante/llm_tools/edit_file.lua
Normal file
@@ -0,0 +1,174 @@
|
||||
local Base = require("avante.llm_tools.base")
|
||||
local Providers = require("avante.providers")
|
||||
local Utils = require("avante.utils")
|
||||
|
||||
---@class AvanteLLMTool
|
||||
local M = setmetatable({}, Base)
|
||||
|
||||
M.name = "edit_file"
|
||||
|
||||
M.enabled = function()
|
||||
return require("avante.config").mode == "agentic" and require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
M.description =
|
||||
"Use this tool to propose an edit to an existing file.\n\nThis will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.\nWhen writing the edit, you should specify each edit in sequence, with the special comment // ... existing code ... to represent unchanged code in between edited lines.\n\nFor example:\n\n// ... existing code ...\nFIRST_EDIT\n// ... existing code ...\nSECOND_EDIT\n// ... existing code ...\nTHIRD_EDIT\n// ... existing code ...\n\nYou should still bias towards repeating as few lines of the original file as possible to convey the change.\nBut, each edit should contain sufficient context of unchanged lines around the code you're editing to resolve ambiguity.\nDO NOT omit spans of pre-existing code (or comments) without using the // ... existing code ... comment to indicate its absence. If you omit the existing code comment, the model may inadvertently delete these lines.\nIf you plan on deleting a section, you must provide context before and after to delete it. If the initial code is ```code \\n Block 1 \\n Block 2 \\n Block 3 \\n code```, and you want to remove Block 2, you would output ```// ... existing code ... \\n Block 1 \\n Block 3 \\n // ... existing code ...```.\nMake sure it is clear what the edit should be, and where it should be applied.\nALWAYS make all edits to a file in a single edit_file instead of multiple edit_file calls to the same file. The apply model can handle many distinct edits at once."
|
||||
|
||||
---@type AvanteLLMToolParam
|
||||
M.param = {
|
||||
type = "table",
|
||||
fields = {
|
||||
{
|
||||
name = "target_file",
|
||||
description = "The target file to modify.",
|
||||
type = "string",
|
||||
},
|
||||
{
|
||||
name = "instructions",
|
||||
type = "string",
|
||||
description = "A single sentence instruction describing what you are going to do for the sketched edit. This is used to assist the less intelligent model in applying the edit. Use the first person to describe what you are going to do. Use it to disambiguate uncertainty in the edit.",
|
||||
},
|
||||
{
|
||||
name = "code_edit",
|
||||
type = "string",
|
||||
description = "Specify ONLY the precise lines of code that you wish to edit. NEVER specify or write out unchanged code. Instead, represent all unchanged code using the comment of the language you're editing in - example: // ... existing code ...",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
---@type AvanteLLMToolReturn[]
|
||||
M.returns = {
|
||||
{
|
||||
name = "success",
|
||||
description = "Whether the file was edited successfully",
|
||||
type = "boolean",
|
||||
},
|
||||
{
|
||||
name = "error",
|
||||
description = "Error message if the file could not be edited",
|
||||
type = "string",
|
||||
optional = true,
|
||||
},
|
||||
}
|
||||
|
||||
---@type AvanteLLMToolFunc<{ target_file: string, instructions: string, code_edit: string, streaming?: boolean, tool_use_id?: string }>
|
||||
M.func = vim.schedule_wrap(function(input, opts)
|
||||
local on_complete = opts.on_complete
|
||||
if not on_complete then return false, "on_complete not provided" end
|
||||
local provider = Providers["morph"]
|
||||
if not provider then return false, "morph provider not found" end
|
||||
if not provider.is_env_set() then return false, "morph provider not set" end
|
||||
|
||||
local original_code
|
||||
local ok, lines = pcall(Utils.read_file_from_buf_or_disk, input.target_file)
|
||||
if not ok then
|
||||
local f = io.open(input.target_file, "r")
|
||||
if f then
|
||||
original_code = f:read("*all")
|
||||
f:close()
|
||||
end
|
||||
else
|
||||
original_code = table.concat(lines or {}, "\n")
|
||||
end
|
||||
|
||||
local provider_conf = Providers.parse_config(provider)
|
||||
|
||||
local body = {
|
||||
model = provider_conf.model,
|
||||
messages = {
|
||||
{
|
||||
role = "user",
|
||||
content = "<instructions>"
|
||||
.. input.instructions
|
||||
.. "</instructions>\n<code>"
|
||||
.. original_code
|
||||
.. "</code>\n<update>"
|
||||
.. input.code_edit
|
||||
.. "</update>",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local temp_file = vim.fn.tempname()
|
||||
local curl_body_file = temp_file .. "-request-body.json"
|
||||
local json_content = vim.json.encode(body)
|
||||
vim.fn.writefile(vim.split(json_content, "\n"), curl_body_file)
|
||||
|
||||
-- Construct curl command
|
||||
local curl_cmd = {
|
||||
"curl",
|
||||
"-X",
|
||||
"POST",
|
||||
"-H",
|
||||
"Content-Type: application/json",
|
||||
"-d",
|
||||
"@" .. curl_body_file,
|
||||
Utils.url_join(provider_conf.endpoint, "/chat/completions"),
|
||||
}
|
||||
|
||||
-- Add authorization header if available
|
||||
if Providers.env.require_api_key(provider_conf) then
|
||||
local api_key = provider.parse_api_key()
|
||||
table.insert(curl_cmd, 4, "-H")
|
||||
table.insert(curl_cmd, 5, "Authorization: Bearer " .. api_key)
|
||||
end
|
||||
|
||||
vim.system(
|
||||
curl_cmd,
|
||||
{
|
||||
text = true,
|
||||
},
|
||||
vim.schedule_wrap(function(result)
|
||||
-- Clean up temporary file
|
||||
vim.fn.delete(curl_body_file)
|
||||
|
||||
if result.code ~= 0 then
|
||||
local error_msg = result.stderr
|
||||
if error_msg ~= nil and error_msg ~= "" then
|
||||
on_complete(false, "Failed to edit file: " .. error_msg)
|
||||
return
|
||||
end
|
||||
on_complete(false, "Failed to edit file: " .. result.code)
|
||||
return
|
||||
end
|
||||
|
||||
local response_body = result.stdout or ""
|
||||
if response_body == "" then
|
||||
on_complete(false, "Empty response from server")
|
||||
return
|
||||
end
|
||||
|
||||
local ok_, jsn = pcall(vim.json.decode, response_body)
|
||||
if not ok_ then
|
||||
on_complete(false, "Failed to parse JSON response: " .. response_body)
|
||||
return
|
||||
end
|
||||
|
||||
if jsn.error then
|
||||
if type(jsn.error) == "table" and jsn.error.message then
|
||||
on_complete(false, jsn.error.message or vim.inspect(jsn.error))
|
||||
else
|
||||
on_complete(false, vim.inspect(jsn.error))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not jsn.choices or not jsn.choices[1] or not jsn.choices[1].message then
|
||||
on_complete(false, "Invalid response format")
|
||||
return
|
||||
end
|
||||
|
||||
local str_replace = require("avante.llm_tools.str_replace")
|
||||
local new_input = {
|
||||
path = input.target_file,
|
||||
old_str = original_code,
|
||||
new_str = jsn.choices[1].message.content,
|
||||
streaming = input.streaming,
|
||||
tool_use_id = input.tool_use_id,
|
||||
}
|
||||
str_replace.func(new_input, opts)
|
||||
end)
|
||||
)
|
||||
end)
|
||||
|
||||
return M
|
||||
@@ -1088,6 +1088,7 @@ You can delete the first file by providing a path of "directory1/a/something.txt
|
||||
require("avante.llm_tools.get_diagnostics"),
|
||||
require("avante.llm_tools.bash"),
|
||||
require("avante.llm_tools.attempt_completion"),
|
||||
require("avante.llm_tools.edit_file"),
|
||||
{
|
||||
name = "web_search",
|
||||
description = "Search the web",
|
||||
|
||||
@@ -10,7 +10,9 @@ M.name = "insert"
|
||||
|
||||
M.description = "The insert tool allows you to insert text at a specific location in a file."
|
||||
|
||||
function M.enabled() return require("avante.config").mode == "agentic" end
|
||||
function M.enabled()
|
||||
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
---@type AvanteLLMToolParam
|
||||
M.param = {
|
||||
|
||||
@@ -17,7 +17,10 @@ M.description =
|
||||
"Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file. This tool should be used when you need to make targeted changes to specific parts of a file."
|
||||
|
||||
M.support_streaming = true
|
||||
-- function M.enabled() return Config.provider:match("ollama") == nil end
|
||||
|
||||
function M.enabled()
|
||||
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
---@type AvanteLLMToolParam
|
||||
M.param = {
|
||||
|
||||
@@ -10,7 +10,9 @@ M.name = "write_to_file"
|
||||
M.description =
|
||||
"Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file."
|
||||
|
||||
function M.enabled() return require("avante.config").mode == "agentic" end
|
||||
function M.enabled()
|
||||
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
---@type AvanteLLMToolParam
|
||||
M.param = {
|
||||
|
||||
@@ -1242,11 +1242,13 @@ end
|
||||
function M.read_file_from_buf_or_disk(filepath)
|
||||
local abs_path = filepath:sub(1, 7) == "term://" and filepath or M.join_paths(M.get_project_root(), filepath)
|
||||
--- Lookup if the file is loaded in a buffer
|
||||
local bufnr = vim.fn.bufnr(abs_path)
|
||||
if bufnr ~= -1 and vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
-- If buffer exists and is loaded, get buffer content
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
return lines, nil
|
||||
local ok, bufnr = pcall(vim.fn.bufnr, abs_path)
|
||||
if ok then
|
||||
if bufnr ~= -1 and vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
-- If buffer exists and is loaded, get buffer content
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
return lines, nil
|
||||
end
|
||||
end
|
||||
|
||||
local stat = vim.uv.fs_stat(abs_path)
|
||||
|
||||
Reference in New Issue
Block a user