做一个「运维知识库 + 多模态检索问答」的案例

Ops 多模态 RAG(Retrieval-Augmented Generation) ,结合 Spring AI + Qdrant,可以用来做一个「运维知识库 + 多模态检索问答」的案例。下面我将简单实现一下这个案例:


🌐 场景设计

背景

  • 运维团队需要快速排查问题,但信息分散在 文档(markdown/PDF)、图片(监控截图)、日志(结构化/半结构化) 里。
  • 我们用 Spring AI (方便整合LLM)+ Qdrant (向量数据库)做一个 多模态 RAG ,支持输入:
    • 文本问题(自然语言问答)
    • 图片截图(例如:用户上传一张服务器 CPU 监控图,让系统结合已有知识库解释异常)
    • 日志片段(半结构化输入)

目标

  • 用户输入"CPU usage looks spiky" + 一张 CPU 曲线图,系统去 Qdrant 检索最相关的知识文档 & 历史故障案例,并由 LLM 给出解释 + 建议。

🔧 技术架构

  1. Spring AI
    • ChatClient 调用大模型(支持文本和多模态输入)
    • 集成 Qdrant 作为向量检索层
  2. Qdrant
    • 存储多模态 Embeddings(文本/图像/日志)
    • 使用 collection 区分数据类型(如 ops_text_docs, ops_images, ops_logs
  3. Embedding 模型
    • 文本:text-embedding-3-small
    • 图像:CLIP-based embedding(如 openai/clip-vit-base-patch32
    • 日志:同文本 embedding 处理

📂 Qdrant Schema 设计

json 复制代码
{
  "ops_text_docs": {
    "vector_size": 1536,
    "distance": "Cosine"
  },
  "ops_images": {
    "vector_size": 512,
    "distance": "Cosine"
  },
  "ops_logs": {
    "vector_size": 1536,
    "distance": "Cosine"
  }
}

💻 Spring AI 代码示例

1. 配置依赖(pom.xml

xml 复制代码
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
  <version>1.0.0-M1</version>
</dependency>
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-qdrant-spring-boot-starter</artifactId>
  <version>1.0.0-M1</version>
</dependency>

2. Qdrant 配置(application.yml

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
    vectorstore:
      qdrant:
        host: localhost
        port: 6333

3. 向量存储服务

java 复制代码
@Service
public class KnowledgeIngestService {

    private final QdrantVectorStore qdrant;
    private final OpenAiEmbeddingClient embeddingClient;

    public KnowledgeIngestService(QdrantVectorStore qdrant,
                                  OpenAiEmbeddingClient embeddingClient) {
        this.qdrant = qdrant;
        this.embeddingClient = embeddingClient;
    }

    public void ingestTextDoc(String id, String content) {
        EmbeddingResponse embedding = embeddingClient.embed(content);
        qdrant.add(
            new Document(id, content, embedding.getVector())
        );
    }

    public void ingestLog(String id, String log) {
        EmbeddingResponse embedding = embeddingClient.embed(log);
        qdrant.add(
            new Document(id, log, embedding.getVector())
        );
    }

    public void ingestImage(String id, byte[] imageBytes) {
        float[] vector = MyClipEmbeddingModel.encodeImage(imageBytes);
        qdrant.add(
            new Document(id, "image", vector)
        );
    }
}

4. RAG 服务

java 复制代码
@Service
public class OpsRagService {

    private final QdrantVectorStore qdrant;
    private final ChatClient chatClient;

    public OpsRagService(QdrantVectorStore qdrant, ChatClient chatClient) {
        this.qdrant = qdrant;
        this.chatClient = chatClient;
    }

    public String answerQuestion(String question, byte[] optionalImage, String optionalLog) {
        // 1. 构造查询向量
        List<float[]> queryVectors = new ArrayList<>();
        queryVectors.add(embedding(question));
        if (optionalLog != null) queryVectors.add(embedding(optionalLog));
        if (optionalImage != null) queryVectors.add(MyClipEmbeddingModel.encodeImage(optionalImage));

        // 2. 检索知识库
        List<Document> retrievedDocs = new ArrayList<>();
        for (float[] qv : queryVectors) {
            retrievedDocs.addAll(qdrant.similaritySearch(qv, 3));
        }

        // 3. 拼接上下文
        String context = retrievedDocs.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n---\n"));

        // 4. 调用 LLM
        return chatClient.prompt()
                .system("You are an Ops assistant. Use the following context:\n" + context)
                .user(question)
                .call()
                .content();
    }

    private float[] embedding(String text) {
        return new OpenAiEmbeddingClient().embed(text).getVector();
    }
}

🚀 使用方式

java 复制代码
@RestController
@RequestMapping("/ops")
public class OpsController {

    private final OpsRagService ragService;

    public OpsController(OpsRagService ragService) {
        this.ragService = ragService;
    }

    @PostMapping("/ask")
    public String ask(@RequestParam String question,
                      @RequestParam(required=false) MultipartFile image,
                      @RequestParam(required=false) String log) throws IOException {
        return ragService.answerQuestion(
                question,
                image != null ? image.getBytes() : null,
                log
        );
    }
}

✅ 这样,你就有了一个 Spring AI + Qdrant 的多模态 RAG 运维知识库

  • 可以问文本问题
  • 可以上传监控截图
  • 可以输入日志片段
  • 最后由大模型结合知识库,给出解释和建议。
相关推荐
小虎AI生活43 分钟前
从写代码到被出版社认作"科普推广人":一场新书发布会给我的触动
ai编程
怕浪猫2 小时前
DeepSeek Harness 开发者预览版:一切皆插件
aigc·agent
Goboy3 小时前
那份让我加班3天的竞品调研报告,我用 TRAE Work 4小时搞定了
ai编程·trae
答案—answer4 小时前
VibeCoding 能做到什么程度?我用它做了一座 3D 数字博物馆
3d·ai编程·threejs·vibecoding
libokaifa5 小时前
从零手搓一个 GPT:只用 Python 标准库,写一个会起名的字符级模型
ai编程
wangyue_msn_865 小时前
Agent知识学习笔记——03 RAG
笔记·学习·ai编程
火云牌神5 小时前
前后端分离:约束 AI 分工,避免接口耦合与职责错乱
人工智能·系统架构·ai编程·前后端分离·vibecoding
刘立军6 小时前
领域驱动设计:给 AI 划定上下文边界,告别“大泥球”代码
架构·ai编程·领域驱动设计
岛雨QA6 小时前
Claude Code国内无障碍接入 DeepSeek使用指南
ai编程·claude·deepseek
ServBay7 小时前
DeepSeek V4 Pro 发布,1.6T 参数、1M 上下文,又有人坐不住了
aigc·ai编程·deepseek