Maison > interface Web > js tutoriel > Créer votre propre copilote GitHub : un guide étape par étape des outils de complétion de code

Créer votre propre copilote GitHub : un guide étape par étape des outils de complétion de code

DDD
Libérer: 2024-09-14 12:15:37
original
1191 Les gens l'ont consulté

Avez-vous déjà pensé que créer un outil de complétion de code comme GitHub Copilot était complexe ? Étonnamment, ce n’est pas aussi difficile qu’il y paraît !

En tant qu'ingénieur, j'ai toujours été fasciné par la façon dont les outils de complétion de code fonctionnent sous le capot. J'ai donc procédé à une rétro-ingénierie du processus pour voir si je pouvais en créer un moi-même.

En voici un que j'ai construit moi-même et que je l'ai publié - LLM-Autocompleter

Les outils assistés par l'IA devenant la norme dans le développement de logiciels, la création de votre propre outil de complétion de code est un excellent moyen d'en apprendre davantage sur le protocole Language Server (LSP), les API et l'intégration avec des modèles avancés tels que le GPT d'OpenAI. De plus, c’est un projet incroyablement enrichissant.

Les outils de complétion de code combinent essentiellement un serveur Language Server Protocol (LSP) avec des mécanismes de complétion de code en ligne provenant de plates-formes telles que VS Code. Dans ce didacticiel, nous exploiterons l'API de complétion en ligne de VS Code et créerons notre propre serveur LSP.

Avant de plonger dans le vif du sujet, comprenons ce qu'est un serveur LSP.

Protocole de serveur de langue (LSP)

Un serveur LSP est un service backend qui fournit des fonctionnalités spécifiques au langage aux éditeurs de texte ou aux environnements de développement intégrés (IDE). Il agit comme un pont entre l'éditeur (le client) et les outils spécifiques au langage, offrant des fonctionnalités telles que :

  • Complètement du code (suggérant des extraits de code au fur et à mesure que vous tapez),

  • Définition d'accès (navigation vers la partie du code où un symbole est défini),

  • Vérification des erreurs (mise en évidence des erreurs de syntaxe en temps réel).

L'idée derrière le Language Server Protocol (LSP) est de standardiser le protocole sur la manière dont ces serveurs et outils de développement communiquent. De cette façon, un seul serveur de langue peut être réutilisé dans plusieurs outils de développement et LSP n'est qu'un protocole.

En standardisant la façon dont ces serveurs communiquent avec les éditeurs via LSP, les développeurs peuvent créer des fonctionnalités spécifiques au langage qui fonctionnent de manière transparente sur diverses plates-formes, telles que VS Code, Sublime Text et même Vim.

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

Maintenant que vous comprenez les bases de LSP, passons à la création de notre propre outil de complétion de code, étape par étape.

Nous commencerons par utiliser un exemple d’extension de complétion en ligne fournie par VS Code. Vous pouvez le cloner directement depuis GitHub :

vscode-sample-inlinecompletion

Maintenant, allons-y, configurons le serveur lsp, vous pouvez suivre la structure ci-dessous

.
├── 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
Copier après la connexion

pour plus d'informations, jetez également un œil à lsp-sample

Code

Je vous donnerais des morceaux de code, vous devez assembler les choses, je veux que vous appreniez. L'image ci-dessous montre ce que nous allons construire.

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

Allons dans client/src/extension.ts et supprimons tout de la fonction d'activation

export function activate(context: ExtensionContext) {
}
Copier après la connexion

commençons la configuration

  1. Créer un client lsp et démarrer le.
  • serverModule : pointe vers le chemin du script principal du serveur de langue.
  • debugOptions : utile pour exécuter le serveur en mode débogage.

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();
}
Copier après la connexion
  1. Recevez des données sur le serveur lsp, accédez à server/src/server.ts

Quelques renseignements

nous avons différents types de protocoles que nous pouvons suivre pour communiquer entre le serveur et le client.
pour plus d'informations vous pouvez vous rendre sur microsoft-lsp-docs

Pourquoi stdio ? Stdio est l'un des protocoles de communication les plus largement pris en charge entre clients et serveurs. Cela permet au serveur LSP que nous construisons de fonctionner non seulement dans VS Code mais également dans d'autres éditeurs comme Vim et Sublime Text.

serveur.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);
        }
    }
});
Copier après la connexion

initialiser.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",
        },
    };
};
Copier après la connexion

exit.ts

export const exit = () => {
    process.exit(0);
  };
Copier après la connexion

shutdown.ts

export const shutdown = () => {
    return null;
  };
Copier après la connexion

Une fois terminé avec la fonction de base, vous pouvez maintenant exécuter le vscode en mode débogage en utilisant la touche F5 du clavier ou suivre le guide de débogage


Commençons maintenant par ajouter un fournisseur en ligne et obtenons la demande et la réponse en fonction

Ajoutons une nouvelle méthode dans le methodStore

serveur.ts

const methodStore: Record<string, any> = {
  exit, 
  initialize,
  shutdown,
  "textDocument/generation": generation
};
Copier après la connexion

génération.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;
}

Copier après la connexion

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}`
    };  
};
Copier après la connexion

Enregistrons maintenant les fournisseurs en ligne

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 [];
    }
};

Copier après la connexion

Ce blog fournit les bases dont vous avez besoin pour créer votre propre outil de complétion de code, mais le voyage ne s'arrête pas là. Je vous encourage à expérimenter, rechercher et améliorer ce code, en explorant différentes fonctionnalités de LSP et d'IA pour adapter l'outil à vos besoins.

Quiconque essaie de mettre en œuvre cela, je veux qu'il apprenne, recherche et assemble les choses.

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal