Direkt zum Inhalt

Llama 4 mit RAG: Ein Leitfaden mit Demo-Projekt

Lerne, wie du mit Llama 4 eine Retrieval-Augmented Generation (RAG) Pipeline aufbaust, um eine einfache Webanwendung zu erstellen.
Aktualisierte 14. Apr. 2025  · 12 Min. Lesezeit

Llama 4 Scout wird mit einem riesigen Kontextfenster von 10 Millionen Token beworben, aber sein Training war auf eine maximale Eingabegröße von 256k Token beschränkt. Das bedeutet, dass die Leistung bei größeren Eingaben abnehmen kann. Um dies zu verhindern, können wir Llama4 mit einer RAG-Pipeline (retrieval-augmented generation) verwenden.

In diesem Tutorial erkläre ich dir Schritt für Schritt, wie du mit dem LangChain-Ökosystem eine RAG-Pipeline aufbaust und eine Webanwendung erstellst, mit der Nutzer Dokumente hochladen und Fragen dazu stellen können.

Wir halten unsere Leserinnen und Leser mit The Median auf dem Laufenden, unserem kostenlosen Freitags-Newsletter, der die wichtigsten Meldungen der Woche aufschlüsselt. Melde dich an und bleibe in nur ein paar Minuten pro Woche auf dem Laufenden:

Warum Llama 4 mit RAG verwenden?

Llama 4 verfügt über ein Kontextfenster mit 10 Millionen Token, d.h. wir können es mit 100 Büchern füttern und erhalten kontextbezogene Antworten zu ihnen. Doch trotz dieser beeindruckenden Funktion gibt es zwingende Gründe, Llama 4 mit RAG. Hier ist der Grund dafür:

1. Leistungseinschränkungen bei großen Eingaben

Obwohl Llama 4 ein riesiges Kontextfenster unterstützt, war sein Training auf eine maximale Eingabegröße von 256k Token beschränkt. Die Leistung des Modells kann sich verschlechtern, wenn die Eingaben diesen Schwellenwert erreichen oder überschreiten. RAG entschärft dieses Problem, indem es nur die wichtigsten Informationen abruft und so sicherstellt, dass die Eingaben kurz und bündig bleiben.

2. Effizienz und Fokus

RAG ermöglicht das Abrufen von hochrelevanten Informationen aus einer Vektor-DatenbankDadurch kann Llama 4 kleinere, gezieltere Eingaben verarbeiten. Dieser Ansatz verbessert sowohl die Effizienz als auch die Genauigkeit.

3. Kosteneinsparungen

Die Verarbeitung umfangreicher Eingaben mit dem vollständigen Kontextfenster von Llama 4 kann sehr rechenintensiv sein. Durch den Einsatz von RAG werden nur die relevantesten Daten abgerufen und verarbeitet, was den Rechenaufwand und die damit verbundenen Kosten erheblich reduziert.

Aufbau der Llama 4 RAG Pipeline

In diesem Tutorial erkläre ich dir Schritt für Schritt, wie du eine einfache Pipeline zur Generierung von Abfragen aufbaust, mit der du eine .docx Datei laden und Fragen zu ihrem Inhalt stellen kannst.

1. Einrichten

Zuerst müssen wir eine GroqCloud API-Schlüssel und speichern ihn als Umgebungsvariable. Als Nächstes werden wir alle notwendigen Python-Pakete installieren, die zum Laden, Teilen, Vektorisieren und Abrufen des Kontexts sowie zum Generieren von Antworten benötigt werden.

%%capture
%pip install langchain
%pip install langchain-community 
%pip install langchainhub 
%pip install langchain-chroma 
%pip install langchain-groq
%pip install langchain-huggingface
%pip install unstructured[docx]

2. Initiierung des LLM und der Einbettung

Wir richten das Modellobjekt über die Groq-API ein, indem wir ihm den Modellnamen und den API-Schlüssel geben. Auf ähnliche Weise laden wir das Einbettungsmodell von Hugging Face herunter und verwenden es als unser Einbettungsmodell.

from langchain_groq import ChatGroq
from langchain_huggingface import HuggingFaceEmbeddings

llm = ChatGroq(model="meta-llama/llama-4-scout-17b-16e-instruct", api_key=groq_api_key)

embed_model = HuggingFaceEmbeddings(model_name="mixedbread-ai/mxbai-embed-large-v1")

3. Laden und Aufteilen der Daten

Wir laden die Datei .docx aus dem Stammordner und teilen sie in Abschnitte von 1000 Zeichen auf.

from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Initialize the text splitter
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100,
    separators=["\n\n", "\n"]
)

# Load the .docx files
loader = DirectoryLoader("./", glob="*.docx", use_multithreading=True)
documents = loader.load()

# Split the documents into chunks
chunks = text_splitter.split_documents(documents)

# Print the number of chunks
print(len(chunks))
29

4. Aufbau und Befüllung des Vektorspeichers

Initialisiere den Chroma-Vektorspeicher und stelle der Vektordatenbank die Textstücke zur Verfügung. Der Text wird in Einbettungen umgewandelt, bevor er gespeichert wird. Danach führen wir eine Ähnlichkeitssuche durch, um zu testen, ob unser Vektorspeicher richtig befüllt ist.

from langchain_chroma import Chroma

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embed_model,
    persist_directory="./Vectordb",
)

query = "What is this tutorial about?"
docs = vectorstore.similarity_search(query)
print(docs[0].page_content)
Learn how to Fine-tune Stable Diffusion XL with DreamBooth and LoRA on your personal images. 

Let's try another prompt:

Prompt:

5. Erstellen der RAG-Pipeline

Als Nächstes werden wir unseren Vektorspeicher in einen Retriever umwandeln und eine Prompt-Vorlage für die RAG-Pipeline erstellen.

# Create retriever
retriever = vectorstore.as_retriever()

# Import PromptTemplate
from langchain_core.prompts import PromptTemplate

# Define a clearer, more professional prompt template
template = """You are an expert assistant tasked with answering questions based on the provided documents.
Use only the given context to generate your answer.
If the answer cannot be found in the context, clearly state that you do not know.
Be detailed and precise in your response, but avoid mentioning or referencing the context itself.

Context:
{context}

Question:
{question}

Answer:"""

# Create the PromptTemplate
rag_prompt = PromptTemplate.from_template(template)

Zum Schluss erstellen wir die RAG-Kette, die sowohl den Kontext als auch die Frage in der RAG-Aufforderung enthält, durchlaufen das Llama 4-Modell und erzeugen dann eine saubere Antwort.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

Wir werden jetzt die RAG-Kette über das Dokument befragen.

from IPython.display import display, Markdown

response = rag_chain.invoke("What this tutorial about?")
Markdown(response)
This tutorial is about setting up and using the Janus project, specifically Janus Pro, a multimodal model that can understand images and generate images from text prompts, and building a local solution to use the model privately on a laptop GPU. It covers learning about the Janus Series, setting up the Janus project, building a Docker container to run the model locally, and testing its capabilities with various image and text prompts.

Wie du siehst, ist die Antwort sehr genau und kontextabhängig. Wenn du bei der Ausführung des obigen Codes auf Probleme stößt, sieh dir bitte diese DataLab-Arbeitsmappe an: Llama 4 RAG-Pipeline.

Aufbau der Llama 4 Docx Chat-Anwendung

In diesem Abschnitt werden wir den zuvor besprochenen RAG-Pipeline-Code mit Hilfe des Gradio-Pakets in eine funktionale Chat-Anwendung umwandeln. Um loszulegen, musst du Gradio mit dem folgenden pip Befehl installieren:

$ pip install gradio

Die Anwendung ist einfach und benutzerfreundlich gestaltet und ermöglicht es den Nutzern, eine einzelne .docx Datei, mehrere .docx Dateien oder ein .zip Archiv mit .docx Dateien hochzuladen. Die Nutzer können dann Fragen zum Inhalt aller hochgeladenen Dateien stellen und so schnell und effizient Informationen abrufen.

