feat: ReAct tool calling (#2104)

This commit is contained in:
yetone
2025-05-31 08:53:34 +08:00
committed by GitHub
parent 22418bff8b
commit bc403ddcbf
25 changed files with 1358 additions and 188 deletions

View File

@@ -32,6 +32,10 @@ M.param = {
optional = true,
},
},
usage = {
result = "The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.",
command = "A CLI command to execute to show a live demo of the result to the user. For example, use `open index.html` to display a created html website, or `open localhost:3000` to display a locally running development server. But DO NOT use commands like `echo` or `cat` that merely print text. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -183,7 +183,7 @@ M.param = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory, as cwd",
type = "string",
},
@@ -193,6 +193,10 @@ M.param = {
type = "string",
},
},
usage = {
path = "Relative path to the project directory, as cwd",
command = "Command to run",
},
}
---@type AvanteLLMToolReturn[]
@@ -210,9 +214,9 @@ M.returns = {
},
}
---@type AvanteLLMToolFunc<{ rel_path: string, command: string }>
---@type AvanteLLMToolFunc<{ path: string, command: string }>
function M.func(opts, on_log, on_complete, session_ctx)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "Path not found: " .. abs_path end
if on_log then on_log("command: " .. opts.command) end

View File

@@ -49,6 +49,9 @@ M.param = {
},
},
required = { "prompt" },
usage = {
prompt = "The task for the agent to perform",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -19,6 +19,9 @@ M.param = {
type = "string",
},
},
usage = {
path = "The path to the file in the current project scope",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -18,11 +18,15 @@ M.param = {
type = "string",
},
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory, as cwd",
type = "string",
},
},
usage = {
pattern = "Glob pattern",
path = "Relative path to the project directory, as cwd",
},
}
---@type AvanteLLMToolReturn[]
@@ -40,9 +44,9 @@ M.returns = {
},
}
---@type AvanteLLMToolFunc<{ rel_path: string, pattern: string }>
---@type AvanteLLMToolFunc<{ path: string, pattern: string }>
function M.func(opts, on_log, on_complete, session_ctx)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return "", "No permission to access path: " .. abs_path end
if on_log then on_log("path: " .. abs_path) end
if on_log then on_log("pattern: " .. opts.pattern) end

View File

@@ -15,7 +15,7 @@ M.param = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory",
type = "string",
},
@@ -44,6 +44,13 @@ M.param = {
optional = true,
},
},
usage = {
path = "Relative path to the project directory",
query = "Query to search for",
case_sensitive = "Whether to search case sensitively",
include_pattern = "Glob pattern to include files",
exclude_pattern = "Glob pattern to exclude files",
},
}
---@type AvanteLLMToolReturn[]
@@ -61,9 +68,9 @@ M.returns = {
},
}
---@type AvanteLLMToolFunc<{ rel_path: string, query: string, case_sensitive?: boolean, include_pattern?: string, exclude_pattern?: string }>
---@type AvanteLLMToolFunc<{ path: string, query: string, case_sensitive?: boolean, include_pattern?: string, exclude_pattern?: string }>
function M.func(opts, on_log)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return "", "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return "", "No such file or directory: " .. abs_path end

View File

@@ -7,10 +7,10 @@ local Helpers = require("avante.llm_tools.helpers")
local M = {}
---@type AvanteLLMToolFunc<{ rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string }>
function M.read_file_toplevel_symbols(opts, on_log)
local RepoMap = require("avante.repo_map")
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return "", "No permission to access path: " .. abs_path end
if on_log then on_log("path: " .. abs_path) end
if not Path:new(abs_path):exists() then return "", "File does not exists: " .. abs_path end
@@ -121,13 +121,13 @@ function M.write_global_file(opts, on_log, on_complete)
end)
end
---@type AvanteLLMToolFunc<{ rel_path: string, new_rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string, new_path: string }>
function M.rename_file(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end
if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end
local new_abs_path = Helpers.get_abs_path(opts.new_rel_path)
local new_abs_path = Helpers.get_abs_path(opts.new_path)
if on_log then on_log(abs_path .. " -> " .. new_abs_path) end
if not Helpers.has_permission_to_access(new_abs_path) then
return false, "No permission to access path: " .. new_abs_path
@@ -147,13 +147,13 @@ function M.rename_file(opts, on_log, on_complete)
)
end
---@type AvanteLLMToolFunc<{ rel_path: string, new_rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string, new_path: string }>
function M.copy_file(opts, on_log)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end
if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end
local new_abs_path = Helpers.get_abs_path(opts.new_rel_path)
local new_abs_path = Helpers.get_abs_path(opts.new_path)
if not Helpers.has_permission_to_access(new_abs_path) then
return false, "No permission to access path: " .. new_abs_path
end
@@ -163,9 +163,9 @@ function M.copy_file(opts, on_log)
return true, nil
end
---@type AvanteLLMToolFunc<{ rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string }>
function M.delete_file(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "File not found: " .. abs_path end
if not Path:new(abs_path):is_file() then return false, "Path is not a file: " .. abs_path end
@@ -181,9 +181,9 @@ function M.delete_file(opts, on_log, on_complete)
end)
end
---@type AvanteLLMToolFunc<{ rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string }>
function M.create_dir(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if Path:new(abs_path):exists() then return false, "Directory already exists: " .. abs_path end
if not on_complete then return false, "on_complete not provided" end
@@ -198,13 +198,13 @@ function M.create_dir(opts, on_log, on_complete)
end)
end
---@type AvanteLLMToolFunc<{ rel_path: string, new_rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string, new_path: string }>
function M.rename_dir(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "Directory not found: " .. abs_path end
if not Path:new(abs_path):is_dir() then return false, "Path is not a directory: " .. abs_path end
local new_abs_path = Helpers.get_abs_path(opts.new_rel_path)
local new_abs_path = Helpers.get_abs_path(opts.new_path)
if not Helpers.has_permission_to_access(new_abs_path) then
return false, "No permission to access path: " .. new_abs_path
end
@@ -224,9 +224,9 @@ function M.rename_dir(opts, on_log, on_complete)
)
end
---@type AvanteLLMToolFunc<{ rel_path: string }>
---@type AvanteLLMToolFunc<{ path: string }>
function M.delete_dir(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return false, "Directory not found: " .. abs_path end
if not Path:new(abs_path):is_dir() then return false, "Path is not a directory: " .. abs_path end
@@ -552,9 +552,9 @@ function M.rag_search(opts, on_log, on_complete)
)
end
---@type AvanteLLMToolFunc<{ code: string, rel_path: string, container_image?: string }>
---@type AvanteLLMToolFunc<{ code: string, path: string, container_image?: string }>
function M.python(opts, on_log, on_complete)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return nil, "No permission to access path: " .. abs_path end
if not Path:new(abs_path):exists() then return nil, "Path not found: " .. abs_path end
if on_log then on_log("cwd: " .. abs_path) end
@@ -634,6 +634,18 @@ function M.get_tools(user_input, history_messages)
:totable()
end
function M.get_tool_names()
local custom_tools = Config.custom_tools
if type(custom_tools) == "function" then custom_tools = custom_tools() end
---@type AvanteLLMTool[]
local unfiltered_tools = vim.list_extend(vim.list_extend({}, M._tools), custom_tools)
local tool_names = {}
for _, tool in ipairs(unfiltered_tools) do
table.insert(tool_names, tool.name)
end
return tool_names
end
---@type AvanteLLMTool[]
M._tools = {
require("avante.llm_tools.replace_in_file"),
@@ -652,6 +664,9 @@ M._tools = {
type = "string",
},
},
usage = {
query = "Query to search",
},
},
returns = {
{
@@ -679,11 +694,15 @@ M._tools = {
type = "string",
},
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory, as cwd",
type = "string",
},
},
usage = {
code = "Python code to run",
path = "Relative path to the project directory, as cwd",
},
},
returns = {
{
@@ -711,6 +730,9 @@ M._tools = {
type = "string",
},
},
usage = {
scope = "Scope for the git diff (e.g. specific files or directories)",
},
},
returns = {
{
@@ -744,6 +766,10 @@ M._tools = {
optional = true,
},
},
usage = {
message = "Commit message to use",
scope = "Scope for staging files (e.g. specific files or directories)",
},
},
returns = {
{
@@ -768,11 +794,14 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the file in current project scope",
type = "string",
},
},
usage = {
path = "Relative path to the file in current project scope",
},
},
returns = {
{
@@ -790,7 +819,7 @@ M._tools = {
},
require("avante.llm_tools.str_replace"),
require("avante.llm_tools.view"),
require("avante.llm_tools.create"),
require("avante.llm_tools.write_to_file"),
require("avante.llm_tools.insert"),
require("avante.llm_tools.undo_edit"),
{
@@ -820,6 +849,9 @@ M._tools = {
type = "string",
},
},
usage = {
abs_path = "Absolute path to the file in global scope",
},
},
returns = {
{
@@ -867,6 +899,10 @@ M._tools = {
type = "string",
},
},
usage = {
abs_path = "The path to the file in the current project scope",
content = "The content to write to the file",
},
},
returns = {
{
@@ -889,16 +925,20 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the file in current project scope",
type = "string",
},
{
name = "new_rel_path",
name = "new_path",
description = "New relative path for the file",
type = "string",
},
},
usage = {
path = "Relative path to the file in current project scope",
new_path = "New relative path for the file",
},
},
returns = {
{
@@ -921,11 +961,14 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the file in current project scope",
type = "string",
},
},
usage = {
path = "Relative path to the file in current project scope",
},
},
returns = {
{
@@ -948,11 +991,14 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory",
type = "string",
},
},
usage = {
path = "Relative path to the project directory",
},
},
returns = {
{
@@ -975,16 +1021,20 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory",
type = "string",
},
{
name = "new_rel_path",
name = "new_path",
description = "New relative path for the directory",
type = "string",
},
},
usage = {
path = "Relative path to the project directory",
new_path = "New relative path for the directory",
},
},
returns = {
{
@@ -1007,11 +1057,14 @@ M._tools = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory",
type = "string",
},
},
usage = {
path = "Relative path to the project directory",
},
},
returns = {
{
@@ -1042,6 +1095,9 @@ M._tools = {
type = "string",
},
},
usage = {
query = "Query to search",
},
},
returns = {
{
@@ -1069,6 +1125,9 @@ M._tools = {
type = "string",
},
},
usage = {
url = "Url to fetch markdown from",
},
},
returns = {
{

View File

@@ -32,6 +32,11 @@ M.param = {
type = "string",
},
},
usage = {
path = "The path to the file to modify",
insert_line = "The line number after which to insert the text (0 for beginning of file)",
new_str = "The text to insert",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -14,7 +14,7 @@ M.param = {
type = "table",
fields = {
{
name = "rel_path",
name = "path",
description = "Relative path to the project directory",
type = "string",
},
@@ -24,6 +24,10 @@ M.param = {
type = "integer",
},
},
usage = {
path = "Relative path to the project directory",
max_depth = "Maximum depth of the directory",
},
}
---@type AvanteLLMToolReturn[]
@@ -41,9 +45,9 @@ M.returns = {
},
}
---@type AvanteLLMToolFunc<{ rel_path: string, max_depth?: integer }>
---@type AvanteLLMToolFunc<{ path: string, max_depth?: integer }>
function M.func(opts, on_log)
local abs_path = Helpers.get_abs_path(opts.rel_path)
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return "", "No permission to access path: " .. abs_path end
if on_log then on_log("path: " .. abs_path) end
if on_log then on_log("max depth: " .. tostring(opts.max_depth)) end

View File

@@ -16,6 +16,8 @@ M.name = "replace_in_file"
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."
-- function M.enabled() return Config.provider:match("ollama") == nil end
---@type AvanteLLMToolParam
M.param = {
type = "table",
@@ -57,6 +59,10 @@ One or more SEARCH/REPLACE blocks following this exact format:
type = "string",
},
},
usage = {
path = "File path here",
diff = "Search and replace blocks here",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -1,8 +1,4 @@
local Path = require("plenary.path")
local Utils = require("avante.utils")
local Base = require("avante.llm_tools.base")
local Helpers = require("avante.llm_tools.helpers")
local Diff = require("avante.diff")
local Config = require("avante.config")
---@class AvanteLLMTool
@@ -13,6 +9,7 @@ M.name = "str_replace"
M.description =
"The str_replace tool allows you to replace a specific string in a file with a new string. This is used for making precise edits."
-- function M.enabled() return Config.provider:match("ollama") ~= nil end
function M.enabled() return false end
---@type AvanteLLMToolParam
@@ -35,6 +32,11 @@ M.param = {
type = "string",
},
},
usage = {
path = "File path here",
old_str = "old str here",
new_str = "new str here",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -22,6 +22,9 @@ M.param = {
type = "string",
},
},
usage = {
path = "The path to the file whose last edit should be undone",
},
}
---@type AvanteLLMToolReturn[]

