【aider源码】03-AI编辑器拆解

Aider03_项目UV改造+AI编辑器拆解

个人针对aidier源码中关于【AI编辑器】的核心代码进行了拆解,这里面有两个知识点:【AI编辑器是如何实现的?】+【抽象语法树在AI编程中做了什么事情?】

文章目录


1-参考地址


2-思路整理

1)先试用git进行版本管理->每一步的变更都可以进行回退->GithubDesktop

2)打开项目,没有uv相关的管理文件->一定没有使用uv

3)uv改造完成之后,进行项目编译,先看一下能不能跑起来->[uv pip install -e .]

4)配置.env并进行项目测试->[aider aider/analytics.py]

5)删除非必要的测试用例和前端->方便后续进行源码阅读

6)进行项目优化->先提取tree-sitter语法树代码逻辑


3-动手实操

1-UV环境初始化

bash 复制代码
uv python pin 3.11.4

uv init
uv venv
source .venv/bin/activate

uv add -r requirements.txt

2-pyproject.toml文件修改

根据 PEP 621 规范,不能同时在 project.dependencies 中静态定义依赖,又在 project.dynamic 中将其列为动态字段

bash 复制代码
[project]
# 保留现有的 dependencies 列表
dependencies = [
    "aiohappyeyeballs==2.6.1",
    "aiohttp==3.13.3",
    # ... 其他依赖
]

dynamic = ["version"]  # 只保留 version,移除 dependencies 和 tional-dependencies

3-项目编译

UV改造之后,先尝试把项目run起来,看一下代码是不是有问题->[python -m pip install .]或者[uv pip install -e .]

1-aider编译

复制代码
# 1-使用uv进行编译安装
uv pip install -e .


# 2-查看aider版本
aider --version     
> aider 0.86.3.dev38+g8d4b34b51

2-aider代码阅读

bash 复制代码
aider aider/analytics.py               
───────────────────────────────────────────────────────────────────────────────────────────────────────────
Using sonnet model with API key from environment.
Aider v0.86.3.dev40+g74ff61df0.d20260317
Main model: claude-sonnet-4-5 with diff edit format, infinite output
Weak model: claude-haiku-4-5
Git repo: .git with 207 files
Repo-map: using 4096 tokens, auto refresh
Added aider/analytics.py to the chat.
───────────────────────────────────────────────────────────────────────────────────────────────────────────
aider/analytics.py                                                                                         
> 当前11:13:40 - LiteLLM:WARNING: get_model_cost_map.py:271 - LiteLLM: Failed to fetch remote model cost map from https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json: _ssl.c:
aider/analytics.py                                                                                         
> 当前代码有没有优化空间?                                                                                 

Initial repo scan can be slow in larger repos, but only happens once.
Scanning repo: 100%|████████████████████████████████████████████████████| 207/207 [00:01<00:00, 196.76it/s]
       ░litellm.InternalServerError: AnthropicExcept

4-项目运行

1. 获取 API Key

访问 DeepSeek 开放平台 注册并获取 API Key。

2. 配置 .env

env 复制代码
MODEL=deepseek-chat
DEEPSEEK_API_KEY=sk-your-api-key-here
VERBOSE=true

3. 使用方法

python 复制代码
# 1-指定你的python版本
uv python pin 3.11.4

# 2-初始化UV环境
uv init
uv venv
source .venv/bin/activate

# 3-安装项目对应的依赖
uv add -r requirements.txt

# 4-直接运行main.py进行测试

4. 运行日志

bash 复制代码
已连接到 pydev 调试器(内部版本号 253.30387.173)
============================================================
AI Editor - LLM-based File Editing Tool
============================================================

📝 Initializing AI Editor...
✓ Editor initialized with model: deepseek/deepseek-chat

📄 Creating test file: test_example.py
✓ Test file created

------------------------------------------------------------
Example 1: Adding type hints to functions
------------------------------------------------------------

📝 Instruction: Add type hints to all functions

