fix: react prompts (#2537)
This commit is contained in:
@@ -44,6 +44,7 @@ struct TemplateContext {
|
||||
model_name: Option<String>,
|
||||
memory: 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
|
||||
@@ -72,6 +73,7 @@ fn render(state: &State, template: &str, context: TemplateContext) -> LuaResult<
|
||||
model_name => context.model_name,
|
||||
memory => context.memory,
|
||||
todos => context.todos,
|
||||
enable_fastapply => context.enable_fastapply,
|
||||
})
|
||||
.map_err(LuaError::external)
|
||||
.unwrap())
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
-- Helper function to parse a parameter tag like <param_name>value</param_name>
|
||||
|
||||
261
lua/avante/libs/ReAct_parser2.lua
Normal file
261
lua/avante/libs/ReAct_parser2.lua
Normal 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
|
||||
@@ -307,6 +307,7 @@ function M.generate_prompts(opts)
|
||||
system_info = system_info,
|
||||
model_name = provider.model or "unknown",
|
||||
memory = opts.memory,
|
||||
enable_fastapply = Config.behaviour.enable_fastapply,
|
||||
}
|
||||
|
||||
-- Removed the original todos processing logic, now handled in context_messages
|
||||
@@ -804,7 +805,7 @@ function M._stream(opts)
|
||||
return
|
||||
end
|
||||
end
|
||||
M._stream(new_opts)
|
||||
if not streaming_tool_use then M._stream(new_opts) end
|
||||
return
|
||||
end
|
||||
local partial_tool_use = tool_uses[tool_use_index]
|
||||
@@ -849,15 +850,14 @@ function M._stream(opts)
|
||||
streaming = partial_tool_use.state == "generating",
|
||||
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 not is_edit_tool_use and not support_streaming then return end
|
||||
if type(partial_tool_use.input) == "table" then
|
||||
LLMTools.process_tool_use(prompt_opts.tools, partial_tool_use, tool_use_opts)
|
||||
end
|
||||
return
|
||||
else
|
||||
if streaming_tool_use then return end
|
||||
end
|
||||
if streaming_tool_use then return end
|
||||
partial_tool_use_message.is_calling = true
|
||||
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
|
||||
@@ -887,12 +887,11 @@ function M._stream(opts)
|
||||
local completed_attempt_completion_tool_use = nil
|
||||
for idx = #history_messages, 1, -1 do
|
||||
local message = history_messages[idx]
|
||||
if not message.is_user_submission then
|
||||
local use = History.Helpers.get_tool_use_data(message)
|
||||
if use and use.name == "attempt_completion" then
|
||||
completed_attempt_completion_tool_use = message
|
||||
break
|
||||
end
|
||||
if message.is_user_submission then break end
|
||||
local use = History.Helpers.get_tool_use_data(message)
|
||||
if use and use.name == "attempt_completion" then
|
||||
completed_attempt_completion_tool_use = message
|
||||
break
|
||||
end
|
||||
end
|
||||
local unfinished_todos = {}
|
||||
@@ -915,7 +914,7 @@ function M._stream(opts)
|
||||
if #unfinished_todos > 0 then
|
||||
message = History.Message:new(
|
||||
"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,
|
||||
}
|
||||
@@ -923,7 +922,7 @@ function M._stream(opts)
|
||||
else
|
||||
message = History.Message:new(
|
||||
"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,
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ M.returns = {
|
||||
|
||||
---@type AvanteLLMToolFunc<{ path: string, instructions: string, code_edit: string }>
|
||||
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
|
||||
if not on_complete then return false, "on_complete not provided" end
|
||||
local provider = Providers["morph"]
|
||||
|
||||
@@ -1274,7 +1274,7 @@ function M.process_tool_use(tools, tool_use, opts)
|
||||
else
|
||||
---@type 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]
|
||||
end
|
||||
local input_json = tool_use.input
|
||||
|
||||
@@ -50,7 +50,7 @@ function M.func(input, opts)
|
||||
local todos = sidebar.chat_history.todos
|
||||
if not todos or #todos == 0 then return false, "No todos found" end
|
||||
for _, todo in ipairs(todos) do
|
||||
if todo.id == input.id then
|
||||
if tostring(todo.id) == tostring(input.id) then
|
||||
todo.status = input.status
|
||||
break
|
||||
end
|
||||
|
||||
@@ -10,6 +10,8 @@ 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."
|
||||
|
||||
M.support_streaming = false
|
||||
|
||||
function M.enabled()
|
||||
return require("avante.config").mode == "agentic" and not require("avante.config").behaviour.enable_fastapply
|
||||
end
|
||||
|
||||
@@ -113,29 +113,25 @@ function M:parse_messages(opts)
|
||||
end
|
||||
if not provider_conf.disable_tools and use_ReAct_prompt 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
|
||||
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
|
||||
tool_use = msg_
|
||||
tool_use_msg = msg_
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if tool_use then
|
||||
if tool_use_msg then
|
||||
table.insert(contents, {
|
||||
role = "model",
|
||||
parts = {
|
||||
{ text = Utils.tool_use_to_xml(tool_use.content[1]) },
|
||||
{ text = Utils.tool_use_to_xml(tool_use_msg.content[1]) },
|
||||
},
|
||||
})
|
||||
role = "user"
|
||||
table.insert(parts, {
|
||||
text = "["
|
||||
.. tool_use.content[1].name
|
||||
.. " for '"
|
||||
.. (tool_use.content[1].input.path or tool_use.content[1].input.rel_path or "")
|
||||
.. "'] Result:",
|
||||
text = "The result of tool use " .. Utils.tool_use_to_xml(tool_use_msg.content[1]) .. " is:\n",
|
||||
})
|
||||
table.insert(parts, {
|
||||
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
|
||||
|
||||
if use_ReAct_prompt then request_body.generationConfig.stopSequences = { "</tool_use>" } end
|
||||
|
||||
local disable_tools = provider_conf.disable_tools or false
|
||||
|
||||
if not use_ReAct_prompt and not disable_tools and prompt_opts.tools then
|
||||
|
||||
@@ -3,7 +3,7 @@ local Config = require("avante.config")
|
||||
local Clipboard = require("avante.clipboard")
|
||||
local Providers = require("avante.providers")
|
||||
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 Prompts = require("avante.utils.prompts")
|
||||
local LlmTools = require("avante.llm_tools")
|
||||
@@ -130,24 +130,20 @@ function M:parse_messages(opts)
|
||||
end
|
||||
if not provider_conf.disable_tools and use_ReAct_prompt 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
|
||||
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
|
||||
tool_use = msg_
|
||||
tool_use_msg = msg_
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if tool_use then
|
||||
if tool_use_msg then
|
||||
msg.role = "user"
|
||||
table.insert(content, {
|
||||
type = "text",
|
||||
text = "["
|
||||
.. tool_use.content[1].name
|
||||
.. " for '"
|
||||
.. (tool_use.content[1].input.path or tool_use.content[1].input.rel_path or "")
|
||||
.. "'] Result:",
|
||||
text = "The result of tool use " .. Utils.tool_use_to_xml(tool_use_msg.content[1]) .. " is:\n",
|
||||
})
|
||||
table.insert(content, {
|
||||
type = "text",
|
||||
@@ -258,7 +254,6 @@ function M:add_text_message(ctx, text, state, opts)
|
||||
end
|
||||
local cleaned_xml_content = table.concat(cleaned_xml_lines, "\n")
|
||||
local xml = ReActParser.parse(cleaned_xml_content)
|
||||
local has_tool_use = false
|
||||
if xml and #xml > 0 then
|
||||
local new_content_list = {}
|
||||
local xml_md_openned = false
|
||||
@@ -293,42 +288,45 @@ function M:add_text_message(ctx, text, state, opts)
|
||||
end
|
||||
end
|
||||
if next(input) ~= nil then
|
||||
has_tool_use = true
|
||||
local msg_uuid = ctx.content_uuid .. "-" .. idx
|
||||
local tool_use_id = msg_uuid
|
||||
local tool_message_state = item.partial and "generating" or "generated"
|
||||
local msg_ = HistoryMessage:new("assistant", {
|
||||
type = "tool_use",
|
||||
name = item.tool_name,
|
||||
id = tool_use_id,
|
||||
input = input,
|
||||
}, {
|
||||
state = state,
|
||||
state = tool_message_state,
|
||||
uuid = msg_uuid,
|
||||
turn_id = ctx.turn_id,
|
||||
})
|
||||
msgs[#msgs + 1] = msg_
|
||||
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
|
||||
for _, tool_use in ipairs(ctx.tool_use_list) do
|
||||
if tool_use.id == tool_use_id then
|
||||
tool_use.input_json = input
|
||||
tool_use.input_json = input_json
|
||||
exists = true
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
ctx.tool_use_list[#ctx.tool_use_list + 1] = {
|
||||
uuid = tool_use_id,
|
||||
id = tool_use_id,
|
||||
name = item.tool_name,
|
||||
input_json = input,
|
||||
input_json = input_json,
|
||||
state = "generating",
|
||||
}
|
||||
end
|
||||
opts.on_stop({ reason = "tool_use", streaming_tool_use = item.partial })
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
msg.message.content = table.concat(new_content_list, "\n")
|
||||
msg.message.content = table.concat(new_content_list, "\n"):gsub("\n+$", "\n")
|
||||
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
|
||||
|
||||
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("model", provider_conf.model)
|
||||
|
||||
local stop = nil
|
||||
if use_ReAct_prompt then stop = { "</tool_use>" } end
|
||||
|
||||
return {
|
||||
url = Utils.url_join(provider_conf.endpoint, "/chat/completions"),
|
||||
proxy = provider_conf.proxy,
|
||||
@@ -545,6 +546,7 @@ function M:parse_curl_args(prompt_opts)
|
||||
body = vim.tbl_deep_extend("force", {
|
||||
model = provider_conf.model,
|
||||
messages = self:parse_messages(prompt_opts),
|
||||
stop = stop,
|
||||
stream = true,
|
||||
stream_options = not M.is_mistral(provider_conf.endpoint) and {
|
||||
include_usage = true,
|
||||
|
||||
@@ -3147,6 +3147,10 @@ function Sidebar:create_todos_container()
|
||||
local total_count = #history.todos
|
||||
local focused_idx = 1
|
||||
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
|
||||
local status_content = "[ ]"
|
||||
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)
|
||||
Utils.unlock_buf(todos_buf)
|
||||
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)
|
||||
self:render_header(
|
||||
self.containers.todos.winid,
|
||||
|
||||
@@ -10,6 +10,82 @@
|
||||
|
||||
{%- 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
|
||||
|
||||
@@ -1579,11 +1579,11 @@ end
|
||||
|
||||
---@param tool_use AvanteLLMToolUse
|
||||
function M.tool_use_to_xml(tool_use)
|
||||
local xml = string.format("<tool_use>\n<%s>\n", tool_use.name)
|
||||
for k, v in pairs(tool_use.input or {}) do
|
||||
xml = xml .. string.format("<%s>%s</%s>\n", k, tostring(v), k)
|
||||
end
|
||||
xml = xml .. "</" .. tool_use.name .. ">\n</tool_use>"
|
||||
local tool_use_json = vim.json.encode({
|
||||
name = tool_use.name,
|
||||
input = tool_use.input,
|
||||
})
|
||||
local xml = string.format("<tool_use>%s</tool_use>", tool_use_json)
|
||||
return xml
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
local Config = require("avante.config")
|
||||
local M = {}
|
||||
|
||||
---@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 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_name>
|
||||
<parameter1_name>value1</parameter1_name>
|
||||
<parameter2_name>value2</parameter2_name>
|
||||
...
|
||||
</tool_name>
|
||||
{
|
||||
"name": "tool_name",
|
||||
"input": {
|
||||
"parameter1_name": "value1",
|
||||
"parameter2_name": "value2",
|
||||
...
|
||||
}
|
||||
}
|
||||
</tool_use>
|
||||
|
||||
For example:
|
||||
|
||||
<tool_use>
|
||||
<attempt_completion>
|
||||
<result>
|
||||
I have completed the task...
|
||||
</result>
|
||||
</attempt_completion>
|
||||
{"name": "attempt_completion", "input": {"result": "I have completed the task..."}}
|
||||
</tool_use>
|
||||
|
||||
|
||||
<tool_use>
|
||||
<bash>
|
||||
<path>./src</path>
|
||||
<command>npm run dev</command>
|
||||
</bash>
|
||||
{"name": "bash", "input": {"path": "./src", "command": "npm run dev"}}
|
||||
</tool_use>
|
||||
|
||||
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
|
||||
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 "")
|
||||
)
|
||||
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
|
||||
if tool.param.usage then
|
||||
tool_prompt = tool_prompt
|
||||
.. ("Usage:\n<tool_use>\n<{{name}}>\n"):gsub("{{([%w_]+)}}", function(name) return tool[name] end)
|
||||
for k, v in pairs(tool.param.usage) do
|
||||
tool_prompt = tool_prompt .. "<" .. k .. ">" .. tostring(v) .. "</" .. k .. ">\n"
|
||||
end
|
||||
tool_prompt = tool_prompt
|
||||
.. ("</{{name}}>\n</tool_use>\n"):gsub("{{([%w_]+)}}", function(name) return tool[name] end)
|
||||
tool_prompt = tool_prompt .. "Usage:\n<tool_use>"
|
||||
local tool_use = {
|
||||
name = tool.name,
|
||||
input = tool.param.usage,
|
||||
}
|
||||
local tool_use_json = vim.json.encode(tool_use)
|
||||
tool_prompt = tool_prompt .. tool_use_json .. "</tool_use>\n"
|
||||
end
|
||||
tools_prompts = tools_prompts .. tool_prompt .. "\n"
|
||||
end
|
||||
@@ -92,119 +103,46 @@ Parameters:
|
||||
|
||||
## Example 1: Requesting to execute a command
|
||||
|
||||
<tool_use>
|
||||
<bash>
|
||||
<path>./src</path>
|
||||
<command>npm run dev</command>
|
||||
</bash>
|
||||
</tool_use>
|
||||
<tool_use>{"name": "bash", "input": {"path": "./src", "command": "npm run dev"}}</tool_use>
|
||||
]]
|
||||
|
||||
if Config.behaviour.enable_fastapply then
|
||||
system_prompt = system_prompt
|
||||
.. [[
|
||||
## Example 2: Requesting to create a new file
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
## Example 3: Requesting to make targeted edits to a file
|
||||
|
||||
<tool_use>
|
||||
<replace_in_file>
|
||||
<path>src/components/App.tsx</path>
|
||||
<diff>
|
||||
------- SEARCH
|
||||
import React from 'react';
|
||||
=======
|
||||
import React, { useState } from 'react';
|
||||
+++++++ REPLACE
|
||||
<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>
|
||||
]]
|
||||
else
|
||||
system_prompt = system_prompt
|
||||
.. [[
|
||||
## Example 2: Requesting to create a new file
|
||||
|
||||
------- SEARCH
|
||||
function handleSubmit() {
|
||||
saveData();
|
||||
setLoading(false);
|
||||
}
|
||||
<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>
|
||||
|
||||
=======
|
||||
+++++++ REPLACE
|
||||
## Example 3: Requesting to make targeted edits to a file
|
||||
|
||||
------- SEARCH
|
||||
return (
|
||||
<div>
|
||||
=======
|
||||
function handleSubmit() {
|
||||
saveData();
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
+++++++ REPLACE
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
</tool_use>
|
||||
<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>
|
||||
]]
|
||||
end
|
||||
|
||||
system_prompt = system_prompt
|
||||
.. [[
|
||||
## Example 4: Complete current task
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
## Example 5: Add todos
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
## Example 6: Update todo status
|
||||
|
||||
<tool_use>
|
||||
<update_todo_status>
|
||||
<id>1</id>
|
||||
<status>done</status>
|
||||
</update_todo_status>
|
||||
</tool_use>
|
||||
<tool_use>{"name": "update_todo_status", "input": {"id": "1", "status": "done"}}</tool_use>
|
||||
]]
|
||||
end
|
||||
return system_prompt
|
||||
|
||||
Reference in New Issue
Block a user