Zelfstudie: Een RAG-toepassing bouwen

Bouw een RAG-toepassing (Retrieval-Augmented Generation) die vragen beantwoordt over een verzameling documenten op uw apparaat. RAG combineert zoeken op basis van insluiten met een chatmodel, zodat antwoorden worden gebaseerd op uw eigen gegevens in plaats van alleen te vertrouwen op de trainingskennis van het model.

In deze zelfstudie leert u het volgende:

  • Een project instellen en de Foundry Local SDK installeren
  • Een knowledge base met tekstdocumenten maken
  • Insluitingen genereren voor de documenten
  • Zoeken naar relevante documenten op gelijkenis
  • Antwoorden genereren die gebaseerd zijn op de opgehaalde context
  • Hulpmiddelen opschonen

Voorwaarden

  • Een Windows-, macOS- of Linux-computer met ten minste 8 GB RAM-geheugen.
  • Python 3.11 of hoger geïnstalleerd.

Pakketten installeren

Als u op Windows ontwikkelt of verzendt, selecteert u het tabblad Windows. Het Windows-pakket kan worden geïntegreerd met de Windows ML runtime. Het biedt hetzelfde API-oppervlak met een grotere breedte van hardwareversnelling.

pip install foundry-local-sdk-winml openai

Een knowledge base maken

Een RAG-toepassing heeft een verzameling documenten nodig om te zoeken. Definieer een lijst met teksttekenreeksen die fungeren als de Knowledge Base. In een productietoepassing kan de lijst alinea's zijn uit bestanden, databaserecords of een andere tekstbron.

Maak een bestand met de naam main.py en voeg de volgende code toe:

import math
from foundry_local_sdk import Configuration, FoundryLocalManager

# Knowledge base — each string represents a document
documents = [
    "Foundry Local runs AI models directly on your device without cloud connectivity.",
    "The Foundry Local SDK supports Python, C#, JavaScript, and Rust.",
    "Embedding models convert text into numerical vectors for similarity search.",
    "Foundry Local uses ONNX Runtime for efficient model inference on CPUs and GPUs.",
    "The model catalog provides pre-optimized models that you can download and run locally.",
    "Retrieval-augmented generation grounds model responses in your own data.",
    "Vector similarity search finds documents that are semantically close to a query.",
    "Chat completions generate natural language responses from a prompt and context.",
]

Document insluitingen genereren

Initialiseer de SDK, laad een insluitmodel en converteer elk document naar een numerieke vector. Deze vectoren vertegenwoordigen de semantische betekenis van elk document en maken zoeken naar overeenkomsten mogelijk.

Voeg de volgende code toe aan main.py:

def main():
    # Initialize the SDK
    config = Configuration(app_name="foundry_local_rag")
    FoundryLocalManager.initialize(config)
    manager = FoundryLocalManager.instance

    # Load the embedding model
    embedding_model = manager.catalog.get_model("qwen3-embedding-0.6b")
    embedding_model.download(
        lambda p: print(f"\rDownloading embedding model: {p:.1f}%", end="", flush=True)
    )
    print()
    embedding_model.load()
    embedding_client = embedding_model.get_embedding_client()

    # Embed all documents in a single batch call
    response = embedding_client.generate_embeddings(documents)
    doc_embeddings = [item.embedding for item in response.data]
    print(f"Indexed {len(doc_embeddings)} documents.")

De generate_embeddings methode accepteert een lijst met tekenreeksen en retourneert één vector per invoer. Elke vector legt de semantische betekenis van de tekst vast, zodat vergelijkbare documenten vectoren produceren die dicht bij elkaar liggen in de insluitruimte.

Zoeken naar relevante documenten

Als u documenten wilt zoeken die betrekking hebben op een query, vergelijkt u de insluiting van de query met elke documentinsluiting met behulp van cosinus-overeenkomsten. Cosinus-gelijkenis meet hoe dicht twee vectoren in richting zijn, ongeacht de grootte. Waarden in de buurt van 1,0 geven een hoge gelijkenis aan.

Voeg de volgende helperfuncties hierboven main() toe in main.py:

def cosine_similarity(a, b):
    """Compute cosine similarity between two vectors."""
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    return dot / (norm_a * norm_b) if norm_a and norm_b else 0.0


def find_relevant(query_embedding, doc_embeddings, top_k=2):
    """Return the indices and scores of the top-k most similar documents."""
    scores = []
    for i, doc_emb in enumerate(doc_embeddings):
        score = cosine_similarity(query_embedding, doc_emb)
        scores.append((i, score))
    scores.sort(key=lambda x: x[1], reverse=True)
    return scores[:top_k]

De find_relevant functie rangschikt alle documenten op basis van gelijkenis en retourneert de beste overeenkomsten. Deze benadering werkt goed voor kleine verzamelingen. Voor grotere gegevenssets kunt u een toegewezen vectordatabase overwegen.

Geaarde antwoorden genereren

Laad een chatmodel en combineer de opgehaalde documenten met de vraag van de gebruiker in een systeemprompt. Het chatmodel gebruikt de opgegeven context om een antwoord te genereren dat is gebaseerd op uw documenten.

Voeg de volgende code toe aan de main() functie, na de sectie voor insluiten:

    # Load the chat model
    chat_model = manager.catalog.get_model("qwen2.5-0.5b")
    chat_model.download(
        lambda p: print(f"\rDownloading chat model: {p:.1f}%", end="", flush=True)
    )
    print()
    chat_model.load()
    chat_client = chat_model.get_chat_client()

    print("\nModels loaded. Ready for questions.")
    print("\nThe knowledge base contains information about:")
    print("  - Foundry Local features and architecture")
    print("  - Supported programming languages")
    print("  - Embedding models and vector search")
    print("  - ONNX Runtime inference")
    print("  - The model catalog")
    print("  - RAG and chat completions")
    print("\nExample questions:")
    print('  "What programming languages does the SDK support?"')
    print('  "How does Foundry Local run models?"')
    print('  "What is retrieval-augmented generation?"')
    print('\nType "quit" to exit.\n')

    # Interactive query loop
    while True:
        query = input("Question: ").strip()
        if not query or query.lower() == "quit":
            break

        # Embed the query
        query_response = embedding_client.generate_embedding(query)
        query_embedding = query_response.data[0].embedding

        # Retrieve the most relevant documents
        results = find_relevant(query_embedding, doc_embeddings, top_k=2)
        context = "\n".join(f"- {documents[i]}" for i, _ in results)

        # Build the prompt with retrieved context
        messages = [
            {
                "role": "system",
                "content": (
                    "Answer the user's question using only the provided context. "
                    "If the context doesn't contain enough information, say so.\n\n"
                    f"Context:\n{context}"
                ),
            },
            {"role": "user", "content": query},
        ]

        # Stream the response
        print("Answer: ", end="", flush=True)
        for chunk in chat_client.complete_streaming_chat(messages):
            content = chunk.choices[0].delta.content
            if content:
                print(content, end="", flush=True)
        print("\n")

    # Clean up
    embedding_model.unload()
    chat_model.unload()
    print("Models unloaded. Done!")


if __name__ == "__main__":
    main()

Met de systeemprompt wordt het model geïnstrueerd om alleen de opgehaalde context te beantwoorden. De systeemprompt zorgt ervoor dat reacties verankerd zijn in uw documenten en vermindert onjuiste antwoorden. De streaming-uitvoer drukt elk token af terwijl het wordt gegenereerd, waardoor het antwoord interactief aanvoelt.

Volledige code

Hier volgt de volledige toepassing waarin alle stappen worden gecombineerd:

import math
from foundry_local_sdk import Configuration, FoundryLocalManager

# Knowledge base
documents = [
    "Foundry Local runs AI models directly on your device without cloud connectivity.",
    "The Foundry Local SDK supports Python, C#, JavaScript, and Rust.",
    "Embedding models convert text into numerical vectors for similarity search.",
    "Foundry Local uses ONNX Runtime for efficient model inference on CPUs and GPUs.",
    "The model catalog provides pre-optimized models that you can download and run locally.",
    "Retrieval-augmented generation grounds model responses in your own data.",
    "Vector similarity search finds documents that are semantically close to a query.",
    "Chat completions generate natural language responses from a prompt and context.",
]


def cosine_similarity(a, b):
    """Compute cosine similarity between two vectors."""
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    return dot / (norm_a * norm_b) if norm_a and norm_b else 0.0


