如何与AI结对编程:我与AI的8000行代码实践

简介: 作者分享了跟 AI 协作的一些经验,使用中如何对 AI 输入和反馈,经过磨合后,工作效率会大大提升。

前言

今年我提交了八千多行代码,但没有一行是我自己写的。最近我的工作日常就是:给 AI 提需求,它来实现,出问题了我给它反馈,让它自己修改。AI 编程的能力的发展速度可谓是火箭一般,去年它还只能做代码补全,今年已经可以自己哼哧哼哧把代码都写完了。


现在的 AI 编程当红炸子鸡非 Cursor 莫属,用 Cursor 的过程很有意思,你会很容易活成了我们最不希望变成的人。我一开始用 Cursor 主要用它来开发 AI agent 的一些 demo,比如快速搭建一个 Gradio 界面来方便演示算法微调的对话模型。


用了几天之后, 我发现精神上我是一个程序员,但是实际的工作体验更像是产品经理 + 测试工程师。我们经常会遇到提一句话需求的产品,然后就反怼他能不能把需求写清楚。但在 Cursor 面前,我也会期望它能通过我一句话的需求就能读懂我内心真实的需求,比如:“做一个AI 对话的 Gradio 界面”。


这使用 AI 编程的方式我称之为许愿式编程,当然这种模糊的需求无法让 AI 产出满意的效果。所以我会经常让它推倒重来,不停让它改,就像产品不停的提需求变更一样。如果 AI 也有内心戏,它一定会想:你能不能一次把需求讲清楚?



很多人用不好 AI 编程的主要原因就是讲不清楚需求,对程序员来说,用编程语言来表达想法很简单,但用自然语言表达想法却很困难。所以很多人会有一种感觉,有这个功夫把需求写清楚,我代码都写完了。反复让 AI 返工,真不如自己写的快。


AI 现在没有读心术,我们只能通过自然语言跟它交流,你可以把 AI 当做一个实习生,它无法独立完成任务,需要你给它输入和反馈。一开始可能效率反而低,因为你需要教给它很多东西,当你跟它磨合的很好的时候,效率就会大大提升。



接下来我会分享我跟 AI 协作的一些经验,希望对大家有所帮助。


工具选择

虽然我一开始用的是 Cursor ,但作为一名 Java 程序员,我并不推荐用 Cursor 来开发生产级别的 Java 项目。Cursor 我一般用来快速做一些 Python 小项目,比如批量读取数据调用大模型,开发一个 workflow 快速验证效果等。这些小项目的特点是不需要经过精心设计,跑起来能用就行,所以可以让 Cursor 自由发挥。


但真要用在生产级别的 Java 项目里面,我就不能像产品经理一样,只是描述需求就可以了。我需要帮 AI 把功能点拆的很细,经常是要拆到函数级别,然后给它补充足够的上下文。并且生产级别的项目各种类的引用关系,依赖的引用,日志文件的打印格式等,这些都是 AI 自己无法完成,这些能力 IDEA 已经做的很好了,脱离 IDEA 的环境去写 Java 代码,是非常不方便的。所以我去年一直都是手动复制代码上下文到 Chat 界面里面,让 AI 生成代码,我再拷贝贴到 IDEA 里面,虽然可行,但是并不是很方便。


终于在今年我在网上看到了一款产品:repoprompt(https://repoprompt.com/


完美解决了我的痛点。它本质是半自动的 Cursor,它的生成过程是白盒化的。而Cursor 自己集成了大模型 API,底层的运行过程是个黑盒,这对于小白用户很友好,但对于 Pro Code 用户来说,我期望整个过程是我可控的,同时 Cursor 集成大模型的同时也限制模型的可用范围,你只能用它提供的模型,无法使用最先进的其他模型,比如 o1-pro。



repoprompt白盒化的另一个好处是它会强迫你思考应该给大模型提供哪些上下文,把任务拆小,拆细,这样效率虽然没有全自动那么高,但成功率很高很多,减少了返工时间。

基础用法

repoprompt 最基础的用法就是打开项目,然后选中要给模型参考的上下文,给模型写指令,一键复制指令,它会自动把选中的代码文件和指令拼接成一个 prompt,你只需要粘贴到 Chat 界面中等模型返回结果就行。下面举个例子:

 

复制后的 prompt 如下:


<file_map>
/Users/xiaohansong/IdeaProjects/medigw
└── app
    └── service
        └── src
            └── main
                └── java
                    └── com
                        └── xxx
                            └── medigw
                                └── core
                                    └── service
                                        └── ocr
                                            └── Doc2xOcrService.java
<Referenced APIs>
Path: /Users/xiaohansong/IdeaProjects/medigw/app/service/src/main/java/com/xxx/medigw/core/service/ocr/ParserProgress.java
---
Classes:
  Class: ParserProgress
    Methods:
      - public Integer getTotalProgress() {
      - public void setTotalProgress(Integer totalProgress) {
      - public Map<String, Integer> getProgressMap() {
      - public void setProgressMap(Map<String, Integer> progressMap) {
      - public String getStatus() {
      - public void setStatus(String status) {
      - public Map<String, String> getErrorMap() {
      - public void setErrorMap(Map<String, String> errorMap) {
---
</Referenced APIs>
</file_map>
<file_contents>
File: app/service/src/main/java/com/medigw/core/service/ocr/Doc2xOcrService.java
```java
package com.xxx.medigw.core.service.ocr;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xxx.medigw.common.utils.LoggerUtil;
import com.xxx.medigw.core.service.oss.Doc2xConfig;
import com.xxx.medigw.core.service.oss.OSSService;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class Doc2xOcrService {
    private final static Logger LOGGER = LoggerFactory.getLogger(Doc2xOcrService.class);
    /**
     * doc2x解析服务endpoint
     */
    private static final String OSS_BUCKET_NAME = "dataset";
    private static final String DOC2X_TARGET_BASE_DIR = "doc2x_result/";
    /**
     * 提交文件解析
     *
     * @param sourcePath    读取文件路径
     * @return uuids
     */
    public static String submitFile(String sourcePath) {
        String url = DOC2X_PARSER_ENDPOINT + "/parser/submit";
        try {
            Map<String, Object> params = new HashMap<>();
            params.put("path", sourcePath);
            params.put("bucket", OSS_BUCKET_NAME);
            String result = Request.Post(url)
                    .bodyString(JSON.toJSONString(params), ContentType.APPLICATION_JSON)
                    .addHeader("accessKey", Doc2xConfig.DOC2X_APP_ACCESS_KEY)
                    .addHeader("secretKey", Doc2xConfig.DOC2X_APP_SECRET_KEY)
                    .execute()
                    .returnContent()
                    .asString();
            ParserClientResult<String> resultModel = JSON.parseObject(result,
                    new TypeReference<ParserClientResult<String>>() {
                    });
            if (resultModel.isSuccess() && resultModel.getData() != null) {
                return resultModel.getData();
            } else {
                LoggerUtil.info(LOGGER, "[Doc2xOcrService] submitFile fail, sourcePath={0}, result={1}", sourcePath, result);
            }
        } catch (Exception e) {
            LoggerUtil.error(e, LOGGER, "[Doc2xOcrService] submitFile exception, sourcePath={0}", sourcePath);
        }
        return null;
    }
    public static String fetchResult(String sourcePath) {
        int lastIndex = sourcePath.lastIndexOf(".");
        String resultFilePath = DOC2X_TARGET_BASE_DIR + sourcePath.substring(0, lastIndex) + ".json";
        // 秘钥后面保存在mist
        OSSService ossService = new OSSService("https://oss.aliyun.com",
                Doc2xConfig.DOC2X_OSS_ACCESS_KEY_ID,
                Doc2xConfig.DOC2X_OSS_ACCESS_KEY_SECRET,
                OSS_BUCKET_NAME);
        String result = null;
        try {
            result = ossService.readFile(resultFilePath);
        } catch (Exception e) {
            LoggerUtil.error(e, LOGGER, "[Doc2xOcrService] fetchResult exception, sourcePath={0}", sourcePath);
        } finally {
            ossService.shutdown();
        }
        return result;
    }
```
</file_contents>
<user_instructions>
重构一下代码,去掉 static 方法,OSSService 要复用
</user_instructions>

可以看到 prompt 里面包括了:文件的路径和内容,代码指令。这里面的信息是白盒的,你在使用 Cursor 的时候基本上也是这样的调用大模型的,只不过调用大模型这一步由自己来完成。把上面的 prompt 随便贴到某个 Chat 界面中就能拿到重构后的代码结果,然后把生成的代码贴到 IDEA 里面就行。

进阶用法

在让大模型写代码的时候,经常会出现它写的跟你想要的不是一回事,这里有个小技巧能解决这个问题,那就是让它“先聊再写”。具体的操作就是在 prompt 加入一句话:“先别写代码,咱们先聊聊,直到我让你写代码”。同样是上面那个例子,我们看看效果。



加了这句话之后,大模型就会先说一下自己的思路,这个时候我就可以给它提意见。比如我这里就让它直接改成用 spring bean 注入的方式来重构代码。



高阶用法

前面的用法一个是解决了快速给大模型提供上下文的问题,一个是解决大模型代码生产不符合预期的问题,那能不能让它直接帮我编辑文件呀,省得还要手动复制粘贴?当然可以。repoprompt提供了这个功能。

首先,XML Diff 这里要打勾,这样 prompt 指令里面就会让大模型返回 xml 的格式的代码变更。



XML Diff打勾之后,复制的 prompt 中会增加 200 多行的指令教大模型怎么返回 xml 格式的代码,方便接下来做代码自动合并。注意这个功能只有 Claude sonnet 3.5 以上的版本可用,因为其他模型的代码能力不够强。

XML Diff


<xml_formatting_instructions>
### Role
- You are a **code editing assistant**: You can fulfill edit requests and chat with the user about code or other questions. Provide complete instructions or code lines when replying with xml formatting.
### Capabilities
- Can create new files.
- Can rewrite entire files.
- Can perform partial search/replace modifications.
- Can delete existing files.
Avoid placeholders like `...` or `// existing code here`. Provide complete lines or code.
## Tools & Actions
1. **create** – Create a new file if it doesn’t exist.
2. **rewrite** – Replace the entire content of an existing file.
3. **modify** (search/replace) – For partial edits with <search> + <content>.
4. **delete** – Remove a file entirely (empty <content>).
### **Format to Follow for Repo Prompt's Diff Protocol**
<Plan>
Describe your approach or reasoning here.
</Plan>
<file path="path/to/example.swift" action="one_of_the_tools">
  <change>
    <description>Brief explanation of this specific change</description>
    <search>
===
// Exactly matching lines to find
===
    </search>
    <content>
===
// Provide the new or updated code here. Do not use placeholders
===
    </content>
  </change>
  <!-- Add more <change> blocks if you have multiple edits for the same file -->
</file>
#### Tools Demonstration
1. `<file path="NewFile.swift" action="create">` – Full file in <content>
2. `<file path="DeleteMe.swift" action="delete">` – Empty <content>
3. `<file path="ModifyMe.swift" action="modify">` – Partial edit with `<search>` + `<content>`
4. `<file path="RewriteMe.swift" action="rewrite">` – Entire file in <content>
5. `<file path="RewriteMe.swift" action="rewrite">` – Entire file in <content>. No <search> required.
## Format Guidelines
1. **Plan**: Begin with a `<Plan>` block explaining your approach.
2. **<file> Tag**: e.g. `<file path="Models/User.swift" action="...">`. Must match an available tool.
3. **<change> Tag**: Provide `<description>` to clarify each change. Then `<content>` for new/modified code. Additional rules depend on your capabilities.
4. **modify**: **<search> & <content>**: Provide code blocks enclosed by ===. Respect indentation exactly, ensuring the <search> block matches the original source down to braces, spacing, and any comments. The new <content> will replace the <search> block, and should should fit perfectly in the space left by it's removal.
5. **modify**: For changes to the same file, ensure that you use multiple change blocks, rather than separate file blocks.
6. **rewrite**: For large overhauls; omit `<search>` and put the entire file in `<content>`.
7. **create**: For new files, put the full file in <content>.
8. **delete**: Provide an empty <content>. The file is removed.
## Code Examples
-----
### Example: Search and Replace (Add email property)
<Plan>
Add an email property to `User` via search/replace.
</Plan>
<file path="Models/User.swift" action="modify">
  <change>
    <description>Add email property to User struct</description>
    <search>
===
struct User {
    let id: UUID
    var name: String
}
===
    </search>
    <content>
===
struct User {
    let id: UUID
    var name: String
    var email: String
}
===
    </content>
  </change>
</file>
-----
### Example: Negative Example - Mismatched Search Block
// Example Input (not part of final output, just demonstration)
<file_contents>
File: path/service.swift
```
import Foundation
class Example {
    foo() {
        Bar()
    }
}
```
</file_contents>
<Plan>
Demonstrate how a mismatched search block leads to failed merges.
</Plan>
<file path="path/service.swift" action="modify">
  <change>
    <description>This search block is missing or has mismatched indentation, braces, etc.</description>
    <search>
===
    foo() {
        Bar()
    }
===
    </search>
    <content>
===
    foo() {
        Bar()
        Bar2()
    }
===
    </content>
  </change>
</file>
<!-- This example fails because the <search> block doesn't exactly match the original file contents. -->
-----
### Example: Negative Example - Mismatched Brace Balance
// This negative example shows how adding extra braces in the <content> can break brace matching.
<Plan>
Demonstrate that the new content block has one extra closing brace, causing mismatched braces.
</Plan>
<file path="Functions/MismatchedBracesExample.swift" action="modify">
  <change>
    <description>Mismatched brace balance in the replacement content</description>
    <search>
===
    foo() {
        Bar()
    }
===
    </search>
    <content>
===
    foo() {
        Bar()
    }
    bar() {
        foo2()
    }
}
===
    </content>
  </change>
</file>
<!-- Because the <search> block was only a small brace segment, adding extra braces in <content> breaks the balance. -->
-----
### Example: Negative Example - One-Line Search Block
<Plan>
Demonstrate a one-line search block, which is too short to be reliable.
</Plan>
<file path="path/service.swift" action="modify">
  <change>
    <description>One-line search block is ambiguous</description>
    <search>
===
var email: String
===
    </search>
    <content>
===
var emailNew: String
===
    </content>
  </change>
</file>
<!-- This example fails because the <search> block is only one line and ambiguous. -->
-----
### Example: Negative Example - Ambiguous Search Block
<Plan>
Demonstrate an ambiguous search block that can match multiple blocks (e.g., multiple closing braces).
</Plan>
<file path="path/service.swift" action="modify">
  <change>
    <description>Ambiguous search block with multiple closing braces</description>
    <search>
===
    }
}
===
    </search>
    <content>
===
        foo() {
        }
    }
}
===
    </content>
  </change>
</file>
<!-- This example fails because the <search> block is ambiguous due to multiple matching closing braces. -->
-----
### Example: Full File Rewrite
<Plan>
Rewrite the entire User file to include an email property.
</Plan>
<file path="Models/User.swift" action="rewrite">
  <change>
    <description>Full file rewrite with new email field</description>
    <content>
===
import Foundation
struct User {
    let id: UUID
    var name: String
    var email: String
    init(name: String, email: String) {
        self.id = UUID()
        self.name = name
        self.email = email
    }
}
===
    </content>
  </change>
</file>
-----
### Example: Create New File
<Plan>
Create a new RoundedButton for a custom Swift UIButton subclass.
</Plan>
<file path="Views/RoundedButton.swift" action="create">
  <change>
    <description>Create custom RoundedButton class</description>
    <content>
===
import UIKit
@IBDesignable
class RoundedButton: UIButton {
    @IBInspectable var cornerRadius: CGFloat = 0
}
===
    </content>
  </change>
</file>
-----
### Example: Delete a File
<Plan>
Remove an obsolete file.
</Plan>
<file path="Obsolete/File.swift" action="delete">
  <change>
    <description>Completely remove the file from the project</description>
    <content>
===
===
    </content>
  </change>
</file>
## Final Notes
1. **modify** Always wrap the exact original lines in <search> and your updated lines in <content>, each enclosed by ===.
2. **modify** The <search> block must match the source code exactly—down to indentation, braces, spacing, and any comments. Even a minor mismatch causes failed merges.
3. **modify** Only replace exactly what you need. Avoid including entire functions or files if only a small snippet changes, and ensure the <search> content is unique and easy to identify.
4. **rewrite** Use `rewrite` for major overhauls, and `modify` for smaller, localized edits. Rewrite requires the entire code to be replaced, so use it sparingly.
5. You can always **create** new files and **delete** existing files. Provide full code for create, and empty content for delete. Avoid creating files you know exist already.
6. If a file tree is provided, place your files logically within that structure. Respect the user’s relative or absolute paths.
7. Wrap your final output in ```XML ... ``` for clarity.
8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). Repo Prompt expects raw XML exactly as shown in the examples.
9. **IMPORTANT** IF MAKING FILE CHANGES, YOU MUST USE THE AVAILABLE XML FORMATTING CAPABILITIES PROVIDED ABOVE - IT IS THE ONLY WAY FOR YOUR CHANGES TO BE APPLIED.
10. The final output must apply cleanly with no leftover syntax errors.
</xml_formatting_instructions>

然后大模型就会生成一段 xml 格式的响应。


<Plan>
修改 FileParserServiceImpl 类中的方法签名和实现逻辑,使其与 FileParserService 接口保持一致。主要变更是将返回类型从 Result<T> 改为直接返回 T,并相应调整异常处理逻辑。
</Plan>

<file path="app/service/src/main/java/com/xxx/medigw/core/service/parser/impl/FileParserServiceImpl.java" action="rewrite">
  <change>
    <description>更新 FileParserServiceImpl 实现类,使其方法签名与 FileParserService 接口一致</description>
    <content>
===
package com.xxx.medigw.core.service.parser.impl;

import com.xxx.medigw.common.utils.LoggerUtil;
import com.xxx.medigw.core.service.ocr.Doc2xOcrService;
import com.xxx.medigw.rpc.model.ParserProgress;
import com.xxx.medigw.rpc.model.ParserResult;
import com.xxx.medigw.rpc.FileParserService;
import com.xxx.sofa.runtime.api.annotation.SofaService;
import com.xxx.sofa.runtime.api.annotation.SofaServiceBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 文件解析服务实现类
 *
 * 主要职责:
 * 1. 提供文件解析进度查询功能
 * 2. 提供文件解析结果获取功能
 *
 * 本服务作为业务层与底层OCR服务的中间层,提供统一的错误处理和日志记录
 */
@Service
public class FileParserServiceImpl implements FileParserService {

    /**
     * 日志记录器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(FileParserServiceImpl.class);

    /**
     * 文档解析服务
     */
    @Autowired
    private Doc2xOcrService doc2xOcrService;

    /**
     * 查询文件解析进度
     *
     * 根据任务ID查询文件解析的当前进度状态,提供统一的错误处理
     *
     * @param taskId 解析任务ID
     * @return 解析进度信息,包含状态、总进度百分比、详细进度映射和错误信息(如果有)
     * @throws IllegalArgumentException 当参数无效时抛出
     * @throws RuntimeException 当系统发生异常时抛出
     */
    @Override
    public ParserProgress queryParserProgress(String taskId) {
        // 1. 参数校验
        if (taskId == null || taskId.isEmpty()) {
            LoggerUtil.warn(LOGGER, "查询解析进度失败,任务ID不能为空");
            throw new IllegalArgumentException("任务ID不能为空");
        }

        try {
            // 2. 调用服务查询解析进度
            ParserProgress progress = doc2xOcrService.queryParserProgress(taskId);

            // 3. 处理查询结果
            if (progress == null) {
                LoggerUtil.error(LOGGER, "获取解析进度失败,taskId: {0}", taskId);
                throw new RuntimeException("获取解析进度失败,请稍后重试");
            }

            LoggerUtil.info(LOGGER, "查询解析进度成功,taskId={0}, status={1}, progress={2}%",
                    taskId, progress.getStatus(), progress.getTotalProgress());

            // 4. 返回进度信息
            return progress;
        } catch (Exception e) {
            // 5. 异常处理
            LoggerUtil.error(e, LOGGER, "查询解析进度异常,taskId: {0}", taskId);
            throw new RuntimeException("查询解析进度异常: " + e.getMessage(), e);
        }
    }

    /**
     * 获取文件解析结果
     *
     * 根据任务ID获取文件解析完成后的结果数据,提供统一的错误处理
     *
     * @param taskId 解析任务ID
     * @return 解析结果数据,结构化的ParserResult对象
     * @throws IllegalArgumentException 当参数无效时抛出
     * @throws RuntimeException 当系统发生异常时抛出
     */
    @Override
    public ParserResult fetchResult(String taskId) {
        // 1. 参数校验
        if (taskId == null || taskId.isEmpty()) {
            LoggerUtil.warn(LOGGER, "获取解析结果失败,任务ID不能为空");
            throw new IllegalArgumentException("任务ID不能为空");
        }

        try {
            // 2. 调用服务获取解析结果
            ParserResult result = doc2xOcrService.fetchResult(taskId);

            // 3. 处理获取结果
            if (result == null) {
                LoggerUtil.error(LOGGER, "获取解析结果失败,taskId: {0}", taskId);
                throw new RuntimeException("获取解析结果失败,可能解析尚未完成或解析失败");
            }

            LoggerUtil.info(LOGGER, "获取解析结果成功,taskId={0}, content长度={1}",
                    taskId, result.getContent() != null ? result.getContent().length() : 0);

            // 4. 返回结果数据
            return result;
        } catch (Exception e) {
            // 5. 异常处理
            LoggerUtil.error(e, LOGGER, "获取解析结果异常,taskId: {0}", taskId);
            throw new RuntimeException("获取解析结果异常: " + e.getMessage(), e);
        }
    }
}
===
    </content>
  </change>
</file>

将这段 prompt 和响应贴到 Apply 的界面中,就能进行代码合并。



点击 Merge Changes,就可以自动合并代码。



XML Diff 解释

接下来我解释一下这段神奇的 XML Diff 到底做了什么。这段 prompt 提供了使用 XML 格式指令来控制代码编辑助手进行代码修改的详细指南。它定义了助手的角色、能力、可用的工具和操作,以及进行各种代码编辑操作(如创建、修改、重写和删除文件)时必须遵循的具体 XML 格式。

1.Role (角色)

  • Code Editing Assistant (代码编辑助手): 明确了助手的定位,它是用来编辑代码的。
  • 能力: 助手能够执行编辑请求,并与用户讨论代码或其他问题。
  • 输出格式: 强调助手在回复时需要提供完整的指令或代码行,并使用 XML 格式。


2.Capabilities (能力)

  • Create new files (创建新文件): 助手可以创建新的代码文件。
  • Rewrite entire files (重写整个文件): 助手可以替换现有文件的全部内容。
  • Perform partial search/replace modifications (执行部分搜索/替换修改): 助手可以在文件中查找特定的代码片段并替换它们。
  • Delete existing files (删除现有文件): 助手可以删除文件。
  • 避免占位符:  强调输出的代码必须完整,不能使用 ... 或 // existing code here 这样的占位符。


3.Tools & Actions (工具 & 操作)

这部分列出了助手可用的具体工具,以及每个工具对应的操作:

  • create:  创建新文件。如果文件不存在,则创建它。
  • rewrite:  重写现有文件的全部内容。
  • modify (search/replace): 部分修改文件。使用 <search> 标签指定要查找的内容,<content> 标签指定替换的内容。
  • delete:  删除文件。<content> 标签为空。


4.Format to Follow for Repo Prompt's Diff Protocol (仓库 Prompt 的 Diff 协议格式)

这是核心部分,详细说明了 XML 格式的结构:

  • <Plan>:
  • 这是一个标签,用于描述你的方法或推理。
  • 助手应该在这里解释它计划如何执行请求,例如,它将修改哪些文件,以及修改的原因。
  • <file path="path/to/example.swift" action="one_of_the_tools">
  • <file>标签:  用于指定要操作的文件。
  • path 属性: 文件路径,例如 path/to/example.swift
  • action 属性:  指定要执行的操作,必须是 createrewritemodifydelete 中的一个。
  • 一个 <file> 标签代表对一个文件的操作。
  • <change>
  • <file> 标签内,使用 <change> 标签来描述一个具体的修改。一个 <file> 标签可以包含多个 <change> 标签,用于对同一个文件进行多次修改。
  • <description>:  简要解释这个特定修改的目的。
  • <search>: (仅用于 modify 操作)
  • 使用 <search> 标签来包裹要搜索的代码块。
  • 代码块需要用 === 包裹。
  • 重要:  <search> 块中的代码必须与原始文件中的代码完全匹配,包括缩进、空格、大括号、注释等。
  • <content>:
  • 使用 <content> 标签来包裹要替换或添加的代码块。
  • 代码块需要用 === 包裹。
  • 对于 modify 操作,<content> 中的代码将替换 <search> 块找到的代码。
  • 对于 createrewrite 操作,<content> 包含文件的完整内容。
  • 对于 delete 操作,<content> 为空。
  • <!-- Add more <change> blocks ... -->:  提示可以在同一个 <file> 标签内添加多个 <change> 标签来执行多个编辑操作。


5.Tools Demonstration (工具演示)

提供了一些简单的 XML 示例,演示如何使用不同的工具:

  • <file path="NewFile.swift" action="create">: 创建新文件 NewFile.swift,文件内容放在 <content> 标签中。
  • <file path="DeleteMe.swift" action="delete">: 删除文件 DeleteMe.swift<content> 标签为空。
  • <file path="ModifyMe.swift" action="modify">: 修改文件 ModifyMe.swift,使用 <search><content> 标签进行部分替换。
  • <file path="RewriteMe.swift" action="rewrite">: 重写文件 RewriteMe.swift,整个新文件内容放在 <content> 标签中。
  • <file path="RewriteMe.swift" action="rewrite">: 再次强调 rewrite 操作不需要 <search> 标签。


6.Format Guidelines (格式指南)

总结了 XML 格式的关键规则:

  • <Plan>:  必须以 <Plan> 标签开始,描述方法。
  • <file> Tag:  使用 <file path="..." action="..."> 标签,action 必须是可用的工具之一。
  • <change> Tag:  在 <file> 中使用 <change>,提供 <description><content>。对于 modify,还需要 <search>
  • modify: <search> & <content>:  强调 <search><content> 代码块需要用 === 包裹,并且 <search> 块必须与原始代码完全匹配。
  • modify:  对于同一文件的多个修改,使用多个 <change> 标签,而不是多个 <file> 标签。
  • rewrite:  用于大型修改,省略 <search>,整个新文件内容放在 <content> 中。
  • create:  用于创建新文件,整个文件内容放在 <content> 中。
  • delete:  用于删除文件,<content> 为空。


7.Code Examples(代码示例)

提供了一系列代码示例,包括:

  • Search and Replace (Add email property):  演示 modify 操作,添加 email 属性到 User 结构体。
  • Negative Example - Mismatched Search Block:  展示了 <search> 块不匹配原始代码会导致修改失败。
  • Negative Example - Mismatched Brace Balance:  展示了 <content> 中括号不平衡会导致问题。
  • Negative Example - One-Line Search Block:  说明单行 <search> 块可能过于模糊不可靠。
  • Negative Example - Ambiguous Search Block:  说明模糊的 <search> 块(例如多个匹配的 closing brace)会导致问题。
  • Full File Rewrite:  演示 rewrite 操作,重写整个 User 文件以包含 email 属性。
  • Create New File:  演示 create 操作,创建一个新的 RoundedButton 类。
  • Delete a File:  演示 delete 操作,删除一个文件。


8.Final Notes (最终注意事项)

总结了关键要点和注意事项:

1)modify:  <search><content> 都需要用 === 包裹。

2)modify:  <search> 必须完全匹配原始代码,包括所有细节。

3)modify:  只替换需要修改的部分,<search> 内容应尽可能独特且易于识别。

4)rewrite:  用于大型修改,modify 用于局部修改。rewrite 替换整个文件,谨慎使用。

5)createdelete:  可以随时创建和删除文件。create 提供完整代码,delete 提供空 <content>。避免创建已存在的文件。

6)文件路径:  如果提供文件树,文件应放置在逻辑位置。尊重相对或绝对路径。

7)XML 包裹:  最终输出需要用 ``XML ... `` 包裹,为了清晰。(这可能是一个格式化的约定,在实际应用中可能不需要严格遵守 XML 格式本身的要求,而是要确保输出是 XML 结构)

8)禁止 CDATA:  重要: 不要将 XML 输出包裹在 CDATA 标签中 (<![CDATA[ ... ]]>)。Repo Prompt 期望的是原始 XML。

9)强制 XML 格式: 重要: 如果要进行文件更改,必须使用提供的 XML 格式,这是应用更改的唯一方式。

10)语法正确:  最终输出必须能够干净地应用,并且不能留下语法错误。

总结

AI 编码提升的效率跟程序员的水平是正相关的,现在的 AI 有点像一个实习生,你必须要把想让它做的事情写的很清楚,如果你自己都不知道要如何实现一个功能,AI 就更不知道了。当然开发过程里面还有一大部分的时间是在调试上面,这块要让 AI 帮忙也需要提问者有足够的知识储备。所以我们也不能期望过高,这样你才能有耐心好好带带这位实习生。


随着 AI 能力的进步,很多人觉得 AI 很快就能取代我们这些程序员,他们觉得产品经理可以直接绕过程序员,让 AI 直接开发出他们想要的产品。作为一名有 8 年工作经验,在手工编程古典时代成长起来的程序员,我其实并不担心这个。



写代码本身其实不算是很大的工作量,一旦你熟悉了语法和中间件,写代码确实跟搬砖区别不大。我们大部分时间主要是花在解决环境问题,跟上下游联调,跟产品一起澄清需求这些事情上面。即使 AI 能够帮我们完成全链路的联调,也取代不了程序员。



首先,知道应该修改哪一行代码,比知道如何修改这行代码值钱一千倍。其次,开发软件真正的难点不在于写代码,而是在于定义清楚问题。最后,当 AGI 真的出现的时候,我觉得应该讨论的不是程序员会不会被取代的问题,而是应该担心碳基生物如何跟硅基生物共存了。



来源  |  阿里云开发者公众号

作者  |  萧恺

相关文章
|
2月前
|
人工智能 IDE Java
AI Coding实践:CodeFuse + prompt 从系分到代码
在蚂蚁国际信贷业务系统建设过程中,技术团队始终面临双重考验:一方面需应对日益加速的需求迭代周期,满足严苛的代码质量规范与金融安全合规要求;另一方面,跨地域研发团队的协同效率与代码标准统一性,在传统开发模式下逐渐显现瓶颈。为突破效率制约、提升交付质量,我们积极探索人工智能辅助代码生成技术(AI Coding)的应用实践。本文基于蚂蚁国际信贷技术团队近期的实际项目经验,梳理AI辅助开发在金融级系统快速迭代场景中的实施要点并分享阶段性实践心得。
386 25
AI Coding实践:CodeFuse + prompt 从系分到代码
|
2月前
|
消息中间件 人工智能 Kafka
AI 时代的数据通道:云消息队列 Kafka 的演进与实践
云消息队列 Kafka 版通过在架构创新、性能优化与生态融合等方面的突破性进展,为企业构建实时数据驱动的应用提供了坚实支撑,持续赋能客户业务创新。
339 28
|
2月前
|
人工智能 自然语言处理 测试技术
从人工到AI驱动:天猫测试全流程自动化变革实践
天猫技术质量团队探索AI在测试全流程的落地应用,覆盖需求解析、用例生成、数据构造、执行验证等核心环节。通过AI+自然语言驱动,实现测试自动化、可溯化与可管理化,在用例生成、数据构造和执行校验中显著提效,推动测试体系从人工迈向AI全流程自动化,提升效率40%以上,用例覆盖超70%,并构建行业级知识资产沉淀平台。
从人工到AI驱动:天猫测试全流程自动化变革实践
|
2月前
|
数据采集 存储 人工智能
从0到1:天猫AI测试用例生成的实践与突破
本文系统阐述了天猫技术团队在AI赋能测试领域的深度实践与探索,讲述了智能测试用例生成的落地路径。
从0到1:天猫AI测试用例生成的实践与突破
|
2月前
|
人工智能 新制造
TsingtaoAI受邀参加宁波AI海曙科创训练营并分享技术落地实践
10月12日至15日,由宁波市海曙区组织部主办的AI海曙科创训练营在宁波成功举办。作为受邀企业代表,TsingtaoAI团队深入参与了多项活动,与政府领导、行业专家及科创企业代表围绕AI技术在制造业、成果转化等领域的实际应用展开交流,用真实案例诠释了“技术扎根产业”的价值逻辑。
89 2
|
2月前
|
人工智能 搜索推荐 算法
用AI提示词搞定基金定投:技术人的理财工具实践
本文将AI提示词工程应用于基金定投,为技术人打造一套系统化、可执行的理财方案。通过结构化指令,AI可生成个性化定投策略,覆盖目标设定、资产配置、风险控制与动态调整,帮助用户降低决策门槛,规避情绪干扰,实现科学理财。
521 13
|
2月前
|
存储 人工智能 NoSQL
AI大模型应用实践 八:如何通过RAG数据库实现大模型的私有化定制与优化
RAG技术通过融合外部知识库与大模型,实现知识动态更新与私有化定制,解决大模型知识固化、幻觉及数据安全难题。本文详解RAG原理、数据库选型(向量库、图库、知识图谱、混合架构)及应用场景,助力企业高效构建安全、可解释的智能系统。
|
2月前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段四:学术分析 AI 项目 RAG 落地指南:基于 Spring AI 的本地与阿里云知识库实践
本文介绍RAG(检索增强生成)技术,结合Spring AI与本地及云知识库实现学术分析AI应用,利用阿里云Qwen-Plus模型提升回答准确性与可信度。
915 90
AI 超级智能体全栈项目阶段四:学术分析 AI 项目 RAG 落地指南:基于 Spring AI 的本地与阿里云知识库实践
|
2月前
|
人工智能 运维 关系型数据库
云栖大会|AI时代的数据库变革升级与实践:Data+AI驱动企业智能新范式
2025云栖大会“AI时代的数据库变革”专场,阿里云瑶池联合B站、小鹏、NVIDIA等分享Data+AI融合实践,发布PolarDB湖库一体化、ApsaraDB Agent等创新成果,全面展现数据库在多模态、智能体、具身智能等场景的技术演进与落地。
|
3月前
|
SQL 人工智能 数据可视化
高校迎新管理系统:基于 smardaten AI + 无代码开发实践
针对高校迎新痛点,基于smardaten无代码平台构建全流程数字化管理系统,集成信息采集、绿色通道、宿舍管理等七大模块,通过AI生成框架、可视化配置审批流与权限,实现高效、精准、可扩展的迎新服务,大幅提升管理效率与新生体验。

热门文章

最新文章