Heim  >  Artikel  >  Web-Frontend  >  Erstellen Sie Ihren eigenen GitHub-Copilot: Eine Schritt-für-Schritt-Anleitung für Tools zur Codevervollständigung

Erstellen Sie Ihren eigenen GitHub-Copilot: Eine Schritt-für-Schritt-Anleitung für Tools zur Codevervollständigung

DDD
DDDOriginal
2024-09-14 12:15:37885Durchsuche

Haben Sie jemals gedacht, dass die Entwicklung eines Code-Vervollständigungstools wie GitHub Copilot komplex wäre? Überraschenderweise ist es gar nicht so schwer, wie es scheint!

Als Ingenieur war ich schon immer fasziniert davon, wie Code-Vervollständigungstools unter der Haube funktionieren. Also habe ich den Prozess rückentwickelt, um zu sehen, ob ich selbst eines bauen könnte.

Hier ist einer, den ich selbst erstellt und veröffentlicht habe – LLM-Autocompleter

Da KI-gestützte Tools in der Softwareentwicklung zur Norm werden, ist die Erstellung eines eigenen Tools zur Codevervollständigung eine großartige Möglichkeit, mehr über das Language Server Protocol (LSP), APIs und die Integration mit erweiterten Modellen wie GPT von OpenAI zu erfahren. Außerdem ist es ein unglaublich lohnendes Projekt.

Code-Vervollständigungstools kombinieren im Wesentlichen einen Language Server Protocol (LSP)-Server mit Inline-Code-Vervollständigungsmechanismen von Plattformen wie VS Code. In diesem Tutorial nutzen wir die Inline-Vervollständigungs-API von VS Code und erstellen unseren eigenen LSP-Server.

Bevor wir eintauchen, wollen wir verstehen, was ein LSP-Server ist.

Language Server Protocol (LSP)

Ein LSP-Server ist ein Backend-Dienst, der sprachspezifische Funktionen für Texteditoren oder integrierte Entwicklungsumgebungen (IDEs) bereitstellt. Es fungiert als Brücke zwischen dem Editor (dem Client) und den sprachspezifischen Tools und bietet Funktionen wie:

  • Code-Vervollständigung (Vorschlagen von Codeausschnitten während der Eingabe),

  • Gehe-zu-Definition (Navigieren zu dem Teil des Codes, in dem ein Symbol definiert ist),

  • Fehlerprüfung (Hervorhebung von Syntaxfehlern in Echtzeit).

Die Idee hinter dem Language Server Protocol (LSP) besteht darin, das Protokoll für die Kommunikation solcher Server und Entwicklungstools zu standardisieren. Auf diese Weise kann ein einzelner Sprachserver in mehreren Entwicklungstools wiederverwendet werden und LSP ist nur ein Protokoll.

Durch die Standardisierung der Kommunikation dieser Server mit Editoren über LSP können Entwickler sprachspezifische Funktionen erstellen, die nahtlos auf einer Vielzahl von Plattformen funktionieren, wie VS Code, Sublime Text und sogar Vim.

Building Your Own GitHub Copilot: A Step-by-Step Guide to Code Completion Tools

Da Sie nun die Grundlagen von LSP verstanden haben, beginnen wir Schritt für Schritt mit der Entwicklung unseres eigenen Code-Vervollständigungstools.

Wir beginnen mit der Verwendung einer Beispiel-Inline-Vervollständigungserweiterung, die von VS Code bereitgestellt wird. Sie können es direkt von GitHub klonen:

vscode-sample-inlinecompletion

Jetzt richten wir den LSP-Server ein. Sie können der folgenden Struktur folgen

.
├── client // Language Client
│   ├── src
│   │   ├── test // End to End tests for Language Client / Server
│   │   └── extension.ts // Language Client entry point
├── package.json // The extension manifest.
└── server // Language Server
    └── src
        └── server.ts // Language Server entry point

