En 2023, la popularité de l’IA générative conduira de plus en plus d’organisations à introduire le codage assisté par l’IA. Ce qui est légèrement différent de GitHub Copilot publié en 2021, c'est que la complétion du code n'est qu'un scénario parmi de nombreux. Un grand nombre d'entreprises explorent des scénarios tels que la génération de code complet et la révision du code en fonction des exigences, et introduisent également l'IA générative pour améliorer l'efficacité du développement.
Dans ce contexte, nous (la communauté open source Thoughtworks) avons également mis en open source une série d'outils auxiliaires d'IA pour aider davantage d'organisations à créer leurs propres assistants de codage assistés par IA :
Parce que lorsque nous avons conçu AutoDev, les différents modèles open source étaient en constante évolution. Dans ce contexte, ses étapes sont :
Par conséquent, ce tutoriel est également centré autour de ces trois étapes. De plus, sur la base de notre expérience, l'exemple de pile technologique pour ce didacticiel :
Étant donné que notre expérience en IA est relativement limitée, il y aura inévitablement des erreurs. Par conséquent, nous espérons également travailler avec davantage de développeurs pour construire ce projet open source.
En combinaison avec la partie intelligence artificielle du rapport JetBrains 2023 « Developer Ecosystem », nous pouvons résumer quelques scénarios généraux qui reflètent les domaines dans lesquels l'IA générative peut jouer un rôle dans le processus de développement. Voici quelques-uns des principaux scénarios :
Lorsque nous avons construit AutoDev, nous avons également découvert des scénarios tels que la création de SQL DDL, la génération d'exigences, de TDD, etc. donc. Nous offrons la possibilité de personnaliser les scénarios afin que les développeurs puissent personnaliser leurs propres capacités d'IA. Pour plus de détails, voir : https://ide.unitmesh.cc/customize.
Dans le codage quotidien, il existe plusieurs scénarios différents avec des exigences différentes en matière de vitesse de réponse de l'IA (à titre d'exemple) :
scène | Vitesse de réponse | Générer des exigences de qualité | Taille attendue | illustrer |
---|---|---|---|---|
achèvement du code | rapide | milieu | 1 ~ 6B | La complétion du code est le scénario le plus courant dans le codage quotidien, et la vitesse de réponse est cruciale. |
Génération de documents | milieu | milieu | 1 | La génération de documentation nécessite une compréhension complète de la structure du code, et la rapidité et la qualité sont tout aussi importantes. |
révision du code | rapide | milieu | 1 | Les révisions de code nécessitent des conseils de haute qualité mais doivent également être aussi réactives que possible. |
Génération de tests unitaires | rapide | milieu | 6B~ | Les tests unitaires génèrent moins de contexte, et la réactivité et la qualité de l'IA sont tout aussi importantes. |
refactorisation du code | milieu | haut | 32B~ | La refactorisation du code peut nécessiter une meilleure compréhension contextuelle et les temps de réponse peuvent être modérément ralentis. |
génération de demande | milieu | haut | 32B~ | La génération de demande est un scénario relativement complexe et la vitesse de réponse peut être modérément ralentie pour garantir la précision. |
Recherche et interprétation de codes en langage naturel | Moyen-Faible | haut | 32B~ | La recherche et l'interprétation de codes en langage naturel sont des scénarios relativement complexes, et la vitesse de réponse peut être modérément ralentie pour garantir l'exactitude. |
PS : Le 32B ici n'est exprimé qu'en ordre de grandeur, car l'effet sera meilleur avec un modèle plus grand.
Par conséquent, nous le résumons comme suit : un grand, un intermédiaire, un micro et trois modèles, fournissant un codage complet assisté par l'IA :
La complétion du code de l'IA peut combiner des outils IDE pour analyser le contexte du code et les règles du langage de programmation, et l'IA générera ou suggérera automatiquement des extraits de code. Dans les outils de complétion de code similaires à GitHub Copilot, ils sont généralement divisés en trois modes de subdivision :
Achèvement en ligne (en ligne)
Semblable au mode FIM (remplir au milieu), le contenu terminé se trouve dans la ligne actuelle. Par exemple : BlotPost blogpost = new
, la complétion est : BlogPost();
pour obtenir : BlogPost blogpost = new BlogPost();
Nous pouvons utiliser Deepseek Coder comme exemple pour voir l'effet dans ce scénario :
< |fim▁begin| > def quick_sort(arr):
if len(arr) < = 1:
return arr
pivot = arr[0]
left = []
right = []
< |fim▁hole| >
if arr[i] < pivot:
left.append(arr[i])
else:
right.append(arr[i])
return quick_sort(left) + [pivot] + quick_sort(right) < |fim▁end| >
Ici, nous devons combiner le code avant et après le curseur.
Achèvement en bloc (InBlock)
Réalisé grâce à l'apprentissage contextuel (In-Context Learning), le contenu de complétion se trouve dans le bloc fonctionnel actuel. Par exemple, le code d'origine est :
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
}
Le code complété est :
val blogPost = BlogPost (
title = blogDto.title,
content = blogDto.content,
author = blogDto.author
)
return blogRepository.save(blogPost)
AprèsBloc
Réalisé grâce à l'apprentissage contextuel (In-Context Learning), achèvement après le bloc fonctionnel actuel, tel que : achèvement d'une nouvelle fonction après le bloc fonctionnel actuel. Par exemple, le code d'origine est :
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
// ...
}
Le code complété est :
fun updateBlog ( id : Long , blogDto : CreateBlogDto ): BlogPost {
// ...
}
fun deleteBlog ( id : Long ) {
// ...
}
Lorsque nous construisons la fonction de complétion de l'IA correspondante, nous devons également envisager de l'appliquer à l'ensemble de données de modèle correspondant pour améliorer la qualité de la complétion et offrir une meilleure expérience utilisateur.
Quelques ressources connexes pour la rédaction de cet article :
Les explications du code sont conçues pour aider les développeurs à gérer et à comprendre plus efficacement les bases de code volumineuses. Ces assistants peuvent répondre aux questions sur la base de code, fournir de la documentation, rechercher du code, identifier les sources d'erreurs, réduire la duplication de code, etc., améliorant ainsi l'efficacité du développement, réduisant les taux d'erreur et réduisant la charge de travail des développeurs.
Dans ce scénario, selon la qualité de la génération attendue, elle est généralement composée de deux modèles : un grand et un micro ou un moyen et un micro. Le modèle plus grand a de meilleurs résultats en termes de qualité de génération. Combinée à notre expérience de conception dans l’outil Chocolate Factory, une telle fonction peut généralement être divisée en plusieurs étapes :
En tant qu'application RAG, elle est divisée en deux parties : l'indexation et la requête.
Lors de la phase d'indexation, nous devons indexer la base de code, ce qui implique la segmentation du texte, la vectorisation, l'indexation de bases de données et d'autres technologies. L'un des éléments les plus difficiles est le fractionnement. Les règles de fractionnement auxquelles nous faisons référence sont : https://docs.sweep.dev/blogs/chunking-2m-files. Tout de suite:
Dans différents scénarios, nous pouvons également diviser de différentes manières. Par exemple, dans Chocolate Factory, nous divisons via AST pour garantir la qualité du contexte généré.
Lors de la phase d'interrogation, nous devons combiner certaines de nos technologies de recherche traditionnelles, telles que la recherche par vectorisation, la recherche de chemin, etc., pour garantir la qualité de la recherche. Dans le même temps, dans le scénario chinois, nous devons également considérer la question de la conversion vers le chinois, comme la conversion de l'anglais vers le chinois pour garantir la qualité de la recherche.
Pour une assistance quotidienne, nous pouvons également y parvenir grâce à l'IA générative, comme la création automatique de SQL DDL, la création automatique de cas de test, la création automatique d'exigences, etc. Ceux-ci ne peuvent être obtenus qu'en personnalisant les mots d'invite et en combinant des connaissances spécifiques dans un domaine. Je n'entrerai donc pas dans les détails ici.
Outre le modèle, le contexte est également un facteur important affectant les capacités d’assistance de l’IA. Lorsque nous avons construit AutoDev, nous avons également découvert deux modes contextuels différents :
Une comparaison simple est la suivante :
contexte pertinent | contexte similaire | |
---|---|---|
Technologie de recherche | analyse de code statique | Recherche de similarité |
informations sur la structure des données | AST, CFG | Morceau similaire |
Capacités multiplateformes | Dépend de l'IDE ou d'un analyseur indépendant | Ne dépend pas de plateformes spécifiques |
qualité contextuelle | extrêmement élevé | haut |
Générer des résultats | extrêmement élevé | haut |
coût de construction | Dépend de la langue et de la plateforme | Faible |
Lorsque la prise en charge de l'IDE est limitée, le contexte entraîne des coûts de performance plus élevés.
GitHub Copilot adopte un modèle architectural de contexte similaire, et son architecture détaillée est structurée comme suit :
Dans les documents de recherche du projet « public » Copilot-Explorer, vous pouvez voir comment Prompt est construit. Voici la demande d'invite envoyée à :
{
"prefix" : " # Path: codeviz \ app.py n #.... " ,
"suffix" : " if __name__ == '__main__': rn app.run(debug=True) " ,
"isFimEnabled" : true ,
"promptElementRanges" : [
{
"kind" : " PathMarker " ,
"start" : 0 ,
"end" : 23
},
{
"kind" : " SimilarFile " ,
"start" : 23 ,
"end" : 2219
},
{
"kind" : " BeforeCursor " ,
"start" : 2219 ,
"end" : 3142
}
]
}
dans:
prefix
utilisée pour créer l'invite est construite à partir de promptElements, qui comprend : BeforeCursor
, AfterCursor
, SimilarFile
, ImportedFile
, LanguageMarker
, PathMarker
, RetrievalSnippet
et d'autres types. À partir des noms de plusieurs PromptElementKind
, nous pouvons également voir sa véritable signification.suffix
utilisée pour construire l'invite est déterminée par la partie où se trouve le curseur. En fonction de la limite supérieure des jetons (2048), le nombre de positions restant à calculer. Le calcul du jeton ici est le véritable calcul du jeton LLM. Dans Copilot, il est calculé par Cushman002. La longueur du jeton des caractères chinois est différente, par exemple : { context: "console.log('你好,世界')", lineCount: 1, tokenLength: 30 }
, où la longueur du contenu en contexte est de 20, mais tokenLength est 30, la longueur des caractères chinois est 5 (y compris ,
) et le jeton occupé par un seul caractère est 3.Voici un exemple plus détaillé de contexte d'application Java :
// Path: src/main/cc/unitmesh/demo/infrastructure/repositories/ProductRepository.java
// Compare this snippet from src/main/cc/unitmesh/demo/domain/product/Product.java:
// ....
// Compare this snippet from src/main/cc/unitmesh/demo/application/ProductService.java:
// ...
// @Component
// public class ProductService {
// //...
// }
//
package cc . unitmesh . demo . repositories ;
// ...
@ Component
public class ProductRepository {
//...
Dans le contexte informatique, GitHub Copilot utilise le coefficient Jaccard (Jaccard Similarity). Cette partie de l'implémentation est implémentée dans Agent. Pour une logique plus détaillée, veuillez vous référer à : Après avoir passé plus d'un demi-mois, j'ai finalement fait de l'ingénierie inverse sur Github Copilot.
Ressources connexes :
Comme mentionné ci-dessus, le code concerné s'appuie sur une analyse de code statique , principalement à l'aide d'informations structurelles du code, telles que AST, CFG, DDG, etc. Dans différents scénarios et plates-formes, nous pouvons combiner différents outils d'analyse de code statique. Voici quelques outils d'analyse de code statique courants :
Dans le scénario d'achèvement, grâce à l'analyse de code statique, nous pouvons obtenir le contexte actuel, tel que : la fonction actuelle, la classe actuelle, le fichier actuel, etc. Voici un exemple de contexte d'AutoDev pour générer des tests unitaires :
// here are related classes:
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/service/BlogService.java
// class BlogService {
// blogRepository
// + public BlogPost createBlog(BlogPost blogDto)
// + public BlogPost getBlogById(Long id)
// + public BlogPost updateBlog(Long id, BlogPost blogDto)
// + public void deleteBlog(Long id)
// }
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/dto/CreateBlogRequest.java
// class CreateBlogRequest ...
// 'filePath: /Users/phodal/IdeaProjects/untitled/src/main/java/cc/unitmesh/untitled/demo/entity/BlogPost.java
// class BlogPost {...
@ ApiOperation ( value = "Create a new blog" )
@ PostMapping ( "/" )
public BlogPost createBlog ( @ RequestBody CreateBlogRequest request ) {
Dans cet exemple, le contexte de createBlog
est analysé pour obtenir les classes d'entrée et de sortie de la fonction : CreateBlogRequest
, les informations BlogPost
et les informations de classe BlogService, qui sont fournies au modèle en tant que contexte (fournis dans les commentaires). À ce stade, le modèle génère des constructeurs plus précis, ainsi que des cas de test plus précis.
Étant donné que le contexte pertinent repose sur l'analyse du code statique de différents langages et des API de différents IDE, nous devons également nous adapter aux différents langages et aux différents IDE. En termes de coût de construction, il est plus cher par rapport à des contextes similaires.
Les IDE et les éditeurs sont les principaux outils des développeurs, et leurs coûts de conception et d'apprentissage sont relativement élevés. Tout d’abord, nous pouvons utiliser le modèle officiel pour générer :
Ensuite, ajoutez des fonctionnalités par-dessus (n'est-ce pas très simple), bien sûr que non. Voici quelques ressources de plug-in IDEA à titre de référence :
Bien entendu, il est plus approprié de se référer au plug-in AutoDev.
Vous pouvez directement utiliser le template officiel pour générer le plug-in correspondant : https://github.com/JetBrains/intellij-platform-plugin-template
Pour l'implémentation du plug-in IDEA, il est principalement implémenté via Action et Listener, qui doivent uniquement être enregistrés dans plugin.xml
. Pour plus de détails, veuillez vous référer à la documentation officielle : IntelliJ Platform Plugin SDK
Étant donné que nous n'avons pas pris en compte le problème de compatibilité avec la version IDE d'AutoDev au début, afin d'être compatible ultérieurement avec l'ancienne version de l'IDE, nous devons effectuer un traitement de compatibilité sur le plug-in. Par conséquent, comme décrit dans le document officiel : Build Number Ranges, nous pouvons voir que différentes versions ont des exigences différentes pour le JDK. Voici les exigences pour les différentes versions :
Numéro de succursale | Version de la plateforme IntelliJ |
---|---|
233 | 2023.3 |
232 | 2023.2 |
231 | 2023.1 |
223 | 2022.3 |
222 | 2022.2 REMARQUE Java 17 est désormais requis (article de blog) |
221 | 2022.1 |
213 | 2021.3 |
212 | 2021.2 |
211 | 2021.1 |
203 | 2020.3 REMARQUE Java 11 est désormais requis (article de blog) |
Et configurez-le dans gradle.properties
:
pluginSinceBuild = 223
pluginUntilBuild = 233.*
La configuration ultérieure de la compatibilité est gênante, vous pouvez donc vous référer à la conception d'AutoDev.
En termes de complétion automatique du code, les fabricants nationaux font principalement référence à la mise en œuvre de GitHub Copilot, et la logique n'est pas compliquée.
Déclencher à l'aide des touches de raccourci
Il surveille principalement les entrées de l'utilisateur dans Action, puis :
Fonction | touche de raccourci | illustrer |
---|---|---|
requestCompletions | Alt + / | Obtenez le contexte actuel, puis obtenez les résultats d'achèvement via le modèle |
appliquerIncrustations | TAB | Afficher les résultats d'achèvement sur l'EDI |
disposerIncrustations | ESC | Annuler l'achèvement |
cycleNextIncrustations | Alt + ] | Passer au résultat d'achèvement suivant |
cyclePrevIncrustations | Alt + [ | Passer au résultat d'achèvement précédent |
Utiliser la méthode de déclenchement automatique
Il surveille principalement les entrées de l'utilisateur via EditorFactoryListener
, puis déclenche différents résultats d'achèvement en fonction de différentes entrées. Le code de base est le suivant :
class AutoDevEditorListener : EditorFactoryListener {
override fun editorCreated ( event : EditorFactoryEvent ) {
// ...
editor.document.addDocumentListener( AutoDevDocumentListener (editor), editorDisposable)
editor.caretModel.addCaretListener( AutoDevCaretListener (editor), editorDisposable)
// ...
}
class AutoDevCaretListener ( val editor : Editor ) : CaretListener {
override fun caretPositionChanged ( event : CaretEvent ) {
// ...
val wasTypeOver = TypeOverHandler .getPendingTypeOverAndReset(editor)
// ...
llmInlayManager.disposeInlays(editor, InlayDisposeContext . CaretChange )
}
}
class AutoDevDocumentListener ( val editor : Editor ) : BulkAwareDocumentListener {
override fun documentChangedNonBulk ( event : DocumentEvent ) {
// ...
val llmInlayManager = LLMInlayManager .getInstance()
llmInlayManager
.editorModified(editor, changeOffset)
}
}
}
Ensuite, en fonction de différentes entrées, différents résultats d'achèvement sont déclenchés et la structure est traitée.
Code d'achèvement du rendu
Par la suite, nous devons implémenter un Inlay Render, qui hérite de EditorCustomElementRenderer
.
Combiné avec les capacités d'interface de l'EDI, nous devons ajouter l'action correspondante, le groupe correspondant et l'icône correspondante. Voici un exemple d’action :
<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
Voici quelques groupes d'actions d'AutoDev :
Identifiant du groupe | Utilisations de l'IA | Description |
---|---|---|
AfficherGroupeIntentions | Refactorisation de code, interprétation de code, génération de code, tests de code | Utilisé pour afficher des astuces dans le contexte du code et accessible via Alt + Enter et ⌥ + Enter sur macOS. |
ConsoleÉditeurPopupMenu | corriger les erreurs | Le menu affiché dans la console, comme la console de la structure d'exécution du programme. |
Vcs.MessageActionGroup | Génération d'informations sur le code | Menu pour écrire des messages de validation dans VCS. |
Vcs.Log.ContextMenu | Révision de code, interprétation de code, génération de code | Menu de visualisation des logs dans VCS, fonctions disponibles : inspection IA du code, génération des logs de release. |
ÉditeurPopupMenu | Tous sont acceptables | Menu contextuel, vous pouvez également ajouter l'ActionGroup correspondant |
Lors de l'écriture de ShowIntentionsGroup, on peut se référer à l'implémentation d'AutoDev pour construire le Group correspondant :
< group id = " AutoDevIntentionsActionGroup " class = " cc.unitmesh.devti.intentions.IntentionsActionGroup "
icon = " cc.unitmesh.devti.AutoDevIcons.AI_COPILOT " searchable = " false " >
< add-to-group group-id = " ShowIntentionsGroup " relative-to-action = " ShowIntentionActions " anchor = " after " />
</ group >
En raison de la stratégie de plate-forme d'Intellij, la différence entre l'exécution dans un IDE Java (Intellij IDEA) et d'autres IDE tels que l'IDE Python (Pycharm) devient encore plus grande. Nous devons assurer une compatibilité basée sur les produits multiplateformes. Pour une introduction détaillée, veuillez vous référer à : Compatibilité des plugins avec les produits de la plateforme IntelliJ.
Premièrement, l'architecture du plug-in est encore plus modularisée, c'est-à-dire que différents modules sont fournis pour différentes langues. Voici l'architecture modulaire d'AutoDev :
java/ # Java 语言插件
src/main/java/cc/unitmesh/autodev/ # Java 语言入口
src/main/resources/META-INF/plugin.xml
plugin/ # 多平台入口
src/main/resources/META-INF/plugin.xml
src/ # 即核心模块
main/resource/META-INF/core.plugin.xml
Dans plugin/plugin.xml
, nous devons ajouter les depends
et extensions
correspondantes. Voici un exemple :
< idea-plugin package = " cc.unitmesh " xmlns : xi = " http://www.w3.org/2001/XInclude " allow-bundled-update = " true " >
< xi : include href = " /META-INF/core.xml " xpointer = " xpointer(/idea-plugin/*) " />
< content >
< module name = " cc.unitmesh.java " />
<!-- 其它模块 -->
</ content >
</ idea-plugin >
Dans java/plugin.xml
, nous devons ajouter les depends
et extensions
correspondantes. Voici un exemple :
< idea-plugin package = " cc.unitmesh.java " >
<!-- suppress PluginXmlValidity -->
< dependencies >
< plugin id = " com.intellij.modules.java " />
< plugin id = " org.jetbrains.plugins.gradle " />
</ dependencies >
</ idea-plugin >
Par la suite, Intellij chargera automatiquement le module correspondant pour obtenir un support multilingue. En fonction des différentes langues que nous prévoyons de prendre en charge, nous avons besoin plugin.xml
correspondant, tel que :
cc.unitmesh.javascript.xml
cc.unitmesh.rust.xml
cc.unitmesh.python.xml
cc.unitmesh.kotlin.xml
cc.unitmesh.java.xml
cc.unitmesh.go.xml
cc.unitmesh.cpp.xml
Enfin, implémentez simplement les fonctions correspondantes dans différents modules de langage.
Pour simplifier ce processus, nous utilisons Unit Eval pour montrer comment créer deux contextes similaires.
Grâce à l'analyse de code statique, nous pouvons obtenir la fonction actuelle, la classe actuelle, le fichier actuel, etc. Combinez ensuite la similarité du chemin pour trouver le contexte le plus pertinent.
private fun findRelatedCode ( container : CodeContainer ): List < CodeDataStruct > {
// 1. collects all similar data structure by imports if exists in a file tree
val byImports = container. Imports
.mapNotNull {
context.fileTree[it. Source ]?.container?. DataStructures
}
.flatten()
// 2. collects by inheritance tree for some node in the same package
val byInheritance = container. DataStructures
.map {
(it. Implements + it. Extend ).mapNotNull { i ->
context.fileTree[i]?.container?. DataStructures
}.flatten()
}
.flatten()
val related = (byImports + byInheritance).distinctBy { it. NodeName }
// 3. convert all similar data structure to uml
return related
}
class RelatedCodeStrategyBuilder ( private val context : JobContext ) : CodeStrategyBuilder {
override fun build (): List < TypedIns > {
// ...
val findRelatedCodeDs = findRelatedCode(container)
val relatedCodePath = findRelatedCodeDs.map { it. FilePath }
val jaccardSimilarity = SimilarChunker .pathLevelJaccardSimilarity(relatedCodePath, currentPath)
val relatedCode = jaccardSimilarity.mapIndexed { index, d ->
findRelatedCodeDs[index] to d
}.sortedByDescending {
it.second
}.take( 3 ).map {
it.first
}
// ...
}
}
Pour le code ci-dessus, nous pouvons utiliser les informations d'importation du code dans le cadre du code concerné. Recherchez ensuite le code pertinent via la relation d'héritage du code. Enfin, le contexte le plus proche est trouvé grâce à la similarité de chemin.
Recherchez d'abord, puis trouvez le code associé grâce à la similarité du code. La logique de base est affichée :
fun pathLevelJaccardSimilarity ( chunks : List < String >, text : String ): List < Double > {
// ...
}
fun tokenize ( chunk : String ): List < String > {
return chunk.split( Regex ( " [^a-zA-Z0-9] " )).filter { it.isNotBlank() }
}
fun similarityScore ( set1 : Set < String >, set2 : Set < String >): Double {
// ...
}
Pour plus de détails, voir : SimilarChunker
FAIRE
TreeSitter est un framework permettant de générer des analyseurs personnalisés efficaces, développé par GitHub. Il utilise un analyseur LR(1), ce qui signifie qu'il peut analyser n'importe quelle langue en temps O(n) au lieu de temps O(n²). Il utilise également une technique appelée « réutilisation de l'arbre syntaxique » qui lui permet de mettre à jour les arbres syntaxiques sans réanalyser l'intégralité du fichier.
Étant donné que TreeSitter fournit déjà un support multilingue, vous pouvez utiliser Node.js, Rust et d'autres langages pour créer les plug-ins correspondants. Voir : TreeSitter pour plus de détails.
Selon nos intentions, il existe différentes manières d'utiliser TreeSitter :
Analyser le symbole
Dans le moteur de recherche en langage naturel Bloop, nous utilisons TreeSitter pour analyser les symboles afin d'obtenir une meilleure qualité de recherche.
; ; methods
(method_declaration
name: (identifier) @hoist.definition.method)
Ensuite, décidez comment l'afficher en fonction du type :
pub static JAVA : TSLanguageConfig = TSLanguageConfig {
language_ids : & [ "Java" ] ,
file_extensions : & [ "java" ] ,
grammar : tree_sitter_java :: language ,
scope_query : MemoizedQuery :: new ( include_str ! ( "./scopes.scm" ) ) ,
hoverable_query : MemoizedQuery :: new (
r#"
[(identifier)
(type_identifier)] @hoverable
"# ,
) ,
namespaces : & [ & [
// variables
"local" ,
// functions
"method" ,
// namespacing, modules
"package" ,
"module" ,
// types
"class" ,
"enum" ,
"enumConstant" ,
"record" ,
"interface" ,
"typedef" ,
// misc.
"label" ,
] ] ,
} ;
Code de bloc
Voici comment TreeSitter est utilisé pour améliorer le fragment de code de LlamaIndex en nettoyant les CST Tree-Sitter :
from tree_sitter import Tree
def chunker (
tree : Tree ,
source_code : bytes ,
MAX_CHARS = 512 * 3 ,
coalesce = 50 # Any chunk less than 50 characters long gets coalesced with the next chunk
) -> list [ Span ]:
# 1. Recursively form chunks based on the last post (https://docs.sweep.dev/blogs/chunking-2m-files)
def chunk_node ( node : Node ) -> list [ Span ]:
chunks : list [ Span ] = []
current_chunk : Span = Span ( node . start_byte , node . start_byte )
node_children = node . children
for child in node_children :
if child . end_byte - child . start_byte > MAX_CHARS :
chunks . append ( current_chunk )
current_chunk = Span ( child . end_byte , child . end_byte )
chunks . extend ( chunk_node ( child ))
elif child . end_byte - child . start_byte + len ( current_chunk ) > MAX_CHARS :
chunks . append ( current_chunk )
current_chunk = Span ( child . start_byte , child . end_byte )
else :
current_chunk += Span ( child . start_byte , child . end_byte )
chunks . append ( current_chunk )
return chunks
chunks = chunk_node ( tree . root_node )
# 2. Filling in the gaps
for prev , curr in zip ( chunks [: - 1 ], chunks [ 1 :]):
prev . end = curr . start
curr . start = tree . root_node . end_byte
# 3. Combining small chunks with bigger ones
new_chunks = []
current_chunk = Span ( 0 , 0 )
for chunk in chunks :
current_chunk += chunk
if non_whitespace_len ( current_chunk . extract ( source_code )) > coalesce