1. 项目概述:为什么我们要“手搓”一个代码解释器?
最近,AI 编程助手已经成了我们日常开发的“标配”。无论是写业务逻辑、调试报错,还是生成单元测试,动动嘴皮子就能得到一段可用的代码,效率提升是实打实的。但不知道你有没有遇到过这样的场景:你拿到了一段 AI 生成的、或者从 Stack Overflow 上“借鉴”来的复杂代码片段,它看起来能跑,但你就是不太确定它内部每一步到底在干什么,尤其是涉及数据转换或者算法逻辑的时候。这时候,如果有一个工具,能像一位耐心的导师,把代码掰开揉碎了,一步一步解释给你听,那该多好。
这就是我动手做mini-cc(开源版 Claude Code)的核心动机。市面上优秀的代码解释器,比如 Claude Code,体验确实很棒,但它通常作为闭源商业服务的一部分,我们无法窥探其内部机制,也无法根据自己的需求进行定制或集成到本地工作流中。作为一个喜欢“知其所以然”的开发者,我决定自己动手,用开源技术栈“复刻”一个轻量级、可掌控的代码解释核心。这不仅仅是一个玩具项目,它让我深入理解了代码静态分析、抽象语法树(AST)解析以及自然语言生成(NLG)在编程领域的结合点。今天,我就把这个项目的设计思路、核心实现和踩过的坑,毫无保留地分享给你。无论你是想学习如何构建类似的 AI 工具,还是单纯想拥有一个可以离线运行、保护代码隐私的私人代码解释器,这篇文章都会给你一条清晰的路径。
2. 整体架构设计:从输入代码到自然语言解释
一个代码解释器的核心工作流,可以抽象为“解析 -> 理解 -> 表达”三步。mini-cc的设计也紧紧围绕这个流程展开,目标是构建一个轻量、模块化且易于扩展的管道(Pipeline)。
2.1 核心流程拆解
整个系统的运行始于用户输入的一段源代码。我们以一段简单的 Python 代码为例:
def calculate_stats(numbers): if not numbers: return None, None total = sum(numbers) count = len(numbers) average = total / count max_val = max(numbers) return average, max_val系统需要将这段文本,转化为人类可读的解释,例如:“这个函数名为calculate_stats,它接收一个数字列表numbers作为参数。首先,它检查列表是否为空,如果为空则返回两个None。否则,它计算列表的总和与长度,进而得到平均值。同时,它也找出列表中的最大值。最后,函数返回计算出的平均值和最大值。”
为了实现这个目标,我设计了以下四个核心阶段:
- 语言识别与预处理:系统首先需要判断代码属于哪种编程语言(Python、JavaScript、Java 等)。这决定了后续使用哪种解析器。
- 语法解析与 AST 生成:使用对应语言的解析器(如 Python 的
ast模块),将代码文本转换为结构化的抽象语法树。AST 是后续所有分析的基础。 - 语义分析与信息提取:遍历 AST,提取关键信息。这包括:
- 结构信息:函数/类定义、控制流(if/for/while)、作用域。
- 数据流信息:变量的定义、赋值、引用关系。
- 逻辑信息:表达式的作用、函数调用的目的。
- 自然语言生成(NLG):将上一步提取的结构化信息,按照预设的模板或通过微调的小型语言模型,组织成连贯、易懂的自然语言句子。
2.2 技术栈选型与考量
在技术选型上,我遵循了“优先成熟开源库,核心逻辑自己掌控”的原则。
解析层:
- Python:首选内置的
ast模块。它无需额外依赖,功能强大,能生成标准的 Python AST。对于更复杂的需要(如类型推断),可以配合libcst或tree-sitter(一个多语言解析器生成工具)。 - JavaScript/TypeScript:
@babel/parser是不二之选。它是现代 JS 生态的事实标准,支持 ES 最新特性以及 JSX、TypeScript 语法。 - Java:可以使用
Eclipse JDT或JavaParser库。考虑到集成简便性,JavaParser的轻量级特性更符合mini-cc的定位。 - 多语言支持:如果希望用一个工具解析多种语言,
tree-sitter是终极方案。它通过统一的 C 库和不同语言的语法定义文件(grammar)来工作,性能优异。但初始集成复杂度稍高。mini-cc的第一版选择了按语言分治的策略,未来扩展为tree-sitter后端。
- Python:首选内置的
分析与生成层:
- 核心逻辑(自研):AST 遍历、模式匹配、信息聚合等核心分析逻辑,我选择用 Python 从头实现。这保证了最大的灵活性和可调试性。例如,识别一个
for循环是在遍历列表还是字典,需要自定义规则。 - 自然语言生成:这里有两条路径:
- 模板引擎:对于简单的、结构清晰的代码,使用 Jinja2 或纯字符串模板足以生成不错的解释。优点是确定性强、速度快、无依赖。
mini-cc的初期版本采用了这种方式。 - 轻量级 LLM:为了获得更灵活、更像“人话”的解释,可以集成一个本地运行的、经过微调的小模型(如 7B 参数的 CodeLlama 或 DeepSeek-Coder)。通过设计特定的 Prompt(例如:“请用简洁的中文,逐步解释以下 Python 函数的功能”),让模型基于 AST 提取的关键信息进行生成。这属于进阶方案,需要考虑模型加载、推理速度和对硬件的要求。
- 模板引擎:对于简单的、结构清晰的代码,使用 Jinja2 或纯字符串模板足以生成不错的解释。优点是确定性强、速度快、无依赖。
- 核心逻辑(自研):AST 遍历、模式匹配、信息聚合等核心分析逻辑,我选择用 Python 从头实现。这保证了最大的灵活性和可调试性。例如,识别一个
项目结构与部署:
- 语言:Python 作为胶水语言,生态丰富,在数据处理和快速原型方面有优势。
- 接口:提供 CLI(命令行)工具是必须的,方便集成到脚本或 IDE。同时,设计一个简单的 HTTP API 服务(使用 FastAPI),可以方便地被 Web 前端或其他服务调用。
- 部署:打包成 PyPI 包,支持
pip install mini-cc。对于包含本地模型的版本,提供 Docker 镜像,一键解决环境依赖。
选型心得:起步阶段,切忌追求大而全。我强烈建议先从单一语言(如 Python)和模板生成做起,快速跑通整个流程,验证核心价值。之后再迭代加入更多语言和更智能的生成方式。否则很容易陷入复杂工具链的泥潭,迟迟看不到成果。
3. 核心模块深度解析
这一部分,我们深入mini-cc的几个核心模块,看看代码是如何从文本变成解释的。
3.1 语法解析器:将代码转化为树
以 Python 为例,ast模块的使用非常简单,但理解其产出至关重要。
import ast code = """ def greet(name): return f\"Hello, {name}!\" """ tree = ast.parse(code) # 此时,tree 就是一个 Module 节点,它包含了整个代码的结构。我们可以用ast.dump(tree, indent=2)来查看这棵树的原始形态。你会看到类似下面的结构(已简化):
Module( body=[ FunctionDef( name='greet', args=arguments(...), body=[ Return( value=JoinedStr(...) ) ] ) ] )这棵树精确描述了代码的语法结构,但缺乏语义信息。比如,它知道name是一个参数,但不知道name在JoinedStr中被使用了。这就需要我们通过遍历来建立联系。
3.2 AST 遍历与访问者模式
ast模块提供了ast.NodeVisitor类,允许我们以“访问者”模式遍历 AST。我们可以为感兴趣的节点类型定义处理方法。
class CodeAnalyzer(ast.NodeVisitor): def __init__(self): self.functions = [] self.variables = {} def visit_FunctionDef(self, node): # 捕获函数定义 func_info = { 'name': node.name, 'args': [arg.arg for arg in node.args.args], 'docstring': ast.get_docstring(node) } self.functions.append(func_info) # 继续遍历函数体内部 self.generic_visit(node) def visit_Assign(self, node): # 捕获赋值语句 for target in node.targets: if isinstance(target, ast.Name): var_name = target.id # 这里可以尝试简单推断值,但复杂表达式很难 self.variables[var_name] = 'assigned' self.generic_visit(node) def visit_Call(self, node): # 捕获函数调用 if isinstance(node.func, ast.Name): print(f\"函数 `{node.func.id}` 被调用\") self.generic_visit(node) analyzer = CodeAnalyzer() analyzer.visit(tree) print(analyzer.functions) # 输出函数信息通过这种方式,我们就能从 AST 中提取出函数列表、变量名、调用关系等关键信息。这是整个系统的“理解”环节。
3.3 语义分析增强:理解代码在“做什么”
单纯的语法信息不足以生成高质量解释。我们需要加入一些语义推理。例如:
- 变量类型推断:对于
x = len(data),我们可以推断x是整数。对于y = x / 2,如果x是整数,y可能是浮点数。mini-cc实现了一个简单的基于规则的推断器,虽然不如专业的类型检查器强大,但对于常见模式足够有效。 - 操作意图识别:
for item in list:通常是在“遍历列表”;if not value:是在“检查值是否为空或假”。我们需要建立一套“模式-意图”的映射词典。 - 上下文关联:在函数内部,识别出哪些变量是参数,哪些是局部定义的,哪些是来自外部作用域的(全局变量或闭包)。这需要在遍历时维护一个作用域栈。
这部分是mini-cc的“大脑”,也是最体现工程技巧的地方。代码的“智能”程度,很大程度上取决于这里积累的规则和模式的丰富性。
3.4 解释生成策略:从模板到模型
有了结构化和语义化的信息,下一步就是“说人话”。
1. 基于模板的生成:这是最直接的方法。我们为不同的语法结构预定义解释模板。
# 一个简单的模板字典 TEMPLATES = { 'FunctionDef': \"定义了一个名为 `{name}` 的函数,它接受参数:{params}。\", 'Return': \"函数返回了:{value}。\", 'For': \"这是一个循环,它遍历 `{target}` 中的每一个元素。\", } # 在遍历节点时,填充模板 def generate_for_loop(node): # 从 node 中提取 target, iter 等信息 iter_desc = explain(node.iter) # 递归解释迭代对象 return TEMPLATES['For'].format(target=iter_desc)这种方法的优点是快、稳定、可预测。缺点是解释比较刻板,对于复杂或嵌套的逻辑,生成的文本可能不流畅。
2. 基于轻量级 LLM 的生成:我们可以将前面提取的信息(如函数签名、主要变量、关键操作步骤)整理成一段结构化的文本描述,作为 Prompt 输入给一个本地运行的轻量级语言模型。
Prompt: 你是一个代码解释助手。请根据以下结构化信息,生成一段流畅的中文解释。 代码元素:函数 函数名:calculate_stats 参数:numbers (一个列表) 主要步骤: 1. 检查 numbers 是否为空。 2. 计算总和 total。 3. 计算长度 count。 4. 计算平均值 average = total / count。 5. 找出最大值 max_val。 6. 返回 (average, max_val)。 请生成解释:然后让模型补全。通过精心设计的 Prompt,可以让模型输出风格一致、语言自然的解释。mini-cc的进阶版集成了llama.cpp或Ollama来本地运行CodeLlama-7B-Instruct这类模型,效果提升显著,但代价是响应时间从毫秒级增长到秒级。
实操心得:不要小看模板方法。对于 80% 的常见代码片段,精心设计的模板足以生成清晰准确的解释,且零延迟。建议将 LLM 作为“增强模式”或“疑难解答模式”,当模板无法很好处理(比如遇到非常规的算法或复杂的嵌套条件)时再启用。这样可以在体验和资源消耗之间取得最佳平衡。
4. 实现过程与关键代码
让我们聚焦于mini-cc最核心的 Python 解释器实现,看看各个模块是如何串联起来的。
4.1 项目结构与入口点
mini-cc/ ├── mini_cc/ │ ├── __init__.py │ ├── cli.py # 命令行入口 │ ├── server.py # HTTP API 入口 │ ├── core/ │ │ ├── __init__.py │ │ ├── parser.py # 语言识别与解析路由 │ │ ├── analyzer.py # 语义分析器 │ │ └── generator.py # 解释生成器 │ └── languages/ # 各语言具体实现 │ ├── python.py │ ├── javascript.py │ └── ... ├── templates/ # 各语言的解释模板 ├── requirements.txt └── README.md核心入口是一个explain函数,它位于mini_cc/core/parser.py中:
# mini_cc/core/parser.py import os from pathlib import Path from .languages.python import PythonAnalyzer from .languages.javascript import JavaScriptAnalyzer # ... 导入其他语言分析器 class CodeParser: def __init__(self): self._language_detectors = { '.py': 'python', '.js': 'javascript', '.ts': 'typescript', '.java': 'java', # ... 更多扩展 } def detect_language(self, code_snippet, file_extension=None): """检测代码语言。优先使用文件后缀,其次使用启发式规则。""" if file_extension and file_extension in self._language_detectors: return self._language_detectors[file_extension] # 简单的启发式检测(可根据需要增强) if 'def ' in code_snippet and ':' in code_snippet and ('import ' in code_snippet or '\n' in code_snippet): return 'python' elif 'function ' in code_snippet or 'const ' in code_snippet or 'let ' in code_snippet: return 'javascript' # ... 其他规则 return 'unknown' def explain(self, code, language=None, file_path=None): """主解释函数。""" # 1. 语言检测 if not language: ext = Path(file_path).suffix if file_path else None language = self.detect_language(code, ext) # 2. 路由到对应的语言分析器 analyzer = self._get_analyzer(language) if not analyzer: return f\"错误:暂不支持 {language} 语言。\" # 3. 执行分析 analysis_result = analyzer.analyze(code) # 4. 生成解释 explanation = analyzer.generate_explanation(analysis_result) return explanation def _get_analyzer(self, language): """工厂方法,获取语言分析器实例。""" analyzers = { 'python': PythonAnalyzer, 'javascript': JavaScriptAnalyzer, # ... } analyzer_class = analyzers.get(language) return analyzer_class() if analyzer_class else None4.2 Python 分析器实现详解
这是mini_cc/languages/python.py的核心部分:
import ast import inspect from typing import Dict, List, Any from ..core.generator import TemplateGenerator class PythonAnalyzer: def __init__(self): self.generator = TemplateGenerator(language='python') def analyze(self, code: str) -> Dict[str, Any]: """分析 Python 代码,返回结构化信息。""" try: tree = ast.parse(code) except SyntaxError as e: return {\"error\": f\"语法错误: {e}\"} analyzer = _PythonASTAnalyzer() analyzer.visit(tree) result = { \"functions\": analyzer.functions, \"classes\": analyzer.classes, \"imports\": analyzer.imports, \"global_vars\": analyzer.global_vars, \"summary\": analyzer.get_summary() } return result def generate_explanation(self, analysis_result: Dict[str, Any]) -> str: """基于分析结果生成解释。""" if \"error\" in analysis_result: return analysis_result[\"error\"] # 使用模板生成器 explanation = self.generator.generate(analysis_result) return explanation class _PythonASTAnalyzer(ast.NodeVisitor): """内部使用的 AST 访问者,负责提取信息。""" def __init__(self): super().__init__() self.functions = [] self.classes = [] self.imports = [] self.global_vars = {} self._current_scope = [] # 用于跟踪作用域 def visit_Import(self, node): for alias in node.names: self.imports.append({\"module\": alias.name, \"alias\": alias.asname}) self.generic_visit(node) def visit_ImportFrom(self, node): module = node.module or '' for alias in node.names: self.imports.append({\"module\": module, \"name\": alias.name, \"alias\": alias.asname}) self.generic_visit(node) def visit_FunctionDef(self, node): func_info = { \"name\": node.name, \"args\": self._extract_args(node.args), \"decorators\": [ast.dump(d) for d in node.decorator_list], \"docstring\": ast.get_docstring(node), \"body_summary\": self._summarize_body(node.body) } self.functions.append(func_info) # 进入新的作用域 self._current_scope.append(f\"func:{node.name}\") self.generic_visit(node) self._current_scope.pop() def _extract_args(self, args_node): """提取函数参数信息。""" args = [] for arg in args_node.args: arg_info = {\"name\": arg.arg} # 尝试获取类型注解(Python 3.5+) if arg.annotation: arg_info[\"type\"] = ast.unparse(arg.annotation) if hasattr(ast, 'unparse') else \"未知类型\" args.append(arg_info) return args def _summarize_body(self, body_nodes): """对函数体进行简要总结,识别关键操作。""" summary = [] for node in body_nodes: if isinstance(node, ast.Assign): # 例如:识别 `total = sum(numbers)` for target in node.targets: if isinstance(target, ast.Name): # 这里可以进一步分析 node.value 是什么 if isinstance(node.value, ast.Call): call_func = node.value.func if isinstance(call_func, ast.Name): summary.append(f\"计算并赋值给 `{target.id}` (通过 `{call_func.id}` 函数)\") elif isinstance(node, ast.Return): summary.append(\"返回结果\") elif isinstance(node, ast.If): summary.append(\"条件判断\") # ... 处理其他节点类型 return summary[:5] # 只取前几个关键操作,避免过长 def get_summary(self): """生成整体摘要。""" summary_lines = [] if self.imports: modules = [imp.get('module') or imp.get('name') for imp in self.imports] summary_lines.append(f\"导入了以下模块:{', '.join(filter(None, modules))}\") if self.functions: func_names = [f[\"name\"] for f in self.functions] summary_lines.append(f\"定义了 {len(func_names)} 个函数:{', '.join(func_names)}\") if self.classes: class_names = [c[\"name\"] for c in self.classes] summary_lines.append(f\"定义了 {len(class_names)} 个类:{', '.join(class_names)}\") return \";\".join(summary_lines)4.3 模板生成器示例
mini_cc/core/generator.py中的模板生成器负责组装最终的解释文本。这里展示一个简化版本:
# mini_cc/core/generator.py import os import json from jinja2 import Environment, FileSystemLoader class TemplateGenerator: def __init__(self, language='python'): # 定位模板目录 template_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'templates', language) self.env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True) def generate(self, analysis_data: Dict) -> str: """根据分析数据选择并渲染模板。""" # 根据代码的主要结构选择主模板 if analysis_data.get('functions'): # 如果主要是函数,使用函数模板 template = self.env.get_template('function.j2') elif analysis_data.get('classes'): template = self.env.get_template('class.j2') else: # 脚本或片段 template = self.env.get_template('script.j2') rendered = template.render(**analysis_data) return rendered对应的一个 Jinja2 模板文件templates/python/function.j2可能长这样:
{% if functions %} {% for func in functions %} ## 函数 `{{ func.name }}` 这个函数{% if func.args %}接收以下参数: {% for arg in func.args %}- `{{ arg.name }}`{% if arg.type %} (类型: {{ arg.type }}){% endif %} {% endfor %}{% else %}没有参数。{% endif %} {% if func.docstring %} **描述**:{{ func.docstring }} {% endif %} **主要逻辑**: {% if func.body_summary %} {% for line in func.body_summary %}- {{ line }} {% endfor %} {% else %}(函数体逻辑较为简单或未详细解析){% endif %} {% endfor %} {% endif %} {% if summary %} **整体摘要**:{{ summary }} {% endif %}4.4 CLI 与 HTTP API 封装
为了让工具好用,必须提供便捷的接口。
CLI 实现 (mini_cc/cli.py):
import click from mini_cc.core.parser import CodeParser @click.command() @click.argument('input', type=click.Path(exists=True)) @click.option('--language', '-l', help='指定代码语言(如 python, js)') def explain(input, language): """解释 INPUT 文件或代码片段中的代码。""" parser = CodeParser() if os.path.isfile(input): with open(input, 'r', encoding='utf-8') as f: code = f.read() explanation = parser.explain(code, language, file_path=input) else: # 假设 input 是直接传入的代码字符串 code = input explanation = parser.explain(code, language) click.echo(explanation) if __name__ == '__main__': explain()使用方式:mini-cc ./my_script.py或mini-cc \"def foo(): return 42\"。
HTTP API 实现 (mini_cc/server.py):
from fastapi import FastAPI, HTTPException from pydantic import BaseModel from mini_cc.core.parser import CodeParser app = FastAPI(title=\"mini-cc API\", description=\"一个轻量级代码解释服务\") parser = CodeParser() class ExplainRequest(BaseModel): code: str language: str = None @app.post(\"/explain\") async def explain_code(request: ExplainRequest): try: explanation = parser.explain(request.code, request.language) return {\"explanation\": explanation} except Exception as e: raise HTTPException(status_code=500, detail=f\"解释失败: {str(e)}\") @app.get(\"/health\") async def health(): return {\"status\": \"healthy\"}运行uvicorn mini_cc.server:app --reload即可启动服务,通过POST /explain接口提交代码获取解释。
5. 常见问题、优化与扩展方向
在实际开发和测试mini-cc的过程中,我遇到了不少典型问题,也积累了一些优化思路。
5.1 典型问题与排查
问题:解析复杂语法或新语言特性时失败。
- 现象:
ast.parse或@babel/parser抛出语法错误。 - 原因:使用的解析器版本过低,不支持最新的语言语法(如 Python 的 match 语句,JS 的可选链
?.)。 - 解决:
- 确保使用最新版本的解析库。
- 在语言检测后,可以尝试用不同的解析模式(如
@babel/parser的sourceType: 'module'或'script')。 - 对于确实无法解析的代码,在错误处理中给出友好提示,并建议用户检查语法或尝试简化代码片段。
- 现象:
问题:生成的解释过于冗长或琐碎。
- 现象:对于一个简单的循环,解释器可能逐行解释
i = 0,i < 10,i += 1,显得很啰嗦。 - 原因:模板或分析规则过于底层,没有对常见模式进行抽象。
- 解决:在
_summarize_body这类方法中,加入模式聚合。例如,识别出完整的for i in range(10):模式后,直接生成“这是一个循环 10 次的 for 循环”,而不是解释每一部分。建立“模式-摘要”规则库。
- 现象:对于一个简单的循环,解释器可能逐行解释
问题:对库函数或自定义函数的行为理解不足。
- 现象:解释
df.groupby('col').mean()时,只能说出“调用了groupby和mean方法”,无法说明这是在按列分组并计算平均值。 - 原因:静态分析难以获知第三方库函数的语义。
- 解决:
- 建立常识库:为常用标准库和流行第三方库(如 pandas, numpy, requests)的函数维护一个简单的“功能描述”映射表。
- 依赖文档:在进阶版本中,可以尝试在许可范围内,索引这些库的官方文档,通过检索增强(RAG)来获取更准确的描述。但这会显著增加系统复杂度。
- 现象:解释
问题:处理大型文件时速度慢或内存占用高。
- 现象:解释一个上千行的文件时,响应缓慢。
- 原因:AST 可能很大,遍历和生成操作消耗资源。
- 解决:
- 增量/分段解释:不一次性解释整个文件,而是允许用户选择函数、类或代码块进行解释。
- 优化分析粒度:对于大型文件,只分析顶层结构(函数和类定义),忽略函数体内的细节,除非用户明确请求。
- 缓存:对解析过的、未改变的代码片段进行 AST 缓存。
5.2 性能优化与体验提升
- 异步处理:对于 HTTP API,使用异步框架(如 FastAPI)和异步的解析/生成任务,避免阻塞主线程,提高并发能力。
- 流式输出:当使用 LLM 生成解释时,支持 Server-Sent Events (SSE) 进行流式输出,让用户能边生成边看到结果,体验更好。
- 增量解释与交互:模仿 IDE 的交互,允许用户点击代码中的某个符号(变量、函数),即时获取对该符号的局部解释。
- 上下文感知:在解释一个函数时,如果能获取到调用它的代码片段,可以生成更贴合使用场景的解释。
5.3 扩展方向
mini-cc作为一个基础框架,有很多有趣的扩展方向:
- 集成到开发环境:开发 VS Code、JetBrains IDE 或 Vim/Neovim 的插件,让代码解释能力深度嵌入工作流。
- 支持更多语言:通过集成
tree-sitter,可以相对统一地支持 Rust、Go、C++ 等语言,降低每增加一门语言的开发成本。 - 解释“为什么”而不仅仅是“是什么”:不仅解释代码在做什么,还能解释某些写法的原因(例如,“这里使用
list comprehension是为了更简洁高效地生成新列表”),这需要更丰富的知识库或更强大的模型。 - 生成测试用例或文档:基于对代码功能的理解,自动为函数生成简单的单元测试用例或文档字符串草稿。
- 安全与代码审查辅助:识别代码中的潜在问题,如可能的无限循环、未处理的异常、低效的算法模式,并给出修改建议。
构建mini-cc的过程,是一个将大模型“黑盒”能力拆解、用可解释的传统技术实现核心功能的有益尝试。它可能永远达不到 Claude Code 那样的通用性和流畅度,但这种“手搓”带来的掌控感、对底层原理的深刻理解,以及根据自己需求定制功能的自由,是使用现成服务无法比拟的。最重要的是,这个项目完全开源、可审计、可离线运行,为注重代码隐私和安全的团队提供了一个可靠的选择。如果你也对“造轮子”感兴趣,不妨从这个项目开始,亲手搭建一个属于你自己的代码解释引擎。