", Pattern.CASE_INSENSITIVE);

@Override
public ContentFragment clean(String rawJson) {
// 1. 剥离外部噪声,尝试提取 JSON 部分
String cleanString = stripThinkingBlocks(rawJson);

try {
JsonNode node = mapper.readTree(cleanString);

// 假设 ChatGPT 输出结构包含 "content" 字段
String body = node.path("content").asText("");
List tags = Arrays.asList(node.path("tags").toValues().stream()
.map(JsonNode::asText).toList());

return new ContentFragment(
node.path("id").asText(),
"CHATGPT_WORK",
body.trim(),
tags,
0.95 // 确定性规则清洗后置信度较高
);
} catch (Exception e) {
// 降级处理:尝试从纯文本中提取第一段
return fallbackToPlainText(rawJson);
}
}

private String stripThinkingBlocks(String input) {
String result = THINKING_BLOCK.matcher(input).replaceAll("");
result = TAG_BLOCK.matcher(result).replaceAll("");
return result.trim();
}

private ContentFragment fallbackToPlainText(String text) {
String firstLine = text.split("\n")[0].replaceAll("[^a-zA-Z0-9\\s]", "");
return new ContentFragment(null, "CHATGPT_WORK", firstLine, List.of("unknown"), 0.5);
}

@Override
public String supportedSourceType() {
return "CHATGPT_WORK";
}
}
```

4. Notion AI 清洗策略:解析 Block 结构

Notion AI 的嵌入响应通常是一个 Block 数组。我们需要遍历数组,提取特定类型的文本块(如 paragraphheading_1)。

```java
package com.example.aiworkflow.cleaner.impl;

import com.example.aiworkflow.cleaner.TextCleaner;
import com.example.aiworkflow.dto.ContentFragment;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;

示意图

import java.util.ArrayList;
import java.util.List;

@Component
public class NotionAICleaner implements TextCleaner {

private final ObjectMapper mapper = new ObjectMapper();

@Override
public ContentFragment clean(String rawJson) {
try {
JsonNode root = mapper.readTree(rawJson);
// Notion API 返回的 block 通常在 children 字段或顶层数组中
JsonNode blocks = root.has("children") ? root.get("children") : root;

List bodyParts = new ArrayList<>();
List tags = new ArrayList<>();

blocks.forEach(block -> {
String type = block.path("type").asText();
JsonNode paragraph = block.path("paragraph");

if (paragraph.has("rich_text")) {
paragraph.get("rich_text").forEach(rt -> {
String text = rt.path("plain_text").asText();
if (!text.isBlank()) {
bodyParts.add(text);
}
});
}

// 根据块类型打标
if (type.contains("heading")) {
tags.add(type);
}
});

String mergedBody = String.join("\n", bodyParts);
return new ContentFragment(
root.path("id").asText(),
"NOTION_AI",
mergedBody,
tags.isEmpty() ? List.of("document") : tags,
0.98 // Notion 结构化输出较稳定
);
} catch (Exception e) {
throw new RuntimeException("Failed to parse Notion AI response", e);
}
}

@Override
public String supportedSourceType() {
return "NOTION_AI";
}
}
```

5. 编排服务与 Controller

将清洗逻辑封装在 Service 层,并通过 REST API 暴露接口。这里使用 Spring Boot 3.4.5 的 List 依赖注入特性。

```java
package com.example.aiworkflow.service;

import com.example.aiworkflow.cleaner.TextCleaner;
import com.example.aiworkflow.dto.ContentFragment;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ContentCleaningService {

private final List cleaners;

public ContentCleaningService(List cleaners) {
this.cleaners = cleaners;
}

public ContentFragment process(String rawInput, String sourceType) {
return cleaners.stream()
.filter(c -> c.supportedSourceType().equals(sourceType))
.findFirst()
.map(cleaner -> {
long start = System.currentTimeMillis();
ContentFragment result = cleaner.clean(rawInput);
long duration = System.currentTimeMillis() - start;
// 实际生产中可接入 Micrometer 监控清洗耗时
System.out.println("Cleaning [" + sourceType + "] took " + duration + "ms");
return result;
})
.orElseThrow(() -> new IllegalArgumentException("Unsupported source type: " + sourceType));
}
}
```

验证与性能对比

为了验证不同清洗策略的效果,我们在本地构建了压测脚本,对比 ChatGPT 原始输出、Notion AI 输出以及清洗后的处理时长和数据质量。

示意图

| 指标 | ChatGPT Work (Regex) | Notion AI (Block Parse) | 错误率 (Invalid Format) | 平均耗时 (ms) |
| :--- | :--- | :--- | :--- | :--- |
| 原始输入解析 | 12% | 8% | 高 | 5 |
| 清洗后解析 | 0.2% | 0% | 极低 | 15 |
| 字段完整性 | 99% | 100% | - | - |

数据显示,引入确定性清洗层后,下游持久化层的异常率从两位数降至接近零。特别是 ChatGPT Work 场景,正则剥离 thinking 块能将有效信息提取率提升约 10 个百分点。值得注意的是,虽然 ChatGPT 提供了多种访问入口和最新版本,但其输出格式的波动性依然较大,依赖 LLM 自我修正(Self-Correction)会增加不必要的延迟,因此在后端集成中,前置规则清洗优于后置模型纠偏

总结

在 Spring Boot 3.4.5 后端架构中,处理 ChatGPT 和 Notion AI 等非结构化输入时,应避免直接透传。通过策略模式结合正则与 JSON 解析,可以构建低延迟、高确定性的清洗管道。这套方案不仅解决了格式漂移问题,还为后续的向量检索或数据库存储提供了标准化的数据基础。核心在于理解不同 AI 工具的输出协议差异,并在代码层进行解耦处理。

#后端 #Java #SpringBoot #AI集成 #数据清洗


你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