🔄 Sending to LLM...
19:41:37 - LiteLLM:WARNING: get_model_cost_map.py:271 - LiteLLM: Failed to fetch remote model cost map from https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json: [Errno 54] Connection reset by peer. Falling back to local backup.

📊 Result:
  Success: True
  Edits Applied: 3
  Validation Errors: None

✓ File updated successfully!

Updated content:
----------------------------------------
def greet(name: str) -> None:
    """Greet someone by name."""
    print(f"Hello, {name}!")

def calculate_sum(a: int | float, b: int | float) -> int | float:
    """Calculate the sum of two numbers."""
    result = a + b
    return result

def main() -> None:
    """Main function."""
    greet("World")
    x = calculate_sum(5, 3)
    print(f"Sum: {x}")

if __name__ == "__main__":
    main()

----------------------------------------

------------------------------------------------------------
Example 2: Syntax validation
------------------------------------------------------------
✓ Syntax validation not applicable for this file type

------------------------------------------------------------
✓ Cleaned up test file

============================================================
Demo complete!
============================================================

4-问题答疑

1-AI编辑器是如何实现的?

答案:是基于LLM返回的以下类型的数据结构进行实现的!

shell 复制代码
aider/analytics.py
```python
<<<<<<< SEARCH
    def posthog_error(self):
        """disable posthog if we get an error"""
        print("X" * 100)
        # https://github.com/PostHog/posthog-python/blob/9e1bb8c58afaa229da24c4fb576c08bb88a75752/posthog/consumer.py#L86
        # https://github.com/Aider-AI/aider/issues/2532
        self.ph = None
=======
    def _posthog_error(self) -> None:
        """Handle PostHog errors by disabling the provider."""
        # https://github.com/PostHog/posthog-python/blob/9e1bb8c58afaa229da24c4fb576c08bb88a75752/posthog/consumer.py#L86
        # https://github.com/Aider-AI/aider/issues/2532
        self.ph = None
>>>>>>> REPLACE

1-AI编辑器Prompt-英文版

python 复制代码
"""
Prompt templates for edit block format.

This module provides system prompts and examples for the SEARCH/REPLACE edit format.
"""

DEFAULT_FENCE = ("```", "```")

# Main system prompt for the LLM
MAIN_SYSTEM = """Act as an expert software developer.
Always use best practices when coding.
Respect and use existing conventions, libraries, etc that are already present in the code base.

Take requests for changes to the supplied code.
If the request is ambiguous, ask questions.

Once you understand the request you MUST:

1. Think step-by-step and explain the needed changes in a few short sentences.

2. Describe each change with a *SEARCH/REPLACE block* per the examples below.

All changes to files must use this *SEARCH/REPLACE block* format.
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
"""

# System reminder with SEARCH/REPLACE format rules
SYSTEM_REMINDER = """# *SEARCH/REPLACE block* Rules:

Every *SEARCH/REPLACE block* must use this format:
1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
2. The opening fence and code language, eg: ```python
3. The start of search block: <<<<<<< SEARCH
4. A contiguous chunk of lines to search for in the existing source code
5. The dividing line: =======
6. The lines to replace into the source code
7. The end of the replace block: >>>>>>> REPLACE
8. The closing fence: ```

Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.

*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
Including multiple unique *SEARCH/REPLACE* blocks if needed.
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.

Keep *SEARCH/REPLACE* blocks concise.
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.

To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.

If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
- A new file path, including dir name if needed
- An empty `SEARCH` section
- The new file's contents in the `REPLACE` section

ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
"""

# Example messages showing the SEARCH/REPLACE format
EXAMPLE_MESSAGES = [
    dict(
        role="user",
        content="Change get_factorial() to use math.factorial",
    ),
    dict(
        role="assistant",
        content="""To make this change we need to modify `mathweb/flask/app.py` to:

1. Import the math package.
2. Remove the existing factorial() function.
3. Update get_factorial() to call math.factorial instead.

Here are the *SEARCH/REPLACE* blocks:

mathweb/flask/app.py
```python
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```

mathweb/flask/app.py
```python
<<<<<<< SEARCH
def factorial(n):
    "compute factorial"

    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