View File

@@ -39,23 +39,22 @@ M.param = {
type = "string",
},
{
name = "view_range",
description = "The range of the file to view. This parameter only applies when viewing files, not directories.",
type = "object",
name = "start_line",
description = "The start line of the view range, 1-indexed",
type = "integer",
optional = true,
fields = {
{
name = "start_line",
description = "The start line of the range, 1-indexed",
type = "integer",
},
{
name = "end_line",
description = "The end line of the range, 1-indexed, and -1 for the end line means read to the end of the file",
type = "integer",
},
},
},
{
name = "end_line",
description = "The end line of the view range, 1-indexed, and -1 for the end line means read to the end of the file",
type = "integer",
optional = true,
},
},
usage = {
path = "The path to the file in the current project scope",
start_line = "The start line of the view range, 1-indexed",
end_line = "The end line of the view range, 1-indexed, and -1 for the end line means read to the end of the file",
},
}
@@ -74,7 +73,7 @@ M.returns = {
},
}
---@type AvanteLLMToolFunc<{ path: string, view_range?: { start_line: integer, end_line: integer } }>
---@type AvanteLLMToolFunc<{ path: string, start_line?: integer, end_line?: integer }>
function M.func(opts, on_log, on_complete, session_ctx)
if on_log then on_log("path: " .. opts.path) end
local abs_path = Helpers.get_abs_path(opts.path)
@@ -84,11 +83,9 @@ function M.func(opts, on_log, on_complete, session_ctx)
local file = io.open(abs_path, "r")
if not file then return false, "file not found: " .. abs_path end
local lines = Utils.read_file_from_buf_or_disk(abs_path)
if opts.view_range then
local start_line = opts.view_range.start_line
local end_line = opts.view_range.end_line
if start_line and end_line and lines then lines = vim.list_slice(lines, start_line, end_line) end
end
local start_line = opts.start_line
local end_line = opts.end_line
if start_line and end_line and lines then lines = vim.list_slice(lines, start_line, end_line) end
local truncated_lines = {}
local is_truncated = false
local size = 0

View File

@@ -0,0 +1,74 @@
local Utils = require("avante.utils")
local Base = require("avante.llm_tools.base")
local Helpers = require("avante.llm_tools.helpers")
---@class AvanteLLMTool
local M = setmetatable({}, Base)
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
---@type AvanteLLMToolParam
M.param = {
type = "table",
fields = {
{
name = "path",
get_description = function()
local res = ("The path of the file to write to (relative to the current working directory {{cwd}})"):gsub(
"{{cwd}}",
Utils.get_project_root()
)
return res
end,
type = "string",
},
{
name = "content",
description = "The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.",
type = "string",
},
},
usage = {
path = "File path here",
content = "File content here",
},
}
---@type AvanteLLMToolReturn[]
M.returns = {
{
name = "success",
description = "Whether the file was created successfully",
type = "boolean",
},
{
name = "error",
description = "Error message if the file was not created successfully",
type = "string",
optional = true,
},
}
---@type AvanteLLMToolFunc<{ path: string, content: string }>
function M.func(opts, on_log, on_complete, session_ctx)
if not on_complete then return false, "on_complete not provided" end
local abs_path = Helpers.get_abs_path(opts.path)
if not Helpers.has_permission_to_access(abs_path) then return false, "No permission to access path: " .. abs_path end
if opts.content == nil then return false, "content not provided" end
local old_lines = Utils.read_file_from_buf_or_disk(abs_path)
local old_content = table.concat(old_lines or {}, "\n")
local replace_in_file = require("avante.llm_tools.replace_in_file")
local diff = "<<<<<<< SEARCH\n" .. old_content .. "\n=======\n" .. opts.content .. "\n>>>>>>> REPLACE"
local new_opts = {
path = opts.path,
diff = diff,
}
return replace_in_file.func(new_opts, on_log, on_complete, session_ctx)
end
return M