Weitere Informationen finden Sie auch unter lsp-sample

Code

Ich würde Ihnen ein paar Code-Teile geben. Sie müssen Dinge zusammenfügen, die Sie lernen sollen. Das Bild unten zeigt, was wir bauen werden.

Building Your Own GitHub Copilot: A Step-by-Step Guide to Code Completion Tools

Lass uns zu client/src/extension.ts gehen und alles aus der Aktivierungsfunktion entfernen

export function activate(context: ExtensionContext) {
}

Lasst uns mit der Einrichtung beginnen

  1. Erstellen Sie einen LSP-Client und starten Sie ihn.
  • serverModule: Zeigt auf den Pfad des Hauptskripts des Sprachservers.
  • debugOptions: Nützlich zum Ausführen des Servers im Debug-Modus.

extension.ts

export function activate(context: ExtensionContext) {
    const serverModule = context.asAbsolutePath(
        path.join("server", "out", "server.js")
    );

    const debugOptions = { execArgv: ['--nolazy', '--
 inspect=6009'] };

  // communication with the server using Stdio
    const serverOptions: ServerOptions = {
        run: {
            module: serverModule,
            transport: TransportKind.stdio,
        },
        debug: {
            module: serverModule,
            transport: TransportKind.stdio,
            options: debugOptions
        }
    };

        const clientOptions: LanguageClientOptions = {
        documentSelector: [{ scheme: 'file' }],
        initializationOptions: serverConfiguration
    };


     client = new LanguageClient(
        'LSP Server Name',
        serverOptions,
        clientOptions
    );

  client.start();
}
  1. Empfangen Sie Daten auf dem LSP-Server. Gehen Sie zu server/src/server.ts

Einige Informationen

Wir haben verschiedene Arten von Protokollen, denen wir folgen können, um zwischen Server und Client zu kommunizieren.
Weitere Informationen finden Sie unter microsoft-lsp-docs

Warum stdio? Stdio ist eines der am weitesten verbreiteten Kommunikationsprotokolle zwischen Clients und Servern. Dadurch kann der LSP-Server, den wir erstellen, nicht nur in VS Code, sondern auch in anderen Editoren wie Vim und Sublime Text funktionieren.

server.ts

const methodStore: Record<string, any> = {
  exit, 
  initialize,
  shutdown,
};

process.stdin.on("data", async (bufferChuck) => {
    buffer += bufferChuck;

    while (true) {
        try {
            // Check for the Content-Length line
            const lengthMatch = buffer.match(/Content-Length: (\d+)\r\n/);
            if (!lengthMatch) break;

            const contentLength = parseInt(lengthMatch[1], 10);
            const messageStart = buffer.indexOf("\r\n\r\n") + 4;

            // Continue unless the full message is in the buffer
            if (buffer.length < messageStart + contentLength) break;

            const rawMessage = buffer.slice(messageStart, messageStart + contentLength);
            const message = JSON.parse(rawMessage);


            const method = methodStore[message.method];

            if (method) {
                const result = await method(message);

                if (result !== undefined) {
                    respond(message.id, result);
                }
            }
            buffer = buffer.slice(messageStart + contentLength);
        } catch (error: any) {

            const errorMessage = {
                jsonrpc: "2.0",
                method: "window/showMessage",
                params: {
                    type: 1, // Error type
                    message: `Error processing request: ${error.message}`
                }
            };

            const errorNotification = JSON.stringify(errorMessage);
            const errorNotificationLength = Buffer.byteLength(errorNotification, "utf-8");
            const errorHeader = `Content-Length: ${errorNotificationLength}\r\n\r\n`;

            process.stdout.write(errorHeader + errorNotification);
        }
    }
});

initialize.ts

export const initialize = (message: RequestMessage): InitializeResult => {

    return {
        capabilities: {
            completionProvider: {
                resolveProvider: true
            },
            textDocumentSync: TextDocumentSyncKind.Incremental,
            codeActionProvider: {
                resolveProvider: true 
            }
        },
        serverInfo: {
            name: "LSP-Server",
            version: "1.0.0",
        },
    };
};

exit.ts

export const exit = () => {
    process.exit(0);
  };

shutdown.ts

export const shutdown = () => {
    return null;
  };

Sobald Sie mit der Grundfunktion fertig sind, können Sie den vscode jetzt im Debugging-Modus ausführen, indem Sie die F5-Taste auf der Tastatur verwenden oder der Debugging-Anleitung folgen


Jetzt beginnen wir mit dem Hinzufügen eines Inline-Anbieters und erhalten die entsprechende Anfrage und Antwort

Fügen wir eine neue Methode zum methodStore hinzu

server.ts

const methodStore: Record<string, any> = {
  exit, 
  initialize,
  shutdown,
  "textDocument/generation": generation
};

generation.ts

export const generation = async (message: any) => {
    if(!message && message !== undefined) return {};

    const text = message.params.textDocument.text as string;

    if(!text) return {};

        const cursorText = getNewCursorText(text, message.params.position.line, message.params.position.character);

  const response = await getResponseFromOpenAI(cursorText, message.params.fsPath);

 return {
    generatedText: response,
  }

}

function getNewCursorText(text: string, line: number, character: number): string {
    const lines = text.split('\n');
    if (line < 0 || line >= lines.length) return text;

    const targetLine = lines[line];
    if (character < 0 || character > targetLine.length) return text;

    lines[line] = targetLine.slice(0, character) + '<CURSOR>' + targetLine.slice(character);
    return lines.join('\n');
}


const getResponseFromOpenAI = async (text: string, fsPath: stiring): Promise<string> => {
     const message = {
          "role": "user",
          "content": text
    };

   const systemMetaData: Paramaters = {
    max_token: 128,
    max_context: 1024,
    messages: [],
    fsPath: fsPath
   } 

   const messages = [systemPrompt(systemMetaData), message]

   const chatCompletion: OpenAI.Chat.ChatCompletion | undefined = await this.open_ai_client?.chat.completions.create({
            messages: messages,
            model: "gpt-3.5-turbo",
            max_tokens: systemMetaData?.max_tokens ?? 128,
        });


        if (!chatCompletion) return "";

        const generatedResponse = chatCompletion.choices[0].message.content;

        if (!generatedResponse) return "";

        return generatedResponse;
}

template.ts

interface Parameters {
    max_tokens: number;
    max_context: number;
    messages: any[];
    fsPath: string;
}

 export const systemPrompt = (paramaters: Parameters | null) => {
    return {
        "role": "system",
        "content": `
        Instructions:
            - You are an AI programming assistant.
            - Given a piece of code with the cursor location marked by <CURSOR>, replace <CURSOR> with the correct code.
            - First, think step-by-step.
            - Describe your plan for what to build in pseudocode, written out in great detail.
            - Then output the code replacing the <CURSOR>.
            - Ensure that your completion fits within the language context of the provided code snippet.
            - Ensure, completion is what ever is needed, dont write beyond 1 or 2 line, unless the <CURSOR> is on start of a function, class or any control statment(if, switch, for, while).

            Rules:
            - Only respond with code.
            - Only replace <CURSOR>; do not include any previously written code.
            - Never include <CURSOR> in your response.
            - Handle ambiguous cases by providing the most contextually appropriate completion.
            - Be consistent with your responses.
            - You should only generate code in the language specified in the META_DATA.
            - Never mix text with code.
            - your code should have appropriate spacing.

            META_DATA: 
            ${paramaters?.fsPath}`
    };  
};

Jetzt registrieren wir die Inline-Anbieter

extension.ts

import {languages} from "vscode";


function getConfiguration(configName: string) {
    if(Object.keys(workspace.getConfiguration(EXTENSION_ID).get(configName)).length > 0){
        return workspace.getConfiguration(EXTENSION_ID).get(configName);
    }
    return null;
}

const inLineCompletionConfig = getConfiguration("inlineCompletionConfiguration");

export function activate(context: ExtensionContext) {
 // OTHER CODE

  languages.registerInlineCompletionItemProvider(
        { pattern: "**" },
        {
            provideInlineCompletionItems: (document: TextDocument, position: Position) => {
                const mode = inLineCompletionConfig["mode"] || 'slow';
                return provideInlineCompletionItems(document, position, mode);
            },
        }

    );

} 



let lastInlineCompletion = Date.now();
let lastPosition: Position | null = null;
let inlineCompletionRequestCounter = 0;

const provideInlineCompletionItems = async (document: TextDocument, position: Position, mode: 'fast' | 'slow') => {
    const params = {
        textDocument: {
            uri: document.uri.toString(),
            text: document.getText(),
        },
        position: position,
        fsPath: document.uri.fsPath.toString()
    };

    inlineCompletionRequestCounter += 1;
    const localInCompletionRequestCounter = inlineCompletionRequestCounter;
    const timeSinceLastCompletion = (Date.now() - lastInlineCompletion) / 1000;
    const minInterval = mode === 'fast' ? 0 : 1 / inLineCompletionConfig["maxCompletionsPerSecond"];

    if (timeSinceLastCompletion < minInterval) {
        await new Promise(r => setTimeout(r, (minInterval - timeSinceLastCompletion) * 1000));
    }

    if (inlineCompletionRequestCounter === localInCompletionRequestCounter) {
        lastInlineCompletion = Date.now();

        let cancelRequest = CancellationToken.None;
        if (lastPosition && position.isAfter(lastPosition)) {
            cancelRequest = CancellationToken.Cancelled;
        }
        lastPosition = position;

        try {
            const result = await client.sendRequest("textDocument/generation", params, cancelRequest);


            const snippetCode = new SnippetString(result["generatedText"]);
            return [new InlineCompletionItem(snippetCode)];
        } catch (error) {
            console.error("Error during inline completion request", error);
            client.sendNotification("window/showMessage", {
                type: 1, // Error type
                message: "An error occurred during inline completion: " + error.message
            });
            return [];
        }
    } else {
        return [];
    }
};

Dieser Blog bietet die Grundlage, die Sie zum Erstellen Ihres eigenen Code-Vervollständigungstools benötigen, aber die Reise endet hier noch nicht. Ich ermutige Sie, diesen Code zu experimentieren, zu erforschen und zu verbessern und verschiedene Funktionen von LSP und KI zu erkunden, um das Tool an Ihre Bedürfnisse anzupassen.

Wer auch immer versucht, dies umzusetzen, ich möchte, dass er lernt, recherchiert und Dinge zusammenfügt.

Ce que vous avez appris

  1. Comprendre les serveurs LSP : Vous avez appris ce qu'est un serveur LSP, comment il alimente des outils spécifiques à un langage et pourquoi il est essentiel pour la prise en charge entre éditeurs.

  2. Création d'extensions VS Code : vous avez découvert comment intégrer la complétion de code dans VS Code à l'aide d'API.

  3. Achèvement du code piloté par l'IA : en vous connectant aux modèles GPT d'OpenAI, vous avez vu comment l'apprentissage automatique peut améliorer la productivité des développeurs grâce à des suggestions intelligentes.

Si vous arrivez ici, j'aime savoir ce que vous avez appris.

S'il vous plaît, likez si vous avez appris quelque chose de nouveau aujourd'hui sur mon blog.

Connectez-vous avec moi - linked-In

Das obige ist der detaillierte Inhalt vonErstellen Sie Ihren eigenen GitHub-Copilot: Eine Schritt-für-Schritt-Anleitung für Tools zur Codevervollständigung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn