揭秘 Spring AI 文档切割:从"暴力分割"到"语义智能"的进阶之路

揭秘 Spring AI 文档切割:从"暴力分割"到"语义智能"的进阶之路

📦 项目源码github.com/XiFYuW/spri...


引言

在构建 RAG(检索增强生成)系统时,文档切割(Document Splitting) 是一个经常被忽视但至关重要的环节。切割质量直接决定了:

  • 检索精度:块太大包含噪声,块太小丢失上下文
  • 生成质量:上下文窗口的利用率
  • 存储成本:冗余内容和向量数量

本文将带你从最简单的字符切割,逐步深入到递归字符切割Markdown 结构切割Token 感知切割语义切割等 6 种高级策略,手把手教你用 Spring AI 实现企业级的文档处理系统。

读完本文,你将收获

  • 理解不同切割策略的适用场景和优缺点
  • 掌握 Spring AI 文档切割的完整实现
  • 学会根据业务场景选择最优策略

目录

  1. 为什么文档切割如此重要?
  2. [6 种文档切割策略详解](#6 种文档切割策略详解 "#%E4%BA%8C6-%E7%A7%8D%E6%96%87%E6%A1%A3%E5%88%87%E5%89%B2%E7%AD%96%E7%95%A5%E8%AF%A6%E8%A7%A3")
  3. [Spring AI 实战:从零实现智能切割服务](#Spring AI 实战:从零实现智能切割服务 "#%E4%B8%89spring-ai-%E5%AE%9E%E6%88%98%E4%BB%8E%E9%9B%B6%E5%AE%9E%E7%8E%B0%E6%99%BA%E8%83%BD%E5%88%87%E5%89%B2%E6%9C%8D%E5%8A%A1")
  4. 策略对比与选型指南
  5. 性能优化与避坑指南
  6. 总结与扩展思考

一、为什么文档切割如此重要?

1.1 切割质量对 RAG 系统的影响

css 复制代码
┌─────────────────────────────────────────────────────────────┐
│                    RAG 系统架构                              │
├─────────────────────────────────────────────────────────────┤
│  文档 → [文档切割] → 文本块 → [Embedding] → 向量存储          │
│                                      ↓                      │
│  用户查询 ──────────────────────→ [相似度检索] → 上下文       │
│                                                      ↓      │
│                                              [LLM 生成]     │
└─────────────────────────────────────────────────────────────┘

切割不当的后果

问题 后果 示例
块太大 包含无关信息,稀释语义 "Spring 框架介绍 + 数据库配置 + 缓存优化"混在一起
块太小 丢失上下文,语义不完整 "它提供了依赖注入功能"------"它"指代不明
切割位置不当 句子被截断,信息断裂 "Spring Boot 的自动配"(配置被截断)

1.2 理想切割的标准

  • 语义完整性:每个块包含完整的语义单元
  • 上下文保留:相邻块之间有适当的重叠
  • 长度适中:适配 Embedding 模型和 LLM 的上下文限制
  • 结构感知:保留文档的层级结构(标题、段落等)

二、6 种文档切割策略详解

2.1 策略总览

策略 核心思想 适用场景 复杂度
CHARACTER 固定字符数切割 简单文本、快速原型
RECURSIVE 按语义层级递归分割 通用文本、LangChain 推荐 ⭐⭐
MARKDOWN 识别 Markdown 结构 技术文档、README ⭐⭐
TOKEN 按 Token 数量切割 精确控制 LLM 上下文 ⭐⭐⭐
SEMANTIC 基于 Embedding 相似度 高质量语义检索 ⭐⭐⭐⭐
SMART_PARAGRAPH 段落+字符混合 平衡效率和质量 ⭐⭐

2.2 递归字符切割(RECURSIVE)

核心思想:按分隔符优先级尝试分割,优先保持语义完整性

scss 复制代码
分隔符优先级(从高到低):
段落(\n\n) → 换行(\n) → 句子(.!?。!?) → 分号(;;) → 逗号(,,) → 空格( ) → 字符

工作流程

java 复制代码
// 1. 尝试按段落分割
"第一章\n\n第一节内容...\n\n第二节内容..."
    ↓
["第一章", "第一节内容...", "第二节内容..."]

// 2. 如果某个块仍太大,递归使用下一个分隔符
"第一节内容..." (长度 > 1000)
    ↓ 按句子分割
["第一节介绍了 Spring Boot 的核心概念。", "它简化了配置过程。", ...]

// 3. 如果句子还太长,继续递归
"第一节介绍了 Spring Boot 的核心概念..." (长度 > 1000)
    ↓ 按逗号分割
["第一节介绍了 Spring Boot 的核心概念", "包括自动配置、起步依赖等", ...]

代码实现

java 复制代码
public List<Document> splitRecursive(String text, String filename, SplitOptions options) {
    String[] separators = {
        "\n\n", "\n", ". ", "? ", "! ", "。", "?", "!", 
        "; ", ";", ", ", ",", " ", ""
    };
    
    // 递归分割实现
    return recursiveSplitInternal(text, 0, chunkSize, chunkOverlap, 0);
}

2.3 Markdown 结构切割(MARKDOWN)

核心思想:识别 Markdown 的标题层级,保留文档结构

markdown 复制代码
# 项目介绍          ← H1
## 安装指南         ← H2
### 环境要求        ← H3
内容...
### 安装步骤        ← H3
内容...
## 使用说明         ← H2
内容...

切割结果

java 复制代码
// Chunk 1
{
  "text": "# 项目介绍\n\n这是项目的详细介绍...",
  "metadata": {
    "heading_level": 1,
    "heading_text": "项目介绍",
    "section_type": "heading"
  }
}

// Chunk 2
{
  "text": "## 安装指南\n\n### 环境要求\n需要 Java 17...",
  "metadata": {
    "heading_level": 2,
    "heading_text": "安装指南",
    "section_type": "heading"
  }
}

优势

  • 保留文档层级结构
  • 检索时可以精确定位到章节
  • 支持代码块、列表等特殊元素

2.4 Token 感知切割(TOKEN)

核心思想:基于 Token 数量而非字符数切割,精确适配 LLM 限制

Token 估算规则

java 复制代码
private int estimateTokens(String text) {
    int chineseChars = countChinese(text);  // 中文 ~1.67 字符/token
    int englishChars = countEnglish(text);  // 英文 ~4 字符/token
    int numbers = countNumbers(text);       // 数字 ~3.3 字符/token
    
    return (int) Math.ceil(
        chineseChars * 0.6 +
        englishChars * 0.25 +
        numbers * 0.3
    );
}

使用场景

  • GPT-4 上下文限制 8K/32K/128K
  • Claude 100K 上下文
  • 需要精确控制输入长度

2.5 语义切割(SEMANTIC)

核心思想:在语义变化处切割,而非固定长度

工作流程

vbnet 复制代码
文本: "Spring Boot 简化了 Java 开发。它提供了自动配置功能。今天天气很好。机器学习是 AI 的核心技术。"

Step 1: 分割为句子
["Spring Boot 简化了 Java 开发。", "它提供了自动配置功能。", "今天天气很好。", "机器学习是 AI 的核心技术。"]

Step 2: 计算相邻句子 Embedding 相似度
句子1 ↔ 句子2: 0.92 (高相似度,同主题)
句子2 ↔ 句子3: 0.31 (低相似度,主题切换)
句子3 ↔ 句子4: 0.28 (低相似度,主题切换)

Step 3: 在相似度低于阈值处切割
Chunk 1: "Spring Boot 简化了 Java 开发。它提供了自动配置功能。"
Chunk 2: "今天天气很好。"
Chunk 3: "机器学习是 AI 的核心技术。"

优势

  • 每个块包含同一主题的完整内容
  • 检索时语义相关性更高
  • 避免话题切换导致的语义断裂

成本考量

  • 需要调用 Embedding API
  • 适合高质量检索场景
  • 不适合实时处理大量文档

三、Spring AI 实战:从零实现智能切割服务

3.1 项目结构

bash 复制代码
src/main/java/org/example/
├── service/
│   ├── DocumentSplitterService.java    # 核心切割服务
│   ├── FileProcessingService.java      # 文件处理服务
│   └── VectorStoreService.java         # 向量存储服务
├── controller/
│   └── VectorStoreController.java      # REST API
└── Application.java

3.2 核心实现:DocumentSplitterService

3.2.1 策略枚举定义
java 复制代码
@Service
public class DocumentSplitterService {
    
    /**
     * 文档切割策略枚举
     */
    public enum SplitStrategy {
        /** 递归字符切割 - 保持语义完整性 */
        RECURSIVE,
        /** Markdown 结构切割 - 保留文档结构 */
        MARKDOWN,
        /** Token 感知切割 - 适配 LLM 限制 */
        TOKEN,
        /** 语义切割 - 基于 Embedding 相似度 */
        SEMANTIC,
        /** 智能段落切割 - 段落+字符混合 */
        SMART_PARAGRAPH,
        /** 固定字符切割 - 简单快速 */
        CHARACTER
    }
}
3.2.2 统一入口方法
java 复制代码
/**
 * 统一文档切割入口 - 带选项
 */
public List<Document> split(String text, String filename, 
                            SplitStrategy strategy, 
                            SplitOptions options) {
    if (text == null || text.isEmpty()) {
        return Collections.emptyList();
    }

    List<Document> documents = switch (strategy) {
        case RECURSIVE -> splitRecursive(text, filename, options);
        case MARKDOWN -> splitMarkdown(text, filename, options);
        case TOKEN -> splitByTokens(text, filename, options);
        case SEMANTIC -> splitSemantic(text, filename, options);
        case SMART_PARAGRAPH -> splitSmartParagraph(text, filename, options);
        case CHARACTER -> splitByCharacter(text, filename, options);
    };

    logger.info("Split document '{}' using {} strategy into {} chunks", 
            filename, strategy, documents.size());
    
    return documents;
}
3.2.3 切割选项类
java 复制代码
/**
 * 切割选项类 - 支持链式调用
 */
public static class SplitOptions {
    private Integer chunkSize;        // 块大小(字符数)
    private Integer chunkOverlap;     // 重叠大小
    private Integer maxTokens;        // 最大 Token 数
    private Double semanticThreshold; // 语义相似度阈值

    public SplitOptions withChunkSize(int chunkSize) {
        this.chunkSize = chunkSize;
        return this;
    }

    public SplitOptions withChunkOverlap(int chunkOverlap) {
        this.chunkOverlap = chunkOverlap;
        return this;
    }

    public SplitOptions withMaxTokens(int maxTokens) {
        this.maxTokens = maxTokens;
        return this;
    }

    public SplitOptions withSemanticThreshold(double threshold) {
        this.semanticThreshold = threshold;
        return this;
    }
}

3.3 递归字符切割实现

java 复制代码
/**
 * 递归字符切割 - LangChain 推荐策略
 */
public List<Document> splitRecursive(String text, String filename, 
                                     SplitOptions options) {
    int chunkSize = options.getChunkSize() != null ? 
                    options.getChunkSize() : DEFAULT_CHUNK_SIZE;
    int chunkOverlap = options.getChunkOverlap() != null ? 
                       options.getChunkOverlap() : DEFAULT_CHUNK_OVERLAP;
    
    String cleanedText = normalizeText(text);
    
    // 递归分割
    List<TextChunk> chunks = recursiveSplitInternal(
        cleanedText, 0, chunkSize, chunkOverlap, 0
    );
    
    // 转换为 Document
    List<Document> documents = new ArrayList<>();
    for (int i = 0; i < chunks.size(); i++) {
        TextChunk chunk = chunks.get(i);
        Map<String, Object> metadata = Map.of(
            "source", filename,
            "chunk_index", i,
            "total_chunks", chunks.size(),
            "split_method", "recursive",
            "separator_used", chunk.getSeparator()
        );
        documents.add(new Document(chunk.getContent(), metadata));
    }
    
    return documents;
}

/**
 * 递归分割内部实现
 */
private List<TextChunk> recursiveSplitInternal(
        String text, 
        int separatorIndex, 
        int chunkSize, 
        int chunkOverlap,
        int depth) {
    
    List<TextChunk> result = new ArrayList<>();
    
    // 如果文本已经小于块大小,直接返回
    if (text.length() <= chunkSize) {
        result.add(new TextChunk(text, 
            depth > 0 ? RECURSIVE_SEPARATORS[separatorIndex - 1] : "none"));
        return result;
    }
    
    // 如果已经用完所有分隔符,强制按字符切割
    if (separatorIndex >= RECURSIVE_SEPARATORS.length) {
        return forceCharacterSplit(text, chunkSize, chunkOverlap);
    }
    
    String separator = RECURSIVE_SEPARATORS[separatorIndex];
    String[] parts = separator.isEmpty() ? 
                     text.split("") : 
                     text.split(Pattern.quote(separator));
    
    StringBuilder currentChunk = new StringBuilder();
    
    for (int i = 0; i < parts.length; i++) {
        String part = parts[i];
        String withSeparator = i < parts.length - 1 ? 
                               part + separator : part;
        
        // 如果单个部分就超过限制,需要递归分割
        if (part.length() > chunkSize) {
            if (currentChunk.length() > 0) {
                result.add(new TextChunk(
                    currentChunk.toString().trim(), separator));
                currentChunk = new StringBuilder();
            }
            
            List<TextChunk> subChunks = recursiveSplitInternal(
                part, separatorIndex + 1, chunkSize, chunkOverlap, depth + 1
            );
            result.addAll(subChunks);
        }
        // 如果添加这个部分会超过限制,先保存当前块
        else if (currentChunk.length() + withSeparator.length() > chunkSize 
                && currentChunk.length() > 0) {
            result.add(new TextChunk(
                currentChunk.toString().trim(), separator));
            
            // 考虑重叠
            if (chunkOverlap > 0 && currentChunk.length() > chunkOverlap) {
                String overlapText = currentChunk.substring(
                    currentChunk.length() - chunkOverlap);
                currentChunk = new StringBuilder(overlapText)
                    .append(withSeparator);
            } else {
                currentChunk = new StringBuilder(withSeparator);
            }
        }
        // 否则添加到当前块
        else {
            currentChunk.append(withSeparator);
        }
    }
    
    // 保存最后一个块
    if (currentChunk.length() > 0) {
        result.add(new TextChunk(
            currentChunk.toString().trim(), separator));
    }
    
    return result;
}

3.4 语义切割实现

java 复制代码
/**
 * 语义切割 - 基于 Embedding 相似度
 */
public List<Document> splitSemantic(String text, String filename, 
                                    SplitOptions options) {
    double threshold = options.getSemanticThreshold() != null ? 
                       options.getSemanticThreshold() : DEFAULT_SEMANTIC_THRESHOLD;
    int maxTokens = options.getMaxTokens() != null ? 
                    options.getMaxTokens() : DEFAULT_MAX_TOKENS * 2;
    
    // 1. 分割为句子
    List<String> sentences = splitIntoSentences(text);
    if (sentences.size() <= 1) {
        return List.of(new Document(text, Map.of(
            "source", filename,
            "split_method", "semantic"
        )));
    }
    
    // 2. 计算每个句子的 Embedding
    List<float[]> embeddings = new ArrayList<>();
    for (String sentence : sentences) {
        float[] embedding = embeddingModel.embed(sentence);
        embeddings.add(embedding);
    }
    
    // 3. 计算相邻句子的相似度,确定切割点
    List<Integer> splitPoints = new ArrayList<>();
    splitPoints.add(0);
    
    for (int i = 0; i < embeddings.size() - 1; i++) {
        double similarity = cosineSimilarity(embeddings.get(i), 
                                              embeddings.get(i + 1));
        if (similarity < threshold) {
            splitPoints.add(i + 1);
        }
    }
    splitPoints.add(sentences.size());
    
    // 4. 根据切割点构建文档块
    List<Document> documents = new ArrayList<>();
    for (int i = 0; i < splitPoints.size() - 1; i++) {
        int startIdx = splitPoints.get(i);
        int endIdx = splitPoints.get(i + 1);
        
        StringBuilder chunk = new StringBuilder();
        for (int j = startIdx; j < endIdx; j++) {
            if (chunk.length() > 0) chunk.append(" ");
            chunk.append(sentences.get(j));
        }
        
        Map<String, Object> metadata = Map.of(
            "source", filename,
            "chunk_index", i,
            "split_method", "semantic",
            "sentence_range", startIdx + "-" + (endIdx - 1),
            "estimated_tokens", estimateTokens(chunk.toString())
        );
        
        documents.add(new Document(chunk.toString(), metadata));
    }
    
    return documents;
}

/**
 * 计算余弦相似度
 */
private double cosineSimilarity(float[] vec1, float[] vec2) {
    double dotProduct = 0.0;
    double norm1 = 0.0;
    double norm2 = 0.0;
    
    for (int i = 0; i < vec1.length; i++) {
        dotProduct += vec1[i] * vec2[i];
        norm1 += vec1[i] * vec1[i];
        norm2 += vec2[i] * vec2[i];
    }
    
    return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}

3.5 REST API 使用示例

java 复制代码
@RestController
@RequestMapping("/api/vector-store")
public class VectorStoreController {

    @PostMapping(value = "/upload", consumes = "multipart/form-data")
    public Mono<ResponseEntity<ApiResponse<FileUploadResponse>>> uploadFile(
            @RequestPart("file") FilePart filePart,
            @RequestParam(name = "splitStrategy", defaultValue = "RECURSIVE") 
                DocumentSplitterService.SplitStrategy splitStrategy,
            @RequestParam(name = "chunkSize", defaultValue = "1000") int chunkSize,
            @RequestParam(name = "chunkOverlap", defaultValue = "200") int chunkOverlap) {
        
        return fileProcessingService.processAndStore(
                filePart, splitStrategy, chunkSize, chunkOverlap)
            .map(result -> ResponseEntity.ok(
                ApiResponse.success("File processed", result)));
    }
}

API 调用示例

bash 复制代码
# 1. 递归字符切割(推荐通用场景)
curl -X POST "http://localhost:8080/api/vector-store/upload" \
  -F "file=@document.txt" \
  -F "splitStrategy=RECURSIVE" \
  -F "chunkSize=1000" \
  -F "chunkOverlap=200"

# 2. Markdown 结构切割(技术文档)
curl -X POST "http://localhost:8080/api/vector-store/upload" \
  -F "file=@readme.md" \
  -F "splitStrategy=MARKDOWN" \
  -F "chunkSize=1500"

# 3. Token 感知切割(精确控制)
curl -X POST "http://localhost:8080/api/vector-store/upload" \
  -F "file=@document.txt" \
  -F "splitStrategy=TOKEN" \
  -F "maxTokens=512"

# 4. 语义切割(高质量检索)
curl -X POST "http://localhost:8080/api/vector-store/upload" \
  -F "file=@article.txt" \
  -F "splitStrategy=SEMANTIC" \
  -F "semanticThreshold=0.7"

四、策略对比与选型指南

4.1 策略对比表

维度 CHARACTER RECURSIVE MARKDOWN TOKEN SEMANTIC
速度 ⚡⚡⚡ ⚡⚡ ⚡⚡ ⚡⚡
语义完整性 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
结构保留
LLM 适配 ⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
实现复杂度 简单 中等 中等 中等 复杂
API 成本

4.2 场景选型决策树

sql 复制代码
开始
  │
  ├─ 文档是 Markdown 格式?
  │     ├─ 是 → 使用 MARKDOWN 策略
  │     └─ 否 → 继续
  │
  ├─ 需要精确控制 Token 数量?
  │     ├─ 是 → 使用 TOKEN 策略
  │     └─ 否 → 继续
  │
  ├─ 追求最高检索质量且预算充足?
  │     ├─ 是 → 使用 SEMANTIC 策略
  │     └─ 否 → 继续
  │
  ├─ 追求速度与质量的平衡?
  │     ├─ 是 → 使用 RECURSIVE 策略(推荐)
  │     └─ 否 → 使用 CHARACTER 策略

4.3 推荐配置

场景 推荐策略 参数配置
通用文本处理 RECURSIVE chunkSize=1000, overlap=200
技术文档/手册 MARKDOWN chunkSize=1500
GPT-4 上下文 TOKEN maxTokens=2000
高质量知识库 SEMANTIC threshold=0.7, maxTokens=1000
快速原型 CHARACTER chunkSize=500, overlap=50

五、性能优化与避坑指南

5.1 常见问题与解决方案

问题 1:语义切割 API 调用过多

现象:处理大文档时 Embedding API 费用激增

解决方案

java 复制代码
// 方案 1:采样计算(每 N 个句子计算一次)
for (int i = 0; i < sentences.size() - 1; i += 3) {
    double similarity = cosineSimilarity(embeddings.get(i), 
                                          embeddings.get(i + 1));
}

// 方案 2:先使用 RECURSIVE 预分割,再对大块使用 SEMANTIC
List<Document> preChunks = splitRecursive(text, filename, options);
for (Document chunk : preChunks) {
    if (estimateTokens(chunk.getText()) > 1000) {
        // 只对大块使用语义切割
        semanticSubChunks.addAll(splitSemantic(chunk.getText(), filename, options));
    }
}
问题 2:中文文本切割位置不当

现象:中文句子被截断,如 "Spring Boot 的自"(动配置被截断)

解决方案

java 复制代码
// 确保分隔符列表包含中文标点
private static final String[] RECURSIVE_SEPARATORS = {
    "\n\n", "\n",
    "。", "?", "!",      // 中文标点优先
    ". ", "? ", "! ",      // 英文标点
    ";", ",",
    "; ", ", ",
    " ", ""
};
问题 3:块重叠导致重复检索

现象:搜索结果中出现重复内容

解决方案

java 复制代码
// 元数据标记重叠区域
metadata.put("is_overlap", true);
metadata.put("parent_chunk", parentIndex);

// 检索时去重
documents.stream()
    .filter(doc -> !Boolean.TRUE.equals(doc.getMetadata().get("is_overlap")))
    .collect(Collectors.toList());

5.2 性能优化技巧

技巧 效果 实现方式
并行处理 提升 3-5 倍 使用 parallelStream() 处理多个文档
批量 Embedding 减少 API 调用 一次请求多个句子的 Embedding
缓存切割结果 避免重复计算 使用 Caffeine 缓存切割后的文档
预编译正则 提升 20% 性能 Pattern 定义为静态常量
java 复制代码
// 并行处理示例
public List<Document> batchSplit(List<File> files, SplitStrategy strategy) {
    return files.parallelStream()
        .flatMap(file -> {
            String content = readFile(file);
            return split(content, file.getName(), strategy, new SplitOptions())
                .stream();
        })
        .collect(Collectors.toList());
}

六、总结与扩展思考

6.1 核心要点回顾

  1. 文档切割是 RAG 系统的关键环节,直接影响检索和生成质量
  2. 递归字符切割(RECURSIVE) 是最佳通用选择,平衡了速度和质量
  3. 语义切割(SEMANTIC) 提供最高质量,但成本较高
  4. 根据场景选择策略,没有"银弹"

6.2 扩展思考

可以在此基础上增加的功能

  1. 多模态切割:支持 PDF、Word、HTML 等格式的结构化提取
  2. 动态块大小:根据内容复杂度自适应调整块大小
  3. 实体感知切割:使用 NER 识别实体,避免在实体中间切割
  4. 层级索引:构建文档的层级索引(章→节→段→句)

可以优化的性能点

  1. 增量切割:只处理变更的部分,避免全量重新切割
  2. 分布式处理:使用消息队列分发切割任务
  3. 智能缓存:基于内容哈希缓存切割结果

6.3 完整代码仓库

本文完整代码已开源,包含:

  • ✅ 6 种切割策略完整实现
  • ✅ REST API 接口
  • ✅ 单元测试和集成测试
  • ✅ 性能基准测试

附录:参考资料

  1. LangChain Text Splitters 文档
  2. OpenAI Embedding 最佳实践
  3. Spring AI 官方文档
  4. Elasticsearch 向量搜索

原创声明:本文为原创教程,转载请注明出处。

欢迎在评论区交流讨论!你更倾向于使用哪种切割策略?

💰 为什么选择 32ai? 低至 0.56 : 1 比率 🔗 快速访问 : 点击访问 --- 直连、无需魔法。

相关推荐
CodeDevMaster1 小时前
从零开始:OpenClaw本地 AI 助手部署指南
人工智能·agent·ai编程
chaors2 小时前
从零学RAG0x04向量检索算法初探
人工智能·程序员·ai编程
chaors2 小时前
Langchain入门到精通0x01:结果解析器
人工智能·langchain·ai编程
甲维斯2 小时前
Claude Code:把“智商”拉到最高!
ai编程
NikoAI编程2 小时前
CLAUDE.md 该怎么写:从零开始打造你的 AI 编程搭档说明书
ai编程·claude
游魂Andy2 小时前
零成本搭建专属AI助手:OpenClaw永久免费部署全攻略
前端·人工智能·ai编程
刀法如飞2 小时前
AI时代,人人都是需求描述工程师
程序员·aigc·ai编程·需求文档
gyx_这个杀手不太冷静3 小时前
OpenCode 进阶使用指南(第三章:MCP 集成)
前端·ai编程
恋猫de小郭4 小时前
OpenAI 亲自教你如何构建可靠 AI 代码,从古法编程转向 Agnet 编程,或者 PUA 你的 AI
前端·人工智能·ai编程