Die Anwendung bietet folgende Funktionen:

  • Dokument laden: Die App extrahiert und lädt Dokumente direkt aus hochgeladenen Dateien.
  • Text aufteilen: Die Dokumente werden in überschaubare Teile aufgeteilt, damit sie effizient bearbeitet und abgerufen werden können.
  • Integration des Vektorspeichers: Anstelle von ChromaDB verwendet es den In-Memory-Vektorspeicher, um Dokumenteneinbettungen zu verwalten.
  • RAG-Pipeline: Kombiniert die Dokumentensuche mit einem Sprachmodell, um genaue Antworten auf der Grundlage der hochgeladenen Inhalte zu geben.
  • Benutzeroberfläche: Verfügt über ein Chat-Interface, in dem du Fragen stellen und ausführliche Antworten auf der Grundlage der bereitgestellten Dokumente erhalten kannst.
  • Fehlerbehandlung: Bietet Feedback zu nicht unterstützten Dateitypen und Problemen beim Hochladen von Dateien und verbessert so die Benutzerfreundlichkeit.
  • Funktion zurücksetzen: Die Nutzer können den Anwendungsstatus zurücksetzen, um mit neuen Dokumenten-Uploads neu zu beginnen.

Fassen wir das alles zusammen:

main.py:

# ========== Standard Library ==========
import os
import tempfile
import zipfile
from typing import List, Optional, Tuple, Union
import collections


# ========== Third-Party Libraries ==========
import gradio as gr
from groq import Groq
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, UnstructuredFileLoader
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_groq import ChatGroq
from langchain_huggingface import HuggingFaceEmbeddings

# ========== Configs ==========
TITLE = """<h1 align="center">🗨️🦙 Llama 4 Docx Chatter</h1>"""
AVATAR_IMAGES = (
    None,
    "./logo.png",
)

# Acceptable file extensions
TEXT_EXTENSIONS = [".docx", ".zip"]

# ========== Models & Clients ==========
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
client = Groq(api_key=GROQ_API_KEY)
llm = ChatGroq(model="meta-llama/llama-4-scout-17b-16e-instruct", api_key=GROQ_API_KEY)
embed_model = HuggingFaceEmbeddings(model_name="mixedbread-ai/mxbai-embed-large-v1")

# ========== Core Components ==========
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100,
    separators=["\n\n", "\n"],
)

rag_template = """You are an expert assistant tasked with answering questions based on the provided documents.
Use only the given context to generate your answer.
If the answer cannot be found in the context, clearly state that you do not know.
Be detailed and precise in your response, but avoid mentioning or referencing the context itself.
Context:
{context}
Question:
{question}
Answer:"""
rag_prompt = PromptTemplate.from_template(rag_template)


# ========== App State ==========
class AppState:
    vectorstore: Optional[InMemoryVectorStore] = None
    rag_chain = None


state = AppState()

# ========== Utility Functions ==========


def load_documents_from_files(files: List[str]) -> List:
    """Load documents from uploaded files directly without moving."""
    all_documents = []

    # Temporary directory if ZIP needs extraction
    with tempfile.TemporaryDirectory() as temp_dir:
        for file_path in files:
            ext = os.path.splitext(file_path)[1].lower()

            if ext == ".zip":
                # Extract ZIP inside temp_dir
                with zipfile.ZipFile(file_path, "r") as zip_ref:
                    zip_ref.extractall(temp_dir)

                # Load all docx from extracted zip
                loader = DirectoryLoader(
                    path=temp_dir,
                    glob="**/*.docx",
                    use_multithreading=True,
                )
                docs = loader.load()
                all_documents.extend(docs)

            elif ext == ".docx":
                # Load single docx directly
                loader = UnstructuredFileLoader(file_path)
                docs = loader.load()
                all_documents.extend(docs)

    return all_documents


def get_last_user_message(chatbot: List[Union[gr.ChatMessage, dict]]) -> Optional[str]:
    """Get last user prompt."""
    for message in reversed(chatbot):
        content = (
            message.get("content") if isinstance(message, dict) else message.content
        )
        if (
            message.get("role") if isinstance(message, dict) else message.role
        ) == "user":
            return content
    return None


# ========== Main Logic ==========




def upload_files(
    files: Optional[List[str]], chatbot: List[Union[gr.ChatMessage, dict]]
):
    """Handle file upload - .docx or .zip containing docx."""
    if not files:
        return chatbot

    file_summaries = []  # <-- Collect formatted file/folder info
    documents = []

    with tempfile.TemporaryDirectory() as temp_dir:
        for file_path in files:
            filename = os.path.basename(file_path)
            ext = os.path.splitext(file_path)[1].lower()

            if ext == ".zip":
                file_summaries.append(f"📦 **{filename}** (ZIP file) contains:")
                try:
                    with zipfile.ZipFile(file_path, "r") as zip_ref:
                        zip_ref.extractall(temp_dir)
                        zip_contents = zip_ref.namelist()

                        # Group files by folder
                        folder_map = collections.defaultdict(list)
                        for item in zip_contents:
                            if item.endswith("/"):
                                continue  # skip folder entries themselves
                            folder = os.path.dirname(item)
                            file_name = os.path.basename(item)
                            folder_map[folder].append(file_name)

                        # Format nicely
                        for folder, files_in_folder in folder_map.items():
                            if folder:
                                file_summaries.append(f"📂 {folder}/")
                            else:
                                file_summaries.append(f"📄 (root)")
                            for f in files_in_folder:
                                file_summaries.append(f"   - {f}")

                    # Load docx files extracted from ZIP
                    loader = DirectoryLoader(
                        path=temp_dir,
                        glob="**/*.docx",
                        use_multithreading=True,
                    )
                    docs = loader.load()
                    documents.extend(docs)

                except zipfile.BadZipFile:
                    chatbot.append(
                        gr.ChatMessage(
                            role="assistant",
                            content=f"❌ Failed to open ZIP file: {filename}",
                        )
                    )

            elif ext == ".docx":
                file_summaries.append(f"📄 **{filename}**")
                loader = UnstructuredFileLoader(file_path)
                docs = loader.load()
                documents.extend(docs)

            else:
                file_summaries.append(f"❌ Unsupported file type: {filename}")

    if not documents:
        chatbot.append(
            gr.ChatMessage(
                role="assistant", content="No valid .docx files found in upload."
            )
        )
        return chatbot

    # Split documents
    chunks = text_splitter.split_documents(documents)
    if not chunks:
        chatbot.append(
            gr.ChatMessage(
                role="assistant", content="Failed to split documents into chunks."
            )
        )
        return chatbot

    # Create Vectorstore
    state.vectorstore = InMemoryVectorStore.from_documents(
        documents=chunks,
        embedding=embed_model,
    )
    retriever = state.vectorstore.as_retriever()

    # Build RAG Chain
    state.rag_chain = (
        {"context": retriever, "question": RunnablePassthrough()}
        | rag_prompt
        | llm
        | StrOutputParser()
    )

    # Final display
    chatbot.append(
        gr.ChatMessage(
            role="assistant",
            content="**Uploaded Files:**\n"
            + "\n".join(file_summaries)
            + "\n\n✅ Ready to chat!",
        )
    )
    return chatbot


def user_message(
    text_prompt: str, chatbot: List[Union[gr.ChatMessage, dict]]
) -> Tuple[str, List[Union[gr.ChatMessage, dict]]]:
    """Add user's text input to conversation."""
    if text_prompt.strip():
        chatbot.append(gr.ChatMessage(role="user", content=text_prompt))
    return "", chatbot


def process_query(
    chatbot: List[Union[gr.ChatMessage, dict]],
) -> List[Union[gr.ChatMessage, dict]]:
    """Process user's query through RAG pipeline."""
    prompt = get_last_user_message(chatbot)
    if not prompt:
        chatbot.append(
            gr.ChatMessage(role="assistant", content="Please type a question first.")
        )
        return chatbot

    if state.rag_chain is None:
        chatbot.append(
            gr.ChatMessage(role="assistant", content="Please upload documents first.")
        )
        return chatbot

    chatbot.append(gr.ChatMessage(role="assistant", content="Thinking..."))

    try:
        response = state.rag_chain.invoke(prompt)
        chatbot[-1].content = response
    except Exception as e:
        chatbot[-1].content = f"Error: {str(e)}"

    return chatbot


def reset_app(
    chatbot: List[Union[gr.ChatMessage, dict]],
) -> List[Union[gr.ChatMessage, dict]]:
    """Reset application state."""
    state.vectorstore = None
    state.rag_chain = None
    return [
        gr.ChatMessage(
            role="assistant", content="App reset! Upload new documents to start."
        )
    ]


# ========== UI Layout ==========

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.HTML(TITLE)
    chatbot = gr.Chatbot(
        label="Llama 4 RAG",
        type="messages",
        bubble_full_width=False,
        avatar_images=AVATAR_IMAGES,
        scale=2,
        height=350,
    )

    with gr.Row(equal_height=True):
        text_prompt = gr.Textbox(
            placeholder="Ask a question...", show_label=False, autofocus=True, scale=28
        )
        send_button = gr.Button(
            value="Send",
            variant="primary",
            scale=1,
            min_width=80,
        )
        upload_button = gr.UploadButton(
            label="Upload",
            file_count="multiple",
            file_types=TEXT_EXTENSIONS,
            scale=1,
            min_width=80,
        )
        reset_button = gr.Button(
            value="Reset",
            variant="stop",
            scale=1,
            min_width=80,
        )

    send_button.click(
        fn=user_message,
        inputs=[text_prompt, chatbot],
        outputs=[text_prompt, chatbot],
        queue=False,
    ).then(fn=process_query, inputs=[chatbot], outputs=[chatbot])

    text_prompt.submit(
        fn=user_message,
        inputs=[text_prompt, chatbot],
        outputs=[text_prompt, chatbot],
        queue=False,
    ).then(fn=process_query, inputs=[chatbot], outputs=[chatbot])

    upload_button.upload(
        fn=upload_files, inputs=[upload_button, chatbot], outputs=[chatbot], queue=False
    )
    reset_button.click(fn=reset_app, inputs=[chatbot], outputs=[chatbot], queue=False)

demo.queue().launch()

Testen der Llama 4 Docx Chat-Anwendung

Jetzt werden wir die Webanwendung ausführen und ihre Funktionen testen. Um die Anwendung zu starten, führe den folgenden Befehl im Terminal aus:

$ python main.py 

Sobald der Webserver läuft, navigierst du zur folgenden URL oder kopierst sie und fügst sie manuell in deinen Browser ein, um die Weboberfläche aufzurufen: http://127.0.0.1:7860

So sieht unser Chat-Interface aus:

llama 4 rag gradio interface

Wir laden eine .zip Datei hoch, die .docx Dateien enthält. Während wir das tun, siehst du alle Ordner und Dateien in der Zip-Datei.

llama 4 rag gradio interface test

Als Nächstes können wir konkrete Fragen zu den Dokumenten stellen:

llama 4 rag gradio interface test

Du kannst auch die Schaltfläche "Zurücksetzen" drücken, um eine neue Datei hochzuladen und in wenigen Sekunden darüber zu chatten.

llama 4 rag gradio interface test

Nachdem du sie lokal getestet hast, kannst du die Webanwendung einfach und kostenlos auf Hugging Face Space bereitstellen. Hier ist der Link zur installierten App: kingabzpro/Llama-4-RAG 

llama 4 rag gradio interface test

Quelle: kingabzpro/Llama-4-RAG 

Der Quellcode, das Notizbuch und die Konfigurationsdateien befinden sich im Verzeichnis kingabzpro/Llama-4-RAG Repository.

Fazit

Auch mit der Einführung von Fenstermodellen mit langem Kontext wird die RAG nie veraltet sein. RAG ist nach wie vor eine effiziente und kostengünstige Methode, um Informationen aus externen Ressourcen abzurufen und den Modellen zusätzlichen Kontext zu geben.

