Milvus + RustFS+ Vibe Coding: Bauen Sie einen leichtgewichtigen RAG Chatbot von Grund auf neu

  • Tutorials
March 10, 2026
Jinghe Ma

Dieser Blog wurde von Jinghe Ma, einem Mitarbeiter der Milvus-Community,verfasst und wird hier mit Genehmigung veröffentlicht.

Ich wollte einen Chatbot, der Fragen aus meiner eigenen Dokumentation beantworten kann, und ich wollte die volle Kontrolle über den Stack dahinter - von der Objektspeicherung bis zur Chat-Schnittstelle. Das brachte mich dazu, einen leichtgewichtigen RAG-Chatbot mit Milvus und RustFS als Kernstück zu bauen.

Milvus ist die am weitesten verbreitete Open-Source-Vektordatenbank für die Entwicklung von RAG-Anwendungen. Es trennt die Berechnung von der Speicherung und hält heiße Daten im Speicher oder auf SSD für eine schnelle Suche, während es sich auf Objektspeicher für ein skalierbares, kosteneffizientes Datenmanagement verlässt. Da es mit S3-kompatiblem Speicher arbeitet, war es die ideale Lösung für dieses Projekt.

Für die Speicherebene wählte ich RustFS, ein in Rust geschriebenes Open-Source-Objektspeichersystem, das mit S3 kompatibel ist. Es kann über Binary, Docker oder Helm Chart bereitgestellt werden. Obwohl es sich noch in der Alpha-Phase befindet und nicht für die Produktion empfohlen wird, war es stabil genug für diesen Build.

Sobald die Infrastruktur eingerichtet war, brauchte ich eine Wissensdatenbank zum Abfragen. Die RustFS-Dokumentation - ca. 80 Markdown-Dateien - war ein geeigneter Ausgangspunkt. Ich zerlegte die Dokumentation, generierte Einbettungen, speicherte sie in Milvus und programmierte den Rest mit Vibe: FastAPI für das Backend und Next.js für die Chat-Oberfläche.

In diesem Beitrag werde ich das gesamte System von Anfang bis Ende vorstellen. Der Code ist unter https://github.com/majinghe/chatbot verfügbar. Es handelt sich um einen funktionierenden Prototyp und nicht um ein produktionsreifes System, aber das Ziel ist es, ein klares, erweiterbares Build bereitzustellen, das Sie für Ihren eigenen Gebrauch anpassen können. Jeder der folgenden Abschnitte geht durch eine Schicht, von der Infrastruktur bis zum Frontend.

Installation von Milvus und RustFS mit Docker Compose

Beginnen wir mit der Installation von Milvus und RustFS.

Milvus kann mit jedem S3-kompatiblen Objektspeicher arbeiten, obwohl MinIO das Standard-Backend im Standard-Setup ist. Da MinIO keine Community-Beiträge mehr annimmt, werden wir es in diesem Beispiel durch RustFS ersetzen.

Um diese Änderung vorzunehmen, aktualisieren Sie die Objektspeicherkonfiguration in configs/milvus.yaml innerhalb des Milvus-Repositorys. Der entsprechende Abschnitt sieht wie folgt aus:

minio:
  address: localhost:9000
  port: 9000
  accessKeyID: rustfsadmin
  secretAccessKey: rustfsadmin
  useSSL: false
  bucketName: "rustfs-bucket"

Es gibt zwei Möglichkeiten, diese Änderung durchzuführen:

  • Einhängen einer lokalen Konfigurationsdatei. Kopieren Sie configs/milvus.yaml lokal, aktualisieren Sie die MinIO-Felder, so dass sie auf RustFS verweisen, und binden Sie sie dann über ein Docker-Volume in den Container ein.
  • Patch beim Start mit yq****. Ändern Sie den Befehl des Containers, um yq gegen /milvus/configs/milvus.yaml auszuführen, bevor der Milvus-Prozess startet.

Dieser Build verwendet den ersten Ansatz. Der Milvus-Dienst in docker-compose.yml erhält einen zusätzlichen Volume-Eintrag:

- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus/milvus.yaml:/milvus/configs/milvus.yaml:ro

Das Docker Compose Setup

Die vollständige docker-compose.yml führt vier Dienste aus.

etcd - Milvus ist für die Speicherung von Metadaten auf etcd angewiesen:

etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.18
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
    command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 30s
      timeout: 20s
      retries: 3

Attu - eine visuelle Benutzeroberfläche für Milvus, entwickelt und freigegeben von Zilliz (Hinweis: Versionen nach 2.6 sind Closed-Source):

  attu:
    container_name: milvus-attu
    image: zilliz/attu:v2.6
    environment:
      - MILVUS_URL=milvus-standalone:19530
    ports:
      - "8000:3000"
    restart: unless-stopped

RustFS - das Objektspeicher-Backend:

rustfs:
    container_name: milvus-rustfs
    image: rustfs/rustfs:1.0.0-alpha.58
    environment:
      - RUSTFS_VOLUMES=/data/rustfs0,/data/rustfs1,/data/rustfs2,/data/rustfs3
      - RUSTFS_ADDRESS=0.0.0.0:9000
      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
      - RUSTFS_CONSOLE_ENABLE=true
      - RUSTFS_EXTERNAL_ADDRESS=:9000  # Same as internal since no port mapping
      - RUSTFS_CORS_ALLOWED_ORIGINS=*
      - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
      - RUSTFS_ACCESS_KEY=rustfsadmin
      - RUSTFS_SECRET_KEY=rustfsadmin
    ports:
      - "9000:9000"# S3 API port
      - "9001:9001"# Console port
    volumes:
      - rustfs_data_0:/data/rustfs0
      - rustfs_data_1:/data/rustfs1
      - rustfs_data_2:/data/rustfs2
      - rustfs_data_3:/data/rustfs3
      - logs_data:/app/logs
    restart: unless-stopped
    healthcheck:
      test:
        [
          "CMD",
          "sh", "-c",
          "curl -f http://localhost:9000/health && curl -f http://localhost:9001/health"
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Milvus - läuft im Standalone-Modus:

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.6.0
    command: ["milvus", "run", "standalone"]
    security_opt:
    - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: rustfs:9000
      MQ_TYPE: woodpecker
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus/milvus.yaml:/milvus/configs/milvus.yaml:ro
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 30s
      start_period: 90s
      timeout: 20s
      retries: 3
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - "etcd"
      - "rustfs"

Alles starten

Sobald die Konfiguration eingerichtet ist, starten Sie alle vier Dienste:

docker compose -f docker-compose.yml up -d

Mit können Sie überprüfen, ob alles läuft:

docker ps
CONTAINER ID   IMAGE                                             COMMAND                  CREATED          STATUS                      PORTS                                                                                      NAMES
4404b5cc6f7e   milvusdb/milvus:v2.6.0                            "/tini -- milvus run…"   53 minutes ago   Up 53 minutes (healthy)     0.0.0.0:9091->9091/tcp, :::9091->9091/tcp, 0.0.0.0:19530->19530/tcp, :::19530->19530/tcp   milvus-standalone
40ddc8ed08bb   zilliz/attu:v2.6                                  "docker-entrypoint.s…"   53 minutes ago   Up 53 minutes               0.0.0.0:8000->3000/tcp, :::8000->3000/tcp                                                  milvus-attu
3d2c8d80a8ce   quay.io/coreos/etcd:v3.5.18                       "etcd -advertise-cli…"   53 minutes ago   Up 53 minutes (healthy)     2379-2380/tcp                                                                              milvus-etcd
d760f6690ea7   rustfs/rustfs:1.0.0-alpha.58                      "/entrypoint.sh rust…"   53 minutes ago   Up 53 minutes (unhealthy)   0.0.0.0:9000-9001->9000-9001/tcp, :::9000-9001->9000-9001/tcp                              milvus-rustfs

Wenn alle vier Container hochgefahren sind, sind Ihre Dienste verfügbar unter:

  • Milvus: :19530
  • RustFS: :9000
  • Attu: :8000

Vektorisierung der RustFS-Dokumente und Speicherung der Einbettungen in Milvus

Wenn Milvus und RustFS laufen, besteht der nächste Schritt darin, die Wissensbasis aufzubauen. Das Ausgangsmaterial ist die chinesische RustFS-Dokumentation: 80 Markdown-Dateien, die Sie zerhacken, einbetten und in Milvus speichern werden.

Lesen und Chunking der Docs

Das Skript liest rekursiv jede .md-Datei im docs-Ordner und teilt dann den Inhalt jeder Datei nach Zeilenumbruch in Chunks auf:

# 3. Read Markdown files
def load_markdown_files(folder):
    files = glob.glob(os.path.join(folder, "**", "*.md"), recursive=True)
    docs = []
    for f in files:
        with open(f, "r", encoding="utf-8") as fp:
            docs.append(fp.read())
    return docs

# 4. Split documents (simple paragraph-based splitting) def split_into_chunks(text, max_len=500): chunks, current = [], [] for line in text.split(“\n”): if len(" ".join(current)) + len(line) < max_len: current.append(line) else: chunks.append(" ".join(current)) current = [line] if current: chunks.append(" ".join(current)) return chunks

Diese Chunking-Strategie ist absichtlich einfach. Wenn Sie eine genauere Kontrolle wünschen - Aufteilung nach Kopfzeilen, Beibehaltung von Codeblöcken oder Überlappung von Chunks zum besseren Auffinden - ist dies der richtige Ort, um zu iterieren.

Einbetten der Chunks

Wenn die Chunks fertig sind, betten Sie sie mit OpenAIs text-embedding-3-large-Modell ein, das 3072-dimensionale Vektoren ausgibt:

def embed_texts(texts):
    response = client.embeddings.create(
        model="text-embedding-3-large",
        input=texts
    )
    return [d.embedding for d in response.data]

Speichern von Einbettungen in Milvus

Milvus organisiert die Daten in Sammlungen, die jeweils durch ein Schema definiert sind. Hier speichert jeder Datensatz den Rohtextchunk zusammen mit seinem Einbettungsvektor:

# Connect to Milvus
connections.connect("default", host="ip", port="19530")

# Define the schema fields = [ FieldSchema(name=“id”, dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name=“content”, dtype=DataType.VARCHAR, max_length=2000), FieldSchema(name=“embedding”, dtype=DataType.FLOAT_VECTOR, dim=3072), ] schema = CollectionSchema(fields, description=“Markdown docs collection”)

# Create the collection if utility.has_collection(“docs_collection”): utility.drop_collection(“docs_collection”)

collection = Collection(name=“docs_collection”, schema=schema)

# Insert data collection.insert([all_chunks, embeddings]) collection.flush()

Sobald das Einfügen abgeschlossen ist, können Sie die Sammlung in Attu unter :8000 überprüfen - Sie sollten docs_collection unter Sammlungen aufgeführt sehen.

Sie können auch in RustFS unter :9000 überprüfen, ob die zugrunde liegenden Daten im Objektspeicher gelandet sind.

Aufbau einer RAG-Pipeline mit Milvus und OpenAIs GPT-5

Mit den in Milvus gespeicherten Einbettungen haben Sie alles, was Sie zum Aufbau der RAG-Pipeline benötigen. Der Ablauf ist: Einbetten der Benutzeranfrage, Abrufen der semantisch ähnlichsten Chunks aus Milvus, Zusammenstellen eines Prompts und Aufrufen des GPT-5. Der Aufbau hier verwendet OpenAIs GPT-5, aber jedes Chat-fähige Modell funktioniert hier - die Abrufschicht ist das, worauf es ankommt, und Milvus handhabt das unabhängig davon, welcher LLM die endgültige Antwort erzeugt.

# 1. Embed the query
query_embedding = embed_texts(query)

# 2. Retrieve similar documents from Milvus search_params = {“metric_type”: “COSINE”, “params”: {“nprobe”: 10}} results = collection.search( data=[query_embedding], anns_field=“embedding”, param=search_params, limit=3, output_fields=[“content”] )

docs = [hit.entity.get(“text”) for hit in results[0]]

# 3. Assemble the RAG prompt prompt = f"You are a RustFS expert. Answer the question based on the following documents:\n\n{docs}\n\nUser question: {query}"

# 4. Call the LLM response = client.chat.completions.create( model=“gpt-5”, # swap to any OpenAI model, or replace this call with another LLM provider messages=[{“role”: “user”, “content”: prompt}], # max_tokens=16384, # temperature=1.0, # top_p=1.0, )

answer = response.choices[<span class="hljs-number">0</span>].message.content

<span class="hljs-keyword">return</span> {<span class="hljs-string">&quot;answer&quot;</span>: answer, <span class="hljs-string">&quot;sources&quot;</span>: docs}

Um es zu testen, führen Sie eine Abfrage durch:

How do I install RustFS in Docker?

Abfrageergebnisse:

Alles in einen FastAPI + Next.js Chatbot verpacken

Die RAG-Pipeline funktioniert, aber jedes Mal, wenn man eine Frage stellen möchte, ein Python-Skript auszuführen, verfehlt den Zweck. Also habe ich AI um einen Stack-Vorschlag gebeten. Die Antwort: FastAPI für das Backend - der RAG-Code ist bereits Python, also ist die Einbettung in einen FastAPI-Endpunkt die natürliche Lösung - und Next.js für das Frontend. FastAPI stellt die RAG-Logik als HTTP-Endpunkt zur Verfügung; Next.js ruft sie auf und rendert die Antwort in einem Chatfenster.

FastAPI Backend

FastAPI verpackt die RAG-Logik in einen einzigen POST-Endpunkt. Jeder Client kann nun Ihre Wissensdatenbank mit einer JSON-Anfrage abfragen:

app = FastAPI()

@app.post(“/chat”) def chat(req: ChatRequest): query = req.query

Starten Sie den Server mit:

uvicorn main:app --reload --host 0.0.0.0 --port 9999
INFO:     Will watch for changes in these directories: ['/home/xiaomage/milvus/chatbot/.venv']
INFO:     Uvicorn running on http://0.0.0.0:9999 (Press CTRL+C to quit)
INFO:     Started reloader process [2071374] using WatchFiles
INFO:     Started server process [2071376]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Next.js Frontend

Das Frontend sendet die Anfrage des Benutzers an den FastAPI-Endpunkt und rendert die Antwort. Die zentrale Abfragelogik:

javascript

   try {
      const res = await fetch('http://localhost:9999/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query: input }),
      });
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.<span class="hljs-title function_">json</span>();
  <span class="hljs-keyword">const</span> <span class="hljs-attr">botMessage</span>: <span class="hljs-title class_">Message</span> = { <span class="hljs-attr">role</span>: <span class="hljs-string">&#x27;bot&#x27;</span>, <span class="hljs-attr">content</span>: data.<span class="hljs-property">answer</span> || <span class="hljs-string">&#x27;No response&#x27;</span> };
  <span class="hljs-title function_">setMessages</span>(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> [...prev, userMessage, botMessage]);
} <span class="hljs-keyword">catch</span> (error) {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(error);
  <span class="hljs-keyword">const</span> <span class="hljs-attr">botMessage</span>: <span class="hljs-title class_">Message</span> = { <span class="hljs-attr">role</span>: <span class="hljs-string">&#x27;bot&#x27;</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">&#x27;Error connecting to server.&#x27;</span> };
  <span class="hljs-title function_">setMessages</span>(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> [...prev, botMessage]);
} <span class="hljs-keyword">finally</span> {
  <span class="hljs-title function_">setLoading</span>(<span class="hljs-literal">false</span>);
}

Starten Sie das Frontend mit:

pnpm run dev -H 0.0.0.0 -p 3000

> rag-chatbot@0.1.0 dev /home/xiaomage/milvus/chatbot-web/rag-chatbot > next dev --turbopack -H 0.0.0.0 -p 3000

Next.js 15.5.3 (Turbopack)

  • Local: http://localhost:3000
  • Network: http://0.0.0.0:3000

Starting… ✓ Ready in 1288ms

Öffnen Sie http://<ip>:3000/chat in Ihrem Browser.

Geben Sie eine Frage ein:

How do I install RustFS in Docker?

Antwort der Chat-Schnittstelle::

Und schon ist der Chatbot fertig.

Fazit

Was als Neugier auf das Speicher-Backend von Milvus begann, wurde zu einem voll funktionsfähigen RAG-Chatbot - und der Weg vom einen zum anderen war kürzer als erwartet. Hier ist, was der Build umfasst, von Anfang bis Ende:

  • Milvus + RustFS mit Docker Compose. Milvus läuft im Standalone-Modus mit RustFS als Objektspeicher-Backend und ersetzt damit das standardmäßige MinIO. Insgesamt vier Dienste: etcd, Milvus, RustFS und Attu.
  • Vektorisierung der Wissensbasis. Die RustFS-Dokumentation - 80 Markdown-Dateien - wird gechunked, mit text-embedding-3-large eingebettet und in Milvus als 466 Vektoren gespeichert.
  • Die RAG-Pipeline. Zur Abfragezeit wird die Frage des Benutzers auf die gleiche Weise eingebettet, Milvus ruft die drei semantisch ähnlichsten Chunks ab, und GPT-5 generiert eine Antwort, die auf diesen Dokumenten basiert.
  • Die Chatbot-Benutzeroberfläche. FastAPI verpackt die Pipeline in einen einzigen POST-Endpunkt; Next.js setzt ein Chatfenster davor. Man muss nicht mehr in ein Terminal springen, um eine Frage zu stellen.

Ein paar meiner Erkenntnisse aus diesem Prozess:

  • Die Dokumentation von Milvus ist großartig. Vor allem die Deployment-Abschnitte sind klar, vollständig und einfach zu verstehen.
  • Die Arbeit mitRustFS als Objektspeicher-Backend ist ein Vergnügen. Es für MinIO einzusetzen, war weniger aufwändig als erwartet.
  • Vibe Coding ist schnell, bis der Scope die Kontrolle übernimmt. Eines führte zum anderen - Milvus zu RAG zu Chatbot zu "vielleicht sollte ich das Ganze dockerisieren". Die Anforderungen konvergieren nicht von selbst.
  • Beim Debuggen lernt man mehr als beim Lesen. Jeder Fehler in diesem Build ließ den nächsten Abschnitt schneller klicken, als es jede Dokumentation getan hätte.

Der gesamte Code dieses Builds ist unter github.com/majinghe/chatbot zu finden. Wenn Sie Milvus selbst ausprobieren wollen, ist der Schnellstart ein guter Anfang. Wenn du über deine Arbeit sprechen möchtest oder auf etwas Unerwartetes stößt, kannst du uns im Milvus Slack finden. Wenn Sie lieber ein persönliches Gespräch führen möchten, können Sie auch einen Termin während der Bürozeiten vereinbaren.

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    Weiterlesen