Skip to content

Commit

Permalink
refactor: refactor planning prompts to resolve line number issues and…
Browse files Browse the repository at this point in the history
… indentation issues (#382)
  • Loading branch information
yetone authored Aug 30, 2024
1 parent 07c1d70 commit 2997d46
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 334 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ We would like to express our heartfelt gratitude to the contributors of the foll
| [ChatGPT.nvim](https://github.com/jackMort/ChatGPT.nvim) | Apache 2.0 License | Calculation of tokens count | https://github.com/yetone/avante.nvim/blob/main/lua/avante/utils/tokens.lua |
| [img-clip.nvim](https://github.com/HakonHarnes/img-clip.nvim) | MIT License | Clipboard image support | https://github.com/yetone/avante.nvim/blob/main/lua/avante/clipboard.lua |
| [copilot.lua](https://github.com/zbirenbaum/copilot.lua) | MIT License | Copilot support | https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/copilot.lua |
| [Claude Engineer](https://github.com/Doriandarko/claude-engineer) | No License | Some prompts | https://github.com/yetone/avante.nvim/blob/main/lua/avante/llm.lua |
The high quality and ingenuity of these projects' source code have been immensely beneficial throughout our development process. We extend our sincere thanks and respect to the authors and contributors of these projects. It is the selfless dedication of the open-source community that drives projects like avante.nvim forward.
Expand Down
2 changes: 1 addition & 1 deletion lua/avante/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ M.defaults = {
model = "claude-3-5-sonnet-20240620",
timeout = 30000, -- Timeout in milliseconds
temperature = 0,
max_tokens = 4096,
max_tokens = 8000,
["local"] = false,
},
---@type AvanteSupportedProvider
Expand Down
209 changes: 122 additions & 87 deletions lua/avante/llm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,113 +13,146 @@ M.CANCEL_PATTERN = "AvanteLLMEscape"

------------------------------Prompt and type------------------------------

---@alias AvanteSystemPrompt string
local system_prompt = [[
You are an excellent programming expert.
]]

-- Copy from: https://github.com/Doriandarko/claude-engineer/blob/15c94963cbf9d01b8ae7bbb5d42d7025aa0555d5/main.py#L276
---@alias AvanteBasePrompt string
local planning_mode_prompt = [[
Your primary task is to suggest code modifications with precise line number ranges. Follow these instructions meticulously:
1. Carefully analyze the original code, paying close attention to its structure and line numbers. Line numbers start from 1 and include ALL lines, even empty ones.
2. When suggesting modifications:
a. Use the language in the question to reply. If there are non-English parts in the question, use the language of those parts.
b. Explain why the change is necessary or beneficial.
c. If an image is provided, make sure to use the image in conjunction with the code snippet.
d. Provide the exact code snippet to be replaced using this format:
Replace lines: {{start_line}}-{{end_line}}
```{{language}}
{{suggested_code}}
```
3. Crucial guidelines for suggested code snippets:
- Only apply the change(s) suggested by the most recent assistant message (before your generation).
- Do not make any unrelated changes to the code.
- Produce a valid full rewrite of the entire original file without skipping any lines. Do not be lazy!
- Do not arbitrarily delete pre-existing comments/empty Lines.
- Do not omit large parts of the original file for no reason.
- Do not omit any needed changes from the requisite messages/code blocks.
- If there is a clicked code block, bias towards just applying that (and applying other changes implied).
- Please keep your suggested code changes minimal, and do not include irrelevant lines in the code snippet.
- Maintain the SAME indentation in the returned code as in the source code
4. Crucial guidelines for line numbers:
- The content regarding line numbers MUST strictly follow the format "Replace lines: {{start_line}}-{{end_line}}". Do not be lazy!
- The range {{start_line}}-{{end_line}} is INCLUSIVE. Both start_line and end_line are included in the replacement.
- Count EVERY line, including empty lines and comments lines, comments. Do not be lazy!
- For single-line changes, use the same number for start and end lines.
- For multi-line changes, ensure the range covers ALL affected lines, from the very first to the very last.
- Double-check that your line numbers align perfectly with the original code structure.
5. Final check:
- Review all suggestions, ensuring each line number is correct, especially the start_line and end_line.
- Confirm that no unrelated code is accidentally modified or deleted.
- Verify that the start_line and end_line correctly include all intended lines for replacement.
- Perform a final alignment check to ensure your line numbers haven't shifted, especially the start_line.
- Double-check that your line numbers align perfectly with the original code structure.
- Do not show the full content after these modifications.
Remember: Accurate line numbers are CRITICAL. The range start_line to end_line must include ALL lines to be replaced, from the very first to the very last. Double-check every range before finalizing your response, paying special attention to the start_line to ensure it hasn't shifted down. Ensure that your line numbers perfectly match the original code structure without any overall shift.
local planning_mode_system_prompt_tpl = [[
You are an AI coding agent that generates code according to the instructions. Follow these steps:
1. Review the entire file content to understand the context:
${file_content}
2. Carefully analyze the selected code:
${selected_code}
3. Carefully analyze the specific instructions:
${instructions}
4. Take into account the overall project context:
${project_context}
5. Consider the memory of previous edits:
${memory_context}
6. Consider the full context of all files in the project:
${full_file_contents_context}
7. Generate SEARCH/REPLACE blocks for each necessary change. Each block should:
- Include enough context to uniquely identify the code to be changed
- Provide the exact replacement code, maintaining correct INDENTATION and FORMATTING
- Focus on specific, targeted changes rather than large, sweeping modifications
- The content in the SEARCH tag MUST NOT contain any of your generated content
- The content in the SEARCH tag MUST be based on the original content of the source file
- The content in the SEARCH tag needs to ensure a certain context to guarantee its UNIQUENESS
- The content in the REPLACE tag should also correspond to the context of the SEARCH tag
- There should be NO OVERLAP between the code of each SEARCH tag.
- DO NOT use ``` to wrap code blocks
8. Ensure that your SEARCH/REPLACE blocks:
- Address all relevant aspects of the instructions
- Maintain or enhance code readability and efficiency
- Consider the overall structure and purpose of the code
- Follow best practices and coding standards for the language
- Maintain consistency with the project context and previous edits
- Take into account the full context of all files in the project
IMPORTANT: MUST TO ADD EXPLANATIONS BEFORE AND AFTER EACH SEARCH/REPLACE BLOCK.
USE THE FOLLOWING FORMAT FOR EACH BLOCK:
<SEARCH>
Code to be replaced
</SEARCH>
<REPLACE>
New code to insert
</REPLACE>
If no changes are needed, return an empty list.
]]

local editing_mode_prompt = [[
Your task is to modify the provided code according to the user's request. Follow these instructions precisely:
local editing_mode_system_prompt_tpl = [[
You are an AI coding agent that generates code according to the instructions. Follow these steps:
1. Review the entire file content to understand the context:
${file_content}
2. Carefully analyze the selected code:
${selected_code}
3. Carefully analyze the specific instructions:
${instructions}
4. Take into account the overall project context:
${project_context}
5. Consider the memory of previous edits:
${memory_context}
6. Consider the full context of all files in the project:
${full_file_contents_context}
7. Return ONLY the complete modified code.
1. Carefully analyze the original code and the user's request.
2. Make the necessary modifications to the code as requested.
3. Return ONLY the complete modified code.
4. Do not include any explanations, comments, or line numbers in your response.
5. Ensure the returned code is complete and can be directly used as a replacement for the original code.
6. Preserve the original structure, indentation, and formatting of the code as much as possible.
7. Do not omit any parts of the code, even if they are unchanged.
8. Maintain the SAME indentation in the returned code as in the source code
9. Do NOT include three backticks: ```
10. Only return code part, do NOT return the context part!
8. Do not include any explanations, comments, or line numbers in your response.
9. Ensure the returned code is complete and can be directly used as a replacement for the original code.
11. Preserve the original structure, indentation, and formatting of the code as much as possible.
12. Do not omit any parts of the code, even if they are unchanged.
13. Maintain the SAME indentation in the returned code as in the source code
14. Do NOT include three backticks: ```
15. Only return code part, do NOT return the context part!
Remember: Your response should contain nothing but ONLY the modified code, ready to be used as a direct replacement for the original file.
]]

local group = api.nvim_create_augroup("avante_llm", { clear = true })
local active_job = nil

---@param question string
---@param code_lang string
---@param code_content string
---@param selected_content_content string | nil
---@param mode "planning" | "editing"
---@param on_chunk AvanteChunkParser
---@param on_complete AvanteCompleteParser
M.stream = function(question, code_lang, code_content, selected_content_content, mode, on_chunk, on_complete)
mode = mode or "planning"
---@class StreamOptions
---@field file_content string
---@field selected_code string | nil
---@field instructions string
---@field project_context string | nil
---@field memory_context string | nil
---@field full_file_contents_context string | nil
---@field mode "planning" | "editing"
---@field on_chunk AvanteChunkParser
---@field on_complete AvanteCompleteParser

---@param opts StreamOptions
M.stream = function(opts)
local mode = opts.mode or "planning"
local provider = Config.provider

local system_prompt_tpl = mode == "planning" and planning_mode_system_prompt_tpl or editing_mode_system_prompt_tpl

-- Check if the question contains an image path
local image_path = nil
local original_question = question
if question:match("image: ") then
local lines = vim.split(question, "\n")
local image_paths = {}
local original_instructions = opts.instructions
if opts.instructions:match("image: ") then
local lines = vim.split(opts.instructions, "\n")
for i, line in ipairs(lines) do
if line:match("^image: ") then
image_path = line:gsub("^image: ", "")
local image_path = line:gsub("^image: ", "")
table.insert(image_paths, image_path)
table.remove(lines, i)
original_question = table.concat(lines, "\n")
break
end
end
original_instructions = table.concat(lines, "\n")
end

local system_prompt =
system_prompt_tpl:gsub("%${(.-)}", vim.tbl_deep_extend("force", opts, { instructions = original_instructions }))

---@type AvantePromptOptions
local code_opts = {
base_prompt = mode == "planning" and planning_mode_prompt or editing_mode_prompt,
system_prompt = system_prompt,
question = original_question,
image_path = image_path,
code_lang = code_lang,
code_content = code_content,
selected_code_content = selected_content_content,
user_prompt = opts.selected_code and "Please suggest modifications to the selected code."
or "Please suggest modifications to the file coontent.",
image_paths = image_paths,
}

---@type string
Expand All @@ -129,7 +162,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
local Provider = P[provider]

---@type AvanteHandlerOptions
local handler_opts = { on_chunk = on_chunk, on_complete = on_complete }
local handler_opts = { on_chunk = opts.on_chunk, on_complete = opts.on_complete }
---@type AvanteCurlOutput
local spec = Provider.parse_curl_args(Provider, code_opts)

Expand Down Expand Up @@ -163,7 +196,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
stream = function(err, data, _)
if err then
completed = true
on_complete(err)
opts.on_complete(err)
return
end
if not data then
Expand All @@ -189,7 +222,7 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
end,
on_error = function(err)
completed = true
on_complete(err)
opts.on_complete(err)
end,
callback = function(result)
if result.status >= 400 then
Expand All @@ -201,7 +234,9 @@ M.stream = function(question, code_lang, code_content, selected_content_content,
vim.schedule(function()
if not completed then
completed = true
on_complete("API request failed with status " .. result.status .. ". Body: " .. vim.inspect(result.body))
opts.on_complete(
"API request failed with status " .. result.status .. ". Body: " .. vim.inspect(result.body)
)
end
end)
end
Expand Down
73 changes: 25 additions & 48 deletions lua/avante/providers/claude.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,24 @@ local M = {}

M.api_key_name = "ANTHROPIC_API_KEY"

M.parse_message = function(opts)
local code_prompt_obj = {
type = "text",
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.code_content),
}

if Utils.tokens.calculate_tokens(code_prompt_obj.text) > 1024 then
code_prompt_obj.cache_control = { type = "ephemeral" }
end

if opts.selected_code_content then
code_prompt_obj.text = string.format("<code_context>```%s\n%s```</code_context>", opts.code_lang, opts.code_content)
end

local message_content = {
code_prompt_obj,
}

if opts.selected_code_content then
local selected_code_obj = {
type = "text",
text = string.format("<code>```%s\n%s```</code>", opts.code_lang, opts.selected_code_content),
}

if Utils.tokens.calculate_tokens(selected_code_obj.text) > 1024 then
selected_code_obj.cache_control = { type = "ephemeral" }
---@param prompt_opts AvantePromptOptions
M.parse_message = function(prompt_opts)
local message_content = {}

if Clipboard.support_paste_image() and prompt_opts.image_paths then
for _, image_path in ipairs(prompt_opts.image_paths) do
table.insert(message_content, {
type = "image",
source = {
type = "base64",
media_type = "image/png",
data = Clipboard.get_base64_content(image_path),
},
})
end

table.insert(message_content, selected_code_obj)
end

if Clipboard.support_paste_image() and opts.image_path then
table.insert(message_content, {
type = "image",
source = {
type = "base64",
media_type = "image/png",
data = Clipboard.get_base64_content(opts.image_path),
},
})
end

table.insert(message_content, {
type = "text",
text = string.format("<question>%s</question>", opts.question),
})

local user_prompt = opts.base_prompt
local user_prompt = prompt_opts.user_prompt

local user_prompt_obj = {
type = "text",
Expand Down Expand Up @@ -91,9 +61,9 @@ M.parse_response = function(data_stream, event_state, opts)
end

---@param provider AvanteProviderFunctor
---@param code_opts AvantePromptOptions
---@param prompt_opts AvantePromptOptions
---@return table
M.parse_curl_args = function(provider, code_opts)
M.parse_curl_args = function(provider, prompt_opts)
local base, body_opts = P.parse_config(provider)

local headers = {
Expand All @@ -112,7 +82,14 @@ M.parse_curl_args = function(provider, code_opts)
headers = headers,
body = vim.tbl_deep_extend("force", {
model = base.model,
messages = M.parse_message(code_opts),
system = {
{
type = "text",
text = prompt_opts.system_prompt,
cache_control = { type = "ephemeral" },
},
},
messages = M.parse_message(prompt_opts),
stream = true,
}, body_opts),
}
Expand Down
Loading

0 comments on commit 2997d46

Please sign in to comment.