Außerdem sind RAG-Systeme von Natur aus wirtschaftlicher zu betreiben als die direkte Einspeisung von umfangreichem Kontext in ein Modell, was erhebliche Rechenressourcen erfordert. Das macht RAG zu einer praktischen und skalierbaren Lösung für viele Anwendungsfälle.

Um mehr über Llama 4 zu erfahren, schau dir diese Ressourcen an:


Abid Ali Awan's photo
Author
Abid Ali Awan
LinkedIn
Twitter

Als zertifizierter Data Scientist ist es meine Leidenschaft, modernste Technologien zu nutzen, um innovative Machine Learning-Anwendungen zu entwickeln. Mit meinem fundierten Hintergrund in den Bereichen Spracherkennung, Datenanalyse und Reporting, MLOps, KI und NLP habe ich meine Fähigkeiten bei der Entwicklung intelligenter Systeme verfeinert, die wirklich etwas bewirken können. Neben meinem technischen Fachwissen bin ich auch ein geschickter Kommunikator mit dem Talent, komplexe Konzepte in eine klare und prägnante Sprache zu fassen. Das hat dazu geführt, dass ich ein gefragter Blogger zum Thema Datenwissenschaft geworden bin und meine Erkenntnisse und Erfahrungen mit einer wachsenden Gemeinschaft von Datenexperten teile. Zurzeit konzentriere ich mich auf die Erstellung und Bearbeitung von Inhalten und arbeite mit großen Sprachmodellen, um aussagekräftige und ansprechende Inhalte zu entwickeln, die sowohl Unternehmen als auch Privatpersonen helfen, das Beste aus ihren Daten zu machen.

Themen

Lerne KI mit diesen Kursen!

Lernpfad

Llama Fundamentals

0 Min.
Experiment with Llama 3 to run inference on pre-trained models, fine-tune them on custom datasets, and optimize performance.
Siehe DetailsRight Arrow
Kurs starten
Mehr anzeigenRight Arrow
Verwandt

Der Blog

Top 30 Generative KI Interview Fragen und Antworten für 2024

Dieser Blog bietet eine umfassende Sammlung von Fragen und Antworten zu generativen KI-Interviews, die von grundlegenden Konzepten bis hin zu fortgeschrittenen Themen reichen.
Hesam Sheikh Hassani's photo

Hesam Sheikh Hassani

15 Min.

Der Blog

Die 50 besten AWS-Interview-Fragen und Antworten für 2025

Ein kompletter Leitfaden zur Erkundung der grundlegenden, mittleren und fortgeschrittenen AWS-Interviewfragen, zusammen mit Fragen, die auf realen Situationen basieren.
Zoumana Keita 's photo

Zoumana Keita

15 Min.

Der Blog

Lehrer/innen und Schüler/innen erhalten das Premium DataCamp kostenlos für ihre gesamte akademische Laufbahn

Keine Hacks, keine Tricks. Schüler/innen und Lehrer/innen, lest weiter, um zu erfahren, wie ihr die Datenerziehung, die euch zusteht, kostenlos bekommen könnt.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

4 Min.

Der Blog

Die 20 besten Snowflake-Interview-Fragen für alle Niveaus

Bist du gerade auf der Suche nach einem Job, der Snowflake nutzt? Bereite dich mit diesen 20 besten Snowflake-Interview-Fragen vor, damit du den Job bekommst!
Nisha Arya Ahmed's photo

Nisha Arya Ahmed

15 Min.

Der Blog

Q2 2023 DataCamp Donates Digest

DataCamp Donates hat im zweiten Quartal 2023 über 20.000 Stipendien an unsere gemeinnützigen Partner vergeben. Erfahre, wie fleißige benachteiligte Lernende diese Chancen in lebensverändernde berufliche Erfolge verwandelt haben.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

Der Blog

2022-2023 DataCamp Classrooms Jahresbericht

Zu Beginn des neuen Schuljahres ist DataCamp Classrooms motivierter denn je, das Lernen mit Daten zu demokratisieren. In den letzten 12 Monaten sind über 7.650 neue Klassenzimmer hinzugekommen.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

8 Min.

Mehr anzeigenMehr anzeigen