nvms / wingman

Your pair programming wingman. Supports OpenAI, Anthropic, or any LLM on your local inference server.
https://marketplace.visualstudio.com/items?itemName=nvms.ai-wingman
ISC License
61 stars 10 forks source link

FEAT: LSP Provided Context #2

Open capdevc opened 1 year ago

capdevc commented 1 year ago

I'd love to be able to select some code in the editor, and automatically have the source code for functions, classes, etc referenced in my selected code block included in the prompt as additional context.

The idea would be to use the LSP for whatever language I'm using to provide that via the vscode API (vscode.executeDefinitionProvider etc.)

Example:

def f(x):
    return 1 + x

def g(x):
    return 2 * f(x)

Here, if I select g and have a prompt asking the model to generate a docstring for it, the source code for f would be automatically included in the context.

Fancier versions could do things like trim source code from long definitions if a docstring is available, use vector similarity to find relevant source code in addition to parsing, or replace the source code with an llm generated summary of what a called functions does.

Also, this becomes even more useful with longer context models like Claude 100k.

nvms commented 1 year ago

ohh that's an interesting suggestion.

might be nice to surface this as an additional ContextType:

https://github.com/nvms/wingman/blob/main/src/template_render.ts#L32-L35

will definitely consider this!

nvms commented 1 year ago

since I left that comment, the ContextType stuff was removed as it wasn't very useful and really only existed for UI purposes

capdevc commented 1 year ago

The new OpenAI function calling stuff is really interesting. You could provide the model with a function that provides the source definition of a symbol via the LSP. You could then have the model decide to call that function with whatever symbol it thinks it need to understand and then add that to the context. So in the example above, instead of providing the source for g automatically, the model could request it.

nvms commented 1 year ago

I just read up on all the function calling features - pretty cool!

maybe I'm overthinking this, but:

const symbol = matches.find((match) => match.word === requestedSymbol);

const definition = await vscode.commands.executeCommand<vscode.DefinitionLink[]>(
  "vscode.executeDefinitionProvider",
  new vscode.Position(selection.start.line, selection.start.character + symbol.index)
);

since this is only a feature for the two newer ChatGPT models, I suppose it'd make sense to fall back to the original idea for models that do not support this

capdevc commented 1 year ago

It looks like the LSP spec includes functionality around getting all the symbols in a document: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol

This can be called via vs code commands: vscode.executeDocumentSymbolProvider in https://code.visualstudio.com/api/references/commands

I think this would be much easier than trying to lex the code manually to get symbols.

I should actually look into what's available via the LSP. I had just assumed that there would a be parser in there somewhere that provided an AST that you could get access to, but it looks like I was wrong about that.

There's a vs code extension available that uses tree-sitter to get a parse tree: https://github.com/cursorless-dev/vscode-parse-tree

I seriously doubt it would be worth adding the dependency though... especially since I think the combination of the symbol provider and definition provider from the LSP is probably enough for this use case.

nvms commented 1 year ago

I'm in favor of not bringing in treesitter as a dependency, because I think that means we also need to bring in a parser for every language we want to support (right?), which is maybe a bit too much for this feature.

executeDocumentSymbolProvider seems like a solid path forward. I will need to experiment with it to understand it better, because I'm still fuzzy on how the following would be handled given that the docs say that the result of executeDocumentSymbolProvider is a "list of symbols found in a given text document":

import f from "./f";

const g = () => {
  return f();
};

does this mean that executeDocumentSymbolProvider will include a definition for f in the list for this document's symbols? I think the lack of clarity on my part is the usage of the word "found" as opposed to "defined". either way, I'm leaning towards this instead of building an AST at this point