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

@@ -576,7 +576,7 @@ end
--- remove indentation from code: spaces or tabs
function M.remove_indentation(code)
if not code then return code end
return code:gsub("^%s*", ""):gsub("%s*$", "")
return code:gsub("%s*", "")
end
function M.relative_path(absolute)
@@ -1056,12 +1056,12 @@ function M.update_buffer_lines(ns_id, bufnr, old_lines, new_lines)
if #diffs == 0 then return end
for _, diff in ipairs(diffs) do
local lines = diff.content
-- M.debug("lines", lines)
local text_lines = vim.tbl_map(function(line) return tostring(line) end, lines)
vim.api.nvim_buf_set_lines(bufnr, diff.start_line - 1, diff.end_line - 1, false, text_lines)
for i, line in ipairs(lines) do
line:set_highlights(ns_id, bufnr, diff.start_line + i - 2)
end
vim.cmd("redraw")
end
end
@@ -1467,6 +1467,23 @@ function M.text_to_lines(text, hl)
return lines
end
---@param thinking_text string
---@param hl string | nil
---@return avante.ui.Line[]
function M.thinking_to_lines(thinking_text, hl)
local Line = require("avante.ui.line")
local text_lines = vim.split(thinking_text, "\n")
local lines = {}
table.insert(lines, Line:new({ { M.icon("🤔 ") .. "Thought content:" } }))
table.insert(lines, Line:new({ { "" } }))
for _, text_line in ipairs(text_lines) do
local piece = { "> " .. text_line }
if hl then table.insert(piece, hl) end
table.insert(lines, Line:new({ piece }))
end
return lines
end
---@param item AvanteLLMMessageContentItem
---@param message avante.HistoryMessage
---@param messages avante.HistoryMessage[]
@@ -1475,6 +1492,9 @@ function M.message_content_item_to_lines(item, message, messages)
local Line = require("avante.ui.line")
if type(item) == "string" then return M.text_to_lines(item) end
if type(item) == "table" then
if item.type == "thinking" or item.type == "redacted_thinking" then
return M.thinking_to_lines(item.thinking or item.data or "")
end
if item.type == "text" then return M.text_to_lines(item.text) end
if item.type == "image" then
return { Line:new({ { "![image](" .. item.source.media_type .. ": " .. item.source.data .. ")" } }) }
@@ -1520,18 +1540,6 @@ function M.message_content_item_to_lines(item, message, messages)
end
end
end
elseif tool_result_message then
local tool_result = tool_result_message.message.content[1]
if tool_result.content then
local result_lines = vim.split(tool_result.content, "\n")
for idx, line in ipairs(result_lines) do
if idx ~= #result_lines then
table.insert(lines, Line:new({ { "" }, { string.format(" %s", line) } }))
else
table.insert(lines, Line:new({ { "╰─" }, { string.format(" %s", line) } }))
end
end
end
end
return lines
end

View File

@@ -0,0 +1,144 @@
local M = {}
---@param provider_conf AvanteDefaultBaseProvider
---@param opts AvantePromptOptions
---@return string
function M.get_ReAct_system_prompt(provider_conf, opts)
local system_prompt = opts.system_prompt
local disable_tools = provider_conf.disable_tools or false
if not disable_tools and opts.tools then
local tools_prompts = [[
====
TOOL USE
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
# Tool Use Formatting
Tool use is formatted using XML-style tags. 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_name>
<parameter1_name>value1</parameter1_name>
<parameter2_name>value2</parameter2_name>
...
</tool_name>
For example:
<view>
<path>src/main.js</path>
</view>
Always adhere to this format for the tool use to ensure proper parsing and execution.
# Tools
]]
for _, tool in ipairs(opts.tools) do
local tool_prompt = ([[
## {{name}}
Description: {{description}}
Parameters:
]]):gsub("{{name}}", tool.name):gsub(
"{{description}}",
tool.get_description and tool.get_description() or (tool.description or "")
)
for _, field in ipairs(tool.param.fields) do
if field.optional then
tool_prompt = tool_prompt .. string.format(" - %s: %s\n", field.name, field.description)
else
tool_prompt = tool_prompt
.. string.format(
" - %s: (required) %s\n",
field.name,
field.get_description and field.get_description() or (field.description or "")
)
end
end
if tool.param.usage then
tool_prompt = tool_prompt
.. ("Usage:\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"):gsub("{{([%w_]+)}}", function(name) return tool[name] end)
end
tools_prompts = tools_prompts .. tool_prompt .. "\n"
end
system_prompt = system_prompt .. tools_prompts
system_prompt = system_prompt
.. [[
# Tool Use Examples
## Example 1: Requesting to execute a command
<bash>
<path>./src</path>
<command>npm run dev</command>
</bash>
## Example 2: Requesting to create a new file
<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>
## Example 3: Requesting to make targeted edits to a file
<replace_in_file>
<path>src/components/App.tsx</path>
<diff>
<<<<<<< SEARCH
import React from 'react';
=======
import React, { useState } from 'react';
>>>>>>> REPLACE
<<<<<<< SEARCH
function handleSubmit() {
saveData();
setLoading(false);
}
=======
>>>>>>> REPLACE
<<<<<<< SEARCH
return (
<div>
=======
function handleSubmit() {
saveData();
setLoading(false);
}
return (
<div>
>>>>>>> REPLACE
</diff>
</replace_in_file>
]]
end
return system_prompt
end
return M