fix: react prompts (#2537)

This commit is contained in:
yetone
2025-07-26 16:06:56 +08:00
committed by GitHub
parent bd69ae14f6
commit 8bc149ccd8
14 changed files with 448 additions and 173 deletions

View File

@@ -44,6 +44,7 @@ struct TemplateContext {
model_name: Option<String>, model_name: Option<String>,
memory: Option<String>, memory: Option<String>,
todos: Option<String>, todos: Option<String>,
enable_fastapply: Option<bool>,
} }
// Given the file name registered after add, the context table in Lua, resulted in a formatted // Given the file name registered after add, the context table in Lua, resulted in a formatted
@@ -72,6 +73,7 @@ fn render(state: &State, template: &str, context: TemplateContext) -> LuaResult<
model_name => context.model_name, model_name => context.model_name,
memory => context.memory, memory => context.memory,
todos => context.todos, todos => context.todos,
enable_fastapply => context.enable_fastapply,
}) })
.map_err(LuaError::external) .map_err(LuaError::external)
.unwrap()) .unwrap())

View File

@@ -1,14 +1,3 @@
---@class avante.TextContent
---@field type "text"
---@field text string
---@field partial boolean
---
---@class avante.ToolUseContent
---@field type "tool_use"
---@field tool_name string
---@field tool_input table
---@field partial boolean
local M = {} local M = {}
-- Helper function to parse a parameter tag like <param_name>value</param_name> -- Helper function to parse a parameter tag like <param_name>value</param_name>

View File

