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

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 运维知识库:

  • 可以问文本问题
  • 可以上传监控截图
  • 可以输入日志片段
  • 最后由大模型结合知识库,给出解释和建议。
相关推荐
DisonTangor1 分钟前
MOSS-Image-Base 实战:从文生图到 JSON 布局精准控图
人工智能·ai作画·开源·aigc·1024程序员节
Younglina2 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
不老刘3 小时前
异构双 Agent 协同:构建无人值守的长任务自动化研发流水线
运维·人工智能·自动化·ai编程·1024程序员节
技术小甜甜3 小时前
【Cursor】Skill 被标黄色警告,怎么改成合格的 SKILL.md
开发语言·javascript·ai编程·cursor·1024程序员节·ai agent·skills
金銀銅鐵3 小时前
[Java] 用 CLI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
Bigger3 小时前
PR 没被合并,我把 Tauri 版自己做出来了:安装包从 100MB 砍到 33MB
开源·ai编程·掘金社区
Dawson Zhu3 小时前
【CUDA编程入门⑥】CPU多线程并行优化:OpenMP、NUMA与SIMD向量化
人工智能·语言模型·架构·aigc·agi
xieliyu.4 小时前
【JavaEE进阶】Spring Boot 日志:从 System.out 到 @Slf4j,6 个级别 + 4 类配置一篇讲透
java·spring boot·spring·java-ee·springboot·logback·slf4j
ZzT4 小时前
Claude Code 用量去哪了:实测 7 天 4423 次请求,六成花在重读对话上
ai编程·claude
Dawson Zhu5 小时前
【CUDA编程入门③】经典算子实战:Reduce归约——从朴素实现到高性能优化的演进之路
人工智能·语言模型·架构·aigc·agi