AI代码助手正在改变开发者的工作方式,从GitHub Copilot到Cursor,AI辅助编程已成为趋势。本文将详细讲解如何使用Go构建一个AI代码助手,实现代码生成、代码解释、Bug修复等功能。
一、AI代码助手架构
1.1 整体架构设计
/*
AI代码助手核心架构:
┌─────────────────────────────────────────────────────────────────┐
│ IDE插件/CLI │
│ 代码上下文、用户指令 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 代码助手服务 │
│ 上下文处理、Prompt构建、结果解析 │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ OpenAI │ │ 代码索引 │ │ 知识库 │
│ GPT-4 │ │ 语义搜索 │ │ 文档/示例 │
└───────────────┘ └───────────────┘ └───────────────┘
核心功能:
1. 代码生成:根据注释/描述生成代码
2. 代码补全:智能补全代码片段
3. 代码解释:解释代码功能和逻辑
4. Bug修复:分析并修复代码问题
5. 代码重构:优化代码结构和性能
*/
1.2 数据模型
package model
// CodeContext 代码上下文
type CodeContext struct {
FilePath string`json:"file_path"`
Language string`json:"language"`
CurrentCode string`json:"current_code"`
CursorLine int`json:"cursor_line"`
Selection string`json:"selection"`
Imports []string`json:"imports"`
Functions []string`json:"functions"`
}
// CodeRequest 代码请求
type CodeRequest struct {
Action string`json:"action"`// generate, complete, explain, fix, refactor
Instruction string`json:"instruction"`
Context *CodeContext `json:"context"`
}
// CodeResponse 代码响应
type CodeResponse struct {
Code string`json:"code"`
Explanation string`json:"explanation"`
Suggestions []string`json:"suggestions"`
}
二、代码生成服务
2.1 Prompt构建器
package assistant
import (
"fmt"
"strings"
)
// PromptBuilder Prompt构建器
type PromptBuilder struct {
templates map[string]string
}
// NewPromptBuilder 创建Prompt构建器
funcNewPromptBuilder() *PromptBuilder {
return &PromptBuilder{
templates: map[string]string{
"generate": `你是一个专业的%s程序员。请根据以下要求生成代码:
要求:%s
当前文件:%s
已有代码上下文:
%s
请生成符合以下标准的代码:
1. 代码简洁、可读性强
2. 遵循%s最佳实践
3. 包含必要的错误处理
4. 添加适当的注释
只返回代码,不要解释。`,
"complete": `你是一个代码补全助手。请补全以下%s代码:
文件:%s
当前代码:
%s
光标位置在第%d行。请补全代码,使其完整且正确。
只返回补全的代码部分。`,
"explain": `请解释以下%s代码的功能和逻辑:
%s
请从以下方面解释:
1. 代码的主要功能
2. 关键逻辑和算法
3. 输入输出说明
4. 潜在的问题或改进建议`,
"fix": `以下%s代码存在问题,请修复:
%s
错误信息:%s
请:
1. 分析问题原因
2. 提供修复后的代码
3. 解释修复内容`,
"refactor": `请重构以下%s代码,使其更加优雅和高效:
%s
重构要求:%s
请提供重构后的代码,并说明改进点。`,
},
}
}
// BuildPrompt 构建Prompt
func(b *PromptBuilder)BuildPrompt(req *CodeRequest)string {
ctx := req.Context
switch req.Action {
case"generate":
return fmt.Sprintf(b.templates["generate"],
ctx.Language, req.Instruction, ctx.FilePath, ctx.CurrentCode, ctx.Language)
case"complete":
return fmt.Sprintf(b.templates["complete"],
ctx.Language, ctx.FilePath, ctx.CurrentCode, ctx.CursorLine)
case"explain":
code := ctx.Selection
if code == "" {
code = ctx.CurrentCode
}
return fmt.Sprintf(b.templates["explain"], ctx.Language, code)
case"fix":
return fmt.Sprintf(b.templates["fix"],
ctx.Language, ctx.CurrentCode, req.Instruction)
case"refactor":
return fmt.Sprintf(b.templates["refactor"],
ctx.Language, ctx.CurrentCode, req.Instruction)
default:
return req.Instruction
}
}
2.2 代码助手服务
// CodeAssistant 代码助手
type CodeAssistant struct {
client *openai.Client
promptBuilder *PromptBuilder
codeIndex *CodeIndex
}
// NewCodeAssistant 创建代码助手
funcNewCodeAssistant(apiKey string) *CodeAssistant {
return &CodeAssistant{
client: openai.NewClient(apiKey),
promptBuilder: NewPromptBuilder(),
codeIndex: NewCodeIndex(),
}
}
// Process 处理请求
func(a *CodeAssistant)Process(ctx context.Context, req *CodeRequest)(*CodeResponse, error) {
// 构建Prompt
prompt := a.promptBuilder.BuildPrompt(req)
// 获取相关代码片段
if req.Action == "generate" {
relatedCode := a.codeIndex.Search(req.Instruction, 3)
iflen(relatedCode) > 0 {
prompt += "\n\n参考代码示例:\n" + strings.Join(relatedCode, "\n---\n")
}
}
// 调用AI
messages := []openai.ChatMessage{
{Role: "system", Content: "你是一个专业的编程助手,擅长生成高质量的代码。"},
{Role: "user", Content: prompt},
}
resp, err := a.client.CreateChatCompletion(ctx, &openai.ChatCompletionRequest{
Model: "gpt-4",
Messages: messages,
Temperature: 0.2, // 低温度保证代码准确性
MaxTokens: 2000,
})
if err != nil {
returnnil, err
}
content := resp.Choices[0].Message.Content
// 解析响应
return a.parseResponse(req.Action, content), nil
}
// parseResponse 解析响应
func(a *CodeAssistant)parseResponse(action, content string) *CodeResponse {
resp := &CodeResponse{}
switch action {
case"generate", "complete", "fix", "refactor":
// 提取代码块
resp.Code = extractCodeBlock(content)
resp.Explanation = extractExplanation(content)
case"explain":
resp.Explanation = content
}
return resp
}
// extractCodeBlock 提取代码块
funcextractCodeBlock(content string)string {
// 匹配 ```language ... ``` 格式
start := strings.Index(content, "```")
if start == -1 {
return content
}
// 跳过语言标识
start = strings.Index(content[start:], "\n") + start + 1
end := strings.LastIndex(content, "```")
if end > start {
return strings.TrimSpace(content[start:end])
}
return content
}
三、代码索引与搜索
3.1 代码索引服务
package index
import (
"context"
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"strings"
)
// CodeIndex 代码索引
type CodeIndex struct {
client *openai.Client
embeddings map[string][]float64
snippets map[string]*CodeSnippet
}
// CodeSnippet 代码片段
type CodeSnippet struct {
ID string
FilePath string
Name string
Type string// function, struct, interface
Code string
Doc string
}
// NewCodeIndex 创建代码索引
funcNewCodeIndex(client *openai.Client) *CodeIndex {
return &CodeIndex{
client: client,
embeddings: make(map[string][]float64),
snippets: make(map[string]*CodeSnippet),
}
}
// IndexDirectory 索引目录
func(idx *CodeIndex)IndexDirectory(ctx context.Context, dir string)error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error)error {
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") {
returnnil
}
return idx.indexFile(ctx, path)
})
}
// indexFile 索引文件
func(idx *CodeIndex)indexFile(ctx context.Context, path string)error {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return err
}
// 提取函数
for _, decl := range node.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {
snippet := &CodeSnippet{
ID: fmt.Sprintf("%s:%s", path, fn.Name.Name),
FilePath: path,
Name: fn.Name.Name,
Type: "function",
Code: extractFuncCode(fset, fn),
Doc: extractDoc(fn.Doc),
}
idx.snippets[snippet.ID] = snippet
}
}
// 批量生成向量
return idx.generateEmbeddings(ctx)
}
// generateEmbeddings 生成向量
func(idx *CodeIndex)generateEmbeddings(ctx context.Context)error {
var texts []string
var ids []string
for id, snippet := range idx.snippets {
if _, exists := idx.embeddings[id]; !exists {
texts = append(texts, snippet.Doc+" "+snippet.Code)
ids = append(ids, id)
}
}
iflen(texts) == 0 {
returnnil
}
// 批量获取向量
resp, err := idx.client.CreateEmbedding(ctx, texts)
if err != nil {
return err
}
for i, id := range ids {
idx.embeddings[id] = resp.Data[i].Embedding
}
returnnil
}
// Search 搜索相关代码
func(idx *CodeIndex)Search(query string, topK int) []string {
ctx := context.Background()
// 获取查询向量
resp, err := idx.client.CreateEmbedding(ctx, []string{query})
if err != nil {
returnnil
}
queryEmbedding := resp.Data[0].Embedding
// 计算相似度
type result struct {
id string
score float64
}
var results []result
for id, embedding := range idx.embeddings {
score := cosineSimilarity(queryEmbedding, embedding)
results = append(results, result{id, score})
}
// 排序
sort.Slice(results, func(i, j int)bool {
return results[i].score > results[j].score
})
// 返回代码
var codes []string
for i := 0; i < topK && i < len(results); i++ {
if snippet, exists := idx.snippets[results[i].id]; exists {
codes = append(codes, snippet.Code)
}
}
return codes
}
3.2 AST解析器
// ASTParser AST解析器
type ASTParser struct{}
// ParseGoFile 解析Go文件
func(p *ASTParser)ParseGoFile(path string)(*FileInfo, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
returnnil, err
}
info := &FileInfo{
Path: path,
Package: node.Name.Name,
Imports: make([]string, 0),
Functions: make([]*FunctionInfo, 0),
Structs: make([]*StructInfo, 0),
}
// 提取导入
for _, imp := range node.Imports {
info.Imports = append(info.Imports, strings.Trim(imp.Path.Value, `"`))
}
// 提取声明
for _, decl := range node.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
info.Functions = append(info.Functions, p.parseFuncDecl(fset, d))
case *ast.GenDecl:
for _, spec := range d.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
if st, ok := ts.Type.(*ast.StructType); ok {
info.Structs = append(info.Structs, p.parseStructType(ts.Name.Name, st))
}
}
}
}
}
return info, nil
}
type FileInfo struct {
Path string
Package string
Imports []string
Functions []*FunctionInfo
Structs []*StructInfo
}
type FunctionInfo struct {
Name string
Receiver string
Parameters []string
Returns []string
Doc string
}
type StructInfo struct {
Name string
Fields []string
}
四、代码补全
4.1 智能补全服务
package completion
import (
"context"
"strings"
)
// CompletionService 补全服务
type CompletionService struct {
client *openai.Client
}
// Complete 代码补全
func(s *CompletionService)Complete(ctx context.Context, req *CompletionRequest)(*CompletionResponse, error) {
// 构建上下文
prefix := req.Code[:req.CursorOffset]
suffix := req.Code[req.CursorOffset:]
prompt := fmt.Sprintf(`补全以下%s代码,光标位置用<CURSOR>标记:
%s<CURSOR>%s
只返回需要插入的代码,不要包含已有代码。`, req.Language, prefix, suffix)
resp, err := s.client.CreateChatCompletion(ctx, &openai.ChatCompletionRequest{
Model: "gpt-3.5-turbo",
Messages: []openai.ChatMessage{
{Role: "system", Content: "你是一个代码补全助手,只返回需要补全的代码。"},
{Role: "user", Content: prompt},
},
Temperature: 0.1,
MaxTokens: 500,
})
if err != nil {
returnnil, err
}
completion := extractCodeBlock(resp.Choices[0].Message.Content)
return &CompletionResponse{
Completion: completion,
InsertText: completion,
}, nil
}
type CompletionRequest struct {
Code string
CursorOffset int
Language string
FilePath string
}
type CompletionResponse struct {
Completion string
InsertText string
}
// ❌ 错误示例:不考虑上下文
funcBadComplete(code string)string {
// 问题:没有考虑代码上下文,补全可能不准确
return"// TODO"
}
// ✅ 正确示例:基于上下文智能补全
funcGoodComplete(ctx context.Context, s *CompletionService, req *CompletionRequest)string {
resp, err := s.Complete(ctx, req)
if err != nil {
return""
}
return resp.Completion
}
4.2 多候选补全
// MultiComplete 多候选补全
func(s *CompletionService)MultiComplete(ctx context.Context, req *CompletionRequest, n int)([]*CompletionResponse, error) {
prefix := req.Code[:req.CursorOffset]
suffix := req.Code[req.CursorOffset:]
prompt := fmt.Sprintf(`为以下%s代码提供%d个不同的补全选项:
%s<CURSOR>%s
以JSON数组格式返回,每个元素是一个补全选项。`, req.Language, n, prefix, suffix)
resp, err := s.client.CreateChatCompletion(ctx, &openai.ChatCompletionRequest{
Model: "gpt-3.5-turbo",
Messages: []openai.ChatMessage{
{Role: "user", Content: prompt},
},
Temperature: 0.5, // 稍高温度获得多样性
MaxTokens: 1000,
})
if err != nil {
returnnil, err
}
// 解析多个补全选项
var completions []string
json.Unmarshal([]byte(resp.Choices[0].Message.Content), &completions)
results := make([]*CompletionResponse, len(completions))
for i, c := range completions {
results[i] = &CompletionResponse{
Completion: c,
InsertText: c,
}
}
return results, nil
}
五、Bug修复与代码审查
5.1 Bug修复服务
package fix
import (
"context"
"fmt"
)
// BugFixService Bug修复服务
type BugFixService struct {
client *openai.Client
}
// FixBug 修复Bug
func(s *BugFixService)FixBug(ctx context.Context, req *BugFixRequest)(*BugFixResponse, error) {
prompt := fmt.Sprintf(`分析并修复以下%s代码中的Bug:
代码:
%s
错误信息:
%s
请:
1. 分析Bug原因
2. 提供修复后的完整代码
3. 解释修复内容`, req.Language, req.Code, req.ErrorMessage)
resp, err := s.client.CreateChatCompletion(ctx, &openai.ChatCompletionRequest{
Model: "gpt-4",
Messages: []openai.ChatMessage{
{Role: "system", Content: "你是一个专业的代码调试专家。"},
{Role: "user", Content: prompt},
},
Temperature: 0.2,
})
if err != nil {
returnnil, err
}
content := resp.Choices[0].Message.Content
return &BugFixResponse{
FixedCode: extractCodeBlock(content),
Explanation: extractExplanation(content),
RootCause: extractRootCause(content),
}, nil
}
type BugFixRequest struct {
Code string
ErrorMessage string
Language string
}
type BugFixResponse struct {
FixedCode string
Explanation string
RootCause string
}
5.2 代码审查服务
// CodeReviewService 代码审查服务
type CodeReviewService struct {
client *openai.Client
}
// Review 代码审查
func(s *CodeReviewService)Review(ctx context.Context, code string, language string)(*ReviewResult, error) {
prompt := fmt.Sprintf(`请审查以下%s代码,从以下方面给出建议:
1. 代码质量和可读性
2. 潜在的Bug和安全问题
3. 性能优化建议
4. 最佳实践遵循情况
代码:
%s
请以JSON格式返回审查结果。`, language, code)
resp, err := s.client.CreateChatCompletion(ctx, &openai.ChatCompletionRequest{
Model: "gpt-4",
Messages: []openai.ChatMessage{
{Role: "system", Content: "你是一个资深的代码审查专家。"},
{Role: "user", Content: prompt},
},
})
if err != nil {
returnnil, err
}
var result ReviewResult
json.Unmarshal([]byte(resp.Choices[0].Message.Content), &result)
return &result, nil
}
type ReviewResult struct {
Score int`json:"score"`// 0-100
Issues []Issue `json:"issues"`
Suggestions []string`json:"suggestions"`
Summary string`json:"summary"`
}
type Issue struct {
Line int`json:"line"`
Severity string`json:"severity"`// error, warning, info
Message string`json:"message"`
Fix string`json:"fix"`
}
六、完整API服务
6.1 代码助手API
package api
import (
"net/http"
"github.com/gin-gonic/gin"
)
// CodeAssistantAPI 代码助手API
type CodeAssistantAPI struct {
assistant *CodeAssistant
completion *CompletionService
bugFix *BugFixService
review *CodeReviewService
}
// RegisterRoutes 注册路由
func(api *CodeAssistantAPI)RegisterRoutes(r *gin.RouterGroup) {
r.POST("/generate", api.Generate)
r.POST("/complete", api.Complete)
r.POST("/explain", api.Explain)
r.POST("/fix", api.Fix)
r.POST("/review", api.Review)
}
// Generate 生成代码
// POST /api/v1/code/generate
func(api *CodeAssistantAPI)Generate(c *gin.Context) {
var req CodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
req.Action = "generate"
resp, err := api.assistant.Process(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
// Complete 代码补全
// POST /api/v1/code/complete
func(api *CodeAssistantAPI)Complete(c *gin.Context) {
var req CompletionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
resp, err := api.completion.Complete(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
// Fix 修复Bug
// POST /api/v1/code/fix
func(api *CodeAssistantAPI)Fix(c *gin.Context) {
var req BugFixRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
resp, err := api.bugFix.FixBug(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
AI代码助手架构总结
💡 AI代码助手正在改变开发方式,掌握这套方案让你具备构建Copilot级工具的能力!
夜雨聆风