# 20.1 项目架构设计

> **本节目标**：设计一个 AI 编程助手的整体架构，明确各模块的职责和交互方式。

---

## 项目目标

我们要构建一个 AI 编程助手，它能够：
- 理解代码仓库的结构和逻辑
- 回答关于代码的问题
- 生成新代码和修改现有代码
- 自动生成测试和修复 Bug

这不是一个简单的"代码补全"工具，而是一个能理解项目上下文、进行多步推理的 Agent。

> 📄 **前沿产品对比**：当前最前沿的 Coding Agent 产品/项目各有不同的架构取向 [1][2]：
>
> | 产品 | 架构特点 | 核心优势 |
> |------|---------|---------|
> | **Devin** (Cognition AI) | 完全自主的 Agent + 虚拟计算环境 | 端到端完成开发任务，含浏览器和终端 |
> | **SWE-Agent** (Princeton) [1] | Agent-Computer Interface (ACI) 设计 | 精心设计的文件编辑和导航命令接口 |
> | **OpenHands** (UIUC) | 模块化 Agent 框架 + Docker 沙箱 | 开源、可扩展、社区活跃 |
> | **Cursor** (Anysphere) | IDE 深度集成 + 上下文感知 | 实时代码补全 + Agent 模式，用户体验最佳 |
> | **Codex CLI** (OpenAI) | 终端原生 + 多模型支持 | 开源、轻量、与 OpenAI 生态深度集成 |
>
> 这些产品的共同架构特征是：**代码理解（AST/LSP）+ 工具调用（文件编辑/终端）+ 多步推理（ReAct/Plan-and-Solve）+ 沙箱执行**。我们的项目将遵循这一架构模式。

![AI 编程助手 Agent 架构](../svg/chapter_coding_agent_01_architecture.svg)

---

## 整体架构

<!-- 架构图见上方 SVG -->

---

## 核心组件设计

```python
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class TaskType(Enum):
    """编程助手支持的任务类型"""
    CODE_QA = "code_qa"              # 代码问答
    CODE_GENERATE = "code_generate"  # 代码生成
    CODE_MODIFY = "code_modify"      # 代码修改
    CODE_REVIEW = "code_review"      # 代码审查
    TEST_GENERATE = "test_generate"  # 测试生成
    BUG_FIX = "bug_fix"            # Bug 修复
    EXPLAIN = "explain"            # 代码解释

@dataclass
class ProjectContext:
    """项目上下文信息"""
    root_path: str
    language: str
    framework: Optional[str] = None
    file_tree: list[str] = field(default_factory=list)
    dependencies: dict = field(default_factory=dict)
    
@dataclass
class CodingTask:
    """编程任务"""
    task_type: TaskType
    description: str
    target_files: list[str] = field(default_factory=list)
    context: Optional[ProjectContext] = None
    constraints: list[str] = field(default_factory=list)

class CodingAssistant:
    """AI 编程助手主类"""
    
    def __init__(self, llm, project_path: str):
        self.llm = llm
        self.project_path = project_path
        self.context = self._build_context()
    
    def _build_context(self) -> ProjectContext:
        """构建项目上下文"""
        import os
        
        # 扫描文件树
        file_tree = []
        for root, dirs, files in os.walk(self.project_path):
            # 跳过隐藏目录和常见的忽略目录
            dirs[:] = [d for d in dirs 
                      if not d.startswith('.') 
                      and d not in ('node_modules', '__pycache__', 'venv')]
            
            for f in files:
                rel_path = os.path.relpath(
                    os.path.join(root, f), self.project_path
                )
                file_tree.append(rel_path)
        
        # 检测语言和框架
        language = self._detect_language(file_tree)
        framework = self._detect_framework(file_tree)
        
        return ProjectContext(
            root_path=self.project_path,
            language=language,
            framework=framework,
            file_tree=file_tree[:200]  # 限制数量
        )
    
    def _detect_language(self, files: list[str]) -> str:
        """检测项目主要语言"""
        ext_count = {}
        lang_map = {
            '.py': 'python', '.js': 'javascript', '.ts': 'typescript',
            '.java': 'java', '.go': 'go', '.rs': 'rust',
        }
        
        for f in files:
            ext = os.path.splitext(f)[1]
            if ext in lang_map:
                ext_count[ext] = ext_count.get(ext, 0) + 1
        
        if ext_count:
            most_common = max(ext_count, key=ext_count.get)
            return lang_map.get(most_common, "unknown")
        return "unknown"
    
    def _detect_framework(self, files: list[str]) -> str:
        """检测使用的框架"""
        indicators = {
            "requirements.txt": "python",
            "package.json": "node",
            "Cargo.toml": "rust",
            "go.mod": "go",
        }
        
        for f in files:
            if os.path.basename(f) in indicators:
                return indicators[os.path.basename(f)]
        return None
    
    async def handle(self, task: CodingTask) -> str:
        """处理编程任务"""
        task.context = self.context
        
        handlers = {
            TaskType.CODE_QA: self._handle_qa,
            TaskType.CODE_GENERATE: self._handle_generate,
            TaskType.CODE_REVIEW: self._handle_review,
            TaskType.BUG_FIX: self._handle_bug_fix,
            TaskType.EXPLAIN: self._handle_explain,
        }
        
        handler = handlers.get(task.task_type)
        if handler:
            return await handler(task)
        else:
            return f"暂不支持任务类型: {task.task_type.value}"
    
    async def _handle_qa(self, task: CodingTask) -> str:
        """处理代码问答"""
        # 搜索相关代码 → 构建上下文 → LLM 回答
        relevant_code = self._search_relevant_code(task.description)
        
        prompt = f"""基于以下项目代码，回答用户的问题。

项目语言：{task.context.language}
相关代码：
{relevant_code}

问题：{task.description}
"""
        response = await self.llm.ainvoke(prompt)
        return response.content
    
    async def _handle_generate(self, task):
        """处理代码生成"""
        prompt = f"""请根据以下需求生成代码。

项目语言：{task.context.language}
需求：{task.description}
约束：{', '.join(task.constraints) if task.constraints else '无'}

请生成完整的、可运行的代码。"""
        
        response = await self.llm.ainvoke(prompt)
        return response.content
    
    async def _handle_review(self, task):
        """处理代码审查"""
        code = self._read_files(task.target_files)
        
        prompt = f"""请审查以下代码，指出：
1. 潜在的 Bug
2. 性能问题
3. 安全隐患
4. 代码风格问题
5. 改进建议

代码：
{code}"""
        
        response = await self.llm.ainvoke(prompt)
        return response.content
    
    async def _handle_bug_fix(self, task):
        """处理 Bug 修复"""
        code = self._read_files(task.target_files)
        
        prompt = f"""以下代码存在 Bug，请找出并修复。

Bug 描述：{task.description}

代码：
{code}

请提供修复后的完整代码和修复说明。"""
        
        response = await self.llm.ainvoke(prompt)
        return response.content
    
    async def _handle_explain(self, task):
        """处理代码解释"""
        code = self._read_files(task.target_files)
        
        prompt = f"""请用通俗易懂的语言解释以下代码：

{code}

要求：
1. 先概述整体功能
2. 逐个解释重要的函数/类
3. 说明关键的设计模式或算法"""
        
        response = await self.llm.ainvoke(prompt)
        return response.content
    
    def _search_relevant_code(self, query: str) -> str:
        """搜索相关代码（简化版，实际应使用向量搜索）"""
        # 简单的关键词匹配搜索
        import os
        
        results = []
        keywords = query.lower().split()
        
        for filepath in self.context.file_tree[:50]:
            full_path = os.path.join(self.project_path, filepath)
            try:
                with open(full_path, 'r') as f:
                    content = f.read()
                    if any(kw in content.lower() for kw in keywords):
                        results.append(f"--- {filepath} ---\n{content[:500]}")
            except (UnicodeDecodeError, FileNotFoundError):
                continue
        
        return "\n\n".join(results[:5]) if results else "未找到相关代码"
    
    def _read_files(self, file_paths: list[str]) -> str:
        """读取文件内容"""
        import os
        
        contents = []
        for path in file_paths:
            full_path = os.path.join(self.project_path, path)
            try:
                with open(full_path) as f:
                    contents.append(f"--- {path} ---\n{f.read()}")
            except FileNotFoundError:
                contents.append(f"--- {path} ---\n[文件不存在]")
        
        return "\n\n".join(contents)
```

---

## 小结

| 组件 | 职责 |
|------|------|
| 用户接口层 | 接收用户指令，展示结果 |
| Agent 核心层 | 意图理解、任务规划、执行控制 |
| 工具层 | 代码搜索、文件操作、AST 分析 |
| 知识层 | 项目索引、向量检索 |

> **下一节预告**：有了架构，我们来实现代码理解能力——让 Agent 真正"读懂"代码。

---

[20.2 代码理解与分析能力](./02_code_understanding.md)

---

## 参考文献

[1] YANG J, JIMENEZ C E, WETTIG A, et al. SWE-agent: Agent-computer interfaces enable automated software engineering[C]//NeurIPS. 2024.

[2] WANG X, CHEN Y, YUAN L, et al. OpenHands: An open platform for AI software developers as generalist agents[R]. arXiv preprint arXiv:2407.16741, 2024.
