ในปี 2023 ความนิยมของ Generative AI จะทำให้องค์กรต่างๆ หันมาใช้การเขียนโค้ดแบบใช้ AI มากขึ้นเรื่อยๆ สิ่งที่แตกต่างเล็กน้อยจาก GitHub Copilot ที่เปิดตัวในปี 2021 คือการเติมโค้ดให้สมบูรณ์เป็นเพียงหนึ่งในหลายๆ สถานการณ์ บริษัทจำนวนมากกำลังสำรวจสถานการณ์ต่างๆ เช่น การสร้างโค้ดที่สมบูรณ์และการตรวจสอบโค้ดตามความต้องการ และยังแนะนำ generative AI เพื่อปรับปรุงประสิทธิภาพการพัฒนาอีกด้วย
ในบริบทนี้ เรา (ชุมชนโอเพ่นซอร์สของ Thoughtworks) ยังได้จัดทำชุดเครื่องมือเสริม AI แบบโอเพ่นซอร์สเพื่อช่วยให้องค์กรจำนวนมากขึ้นสร้างผู้ช่วยเขียนโค้ดที่ได้รับความช่วยเหลือจาก AI ของตนเอง:
เนื่องจากเมื่อเราออกแบบ AutoDev โมเดลโอเพ่นซอร์สต่างๆ จึงมีการพัฒนาอย่างต่อเนื่อง ในบริบทนี้มีขั้นตอนดังนี้:
ดังนั้นบทช่วยสอนนี้จึงเน้นไปที่สามขั้นตอนนี้ด้วย นอกจากนี้ จากประสบการณ์ของเรา กลุ่มเทคโนโลยีตัวอย่างสำหรับบทช่วยสอนนี้:
เนื่องจากประสบการณ์ของเราในด้าน AI ค่อนข้างจำกัด จึงอาจมีข้อผิดพลาดเกิดขึ้นอย่างหลีกเลี่ยงไม่ได้ ดังนั้นเราจึงหวังว่าจะทำงานร่วมกับนักพัฒนาจำนวนมากขึ้นเพื่อสร้างโครงการโอเพ่นซอร์สนี้
เมื่อรวมกับส่วนปัญญาประดิษฐ์ของรายงาน "ระบบนิเวศของนักพัฒนา" ของ JetBrains ปี 2023 แล้ว เราสามารถสรุปสถานการณ์ทั่วไปบางส่วนที่สะท้อนถึงด้านที่ AI เชิงกำเนิดสามารถมีบทบาทในกระบวนการพัฒนาได้ นี่คือสถานการณ์หลักบางส่วน:
เมื่อเราสร้าง AutoDev เรายังค้นพบสถานการณ์ต่างๆ เช่น การสร้าง SQL DDL, การสร้างข้อกำหนด, TDD เป็นต้น ดังนั้น. เรามอบความสามารถในการปรับแต่งสถานการณ์เพื่อให้นักพัฒนาสามารถปรับแต่งความสามารถ AI ของตนเองได้ สำหรับรายละเอียด โปรดดู: https://ide.unitmesh.cc/customize
ในการเขียนโค้ดรายวัน มีหลายสถานการณ์ที่มีข้อกำหนดที่แตกต่างกันสำหรับความเร็วในการตอบสนองของ AI (ตามตัวอย่าง):
ฉาก | ความเร็วในการตอบสนอง | สร้างข้อกำหนดด้านคุณภาพ | ขนาดที่คาดไว้ | แสดงให้เห็น |
---|---|---|---|---|
กรอกโค้ด | เร็ว | กลาง | 1~6B | การกรอกโค้ดให้สมบูรณ์เป็นสถานการณ์ที่พบบ่อยที่สุดในการเขียนโค้ดรายวัน และความเร็วในการตอบสนองเป็นสิ่งสำคัญ |
การสร้างเอกสาร | กลาง | กลาง | 1 | การสร้างเอกสารจำเป็นต้องมีความเข้าใจอย่างถ่องแท้เกี่ยวกับโครงสร้างโค้ด ความเร็วและคุณภาพก็มีความสำคัญไม่แพ้กัน |
ตรวจสอบรหัส | เร็ว | กลาง | 1 | การตรวจสอบโค้ดจำเป็นต้องมีคำแนะนำคุณภาพสูงแต่ก็ต้องตอบสนองให้ได้มากที่สุดด้วย |
การสร้างการทดสอบหน่วย | เร็ว | กลาง | 6B~ | การทดสอบหน่วยสร้างบริบทน้อยลง และการตอบสนองและคุณภาพ AI ก็มีความสำคัญไม่แพ้กัน |
การปรับโครงสร้างรหัสใหม่ | กลาง | สูง | 32B~ | การปรับโครงสร้างโค้ดใหม่อาจต้องมีความเข้าใจตามบริบทมากขึ้น และเวลาตอบสนองอาจช้าลงพอสมควร |
การสร้างความต้องการ | กลาง | สูง | 32B~ | การสร้างความต้องการเป็นสถานการณ์ที่ค่อนข้างซับซ้อน และความเร็วในการตอบสนองสามารถชะลอตัวลงได้ปานกลางเพื่อให้มั่นใจในความถูกต้อง |
การค้นหาและการตีความรหัสภาษาธรรมชาติ | ปานกลาง-ต่ำ | สูง | 32B~ | การค้นหาและการตีความรหัสภาษาธรรมชาติเป็นสถานการณ์ที่ค่อนข้างซับซ้อน และความเร็วในการตอบสนองสามารถช้าลงได้ปานกลางเพื่อให้มั่นใจในความถูกต้อง |
ป.ล.: 32B ในที่นี้แสดงเป็นลำดับความสำคัญเท่านั้น เนื่องจากผลกระทบจะดีกว่าเมื่อใช้โมเดลที่ใหญ่กว่า
ดังนั้นเราจึงสรุปได้เป็น: รุ่นใหญ่หนึ่งรุ่น รุ่นกลางหนึ่งรุ่น รุ่นไมโครหนึ่งรุ่น และ รุ่นสามรุ่น ซึ่งให้การเข้ารหัสที่ได้รับความช่วยเหลือจาก AI ที่ครอบคลุม:
การเติมโค้ด AI ให้สมบูรณ์สามารถรวมเครื่องมือ IDE เพื่อวิเคราะห์บริบทของโค้ดและกฎของภาษาการเขียนโปรแกรม และ AI จะสร้างหรือแนะนำส่วนย่อยของโค้ดโดยอัตโนมัติ ในเครื่องมือเติมโค้ดให้สมบูรณ์ซึ่งคล้ายกับ GitHub Copilot มักจะแบ่งออกเป็นสามโหมดย่อย:
กรอกแบบอินไลน์ (อินไลน์)
คล้ายกับโหมด FIM (เติมตรงกลาง) เนื้อหาที่เสร็จสมบูรณ์จะอยู่ในบรรทัดปัจจุบัน ตัวอย่างเช่น: BlotPost blogpost = new
การดำเนินการคือ: BlogPost();
เพื่อให้บรรลุ: BlogPost blogpost = new BlogPost();
เราสามารถใช้ Deepseek Coder เป็นตัวอย่างเพื่อดูผลกระทบในสถานการณ์นี้:
< |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| >
ที่นี่เราต้องรวมโค้ดก่อนและหลังเคอร์เซอร์
เสร็จสิ้นในบล็อก (InBlock)
บรรลุผลสำเร็จผ่านการเรียนรู้ตามบริบท (In-Context Learning) เนื้อหาที่สมบูรณ์จะอยู่ในบล็อกฟังก์ชันปัจจุบัน ตัวอย่างเช่น รหัสต้นฉบับคือ:
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
}
รหัสที่เสร็จสมบูรณ์คือ:
val blogPost = BlogPost (
title = blogDto.title,
content = blogDto.content,
author = blogDto.author
)
return blogRepository.save(blogPost)
อาฟเตอร์บล็อค
ทำได้โดยการเรียนรู้ตามบริบท (In-Context Learning) สำเร็จหลังจากบล็อกฟังก์ชันปัจจุบัน เช่น เสร็จสิ้นฟังก์ชันใหม่หลังจากบล็อกฟังก์ชันปัจจุบัน ตัวอย่างเช่น รหัสต้นฉบับคือ:
fun createBlog ( blogDto : CreateBlogDto ): BlogPost {
// ...
}
รหัสที่เสร็จสมบูรณ์คือ:
fun updateBlog ( id : Long , blogDto : CreateBlogDto ): BlogPost {
// ...
}
fun deleteBlog ( id : Long ) {
// ...
}
เมื่อเราสร้างฟังก์ชันการเติม AI ให้สมบูรณ์ที่สอดคล้องกัน เรายังต้องพิจารณานำไปใช้กับชุดข้อมูลรูปแบบที่สอดคล้องกันเพื่อปรับปรุงคุณภาพของการเติมให้สมบูรณ์และมอบประสบการณ์ผู้ใช้ที่ดีขึ้น
แหล่งข้อมูลที่เกี่ยวข้องสำหรับการเขียนบทความนี้:
คำอธิบายโค้ดได้รับการออกแบบมาเพื่อช่วยให้นักพัฒนาจัดการและทำความเข้าใจฐานโค้ดขนาดใหญ่ได้อย่างมีประสิทธิภาพมากขึ้น ผู้ช่วยเหล่านี้สามารถตอบคำถามเกี่ยวกับฐานโค้ด จัดเตรียมเอกสาร ค้นหาโค้ด ระบุแหล่งที่มาของข้อผิดพลาด ลดการทำซ้ำโค้ด ฯลฯ ซึ่งจะช่วยปรับปรุงประสิทธิภาพการพัฒนา ลดอัตราข้อผิดพลาด และลดภาระงานของนักพัฒนา
ในสถานการณ์นี้ ขึ้นอยู่กับคุณภาพของรุ่นที่เราคาดหวัง โดยทั่วไปจะประกอบด้วยสองรุ่น: หนึ่งรุ่นใหญ่และหนึ่งรุ่นไมโคร หรือรุ่นกลางหนึ่งรุ่นและรุ่นไมโครหนึ่งรุ่นจะให้ผลลัพธ์ที่ดีกว่าในแง่ของคุณภาพของรุ่น เมื่อรวมกับประสบการณ์การออกแบบของเราในเครื่องมือ Chocolate Factory แล้ว โดยปกติแล้วฟังก์ชันดังกล่าวสามารถแบ่งออกเป็นหลายขั้นตอน:
เนื่องจากแอปพลิเคชัน RAG แบ่งออกเป็นสองส่วน: การจัดทำดัชนีและการสืบค้น
ในขั้นตอนการจัดทำดัชนี เราจำเป็นต้องจัดทำดัชนีฐานโค้ด ซึ่งเกี่ยวข้องกับการแบ่งส่วนข้อความ การทำเวกเตอร์ การทำดัชนีฐานข้อมูล และเทคโนโลยีอื่นๆ องค์ประกอบที่ท้าทายที่สุดประการหนึ่งคือการแยก กฎการแยกที่เราอ้างถึงคือ: https://docs.sweep.dev/blogs/chunking-2m-files ตอนนี้:
ในสถานการณ์ที่แตกต่างกัน เรายังสามารถแบ่งได้หลายวิธี ตัวอย่างเช่น ในโรงงานช็อกโกแลต เราแบ่งผ่าน AST เพื่อรับรองคุณภาพของบริบทที่สร้างขึ้น
ในขั้นตอนการสืบค้น เราจำเป็นต้องรวมเทคโนโลยีการค้นหาแบบดั้งเดิมบางอย่างของเรา เช่น การค้นหาแบบเวกเตอร์ การค้นหาเส้นทาง ฯลฯ เพื่อให้มั่นใจในคุณภาพของการค้นหา ในเวลาเดียวกัน ในสถานการณ์ภาษาจีน เรายังต้องพิจารณาปัญหาของการแปลงเป็นภาษาจีน เช่น การแปลงภาษาอังกฤษเป็นภาษาจีน เพื่อให้มั่นใจในคุณภาพของการค้นหา
สำหรับความช่วยเหลือรายวัน เรายังสามารถทำได้ผ่าน AI เชิงสร้างสรรค์ เช่น การสร้าง SQL DDL โดยอัตโนมัติ การสร้างกรณีทดสอบโดยอัตโนมัติ การสร้างข้อกำหนดโดยอัตโนมัติ เป็นต้น สิ่งเหล่านี้สามารถทำได้โดยการปรับแต่งคำที่พร้อมท์และรวมความรู้เฉพาะด้านเข้าด้วยกันเท่านั้น ดังนั้นฉันจะไม่ลงรายละเอียดที่นี่
นอกจากโมเดลแล้ว บริบทยังเป็นปัจจัยสำคัญที่ส่งผลต่อความสามารถในการช่วยเหลือของ AI เมื่อเราสร้าง AutoDev เรายังค้นพบโหมดบริบทที่แตกต่างกันสองโหมด:
การเปรียบเทียบอย่างง่ายมีดังนี้:
บริบทที่เกี่ยวข้อง | บริบทที่คล้ายกัน | |
---|---|---|
เทคโนโลยีการค้นหา | การวิเคราะห์โค้ดแบบคงที่ | การค้นหาความคล้ายคลึงกัน |
ข้อมูลโครงสร้างข้อมูล | AST, ซีเอฟจี | ก้อนที่คล้ายกัน |
ความสามารถข้ามแพลตฟอร์ม | ขึ้นอยู่กับ IDE หรือตัวแยกวิเคราะห์อิสระ | ไม่ขึ้นอยู่กับแพลตฟอร์มเฉพาะ |
คุณภาพตามบริบท | สูงมาก | สูง |
สร้างผลลัพธ์ | สูงมาก | สูง |
ต้นทุนการสร้าง | ขึ้นอยู่กับภาษาและแพลตฟอร์ม | ต่ำ |
เมื่อการสนับสนุน IDE มีจำกัด บริบทที่เกี่ยวข้องจะนำมาซึ่ง ประสิทธิภาพด้านต้นทุน ที่สูงขึ้น
GitHub Copilot ใช้รูปแบบสถาปัตยกรรมบริบทที่คล้ายกัน และสถาปัตยกรรมโดยละเอียดมีการแบ่งชั้นดังนี้:
ในเอกสารการวิจัยของโครงการ Copilot-Explorer "สาธารณะ" คุณสามารถดูได้ว่าสร้าง Prompt ได้อย่างไร ต่อไปนี้เป็นคำขอพร้อมท์ที่ส่งไปที่:
{
"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
}
]
}
ใน:
prefix
ใช้ในการสร้างพรอมต์ถูกสร้างขึ้นจาก promptElements ซึ่งรวมถึง: BeforeCursor
, AfterCursor
, SimilarFile
, ImportedFile
, LanguageMarker
, PathMarker
, RetrievalSnippet
และประเภทอื่นๆ จากชื่อของ PromptElementKind
หลายชื่อ เราก็สามารถเห็นความหมายที่แท้จริงของมันได้เช่นกันsuffix
ที่ใช้ในการสร้างพรอมต์จะถูกกำหนดโดยส่วนที่เคอร์เซอร์อยู่ ตามขีดจำกัดบนของโทเค็น (2048) จำนวนตำแหน่งที่เหลือที่ต้องคำนวณ การคำนวณโทเค็นที่นี่คือการคำนวณโทเค็น LLM จริง ใน Copilot คำนวณโดย Cushman002 ความยาวโทเค็นของอักขระภาษาจีนจะแตกต่างกัน เช่น: { context: "console.log('你好,世界')", lineCount: 1, tokenLength: 30 }
โดยที่ความยาวของเนื้อหาในบริบทคือ 20 แต่ tokenLength คือ 30 ความยาวของตัวอักษรจีนคือ 5 (รวม ,
) และโทเค็นที่ครอบครองโดยอักขระตัวเดียวคือ 3นี่คือตัวอย่างโดยละเอียดเพิ่มเติมของบริบทแอปพลิเคชัน 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 {
//...
ในบริบทของการคำนวณ GitHub Copilot ใช้ค่าสัมประสิทธิ์ Jaccard (Jaccardคล้ายคลึงกัน) ส่วนนี้ของการใช้งานถูกนำไปใช้ใน Agent สำหรับตรรกะโดยละเอียดเพิ่มเติม โปรดดูที่: หลังจากใช้เวลามากกว่าครึ่งเดือน ในที่สุดฉันก็ทำวิศวกรรมย้อนกลับ Github Copilot
แหล่งข้อมูลที่เกี่ยวข้อง:
ตามที่กล่าวไว้ข้างต้น โค้ดที่เกี่ยวข้องอาศัย การวิเคราะห์โค้ดแบบคงที่ โดยส่วนใหญ่ได้รับความช่วยเหลือจากข้อมูลโครงสร้างของโค้ด เช่น AST, CFG, DDG เป็นต้น ในสถานการณ์และแพลตฟอร์มต่างๆ เราสามารถรวมเครื่องมือวิเคราะห์โค้ดแบบคงที่ต่างๆ ต่อไปนี้คือเครื่องมือวิเคราะห์โค้ดแบบคงที่ทั่วไปบางส่วน:
ในสถานการณ์สมมติที่เสร็จสมบูรณ์ ผ่านการวิเคราะห์โค้ดแบบคงที่ เราสามารถรับบริบทปัจจุบัน เช่น ฟังก์ชันปัจจุบัน คลาสปัจจุบัน ไฟล์ปัจจุบัน ฯลฯ ต่อไปนี้เป็นตัวอย่างบริบทของ AutoDev สำหรับการสร้างการทดสอบหน่วย:
// 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 ) {
ในตัวอย่างนี้ บริบทของฟังก์ชัน createBlog
ถูกวิเคราะห์เพื่อรับคลาสอินพุตและเอาต์พุตของฟังก์ชัน: CreateBlogRequest
ข้อมูล BlogPost
และข้อมูลคลาส BlogService ซึ่งจัดเตรียมให้กับโมเดลเป็นบริบท (ระบุในความคิดเห็น) ณ จุดนี้ แบบจำลองจะสร้างตัวสร้างที่แม่นยำยิ่งขึ้น รวมถึงกรณีทดสอบที่แม่นยำยิ่งขึ้น
เนื่องจากบริบทที่เกี่ยวข้องอาศัยการวิเคราะห์โค้ดแบบคงที่ของภาษาต่างๆ และ API ของ IDE ที่แตกต่างกัน เราจึงต้องปรับให้เข้ากับภาษาและ IDE ที่แตกต่างกันด้วย ในแง่ของต้นทุนการก่อสร้างจะมีราคาแพงกว่าเมื่อเทียบกับบริบทที่คล้ายคลึงกัน
IDE และบรรณาธิการเป็นเครื่องมือหลักสำหรับนักพัฒนา และค่าใช้จ่ายในการออกแบบและการเรียนรู้ก็ค่อนข้างสูง ขั้นแรก เราสามารถใช้เทมเพลตอย่างเป็นทางการเพื่อสร้าง:
จากนั้นเพิ่มฟังก์ชันการทำงานที่ด้านบน (มันไม่ง่ายนัก) แน่นอนไม่ใช่ ต่อไปนี้เป็นทรัพยากรปลั๊กอิน IDEA บางส่วนสำหรับการอ้างอิง:
แน่นอนว่า การอ้างอิงถึงปลั๊กอิน AutoDev จะเหมาะสมกว่า
คุณสามารถใช้เทมเพลตอย่างเป็นทางการโดยตรงเพื่อสร้างปลั๊กอินที่เกี่ยวข้อง: https://github.com/JetBrains/intellij-platform-plugin-template
สำหรับการนำปลั๊กอิน IDEA ไปใช้นั้น ส่วนใหญ่จะใช้งานผ่าน Action และ Listener ซึ่งจำเป็นต้องลงทะเบียนใน plugin.xml
เท่านั้น สำหรับรายละเอียด โปรดดูเอกสารอย่างเป็นทางการ: IntelliJ Platform Plugin SDK
เนื่องจากเราไม่ได้พิจารณาปัญหาความเข้ากันได้กับ AutoDev เวอร์ชัน IDE ในระยะเริ่มต้น เพื่อให้เข้ากันได้กับ IDE เวอร์ชันเก่าในภายหลัง เราจึงจำเป็นต้องดำเนินการประมวลผลความเข้ากันได้กับปลั๊กอิน ดังนั้น ตามที่อธิบายไว้ในเอกสารอย่างเป็นทางการ: Build Number Ranges เราจะเห็นว่าเวอร์ชันต่างๆ มีข้อกำหนดที่แตกต่างกันสำหรับ JDK ต่อไปนี้เป็นข้อกำหนดสำหรับเวอร์ชันที่แตกต่างกัน:
หมายเลขสาขา | เวอร์ชันแพลตฟอร์ม IntelliJ |
---|---|
233 | 2023.3 |
232 | 2023.2 |
231 | 2023.1 |
223 | 2022.3 |
222 | 2022.2 หมายเหตุ ตอนนี้ต้องใช้ Java 17 (โพสต์บล็อก) |
221 | 2022.1 |
213 | 2021.3 |
212 | 2021.2 |
211 | 2021.1 |
203 | 2020.3 หมายเหตุ ตอนนี้ต้องใช้ Java 11 (โพสต์บล็อก) |
และกำหนดค่าเป็น gradle.properties
:
pluginSinceBuild = 223
pluginUntilBuild = 233.*
การกำหนดค่าความเข้ากันได้ในภายหลังนั้นยุ่งยาก ดังนั้นคุณจึงสามารถอ้างอิงถึงการออกแบบของ AutoDev ได้
ในแง่ของการเติมโค้ดอัตโนมัติ ผู้ผลิตในประเทศส่วนใหญ่อ้างถึงการใช้งาน GitHub Copilot และตรรกะก็ไม่ซับซ้อน
ทริกเกอร์โดยใช้ปุ่มลัด
โดยจะตรวจสอบอินพุตของผู้ใช้ในการดำเนินการเป็นหลัก จากนั้น:
การทำงาน | ปุ่มลัด | แสดงให้เห็น |
---|---|---|
คำขอเสร็จสิ้น | Alt + / | รับบริบทปัจจุบัน จากนั้นรับผลลัพธ์ที่สมบูรณ์ผ่านแบบจำลอง |
ใช้อินเลย์ | TAB | แสดงผลเสร็จสิ้นบน IDE |
ทิ้งอินเลย์ | ESC | ยกเลิกการเสร็จสิ้น |
วงจรถัดไปอินเลย์ | Alt + ] | สลับไปยังผลลัพธ์ที่สมบูรณ์ถัดไป |
วงจรก่อนหน้าอินเลย์ | Alt + [ | สลับไปยังผลลัพธ์ที่สมบูรณ์ก่อนหน้า |
ใช้วิธีการเรียกอัตโนมัติ
โดยจะตรวจสอบอินพุตของผู้ใช้เป็นหลักผ่าน EditorFactoryListener
จากนั้นทริกเกอร์ผลลัพธ์ความสำเร็จที่แตกต่างกันตามอินพุตที่ต่างกัน รหัสหลักมีดังนี้:
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)
}
}
}
จากนั้นตามอินพุตที่ต่างกัน ผลลัพธ์ของความสมบูรณ์ที่แตกต่างกันจะถูกกระตุ้นและโครงสร้างได้รับการประมวลผล
เรนเดอร์โค้ดเสร็จสิ้น
ต่อจากนั้น เราจำเป็นต้องใช้ Inlay Render ซึ่งสืบทอดมาจาก EditorCustomElementRenderer
เมื่อรวมกับความสามารถของอินเทอร์เฟซของ IDE เราจำเป็นต้องเพิ่มการดำเนินการที่เกี่ยวข้อง กลุ่มที่เกี่ยวข้อง และไอคอนที่เกี่ยวข้อง ต่อไปนี้เป็นตัวอย่างของการดำเนินการ:
<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
ต่อไปนี้คือ ActionGroups บางส่วนของ AutoDev:
รหัสกลุ่ม | เอไอใช้ | คำอธิบาย |
---|---|---|
แสดงความตั้งใจกรุ๊ป | การปรับโครงสร้างโค้ด การตีความโค้ด การสร้างโค้ด การทดสอบโค้ด | ใช้เพื่อแสดงคำแนะนำในบริบทของโค้ดและเข้าถึงได้ผ่านทางทางลัด Alt + Enter และ ⌥ + Enter บน macOS |
ConsoleEditorPopupMenu | แก้ไขข้อผิดพลาด | เมนูที่แสดงในคอนโซล เช่น คอนโซลของโครงสร้างการทำงานของโปรแกรม |
Vcs.MessageActionGroup | การสร้างข้อมูลโค้ด | เมนูสำหรับเขียนข้อความยืนยันใน VCS |
Vcs.Log.ContextMenu | การทบทวนโค้ด การตีความโค้ด การสร้างโค้ด | เมนูสำหรับการดูบันทึกใน VCS ฟังก์ชั่นที่ใช้ได้: การตรวจสอบรหัสโดย AI การสร้างบันทึกการเผยแพร่ |
ตัวแก้ไข PopupMenu | ทั้งหมดเป็นที่ยอมรับ | เมนูคลิกขวา คุณยังสามารถเพิ่ม ActionGroup ที่เกี่ยวข้องได้ |
เมื่อเขียน ShowIntentionsGroup เราสามารถอ้างถึงการใช้งาน AutoDev เพื่อสร้างกลุ่มที่เกี่ยวข้อง:
< 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 >
เนื่องจากกลยุทธ์แพลตฟอร์มของ Intellij ความแตกต่างระหว่างการทำงานใน Java IDE (Intellij IDEA) และ IDE อื่นๆ เช่น Python IDE (Pycharm) จึงยิ่งใหญ่ยิ่งขึ้น เราจำเป็นต้องจัดเตรียมความเข้ากันได้ตามผลิตภัณฑ์หลายแพลตฟอร์ม สำหรับการแนะนำโดยละเอียด โปรดดูที่: ความเข้ากันได้ของปลั๊กอินกับผลิตภัณฑ์แพลตฟอร์ม IntelliJ
ประการแรก สถาปัตยกรรมปลั๊กอินจะถูกทำให้เป็นโมดูลเพิ่มเติม กล่าวคือ มีการจัดเตรียมโมดูลที่แตกต่างกันสำหรับภาษาที่แตกต่างกัน ต่อไปนี้เป็นสถาปัตยกรรมโมดูลาร์ของ 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
ใน plugin/plugin.xml
เราจำเป็นต้องเพิ่ม depends
และ extensions
ที่เกี่ยวข้อง ต่อไปนี้เป็นตัวอย่าง:
< 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 >
ใน java/plugin.xml
เราจำเป็นต้องเพิ่ม depends
และ extensions
ที่เกี่ยวข้อง ต่อไปนี้เป็นตัวอย่าง:
< idea-plugin package = " cc.unitmesh.java " >
<!-- suppress PluginXmlValidity -->
< dependencies >
< plugin id = " com.intellij.modules.java " />
< plugin id = " org.jetbrains.plugins.gradle " />
</ dependencies >
</ idea-plugin >
ต่อจากนั้น Intellij จะโหลดโมดูลที่เกี่ยวข้องโดยอัตโนมัติเพื่อรองรับหลายภาษา ขึ้นอยู่กับภาษาต่างๆ ที่เราคาดว่าจะรองรับ เราต้องการ plugin.xml
ที่เกี่ยวข้อง เช่น:
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
สุดท้าย เพียงใช้ฟังก์ชันที่เกี่ยวข้องในโมดูลภาษาต่างๆ
เพื่อให้กระบวนการนี้ง่ายขึ้น เราใช้ Unit Eval เพื่อแสดงวิธีสร้างบริบทสองรายการที่คล้ายคลึงกัน
ด้วยการวิเคราะห์โค้ดแบบคงที่ เราสามารถรับฟังก์ชันปัจจุบัน คลาสปัจจุบัน ไฟล์ปัจจุบัน ฯลฯ จากนั้นจึงรวมความคล้ายคลึงกันของเส้นทางเข้าด้วยกันเพื่อค้นหาบริบทที่เกี่ยวข้องมากที่สุด
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
}
// ...
}
}
สำหรับโค้ดข้างต้น เราสามารถใช้ข้อมูลการนำเข้าของโค้ดเป็นส่วนหนึ่งของโค้ดที่เกี่ยวข้องได้ จากนั้นค้นหาโค้ดที่เกี่ยวข้องผ่านความสัมพันธ์แบบสืบทอดของโค้ด ในที่สุด บริบทที่ใกล้เคียงที่สุดก็พบได้ผ่านความคล้ายคลึงกันของเส้นทาง
ค้นหาก่อน จากนั้นค้นหาโค้ดที่เกี่ยวข้องผ่านความคล้ายคลึงกันของโค้ด ตรรกะหลักจะแสดง:
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 {
// ...
}
สำหรับรายละเอียด โปรดดูที่:likeChunker
สิ่งที่ต้องทำ
TreeSitter เป็นเฟรมเวิร์กสำหรับการสร้างพาร์เซอร์แบบกำหนดเองที่มีประสิทธิภาพ ซึ่งพัฒนาโดย GitHub ใช้ตัวแยกวิเคราะห์ LR(1) ซึ่งหมายความว่าสามารถแยกวิเคราะห์ภาษาใด ๆ ในเวลา O(n) แทนที่จะเป็นเวลา O(n²) นอกจากนี้ยังใช้เทคนิคที่เรียกว่า "การใช้แผนผังไวยากรณ์ซ้ำ" ซึ่งช่วยให้อัปเดตแผนผังไวยากรณ์โดยไม่ต้องแยกวิเคราะห์ไฟล์ทั้งหมดใหม่
เนื่องจาก TreeSitter ให้การสนับสนุนหลายภาษาแล้ว คุณจึงสามารถใช้ Node.js, Rust และภาษาอื่นๆ เพื่อสร้างปลั๊กอินที่เกี่ยวข้องได้ ดู: TreeSitter สำหรับรายละเอียด
มีหลายวิธีในการใช้ TreeSitter ขึ้นอยู่กับความตั้งใจของเรา:
สัญลักษณ์แยกวิเคราะห์
ในโค้ดเครื่องมือค้นหาภาษาธรรมชาติ Bloop เราใช้ TreeSitter เพื่อแยกวิเคราะห์สัญลักษณ์เพื่อให้ได้คุณภาพการค้นหาที่ดีขึ้น
; ; methods
(method_declaration
name: (identifier) @hoist.definition.method)
จากนั้น ตัดสินใจว่าจะแสดงอย่างไรตามประเภท:
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" ,
] ] ,
} ;
รหัสก้อน
ต่อไปนี้คือวิธีการใช้ TreeSitter ในการปรับปรุง Code Chunker ของ LlamaIndex โดยการทำความสะอาด 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