使用 Neo4j 分析代码结构并辅助 LLM 代码生成
Neo4j 对代码结构和关系进行建模,为大型语言模型(LLM)提供项目特定的上下文,从而指导其生成更准确、更相关的代码片段
问题描述
大型语言模型(LLM)能够根据自然语言提示生成代码片段,但它们无法直接访问大型代码库的具体结构和依赖关系,导致生成的代码虽然语法正确,却可能与实际项目上下文无关或存在冗余。
为提升生成代码的相关性和正确性,需要将项目特定的上下文知识融入生成过程。
解决方案概述
本项目采用 Neo4j 原生图数据库,将代码库表示为知识图谱,捕捉文件、类、函数等实体及其相互关系,从而支持复杂的代码结构和依赖查询。
从 Neo4j 检索到的丰富上下文可作为 LLM 提示的一部分,指导生成更准确、具备项目语境的代码片段。
代码图模型
图谱模式建模了关键元素及其关系:
(:File {name}):源文件节点(:Class {name}):类节点(:Function {name}):函数/方法节点(:Module {name}):模块/包节点
关系类型:
(:File)-[:CONTAINS]->(:Class)(:Class)-[:DEFINES]->(:Function)(:Function)-[:CALLS]->(:Function)(函数调用)(:Function)-[:IMPORTS]->(:Module)
该模式支持查找调用层级、依赖关系、模块使用情况等丰富查询。
工具与库
- Neo4j Community Edition:本地图数据库
- Python
ast模块:解析 Python 源文件为抽象语法树(AST) - Neo4j Python Driver(
neo4j包):与 Neo4j 数据库交互 - LLM API 或本地模型:如 OpenAI GPT、HuggingFace Transformers
实现细节
1. 使用 ast 解析 Python 代码
Python 标准库的 ast 模块可将 Python 文件解析为树结构,便于提取函数、类定义,以及函数调用和导入信息。
import ast
def extract_functions_from_file(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
source = f.read()
tree = ast.parse(source)
functions = [node.name for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
return functions
2. 写入 Neo4j 图数据库
通过官方 Python 驱动和 Cypher 查询,将函数及其关系插入 Neo4j。
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def add_function(tx, function_name, file_name):
tx.run("""
MERGE (f:Function {name: $function_name})
MERGE (file:File {name: $file_name})
MERGE (file)-[:CONTAINS]->(f)
""", function_name=function_name, file_name=file_name)
with driver.session() as session:
session.write_transaction(add_function, "save_user", "user_service.py")
3. 构建函数调用关系图
遍历 AST 时,检测函数体内的调用,并以 CALLS 关系表示:
class FunctionCallVisitor(ast.NodeVisitor):
def __init__(self):
self.calls = []
def visit_Call(self, node):
if isinstance(node.func, ast.Name):
self.calls.append(node.func.id)
self.generic_visit(node)
def get_function_calls(func_node):
visitor = FunctionCallVisitor()
visitor.visit(func_node)
return visitor.calls
将调用关系记录到 Neo4j:
def add_call_relation(tx, caller, callee):
tx.run("""
MATCH (caller:Function {name: $caller}), (callee:Function {name: $callee})
MERGE (caller)-[:CALLS]->(callee)
""", caller=caller, callee=callee)
4. 查询 Neo4j 获取上下文
示例 Cypher 查询,查找所有调用某函数的函数:
MATCH (caller:Function)-[:CALLS]->(callee:Function {name: 'save_user'})
RETURN caller.name
通过 Python 发起查询,丰富 LLM 提示:
def get_callers(session, function_name):
result = session.run("""
MATCH (caller:Function)-[:CALLS]->(callee:Function {name: $function_name})
RETURN caller.name AS caller_name
""", function_name=function_name)
return [record["caller_name"] for record in result]
5. LLM 提示工程
在调用 LLM 生成代码前,先检索相关图谱上下文(如已有相关函数或模块),并将其融入提示:
context = "项目使用 pandas 进行数据处理。已有函数:save_user, load_user。"
prompt = f"""
鉴于项目背景:
{context}
请编写一个 Python 函数,将用户数据导出为 CSV 文件。
"""
generated_code =