Skip to content

Commit f9ba90d

Browse files
Davidyzguill
andauthored
feat(nvim): Allow the codecompanion tool to send chunks instead of full documents. (#180)
* Add an option to return only chunks to LLMs * refactor(nvim): improve chunk handling and option resolution. * refactor(nvim): Improve options handling and mode-specific messages * refactor(nvim): Allow table type for `max_num` in tool options * docs(nvim): Add type annotations to documentation for setup options * Auto generate docs * fix(nvim): fix tool option type conversions * feat(nvim): clarify line number usage in code chunk descriptions --------- Co-authored-by: Jacob Segal <jacob.e.segal@gmail.com> Co-authored-by: Davidyz <Davidyz@users.noreply.github.com>
1 parent 4e96ad3 commit f9ba90d

File tree

5 files changed

+196
-94
lines changed

5 files changed

+196
-94
lines changed

doc/VectorCode.txt

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -167,30 +167,34 @@ This function initialises the VectorCode client and sets up some default
167167

168168
>lua
169169
-- Default configuration
170-
require("vectorcode").setup({
171-
cli_cmds = {
172-
vectorcode = "vectorcode",
173-
},
174-
async_opts = {
175-
debounce = 10,
176-
events = { "BufWritePost", "InsertEnter", "BufReadPost" },
170+
require("vectorcode").setup(
171+
---@type VectorCode.Opts
172+
{
173+
cli_cmds = {
174+
vectorcode = "vectorcode",
175+
},
176+
---@type VectorCode.RegisterOpts
177+
async_opts = {
178+
debounce = 10,
179+
events = { "BufWritePost", "InsertEnter", "BufReadPost" },
180+
exclude_this = true,
181+
n_query = 1,
182+
notify = false,
183+
query_cb = require("vectorcode.utils").make_surrounding_lines_cb(-1),
184+
run_on_register = false,
185+
},
186+
async_backend = "default", -- or "lsp"
177187
exclude_this = true,
178188
n_query = 1,
179-
notify = false,
180-
query_cb = require("vectorcode.utils").make_surrounding_lines_cb(-1),
181-
run_on_register = false,
182-
},
183-
async_backend = "default", -- or "lsp"
184-
exclude_this = true,
185-
n_query = 1,
186-
notify = true,
187-
timeout_ms = 5000,
188-
on_setup = {
189-
update = false, -- set to true to enable update when `setup` is called.
190-
lsp = false,
189+
notify = true,
190+
timeout_ms = 5000,
191+
on_setup = {
192+
update = false, -- set to true to enable update when `setup` is called.
193+
lsp = false,
194+
}
195+
sync_log_env_var = false,
191196
}
192-
sync_log_env_var = false,
193-
})
197+
)
194198
<
195199

196200
The following are the available options for the parameter of this function: -

docs/neovim.md

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -141,30 +141,34 @@ This function initialises the VectorCode client and sets up some default
141141

142142
```lua
143143
-- Default configuration
144-
require("vectorcode").setup({
145-
cli_cmds = {
146-
vectorcode = "vectorcode",
147-
},
148-
async_opts = {
149-
debounce = 10,
150-
events = { "BufWritePost", "InsertEnter", "BufReadPost" },
144+
require("vectorcode").setup(
145+
---@type VectorCode.Opts
146+
{
147+
cli_cmds = {
148+
vectorcode = "vectorcode",
149+
},
150+
---@type VectorCode.RegisterOpts
151+
async_opts = {
152+
debounce = 10,
153+
events = { "BufWritePost", "InsertEnter", "BufReadPost" },
154+
exclude_this = true,
155+
n_query = 1,
156+
notify = false,
157+
query_cb = require("vectorcode.utils").make_surrounding_lines_cb(-1),
158+
run_on_register = false,
159+
},
160+
async_backend = "default", -- or "lsp"
151161
exclude_this = true,
152162
n_query = 1,
153-
notify = false,
154-
query_cb = require("vectorcode.utils").make_surrounding_lines_cb(-1),
155-
run_on_register = false,
156-
},
157-
async_backend = "default", -- or "lsp"
158-
exclude_this = true,
159-
n_query = 1,
160-
notify = true,
161-
timeout_ms = 5000,
162-
on_setup = {
163-
update = false, -- set to true to enable update when `setup` is called.
164-
lsp = false,
163+
notify = true,
164+
timeout_ms = 5000,
165+
on_setup = {
166+
update = false, -- set to true to enable update when `setup` is called.
167+
lsp = false,
168+
}
169+
sync_log_env_var = false,
165170
}
166-
sync_log_env_var = false,
167-
})
171+
)
168172
```
169173

170174
The following are the available options for the parameter of this function:

lua/vectorcode/integrations/codecompanion/common.lua

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ local vc_config = require("vectorcode.config")
33
local notify_opts = vc_config.notify_opts
44
local logger = vc_config.logger
55

6-
---@class VectorCode.CodeCompanion.ToolOpts
7-
---@field max_num integer?
8-
---@field default_num integer?
9-
---@field include_stderr boolean?
10-
---@field use_lsp boolean?
11-
---@field auto_submit table<string, boolean>?
12-
---@field ls_on_start boolean?
13-
---@field no_duplicate boolean?
6+
---@type VectorCode.CodeCompanion.ToolOpts
7+
local default_options = {
8+
max_num = { chunk = -1, document = -1 },
9+
default_num = { chunk = 50, document = 10 },
10+
include_stderr = false,
11+
use_lsp = false,
12+
auto_submit = { ls = false, query = false },
13+
ls_on_start = false,
14+
no_duplicate = true,
15+
chunk_mode = false,
16+
}
1417

1518
return {
1619
tool_result_source = "VectorCodeToolResult",
@@ -23,6 +26,68 @@ return {
2326
return table.concat(vim.iter(t):flatten(math.huge):totable(), "\n")
2427
end,
2528

29+
---@param opts VectorCode.CodeCompanion.ToolOpts|{}|nil
30+
---@return VectorCode.CodeCompanion.ToolOpts
31+
get_tool_opts = function(opts)
32+
if opts == nil or opts.use_lsp == nil then
33+
opts = vim.tbl_deep_extend(
34+
"force",
35+
opts or {},
36+
{ use_lsp = vc_config.get_user_config().async_backend == "lsp" }
37+
)
38+
end
39+
opts = vim.tbl_deep_extend("force", default_options, opts)
40+
if type(opts.default_num) == "table" then
41+
if opts.chunk_mode then
42+
opts.default_num = opts.default_num.chunk
43+
else
44+
opts.default_num = opts.default_num.document
45+
end
46+
assert(
47+
type(opts.default_num) == "number",
48+
"default_num should be an integer or a table: {chunk: integer, document: integer}"
49+
)
50+
end
51+
if type(opts.max_num) == "table" then
52+
if opts.chunk_mode then
53+
opts.max_num = opts.max_num.chunk
54+
else
55+
opts.max_num = opts.max_num.document
56+
end
57+
assert(
58+
type(opts.max_num) == "number",
59+
"max_num should be an integer or a table: {chunk: integer, document: integer}"
60+
)
61+
end
62+
return opts
63+
end,
64+
65+
---@param result VectorCode.Result
66+
---@return string
67+
process_result = function(result)
68+
local llm_message
69+
if result.chunk then
70+
-- chunk mode
71+
llm_message =
72+
string.format("<path>%s</path><chunk>%s</chunk>", result.path, result.chunk)
73+
if result.start_line and result.end_line then
74+
llm_message = llm_message
75+
.. string.format(
76+
"<start_line>%d</start_line><end_line>%d</end_line>",
77+
result.start_line,
78+
result.end_line
79+
)
80+
end
81+
else
82+
-- full document mode
83+
llm_message = string.format(
84+
"<path>%s</path><content>%s</content>",
85+
result.path,
86+
result.document
87+
)
88+
end
89+
return llm_message
90+
end,
2691
---@param use_lsp boolean
2792
---@return VectorCode.JobRunner
2893
initialise_runner = function(use_lsp)

lua/vectorcode/integrations/codecompanion/func_calling_tool.lua

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,19 @@ local job_runner = nil
1010
---@param opts VectorCode.CodeCompanion.ToolOpts?
1111
---@return CodeCompanion.Agent.Tool
1212
return check_cli_wrap(function(opts)
13-
if opts == nil or opts.use_lsp == nil then
14-
opts = vim.tbl_deep_extend(
15-
"force",
16-
opts or {},
17-
{ use_lsp = vc_config.get_user_config().async_backend == "lsp" }
18-
)
13+
opts = cc_common.get_tool_opts(opts)
14+
assert(
15+
type(opts.max_num) == "number" and type(opts.default_num) == "number",
16+
string.format("Options are not correctly formatted:%s", vim.inspect(opts))
17+
)
18+
---@type "file"|"chunk"
19+
local mode
20+
if opts.chunk_mode then
21+
mode = "chunk"
22+
else
23+
mode = "file"
1924
end
20-
opts = vim.tbl_deep_extend("force", {
21-
max_num = -1,
22-
default_num = 10,
23-
include_stderr = false,
24-
use_lsp = false,
25-
auto_submit = { ls = false, query = false },
26-
ls_on_start = false,
27-
no_duplicate = true,
28-
}, opts or {})
25+
2926
logger.info("Creating CodeCompanion tool with the following args:\n", opts)
3027
local capping_message = ""
3128
if opts.max_num > 0 then
@@ -58,7 +55,6 @@ return check_cli_wrap(function(opts)
5855
end
5956

6057
if action.command == "query" then
61-
local args = { "query", "--pipe", "-n", tostring(action.options.count) }
6258
if action.options.query == nil then
6359
return {
6460
status = "error",
@@ -68,7 +64,14 @@ return check_cli_wrap(function(opts)
6864
if type(action.options.query) == "string" then
6965
action.options.query = { action.options.query }
7066
end
67+
local args = { "query" }
7168
vim.list_extend(args, action.options.query)
69+
vim.list_extend(args, { "--pipe", "-n", tostring(action.options.count) })
70+
if opts.chunk_mode then
71+
vim.list_extend(args, { "--include", "path", "chunk" })
72+
else
73+
vim.list_extend(args, { "--include", "path", "document" })
74+
end
7275
if action.options.project_root == "" then
7376
action.options.project_root = nil
7477
end
@@ -188,6 +191,7 @@ return check_cli_wrap(function(opts)
188191
system_prompt = function()
189192
local guidelines = {
190193
" - The path of a retrieved file will be wrapped in `<path>` and `</path>` tags. Its content will be right after the `</path>` tag, wrapped by `<content>` and `</content>` tags. Do not include the `<path>``</path>` tags in your answers when you mention the paths.",
194+
" - The results may also be chunks of the source code. In this case, the text chunks will be wrapped in <chunk></chunk>. If the starting and ending line ranges are available, they will be wrapped in <start_line></start_line> and <end_line></end_line> tags. Make use of the line numbers (NOT THE XML TAGS) when you're quoting the source code.",
191195
" - If you used the tool, tell users that they may need to wait for the results and there will be a virtual text indicator showing the tool is still running",
192196
" - Include one single command call for VectorCode each time. You may include multiple keywords in the command",
193197
" - VectorCode is the name of this tool. Do not include it in the query unless the user explicitly asks",
@@ -271,43 +275,38 @@ return check_cli_wrap(function(opts)
271275
if cmd.command == "query" then
272276
local max_result = #stdout
273277
if opts.max_num > 0 then
274-
max_result = math.min(opts.max_num, max_result)
278+
max_result = math.min(opts.max_num or 1, max_result)
275279
end
276280
for i, file in pairs(stdout) do
277281
if i <= max_result then
278282
if i == 1 then
283+
user_message = string.format(
284+
"**VectorCode Tool**: Retrieved %d %s(s)",
285+
max_result,
286+
mode
287+
)
279288
if cmd.options.project_root then
280-
user_message = string.format(
281-
"**VectorCode Tool**: Retrieved %s files from %s",
282-
max_result,
283-
cmd.options.project_root
284-
)
285-
else
286-
user_message =
287-
string.format("**VectorCode Tool**: Retrieved %s files", max_result)
289+
user_message = user_message .. " from " .. cmd.options.project_root
288290
end
291+
user_message = user_message .. "\n"
289292
else
290293
user_message = ""
291294
end
292-
local llm_message = string.format(
293-
[[Here is a file the VectorCode tool retrieved:
294-
<path>
295-
%s
296-
</path>
297-
<content>
298-
%s
299-
</content>
300-
]],
301-
file.path,
302-
file.document
295+
agent.chat:add_tool_output(
296+
self,
297+
cc_common.process_result(file),
298+
user_message
303299
)
304-
agent.chat:add_tool_output(self, llm_message, user_message)
305-
agent.chat.references:add({
306-
source = cc_common.tool_result_source,
307-
id = file.path,
308-
path = file.path,
309-
opts = { visible = false },
310-
})
300+
if not opts.chunk_mode then
301+
-- skip referencing because there will be multiple chunks with the same path (id).
302+
-- TODO: figure out a way to deduplicate.
303+
agent.chat.references:add({
304+
source = cc_common.tool_result_source,
305+
id = file.path,
306+
path = file.path,
307+
opts = { visible = false },
308+
})
309+
end
311310
end
312311
end
313312
elseif cmd.command == "ls" then

lua/vectorcode/types.lua

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
---Type definition of the retrieval result.
22
---@class VectorCode.Result
33
---@field path string Path to the file
4-
---@field document string Content of the file
4+
---@field document string? Content of the file
5+
---@field chunk string?
6+
---@field start_line integer?
7+
---@field end_line integer?
58

69
---Type definitions for the cache of a buffer.
710
---@class VectorCode.Cache
@@ -53,3 +56,30 @@
5356
---@field buf_is_enabled fun(bufnr: integer?): boolean Checks if a buffer has been enabled.
5457
---@field make_prompt_component fun(bufnr: integer?, component_cb: (fun(result: VectorCode.Result): string)?): {content: string, count: integer} Compile the retrieval results into a string.
5558
---@field async_check fun(check_item: string?, on_success: fun(out: vim.SystemCompleted)?, on_failure: fun(out: vim.SystemCompleted)?) Checks if VectorCode has been configured properly for your project.
59+
60+
--- This class defines the options available to the CodeCompanion tool.
61+
---@class VectorCode.CodeCompanion.ToolOpts
62+
--- Maximum number of results provided to the LLM.
63+
--- You may set this to a table to configure different values for document/chunk mode.
64+
--- When set to negative values, it means unlimited.
65+
--- Default: `{ document = -1, chunk = -1 }`
66+
---@field max_num integer|{document:integer, chunk: integer}|nil
67+
--- Default number of results provided to the LLM.
68+
--- This value is written in the system prompt and tool description.
69+
--- Users may ask the LLM to request a different number of results in the chat.
70+
--- You may set this to a table to configure different values for document/chunk mode.
71+
--- Default: `{ document = 10, chunk = 50 }`
72+
---@field default_num integer|{document:integer, chunk: integer}|nil
73+
--- Whether the stderr should be provided back to the chat
74+
---@field include_stderr boolean?
75+
--- Whether to use the LSP backend. Default: `false`
76+
---@field use_lsp boolean?
77+
--- Whether to automatically submit the result (no longer necessary in recent CodeCompanion releases). Default: `false`
78+
---@field auto_submit table<string, boolean>?
79+
--- Whether to run `vectorcode ls` and tell the LLM about the indexed projects when initialising the tool. Default: `false`
80+
---@field ls_on_start boolean?
81+
--- Whether to avoid duplicated references. Default: `true`
82+
---@field no_duplicate boolean?
83+
--- Whether to send chunks instead of full files to the LLM. Default: `false`
84+
--- > Make sure you adjust `max_num` and `default_num` accordingly.
85+
---@field chunk_mode boolean?

0 commit comments

Comments
 (0)