def find_relevant(query_embedding, doc_embeddings, top_k=2):
    """Return the indices and scores of the top-k most similar documents."""
    scores = []
    for i, doc_emb in enumerate(doc_embeddings):
        score = cosine_similarity(query_embedding, doc_emb)
        scores.append((i, score))
    scores.sort(key=lambda x: x[1], reverse=True)
    return scores[:top_k]


def main():
    # Initialize the SDK
    config = Configuration(app_name="foundry_local_rag")
    FoundryLocalManager.initialize(config)
    manager = FoundryLocalManager.instance

    # Load the embedding model
    embedding_model = manager.catalog.get_model("qwen3-embedding-0.6b")
    embedding_model.download(
        lambda p: print(f"\rDownloading embedding model: {p:.1f}%", end="", flush=True)
    )
    print()
    embedding_model.load()
    embedding_client = embedding_model.get_embedding_client()

    # Embed all documents
    response = embedding_client.generate_embeddings(documents)
    doc_embeddings = [item.embedding for item in response.data]
    print(f"Indexed {len(doc_embeddings)} documents.")

    # Load the chat model
    chat_model = manager.catalog.get_model("qwen2.5-0.5b")
    chat_model.download(
        lambda p: print(f"\rDownloading chat model: {p:.1f}%", end="", flush=True)
    )
    print()
    chat_model.load()
    chat_client = chat_model.get_chat_client()

    print("\nModels loaded. Ready for questions.")
    print("\nThe knowledge base contains information about:")
    print("  - Foundry Local features and architecture")
    print("  - Supported programming languages")
    print("  - Embedding models and vector search")
    print("  - ONNX Runtime inference")
    print("  - The model catalog")
    print("  - RAG and chat completions")
    print("\nExample questions:")
    print('  "What programming languages does the SDK support?"')
    print('  "How does Foundry Local run models?"')
    print('  "What is retrieval-augmented generation?"')
    print('\nType "quit" to exit.\n')

    # Interactive query loop
    while True:
        query = input("Question: ").strip()
        if not query or query.lower() == "quit":
            break

        # Embed the query
        query_response = embedding_client.generate_embedding(query)
        query_embedding = query_response.data[0].embedding

        # Retrieve the most relevant documents
        results = find_relevant(query_embedding, doc_embeddings, top_k=2)
        context = "\n".join(f"- {documents[i]}" for i, _ in results)

        # Build the prompt with retrieved context
        messages = [
            {
                "role": "system",
                "content": (
                    "Answer the user's question using only the provided context. "
                    "If the context doesn't contain enough information, say so.\n\n"
                    f"Context:\n{context}"
                ),
            },
            {"role": "user", "content": query},
        ]

        # Stream the response
        print("Answer: ", end="", flush=True)
        for chunk in chat_client.complete_streaming_chat(messages):
            content = chunk.choices[0].delta.content
            if content:
                print(content, end="", flush=True)
        print("\n")

    # Clean up
    embedding_model.unload()
    chat_model.unload()
    print("Models unloaded. Done!")


if __name__ == "__main__":
    main()

Voer de toepassing uit:

python main.py

De uitvoer ziet er ongeveer als volgt uit:

Downloading embedding model: 100.0%
Indexed 8 documents.
Downloading chat model: 100.0%

Models loaded. Ready for questions.

The knowledge base contains information about:
  - Foundry Local features and architecture
  - Supported programming languages
  - Embedding models and vector search
  - ONNX Runtime inference
  - The model catalog
  - RAG and chat completions

Example questions:
  "What programming languages does the SDK support?"
  "How does Foundry Local run models?"
  "What is retrieval-augmented generation?"

Type "quit" to exit.

Question: What programming languages does the SDK support?
Answer: The Foundry Local SDK supports Python, C#, JavaScript, and Rust.

Question: quit
Models unloaded. Done!

Hulpmiddelen opschonen

De modelgewichten blijven in uw lokale cache staan nadat u een model hebt uitgeladen. De volgende keer dat u de toepassing uitvoert, wordt de downloadstap overgeslagen en worden de modellen sneller geladen. Er is geen extra opschoonbewerking nodig, tenzij u schijfruimte wilt vrijmaken.