=======
>>>>>>> REPLACE
```

mathweb/flask/app.py
```python
<<<<<<< SEARCH
    return str(factorial(n))
=======
    return str(math.factorial(n))
>>>>>>> REPLACE
```
""",
    ),
    dict(
        role="user",
        content="Refactor hello() into its own file.",
    ),
    dict(
        role="assistant",
        content="""To make this change we need to modify `main.py` and make a new file `hello.py`:

1. Make a new hello.py file with hello() in it.
2. Remove hello() from main.py and replace it with an import.

Here are the *SEARCH/REPLACE* blocks:

hello.py
```python
<<<<<<< SEARCH
=======
def hello():
    "print a greeting"

    print("hello")
>>>>>>> REPLACE
```

main.py
```python
<<<<<<< SEARCH
def hello():
    "print a greeting"

    print("hello")
=======
from hello import hello
>>>>>>> REPLACE
```
""",
    ),
]


class EditBlockPrompts:
    """
    Container for edit block prompt templates.
    """

    main_system = MAIN_SYSTEM
    system_reminder = SYSTEM_REMINDER
    example_messages = EXAMPLE_MESSAGES

    def get_system_prompt(self, final_reminders: str = "") -> str:
        """
        Get the full system prompt with optional final reminders.

        Args:
            final_reminders: Additional reminders to append

        Returns:
            Complete system prompt string
        """
        prompt = self.main_system
        if final_reminders:
            prompt = prompt.replace("{final_reminders}", final_reminders)
        return prompt

    def get_messages(self, user_instruction: str, include_examples: bool = True) -> list:
        """
        Get messages for a completion request.

        Args:
            user_instruction: The user's instruction
            include_examples: Whether to include example messages

        Returns:
            List of message dicts
        """
        messages = []

        if include_examples:
            # Add system prompt
            messages.append({
                "role": "system",
                "content": self.get_system_prompt() + "\n\n" + self.system_reminder
            })

            # Add examples
            messages.extend(self.example_messages)

        # Add user instruction
        messages.append({
            "role": "user",
            "content": user_instruction
        })

        return messages


__all__ = [
    "EditBlockPrompts",
    "MAIN_SYSTEM",
    "SYSTEM_REMINDER",
    "EXAMPLE_MESSAGES",
    "DEFAULT_FENCE",
]

2-AI编辑器Prompt-中文版

python 复制代码
"""
编辑块格式的提示模板。

本模块为 SEARCH/REPLACE 编辑格式提供系统提示和示例。
"""

DEFAULT_FENCE = ("```", "```")

# LLM 的主系统提示
MAIN_SYSTEM = """扮演一位专家级软件开发者。
编码时始终遵循最佳实践。
尊重并使用代码库中已有的约定、库等。

处理对所提供代码的更改请求。
如果请求不明确,请提出问题。

一旦理解了请求,你必须:

1. 逐步思考并用简短的几句话解释所需的更改。

2. 按照下面的示例,用 *SEARCH/REPLACE 块* 描述每一处更改。

对文件的所有更改必须使用此 *SEARCH/REPLACE 块* 格式。
只能通过 *SEARCH/REPLACE 块* 返回代码!
"""

# 包含 SEARCH/REPLACE 格式规则的系统提醒
SYSTEM_REMINDER = """# *SEARCH/REPLACE 块* 规则:

每个 *SEARCH/REPLACE 块* 必须使用以下格式:
1. 完整的文件路径单独占一行,需原样显示。没有粗体星号,没有引号,没有转义字符等。
2. 起始围栏和代码语言,例如:```python
3. 搜索块的开始:<<<<<<< SEARCH
4. 在现有源代码中搜索的一段连续行
5. 分隔线:=======
6. 要替换进源代码的行
7. 替换块的结束:>>>>>>> REPLACE
8. 结束围栏:```

每个 *SEARCH* 部分必须与现有文件内容*完全匹配*,逐字逐句,包括所有注释、文档字符串等。
如果文件包含包装/转义在 json/xml/引号 或其他容器中的代码或其他数据,你需要建议对文件的字面内容进行编辑,包括容器标记。

*SEARCH/REPLACE* 块将*仅替换第一个匹配项*。
如果需要,请包含多个唯一的 *SEARCH/REPLACE* 块。
在每个 SEARCH 部分中包含足够的行,以唯一匹配每组需要更改的行。

保持 *SEARCH/REPLACE* 块简洁。
将大的 *SEARCH/REPLACE* 块分解为一系列较小的块,每个块更改文件的一小部分。
仅包含更改的行,如果需要唯一性,则包含周围几行。
不要在 *SEARCH/REPLACE* 块中包含大段未更改的行。

要在文件内移动代码,请使用 2 个 *SEARCH/REPLACE* 块:1 个用于从当前位置删除它,1 个用于将其插入新位置。

如果要将代码放入新文件中,请使用带有以下内容的 *SEARCH/REPLACE 块*:
- 一个新的文件路径,如有需要包括目录名
- 一个空的 `SEARCH` 部分
- `REPLACE` 部分中的新文件内容

只能通过 *SEARCH/REPLACE 块* 返回代码!
"""

# 展示 SEARCH/REPLACE 格式的示例消息
EXAMPLE_MESSAGES = [
    dict(
        role="user",
        content="更改 get_factorial() 以使用 math.factorial",
    ),
    dict(
        role="assistant",
        content="""要进行此更改,我们需要修改 `mathweb/flask/app.py` 以:

1. 导入 math 包。
2. 删除现有的 factorial() 函数。
3. 更新 get_factorial() 以调用 math.factorial。

以下是 *SEARCH/REPLACE* 块:

mathweb/flask/app.py
```python
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```

mathweb/flask/app.py
```python
<<<<<<< SEARCH
def factorial(n):
    "compute factorial"

    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

=======
>>>>>>> REPLACE
```

mathweb/flask/app.py
```python
<<<<<<< SEARCH
    return str(factorial(n))
=======
    return str(math.factorial(n))
>>>>>>> REPLACE
```
""",
    ),
    dict(
        role="user",
        content="将 hello() 重构到它自己的文件中。",
    ),
    dict(
        role="assistant",
        content="""要进行此更改,我们需要修改 `main.py` 并创建一个新文件 `hello.py`:

1. 创建一个包含 hello() 的新 hello.py 文件。
2. 从 main.py 中删除 hello() 并用导入语句替换它。

以下是 *SEARCH/REPLACE* 块:

hello.py
```python
<<<<<<< SEARCH
=======
def hello():
    "print a greeting"

    print("hello")
>>>>>>> REPLACE
```

main.py
```python
<<<<<<< SEARCH
def hello():
    "print a greeting"

    print("hello")
=======
from hello import hello
>>>>>>> REPLACE
```
""",
    ),
]


class EditBlockPrompts:
    """
    编辑块提示模板的容器。
    """

    main_system = MAIN_SYSTEM
    system_reminder = SYSTEM_REMINDER
    example_messages = EXAMPLE_MESSAGES

    def get_system_prompt(self, final_reminders: str = "") -> str:
        """
        获取带有可选最终提醒的完整系统提示。

        Args:
            final_reminders: 要追加的额外提醒

        Returns:
            完整的系统提示字符串
        """
        prompt = self.main_system
        if final_reminders:
            prompt = prompt.replace("{final_reminders}", final_reminders)
        return prompt

    def get_messages(self, user_instruction: str, include_examples: bool = True) -> list:
        """
        获取补全请求的消息列表。

        Args:
            user_instruction: 用户指令
            include_examples: 是否包含示例消息

        Returns:
            消息字典列表
        """
        messages = []

        if include_examples:
            # 添加系统提示
            messages.append({
                "role": "system",
                "content": self.get_system_prompt() + "\n\n" + self.system_reminder
            })

            # 添加示例
            messages.extend(self.example_messages)

        # 添加用户指令
        messages.append({
            "role": "user",
            "content": user_instruction
        })

        return messages


__all__ = [
    "EditBlockPrompts",
    "MAIN_SYSTEM",
    "SYSTEM_REMINDER",
    "EXAMPLE_MESSAGES",
    "DEFAULT_FENCE",
]

3-LLM返回之后的代码解析


2-抽象语法树在AI编程中做了什么事情?

📌 核心要点

在 AI Editor 中,抽象语法树(AST) 通过 Tree-sitter 实现,是保障 LLM 生成代码质量的关键组件。


🎯 AST 做的 5 件事

1. 语法验证 (find_syntax_errors)
python 复制代码
errors = find_syntax_errors("file.py", code)
# 返回:[1, 5] 表示第 1、5 行有语法错误

作用: LLM 编辑后立即验证,防止破坏代码。

2. 结构提取 (extract_tags)
python 复制代码
tags = list(extract_tags("file.py", "file.py", code))
# 返回:[Tag(name="foo", kind="def", line=5), ...]

作用: 提取函数、类等定义,理解代码结构。

3. 语言检测 (filename_to_lang)
python 复制代码
lang = filename_to_lang("script.py")  # → "python"
parser = get_parser(lang)

作用: 自动识别 30+ 种编程语言。

4. 上下文显示 (TreeContext)
python 复制代码
context = format_tree_context("file.py", code, [5, 10])
# 高亮第 5、10 行及其上下文

作用: 显示代码结构,帮助理解。

5. 查询执行 (run_query)
python 复制代码
captures = run_query(query, tree.root_node)
# 使用查询规则提取特定节点

作用: 精确提取 AST 节点信息。


🔄 完整工作流程

复制代码
用户指令
   ↓
读取文件
   ↓
提取结构 ← AST (extract_tags)
   ↓
发送到 LLM (含上下文)
   ↓
LLM 生成 SEARCH/REPLACE
   ↓
解析并应用编辑
   ↓
语法验证 ← AST (find_syntax_errors) ⭐
   ↓
  有错误?
   ├─ YES → 拒绝写入,提示错误
   └─ NO  → 写入文件成功

💡 为什么需要 AST?

问题:LLM 可能生成错误代码
python 复制代码
# LLM 生成
def calculate(a, b
    return a + b  # ← 缺少闭合括号
解决:AST 立即检测
python 复制代码
errors = find_syntax_errors("file.py", code)
# 返回:[1] → 第 1 行有语法错误

# 拒绝写入,保护代码完整性

相关推荐
jxkejiiii2 小时前
手机无法截屏?可能是手势没设置对(iOS 与 Android 通用排查指南)
人工智能·安全·智能手机
集芯微电科技有限公司2 小时前
氮化镓GaN FET/GaN HEMT功率驱动器选型一览表
人工智能·单片机·嵌入式硬件·深度学习·神经网络·生成对抗网络
艾莉丝努力练剑2 小时前
【Linux:文件 + 进程】进程间通信进阶(1)
linux·运维·服务器·网络·c++·人工智能·进程
珠海西格电力2 小时前
5G+物联网,零碳园区管理系统的“信息高速路”
大数据·人工智能·物联网·算法·5g
咬人喵喵2 小时前
E2 编辑器彩妆行业 SVG 实战解析:技术效果 + 品牌落地 + 营销节点全映射
编辑器
AIDF20262 小时前
多卡推理性能下降如何定位:通信拓扑与 Profiling 实战
大数据·人工智能
狒狒热知识2 小时前
2026企业新闻发稿全景趋势洞察,品牌推广平台核心优选指南
大数据·人工智能
Niuguangshuo2 小时前
从信号到文字:语音识别(ASR)技术链路解析
人工智能·语音识别