是否曾经想要一个完全在本地机器上运行的、由AI驱动的代码审查工具?在本系列的两部分教程中,我们将使用ClientAI和Ollama来构建这样一个工具。
我们的助手将分析Python代码结构,识别潜在问题,并提出改进建议——同时确保你的代码保持私密和安全。
项目概述
我们的代码分析助手将能够:
设置我们的环境
首先,为你的项目创建一个新目录:
mkdir local_task_planner
cd local_task_planner
安装支持Ollama的ClientAI:
pip install clientai[ollama]
确保你的系统上已安装Ollama。你可以从Ollama的官方网站获取它。
现在,让我们创建将要编写代码的文件:
touch code_analyzer.py
并且从我们的核心导入开始:
import ast
import json
import logging
import re
from dataclasses import dataclass
from typing import List
from clientai import ClientAI
from clientai.agent import (
Agent,
ToolConfig,
act,
observe,
run,
synthesize,
think,
)
from clientai.ollama import OllamaManager, OllamaServerConfig
这些组件中的每一个都发挥着至关重要的作用:
构建我们的分析结果
在分析代码时,我们需要一种清晰的方式来组织我们的发现。以下是我们将如何结构化我们的结果:
@dataclass
class CodeAnalysisResult:
"""Results from code analysis."""
complexity: int
functions: List[str]
classes: List[str]
imports: List[str]
issues: List[str]
可以将这视为我们的代码分析报告卡:
构建核心分析引擎
接下来是真正的核心部分——让我们来构建我们的代码分析引擎:
def analyze_python_code_original(code: str) -> CodeAnalysisResult:
"""Analyze Python code structure and complexity."""
try:
tree = ast.parse(code)
functions = []
classes = []
imports = []
complexity = 0
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
functions.append(node.name)
complexity += sum(
1
for _ in ast.walk(node)
if isinstance(_, (ast.If, ast.For, ast.While))
)
elif isinstance(node, ast.ClassDef):
classes.append(node.name)
elif isinstance(node, (ast.Import, ast.ImportFrom)):
for name in node.names:
imports.append(name.name)
return CodeAnalysisResult(
complexity=complexity,
functions=functions,
classes=classes,
imports=imports,
issues=[],
)
except Exception as e:
return CodeAnalysisResult(
complexity=0,
functions=[],
classes=[],
imports=[],
issues=[str(e)]
)
这个函数就像我们的代码侦探一样。它:
实现风格检查
优秀的代码不仅仅是能够正确运行——它还应该是可读和可维护的。以下是我们的风格检查器:
def check_style_issues_original(code: str) -> List[str]:
"""Check for Python code style issues."""
issues = []
for i, line in enumerate(code.split("\n"), 1):
if len(line.strip()) > 88:
issues.append(f"Line {i} exceeds 88 characters")
function_pattern = r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\("
for match in re.finditer(function_pattern, code):
name = match.group(1)
if not name.islower():
issues.append(f"Function '{name}' should use snake_case")
return issues
我们的风格检查器主要关注两个方面:
文档助手
文档对于可维护的代码至关重要。以下是我们的文档生成器:
def generate_docstring(code: str) -> str:
"""Generate docstring for Python code."""
try:
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
args = []
if isinstance(node, ast.FunctionDef):
args = [a.arg for a in node.args.args]
return f"""
Suggested docstring for {node.name}:
Args:
{chr(4).join(f"{arg}: Description of {arg}" for arg in args)}
Returns:
Description of return value
Examples:
```python
# Example usage of {node.name}
```
"""
return "No functions or classes found to document."
except Exception as e:
return f"Error generating docstring: {str(e)}"
这个助手:
使我们的工具具备AI集成能力
为了准备将我们的工具与AI系统集成,我们需要将它们包装成JSON友好的格式:
def analyze_python_code(code: str) -> str:
"""Wrap analyze_python_code_original to return JSON string."""
if not code:
return json.dumps({"error": "No code provided"})
result = analyze_python_code_original(code)
return json.dumps({
"complexity": result.complexity,
"functions": result.functions,
"classes": result.classes,
"imports": result.imports,
"issues": result.issues,
})
def check_style_issues(code: str) -> str:
"""Wrap check_style_issues_original to return JSON string."""
if not code:
return json.dumps({"error": "No code provided"})
issues = check_style_issues_original(code)
return json.dumps({"issues": issues})
这些包装器增加了输入验证、JSON序列化和错误处理,使我们的助手更加防错。