@@ -0,0 +1,261 @@
local JsonParser = require("avante.libs.jsonparser")
---@class avante.TextContent
---@field type "text"
---@field text string
---@field partial boolean
---
---@class avante.ToolUseContent
---@field type "tool_use"
---@field tool_name string
---@field tool_input table
---@field partial boolean
local M = {}
--- Parse the text into a list of TextContent and ToolUseContent
--- The text is a string.
--- For example:
--- parse([[Hello, world!]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world!",
--- partial = false,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name": "write", "input": {"path": "path/to/file.txt", "content": "foo"}}</tool_use>]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {
--- path = "path/to/file.txt",
--- content = "foo",
--- },
--- partial = false,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name": "write", "input": {"path": "path/to/file.txt", "content": "foo"}}</tool_use>I am another tool.<tool_use>{"name": "write", "input": {"path": "path/to/file.txt", "content": "bar"}}</tool_use>hello]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {
--- path = "path/to/file.txt",
--- content = "foo",
--- },
--- partial = false,
--- },
--- {
--- type = "text",
--- text = "I am another tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {
--- path = "path/to/file.txt",
--- content = "bar",
--- },
--- partial = false,
--- },
--- {
--- type = "text",
--- text = "hello",
--- partial = false,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name"]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- }
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name": "write"]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {},
--- partial = true,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name": "write", "input": {"path": "path/to/file.txt"]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {
--- path = "path/to/file.txt",
--- },
--- partial = true,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.<tool_use>{"name": "write", "input": {"path": "path/to/file.txt", "content": "foo bar]])
--- returns
--- {
--- {
--- type = "text",
--- text = "Hello, world! I am a tool.",
--- partial = false,
--- },
--- {
--- type = "tool_use",
--- tool_name = "write",
--- tool_input = {
--- path = "path/to/file.txt",
--- content = "foo bar",
--- },
--- partial = true,
--- },
--- }
---
--- parse([[Hello, world! I am a tool.{"name": "write", "input": {"path": "path/to/file.txt", "content": foo bar]])
--- returns
--- {
--- {
--- type = "text",
--- text = [[Hello, world! I am a tool.{"name": "write", "input": {"path": "path/to/file.txt", "content": foo bar]],
--- partial = false,
--- }
--- }
---
---@param text string
---@return (avante.TextContent|avante.ToolUseContent)[]
function M.parse(text)
local result = {}
local pos = 1
local len = #text
while pos <= len do
local tool_start = text:find("<tool_use>", pos, true)
if not tool_start then
-- No more tool_use tags, add remaining text if any
if pos <= len then
local remaining_text = text:sub(pos)
if remaining_text ~= "" then
table.insert(result, {
type = "text",
text = remaining_text,
partial = false,
})
end
end
break
end
-- Add text before tool_use tag if any
if tool_start > pos then
local text_content = text:sub(pos, tool_start - 1)
if text_content ~= "" then
table.insert(result, {
type = "text",
text = text_content,
partial = false,
})
end
end
-- Find the closing tag
local json_start = tool_start + 10 -- length of "<tool_use>"
local tool_end = text:find("</tool_use>", json_start, true)
if not tool_end then
-- No closing tag found, treat as partial tool_use
local json_text = text:sub(json_start)
json_text = json_text:gsub("^\n+", "")
json_text = json_text:gsub("\n+$", "")
json_text = json_text:gsub("^%s+", "")
json_text = json_text:gsub("%s+$", "")
-- Try to parse complete JSON first
local success, json_data = pcall(function() return vim.json.decode(json_text) end)
if success and json_data and json_data.name then
table.insert(result, {
type = "tool_use",
tool_name = json_data.name,
tool_input = json_data.input or {},
partial = true,
})
else
local jsn = JsonParser.parse(json_text)
if jsn and jsn.name then
table.insert(result, {
type = "tool_use",
tool_name = jsn.name,
tool_input = jsn.input or {},
partial = true,
})
end
end
break
end
-- Extract JSON content
local json_text = text:sub(json_start, tool_end - 1)
local success, json_data = pcall(function() return vim.json.decode(json_text) end)
if success and json_data and json_data.name then
table.insert(result, {
type = "tool_use",
tool_name = json_data.name,
tool_input = json_data.input or {},
partial = false,
})
pos = tool_end + 11 -- length of "</tool_use>"
else
-- Invalid JSON, treat the whole thing as text
local invalid_text = text:sub(tool_start, tool_end + 10)
table.insert(result, {
type = "text",
text = invalid_text,
partial = false,
})
pos = tool_end + 11
end
end
return result
end
return M

View File

@@ -307,6 +307,7 @@ function M.generate_prompts(opts)
system_info = system_info, system_info = system_info,
model_name = provider.model or "unknown", model_name = provider.model or "unknown",
memory = opts.memory, memory = opts.memory,
enable_fastapply = Config.behaviour.enable_fastapply,
} }
-- Removed the original todos processing logic, now handled in context_messages -- Removed the original todos processing logic, now handled in context_messages
@@ -804,7 +805,7 @@ function M._stream(opts)
return return
end end
end end
M._stream(new_opts) if not streaming_tool_use then M._stream(new_opts) end
return return
end end
local partial_tool_use = tool_uses[tool_use_index] local partial_tool_use = tool_uses[tool_use_index]
@@ -849,15 +850,14 @@ function M._stream(opts)
streaming = partial_tool_use.state == "generating", streaming = partial_tool_use.state == "generating",
on_complete = function() end, on_complete = function() end,
} }
if partial_tool_use.state == "generating" and not is_edit_tool_use and not support_streaming then return end
if partial_tool_use.state == "generating" then if partial_tool_use.state == "generating" then
if not is_edit_tool_use and not support_streaming then return end
if type(partial_tool_use.input) == "table" then if type(partial_tool_use.input) == "table" then
LLMTools.process_tool_use(prompt_opts.tools, partial_tool_use, tool_use_opts) LLMTools.process_tool_use(prompt_opts.tools, partial_tool_use, tool_use_opts)
end end
return return
else
if streaming_tool_use then return end
end end
if streaming_tool_use then return end
partial_tool_use_message.is_calling = true partial_tool_use_message.is_calling = true
if opts.on_messages_add then opts.on_messages_add({ partial_tool_use_message }) end if opts.on_messages_add then opts.on_messages_add({ partial_tool_use_message }) end
-- Either on_complete handles the tool result asynchronously or we receive the result and error synchronously when either is not nil -- Either on_complete handles the tool result asynchronously or we receive the result and error synchronously when either is not nil
@@ -887,12 +887,11 @@ function M._stream(opts)
local completed_attempt_completion_tool_use = nil local completed_attempt_completion_tool_use = nil
for idx = #history_messages, 1, -1 do for idx = #history_messages, 1, -1 do
local message = history_messages[idx] local message = history_messages[idx]
if not message.is_user_submission then if message.is_user_submission then break end
local use = History.Helpers.get_tool_use_data(message) local use = History.Helpers.get_tool_use_data(message)
if use and use.name == "attempt_completion" then if use and use.name == "attempt_completion" then
completed_attempt_completion_tool_use = message completed_attempt_completion_tool_use = message
break break
end
end end
end end
local unfinished_todos = {} local unfinished_todos = {}
@@ -915,7 +914,7 @@ function M._stream(opts)
if #unfinished_todos > 0 then if #unfinished_todos > 0 then
message = History.Message:new( message = History.Message:new(
"user", "user",
"<user-reminder>You should use tool calls to answer the question, for example, use update_todo_status if the task step is done or cancelled.</user-reminder>", "<system-reminder>You should use tool calls to answer the question, for example, use update_todo_status if the task step is done or cancelled.</system-reminder>",
{ {
visible = false, visible = false,
} }
@@ -923,7 +922,7 @@ function M._stream(opts)
else else
message = History.Message:new( message = History.Message:new(
"user", "user",
"<user-reminder>You should use tool calls to answer the question, for example, use attempt_completion if the job is done.</user-reminder>", "<system-reminder>You should use tool calls to answer the question, for example, use attempt_completion if the job is done.</system-reminder>",
{ {
visible = false, visible = false,
} }

View File

@@ -53,6 +53,10 @@ M.returns = {
---@type AvanteLLMToolFunc<{ path: string, instructions: string, code_edit: string }> ---@type AvanteLLMToolFunc<{ path: string, instructions: string, code_edit: string }>
M.func = vim.schedule_wrap(function(input, opts) M.func = vim.schedule_wrap(function(input, opts)
if opts.streaming then return false, "streaming not supported" end
if not input.path then return false, "path not provided" end
if not input.instructions then input.instructions = "" end
if not input.code_edit then return false, "code_edit not provided" end
local on_complete = opts.on_complete local on_complete = opts.on_complete
if not on_complete then return false, "on_complete not provided" end if not on_complete then return false, "on_complete not provided" end
local provider = Providers["morph"] local provider = Providers["morph"]

View File

@@ -1274,7 +1274,7 @@ function M.process_tool_use(tools, tool_use, opts)
else else
---@type AvanteLLMTool? ---@type AvanteLLMTool?
local tool = vim.iter(tools):find(function(tool) return tool.name == tool_use.name end) ---@param tool AvanteLLMTool local tool = vim.iter(tools):find(function(tool) return tool.name == tool_use.name end) ---@param tool AvanteLLMTool
if tool == nil then return nil, "This tool is not provided: " .. tool_use.name end if tool == nil then return nil, "This tool is not provided: " .. vim.inspect(tool_use.name) end
func = tool.func or M[tool.name] func = tool.func or M[tool.name]
end end
local input_json = tool_use.input local input_json = tool_use.input

View File

@@ -50,7 +50,7 @@ function M.func(input, opts)
local todos = sidebar.chat_history.todos local todos = sidebar.chat_history.todos
if not todos or #todos == 0 then return false, "No todos found" end if not todos or #todos == 0 then return false, "No todos found" end
for _, todo in ipairs(todos) do for _, todo in ipairs(todos) do
if todo.id == input.id then if tostring(todo.id) == tostring(input.id) then
todo.status = input.status todo.status = input.status
break break
end end

View File

@@ -10,6 +10,8 @@ M.name = "write_to_file"
M.description = 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." "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."
M.support_streaming = false
function M.enabled() function M.enabled()
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
end end

View File

@@ -113,29 +113,25 @@ function M:parse_messages(opts)
end end
if not provider_conf.disable_tools and use_ReAct_prompt then if not provider_conf.disable_tools and use_ReAct_prompt then
if content_items[1].type == "tool_result" then if content_items[1].type == "tool_result" then
local tool_use = nil local tool_use_msg = nil
for _, msg_ in ipairs(opts.messages) do for _, msg_ in ipairs(opts.messages) do
if type(msg_.content) == "table" and #msg_.content > 0 then if type(msg_.content) == "table" and #msg_.content > 0 then
if msg_.content[1].type == "tool_use" and msg_.content[1].id == content_items[1].tool_use_id then if msg_.content[1].type == "tool_use" and msg_.content[1].id == content_items[1].tool_use_id then
tool_use = msg_ tool_use_msg = msg_
break break
end end
end end
end end
if tool_use then if tool_use_msg then
table.insert(contents, { table.insert(contents, {
role = "model", role = "model",
parts = { parts = {
{ text = Utils.tool_use_to_xml(tool_use.content[1]) }, { text = Utils.tool_use_to_xml(tool_use_msg.content[1]) },
}, },
}) })
role = "user" role = "user"
table.insert(parts, { table.insert(parts, {
text = "[" text = "The result of tool use " .. Utils.tool_use_to_xml(tool_use_msg.content[1]) .. " is:\n",
.. tool_use.content[1].name
.. " for '"
.. (tool_use.content[1].input.path or tool_use.content[1].input.rel_path or "")
.. "'] Result:",
}) })
table.insert(parts, { table.insert(parts, {
text = content_items[1].content, text = content_items[1].content,
@@ -189,6 +185,8 @@ function M.prepare_request_body(provider_instance, prompt_opts, provider_conf, r
local use_ReAct_prompt = provider_conf.use_ReAct_prompt == true local use_ReAct_prompt = provider_conf.use_ReAct_prompt == true
if use_ReAct_prompt then request_body.generationConfig.stopSequences = { "</tool_use>" } end
local disable_tools = provider_conf.disable_tools or false local disable_tools = provider_conf.disable_tools or false
if not use_ReAct_prompt and not disable_tools and prompt_opts.tools then if not use_ReAct_prompt and not disable_tools and prompt_opts.tools then

View File

@@ -3,7 +3,7 @@ local Config = require("avante.config")
local Clipboard = require("avante.clipboard") local Clipboard = require("avante.clipboard")
local Providers = require("avante.providers") local Providers = require("avante.providers")
local HistoryMessage = require("avante.history.message") local HistoryMessage = require("avante.history.message")
local ReActParser = require("avante.libs.ReAct_parser") local ReActParser = require("avante.libs.ReAct_parser2")
local JsonParser = require("avante.libs.jsonparser") local JsonParser = require("avante.libs.jsonparser")
local Prompts = require("avante.utils.prompts") local Prompts = require("avante.utils.prompts")
local LlmTools = require("avante.llm_tools") local LlmTools = require("avante.llm_tools")
@@ -130,24 +130,20 @@ function M:parse_messages(opts)
end end
if not provider_conf.disable_tools and use_ReAct_prompt then if not provider_conf.disable_tools and use_ReAct_prompt then
if msg.content[1].type == "tool_result" then if msg.content[1].type == "tool_result" then
local tool_use = nil local tool_use_msg = nil
for _, msg_ in ipairs(opts.messages) do for _, msg_ in ipairs(opts.messages) do
if type(msg_.content) == "table" and #msg_.content > 0 then if type(msg_.content) == "table" and #msg_.content > 0 then
if msg_.content[1].type == "tool_use" and msg_.content[1].id == msg.content[1].tool_use_id then if msg_.content[1].type == "tool_use" and msg_.content[1].id == msg.content[1].tool_use_id then
tool_use = msg_ tool_use_msg = msg_
break break
end end
end end
end end
if tool_use then if tool_use_msg then
msg.role = "user" msg.role = "user"
table.insert(content, { table.insert(content, {
type = "text", type = "text",
text = "[" text = "The result of tool use " .. Utils.tool_use_to_xml(tool_use_msg.content[1]) .. " is:\n",
.. tool_use.content[1].name
.. " for '"
.. (tool_use.content[1].input.path or tool_use.content[1].input.rel_path or "")
.. "'] Result:",
}) })
table.insert(content, { table.insert(content, {
type = "text", type = "text",
@@ -258,7 +254,6 @@ function M:add_text_message(ctx, text, state, opts)
end end
local cleaned_xml_content = table.concat(cleaned_xml_lines, "\n") local cleaned_xml_content = table.concat(cleaned_xml_lines, "\n")
local xml = ReActParser.parse(cleaned_xml_content) local xml = ReActParser.parse(cleaned_xml_content)
local has_tool_use = false
if xml and #xml > 0 then if xml and #xml > 0 then
local new_content_list = {} local new_content_list = {}
local xml_md_openned = false local xml_md_openned = false
@@ -293,42 +288,45 @@ function M:add_text_message(ctx, text, state, opts)
end end
end end
if next(input) ~= nil then if next(input) ~= nil then
has_tool_use = true
local msg_uuid = ctx.content_uuid .. "-" .. idx local msg_uuid = ctx.content_uuid .. "-" .. idx
local tool_use_id = msg_uuid local tool_use_id = msg_uuid
local tool_message_state = item.partial and "generating" or "generated"
local msg_ = HistoryMessage:new("assistant", { local msg_ = HistoryMessage:new("assistant", {
type = "tool_use", type = "tool_use",
name = item.tool_name, name = item.tool_name,
id = tool_use_id, id = tool_use_id,
input = input, input = input,
}, { }, {
state = state, state = tool_message_state,
uuid = msg_uuid, uuid = msg_uuid,
turn_id = ctx.turn_id, turn_id = ctx.turn_id,
}) })
msgs[#msgs + 1] = msg_ msgs[#msgs + 1] = msg_
ctx.tool_use_list = ctx.tool_use_list or {} ctx.tool_use_list = ctx.tool_use_list or {}
local input_json = type(input) == "string" and input or vim.json.encode(input)
local exists = false local exists = false
for _, tool_use in ipairs(ctx.tool_use_list) do for _, tool_use in ipairs(ctx.tool_use_list) do
if tool_use.id == tool_use_id then if tool_use.id == tool_use_id then
tool_use.input_json = input tool_use.input_json = input_json
exists = true exists = true
end end
end end
if not exists then if not exists then
ctx.tool_use_list[#ctx.tool_use_list + 1] = { ctx.tool_use_list[#ctx.tool_use_list + 1] = {
uuid = tool_use_id,
id = tool_use_id, id = tool_use_id,
name = item.tool_name, name = item.tool_name,
input_json = input, input_json = input_json,
state = "generating",
} }
end end
opts.on_stop({ reason = "tool_use", streaming_tool_use = item.partial })
end end
::continue:: ::continue::
end end
msg.message.content = table.concat(new_content_list, "\n") msg.message.content = table.concat(new_content_list, "\n"):gsub("\n+$", "\n")
end end
if opts.on_messages_add then opts.on_messages_add(msgs) end if opts.on_messages_add then opts.on_messages_add(msgs) end
if has_tool_use and state == "generating" then opts.on_stop({ reason = "tool_use", streaming_tool_use = true }) end
end end
function M:add_thinking_message(ctx, text, state, opts) function M:add_thinking_message(ctx, text, state, opts)
@@ -537,6 +535,9 @@ function M:parse_curl_args(prompt_opts)
Utils.debug("endpoint", provider_conf.endpoint) Utils.debug("endpoint", provider_conf.endpoint)
Utils.debug("model", provider_conf.model) Utils.debug("model", provider_conf.model)
local stop = nil
if use_ReAct_prompt then stop = { "</tool_use>" } end
return { return {
url = Utils.url_join(provider_conf.endpoint, "/chat/completions"), url = Utils.url_join(provider_conf.endpoint, "/chat/completions"),
proxy = provider_conf.proxy, proxy = provider_conf.proxy,
@@ -545,6 +546,7 @@ function M:parse_curl_args(prompt_opts)
body = vim.tbl_deep_extend("force", { body = vim.tbl_deep_extend("force", {
model = provider_conf.model, model = provider_conf.model,
messages = self:parse_messages(prompt_opts), messages = self:parse_messages(prompt_opts),
stop = stop,
stream = true, stream = true,
stream_options = not M.is_mistral(provider_conf.endpoint) and { stream_options = not M.is_mistral(provider_conf.endpoint) and {
include_usage = true, include_usage = true,

View File

@@ -3147,6 +3147,10 @@ function Sidebar:create_todos_container()
local total_count = #history.todos local total_count = #history.todos
local focused_idx = 1 local focused_idx = 1
local todos_content_lines = {} local todos_content_lines = {}
if type(history.todos) ~= "table" then
Utils.debug("Invalid todos type", history.todos)
history.todos = {}
end
for idx, todo in ipairs(history.todos) do for idx, todo in ipairs(history.todos) do
local status_content = "[ ]" local status_content = "[ ]"
if todo.status == "done" then if todo.status == "done" then
@@ -3163,7 +3167,7 @@ function Sidebar:create_todos_container()
local todos_buf = api.nvim_win_get_buf(self.containers.todos.winid) local todos_buf = api.nvim_win_get_buf(self.containers.todos.winid)
Utils.unlock_buf(todos_buf) Utils.unlock_buf(todos_buf)
api.nvim_buf_set_lines(todos_buf, 0, -1, false, todos_content_lines) api.nvim_buf_set_lines(todos_buf, 0, -1, false, todos_content_lines)
api.nvim_win_set_cursor(self.containers.todos.winid, { focused_idx, 0 }) pcall(function() api.nvim_win_set_cursor(self.containers.todos.winid, { focused_idx, 0 }) end)
Utils.lock_buf(todos_buf) Utils.lock_buf(todos_buf)
self:render_header( self:render_header(
self.containers.todos.winid, self.containers.todos.winid,

View File

@@ -10,6 +10,82 @@
{%- include "_task-guidelines.avanterules" %} {%- include "_task-guidelines.avanterules" %}
{% if not enable_fastapply -%}
====
EDITING FILES
You have access to two tools for working with files: **write_to_file** and **replace_in_file**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications.
# write_to_file
## Purpose
- Create a new file, or overwrite the entire contents of an existing file.
## When to Use
- Initial file creation, such as when scaffolding a new project.
- Overwriting large boilerplate files where you want to replace the entire content at once.
- When the complexity or number of changes would make replace_in_file unwieldy or error-prone.
- When you need to completely restructure a file's content or change its fundamental organization.
## Important Considerations
- Using write_to_file requires providing the file's complete final content.
- If you only need to make small changes to an existing file, consider using replace_in_file instead to avoid unnecessarily rewriting the entire file.
- While write_to_file should not be your default choice, don't hesitate to use it when the situation truly calls for it.
# replace_in_file
## Purpose
- Make targeted edits to specific parts of an existing file without overwriting the entire file.
## When to Use
- Small, localized changes like updating a few lines, function implementations, changing variable names, modifying a section of text, etc.
- Targeted improvements where only specific portions of the file's content needs to be altered.
- Especially useful for long files where much of the file will remain unchanged.
## Advantages
- More efficient for minor edits, since you don't need to supply the entire file content.
- Reduces the chance of errors that can occur when overwriting large files.
# Choosing the Appropriate Tool
- **Default to replace_in_file** for most changes. It's the safer, more precise option that minimizes potential issues.
- **Use write_to_file** when:
- Creating new files
- The changes are so extensive that using replace_in_file would be more complex or risky
- You need to completely reorganize or restructure a file
- The file is relatively small and the changes affect most of its content
- You're generating boilerplate or template files
# Auto-formatting Considerations
- After using either write_to_file or replace_in_file, the user's editor may automatically format the file
- This auto-formatting may modify the file contents, for example:
- Breaking single lines into multiple lines
- Adjusting indentation to match project style (e.g. 2 spaces vs 4 spaces vs tabs)
- Converting single quotes to double quotes (or vice versa based on project preferences)
- Organizing imports (e.g. sorting, grouping by type)
- Adding/removing trailing commas in objects and arrays
- Enforcing consistent brace style (e.g. same-line vs new-line)
- Standardizing semicolon usage (adding or removing based on style)
- The write_to_file and replace_in_file tool responses will include the final state of the file after any auto-formatting
- Use this final state as your reference point for any subsequent edits. This is ESPECIALLY important when crafting SEARCH blocks for replace_in_file which require the content to match what's in the file exactly.
# Workflow Tips
1. Before editing, assess the scope of your changes and decide which tool to use.
2. For targeted edits, apply replace_in_file with carefully crafted SEARCH/REPLACE blocks. If you need multiple changes, you can stack multiple SEARCH/REPLACE blocks within a single replace_in_file call.
3. For major overhauls or initial file creation, rely on write_to_file.
4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.
{% endif %}
==== ====
RULES RULES

View File

@@ -1579,11 +1579,11 @@ end
---@param tool_use AvanteLLMToolUse ---@param tool_use AvanteLLMToolUse
function M.tool_use_to_xml(tool_use) function M.tool_use_to_xml(tool_use)
local xml = string.format("<tool_use>\n<%s>\n", tool_use.name) local tool_use_json = vim.json.encode({
for k, v in pairs(tool_use.input or {}) do name = tool_use.name,
xml = xml .. string.format("<%s>%s</%s>\n", k, tostring(v), k) input = tool_use.input,
end })
xml = xml .. "</" .. tool_use.name .. ">\n</tool_use>" local xml = string.format("<tool_use>%s</tool_use>", tool_use_json)
return xml return xml
end end

View File

@@ -1,3 +1,4 @@
local Config = require("avante.config")
local M = {} local M = {}
---@param provider_conf AvanteDefaultBaseProvider ---@param provider_conf AvanteDefaultBaseProvider
@@ -16,35 +17,37 @@ You have access to a set of tools that are executed upon the user's approval. Yo
# Tool Use Formatting # Tool Use Formatting
Tool use is formatted using XML-style tags. Each tool use is wrapped in a <tool_use> tag. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: Tool use is formatted using XML-style tags. Each tool use is wrapped in a <tool_use> tag. The tool use content is a valid JSON with tool name and tool input. Here's the structure:
<tool_use> <tool_use>
<tool_name> {
<parameter1_name>value1</parameter1_name> "name": "tool_name",
<parameter2_name>value2</parameter2_name> "input": {
... "parameter1_name": "value1",
</tool_name> "parameter2_name": "value2",
...
}
}
</tool_use> </tool_use>
For example: For example:
<tool_use> <tool_use>
<attempt_completion> {"name": "attempt_completion", "input": {"result": "I have completed the task..."}}
<result>
I have completed the task...
</result>
</attempt_completion>
</tool_use> </tool_use>
<tool_use> <tool_use>
<bash> {"name": "bash", "input": {"path": "./src", "command": "npm run dev"}}
<path>./src</path>
<command>npm run dev</command>
</bash>
</tool_use> </tool_use>
ALWAYS ADHERE TO this format for the tool use to ensure proper parsing and execution. ALWAYS ADHERE TO this format for the tool use to ensure proper parsing and execution.
## RULES
- When outputting the JSON for tool_use, you MUST first output the "name" field and then the "input" field.
- The value of "input" MUST be VALID JSON.
- If the "input" JSON object contains a "path" field, you MUST output the "path" field before any other fields.
## OUTPUT FORMAT ## OUTPUT FORMAT
Please remember you are not allowed to use any format related to function calling or fc or tool_code. Please remember you are not allowed to use any format related to function calling or fc or tool_code.
@@ -71,15 +74,23 @@ Parameters:
field.get_description and field.get_description() or (field.description or "") field.get_description and field.get_description() or (field.description or "")
) )
end end
if field.choices then
tool_prompt = tool_prompt .. " - Choices: "
for i, choice in ipairs(field.choices) do
tool_prompt = tool_prompt .. string.format("%s", choice)
if i ~= #field.choices then tool_prompt = tool_prompt .. ", " end
end
tool_prompt = tool_prompt .. "\n"
end
end end
if tool.param.usage then if tool.param.usage then
tool_prompt = tool_prompt tool_prompt = tool_prompt .. "Usage:\n<tool_use>"
.. ("Usage:\n<tool_use>\n<{{name}}>\n"):gsub("{{([%w_]+)}}", function(name) return tool[name] end) local tool_use = {
for k, v in pairs(tool.param.usage) do name = tool.name,
tool_prompt = tool_prompt .. "<" .. k .. ">" .. tostring(v) .. "</" .. k .. ">\n" input = tool.param.usage,
end }
tool_prompt = tool_prompt local tool_use_json = vim.json.encode(tool_use)
.. ("</{{name}}>\n</tool_use>\n"):gsub("{{([%w_]+)}}", function(name) return tool[name] end) tool_prompt = tool_prompt .. tool_use_json .. "</tool_use>\n"
end end
tools_prompts = tools_prompts .. tool_prompt .. "\n" tools_prompts = tools_prompts .. tool_prompt .. "\n"
end end
@@ -92,119 +103,46 @@ Parameters:
## Example 1: Requesting to execute a command ## Example 1: Requesting to execute a command
<tool_use> <tool_use>{"name": "bash", "input": {"path": "./src", "command": "npm run dev"}}</tool_use>
<bash> ]]
<path>./src</path>
<command>npm run dev</command>
</bash>
</tool_use>
if Config.behaviour.enable_fastapply then
system_prompt = system_prompt
.. [[
## Example 2: Requesting to create a new file ## Example 2: Requesting to create a new file
<tool_use> <tool_use>{"name": "edit_file", "input": {"path": "src/frontend-config.json", "instructions": "write the following content to the file", "code_edit": "// ... existing code ...\nFIRST_EDIT\n// ... existing code ...\nSECOND_EDIT\n// ... existing code ...\nTHIRD_EDIT\n// ... existing code ...\n\n"}}</tool_use>
<write_to_file>
<path>src/frontend-config.json</path>
<content>
{
"apiEndpoint": "https://api.example.com",
"theme": {
"primaryColor": "#007bff",
"secondaryColor": "#6c757d",
"fontFamily": "Arial, sans-serif"
},
"features": {
"darkMode": true,
"notifications": true,
"analytics": false
},
"version": "1.0.0"
}
</content>
</write_to_file>
</tool_use>
## Example 3: Requesting to make targeted edits to a file ## Example 3: Requesting to make targeted edits to a file
<tool_use> <tool_use>{"name": "edit_file", "input": {"path": "src/frontend-config.json", "instructions": "write the following content to the file", "code_edit": "// ... existing code ...\nFIRST_EDIT\n// ... existing code ...\nSECOND_EDIT\n// ... existing code ...\nTHIRD_EDIT\n// ... existing code ...\n\n"}}</tool_use>
<replace_in_file> ]]
<path>src/components/App.tsx</path> else
<diff> system_prompt = system_prompt
------- SEARCH .. [[
import React from 'react'; ## Example 2: Requesting to create a new file
=======
import React, { useState } from 'react';
+++++++ REPLACE
------- SEARCH <tool_use>{"name": "write_to_file", "input": {"path": "src/frontend-config.json", "the_content": "{\n \"apiEndpoint\": \"https://api.example.com\",\n \"theme\": {\n \"primaryColor\": \"#007bff\",\n \"secondaryColor\": \"#6c757d\",\n \"fontFamily\": \"Arial, sans-serif\"\n },\n \"features\": {\n \"darkMode\": true,\n \"notifications\": true,\n \"analytics\": false\n },\n \"version\": \"1.0.0\"\n}"}}</tool_use>
function handleSubmit() {
saveData();
setLoading(false);
}
======= ## Example 3: Requesting to make targeted edits to a file
+++++++ REPLACE
------- SEARCH <tool_use>{"name": "replace_in_file", "input": {"path": "src/components/App.tsx", "the_diff": "------- SEARCH\nimport React from 'react';\n=======\nimport React, { useState } from 'react';\n+++++++ REPLACE\n\n------- SEARCH\nfunction handleSubmit() {\n saveData();\n setLoading(false);\n}\n\n=======\n+++++++ REPLACE\n\n------- SEARCH\nreturn (\n <div>\n=======\nfunction handleSubmit() {\n saveData();\n setLoading(false);\n}\n\nreturn (\n <div>\n+++++++ REPLACE\n"}}}</tool_use>
return ( ]]
<div> end
=======
function handleSubmit() {
saveData();
setLoading(false);
}
return (
<div>
+++++++ REPLACE
</diff>
</replace_in_file>
</tool_use>
system_prompt = system_prompt
.. [[
## Example 4: Complete current task ## Example 4: Complete current task
<tool_use> <tool_use>{"name": "attempt_completion", "input": {"result": "I've successfully created the requested React component with the following features:\n- Responsive layout\n- Dark/light mode toggle\n- Form validation\n- API integration"}}</tool_use>
<attempt_completion>
<result>
I've successfully created the requested React component with the following features:
- Responsive layout
- Dark/light mode toggle
- Form validation
- API integration
</result>
</attempt_completion>
</tool_use>
## Example 5: Add todos ## Example 5: Add todos
<tool_use> <tool_use>{"name": "add_todos", "input": {"todos": [{"id": "1", "content": "Implement a responsive layout", "status": "todo", "priority": "low"}, {"id": "2", "content": "Add dark/light mode toggle", "status": "todo", "priority": "medium"}]}}</tool_use>
<add_todos>
<todos>
[
{
"id": "1",
"content": "Implement a responsive layout",
"status": "todo",
"priority": "low"
},
{
"id": "2",
"content": "Add dark/light mode toggle",
"status": "todo",
"priority": "medium"
},
]
</todos>
</add_todos>
</tool_use>
## Example 6: Update todo status ## Example 6: Update todo status
<tool_use> <tool_use>{"name": "update_todo_status", "input": {"id": "1", "status": "done"}}</tool_use>
<update_todo_status>
<id>1</id>
<status>done</status>
</update_todo_status>
</tool_use>
]] ]]
end end
return system_prompt return system_prompt