Construire RAG avec Milvus et Firecrawl
Firecrawl permet aux développeurs de créer des applications d'IA à partir de données propres extraites de n'importe quel site web. Grâce à ses fonctionnalités avancées de scraping, de crawling et d'extraction de données, Firecrawl simplifie le processus de conversion du contenu des sites web en markdown propre ou en données structurées pour les flux de travail d'IA en aval.
Dans ce tutoriel, nous allons vous montrer comment construire un pipeline de génération améliorée par récupération (RAG) à l'aide de Milvus et Firecrawl. Le pipeline intègre Firecrawl pour le scraping de données web, Milvus pour le stockage vectoriel et OpenAI pour générer des réponses perspicaces et contextuelles.
Préparation
Dépendances et environnement
Pour commencer, installez les dépendances requises en exécutant la commande suivante :
$ pip install firecrawl-py pymilvus openai requests tqdm
Si vous utilisez Google Colab, pour activer les dépendances qui viennent d'être installées, vous devrez peut-être redémarrer le runtime (cliquez sur le menu "Runtime" en haut de l'écran, et sélectionnez "Restart session" (Redémarrer la session) dans le menu déroulant).
Configuration des clés API
Pour utiliser Firecrawl afin de récupérer des données à partir de l'URL spécifiée, vous devez obtenir une FIRECRAWL_API_KEY et la définir en tant que variable d'environnement. Dans cet exemple, nous utiliserons OpenAI comme LLM. Vous devez également préparer la clé OPENAI_API_KEY en tant que variable d'environnement.
import os
os.environ["FIRECRAWL_API_KEY"] = "fc-***********"
os.environ["OPENAI_API_KEY"] = "sk-***********"
Préparer le LLM et le modèle d'intégration
Nous initialisons le client OpenAI pour préparer le modèle d'intégration.
from openai import OpenAI
openai_client = OpenAI()
Définir une fonction pour générer des embeddings de texte à l'aide du client OpenAI. Nous utilisons le modèle text-embedding-3-small comme exemple.
def emb_text(text):
return (
openai_client.embeddings.create(input=text, model="text-embedding-3-small")
.data[0]
.embedding
)
Générer un embedding de test et imprimer sa dimension et ses premiers éléments.
test_embedding = emb_text("This is a test")
embedding_dim = len(test_embedding)
print(embedding_dim)
print(test_embedding[:10])
1536
[0.009889289736747742, -0.005578675772994757, 0.00683477520942688, -0.03805781528353691, -0.01824733428657055, -0.04121600463986397, -0.007636285852640867, 0.03225184231996536, 0.018949154764413834, 9.352207416668534e-05]
Récupérer des données à l'aide de Firecrawl
Initialiser l'application Firecrawl
Nous allons utiliser la bibliothèque firecrawl
pour récupérer des données à partir de l'URL spécifiée au format markdown. Commencez par initialiser l'application Firecrawl :
from firecrawl import FirecrawlApp
app = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
Récupérer le site web cible
Récupérez le contenu de l'URL cible. Le site Web LLM-powered Autonomous Agents propose une exploration approfondie des systèmes d'agents autonomes construits à l'aide de grands modèles de langage (LLM). Nous utiliserons ce contenu pour construire un système RAG.
# Scrape a website:
scrape_status = app.scrape_url(
"https://lilianweng.github.io/posts/2023-06-23-agent/",
params={"formats": ["markdown"]},
)
markdown_content = scrape_status["markdown"]
Traiter le contenu récupéré
Pour rendre le contenu scrappé gérable en vue de son insertion dans Milvus, nous utilisons simplement "# " pour séparer le contenu, ce qui permet de séparer grossièrement le contenu de chaque partie principale du fichier markdown scrappé.
def split_markdown_content(content):
return [section.strip() for section in content.split("# ") if section.strip()]
# Process the scraped markdown content
sections = split_markdown_content(markdown_content)
# Print the first few sections to understand the structure
for i, section in enumerate(sections[:3]):
print(f"Section {i+1}:")
print(section[:300] + "...")
print("-" * 50)
Section 1:
Table of Contents
- [Agent System Overview](#agent-system-overview)
- [Component One: Planning](#component-one-planning) - [Task Decomposition](#task-decomposition)
- [Self-Reflection](#self-reflection)
- [Component Two: Memory](#component-two-memory) - [Types of Memory](#types-of-memory)
- [...
--------------------------------------------------
Section 2:
Agent System Overview [\#](\#agent-system-overview)
In a LLM-powered autonomous agent system, LLM functions as the agent’s brain, complemented by several key components:
- **Planning**
- Subgoal and decomposition: The agent breaks down large tasks into smaller, manageable subgoals, enabling effi...
--------------------------------------------------
Section 3:
Component One: Planning [\#](\#component-one-planning)
A complicated task usually involves many steps. An agent needs to know what they are and plan ahead.
#...
--------------------------------------------------
Chargement des données dans Milvus
Créer la collection
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Comme pour l'argument de MilvusClient
:
Définir
uri
comme un fichier local, par exemple./milvus.db
, est la méthode la plus pratique, car elle utilise automatiquement Milvus Lite pour stocker toutes les données dans ce fichier.Si vous avez des données à grande échelle, vous pouvez configurer un serveur Milvus plus performant sur docker ou kubernetes. Dans cette configuration, veuillez utiliser l'uri du serveur, par exemple
http://localhost:19530
, comme votreuri
.Si vous souhaitez utiliser Zilliz Cloud, le service cloud entièrement géré pour Milvus, ajustez les adresses
uri
ettoken
, qui correspondent au point de terminaison public et à la clé Api dans Zilliz Cloud.
Vérifier si la collection existe déjà et la supprimer si c'est le cas.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Créer une nouvelle collection avec les paramètres spécifiés.
Si nous ne spécifions aucune information de champ, Milvus créera automatiquement un champ id
par défaut pour la clé primaire et un champ vector
pour stocker les données vectorielles. Un champ JSON réservé est utilisé pour stocker les champs non définis par le schéma et leurs valeurs.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Insérer les données
from tqdm import tqdm
data = []
for i, section in enumerate(tqdm(sections, desc="Processing sections")):
embedding = emb_text(section)
data.append({"id": i, "vector": embedding, "text": section})
# Insert data into Milvus
milvus_client.insert(collection_name=collection_name, data=data)
Processing sections: 100%|██████████| 17/17 [00:08<00:00, 2.09it/s]
{'insert_count': 17, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 'cost': 0}
Construire un RAG
Récupérer des données pour une requête
Spécifions une question sur le site web que nous venons de récupérer.
question = "What are the main components of autonomous agents?"
Cherchons la question dans la collection et récupérons les 3 meilleures réponses sémantiques.
search_res = milvus_client.search(
collection_name=collection_name,
data=[emb_text(question)],
limit=3,
search_params={"metric_type": "IP", "params": {}},
output_fields=["text"],
)
Jetons un coup d'œil aux résultats de la recherche de la question.
import json
retrieved_lines_with_distances = [
(res["entity"]["text"], res["distance"]) for res in search_res[0]
]
print(json.dumps(retrieved_lines_with_distances, indent=4))
[
[
"Agent System Overview [\\#](\\#agent-system-overview)\n\nIn a LLM-powered autonomous agent system, LLM functions as the agent\u2019s brain, complemented by several key components:\n\n- **Planning**\n - Subgoal and decomposition: The agent breaks down large tasks into smaller, manageable subgoals, enabling efficient handling of complex tasks.\n - Reflection and refinement: The agent can do self-criticism and self-reflection over past actions, learn from mistakes and refine them for future steps, thereby improving the quality of final results.\n- **Memory**\n - Short-term memory: I would consider all the in-context learning (See [Prompt Engineering](https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/)) as utilizing short-term memory of the model to learn.\n - Long-term memory: This provides the agent with the capability to retain and recall (infinite) information over extended periods, often by leveraging an external vector store and fast retrieval.\n- **Tool use**\n - The agent learns to call external APIs for extra information that is missing from the model weights (often hard to change after pre-training), including current information, code execution capability, access to proprietary information sources and more.\n\n![](agent-overview.png)Fig. 1. Overview of a LLM-powered autonomous agent system.",
0.6343474388122559
],
[
"Table of Contents\n\n- [Agent System Overview](#agent-system-overview)\n- [Component One: Planning](#component-one-planning) - [Task Decomposition](#task-decomposition)\n - [Self-Reflection](#self-reflection)\n- [Component Two: Memory](#component-two-memory) - [Types of Memory](#types-of-memory)\n - [Maximum Inner Product Search (MIPS)](#maximum-inner-product-search-mips)\n- [Component Three: Tool Use](#component-three-tool-use)\n- [Case Studies](#case-studies) - [Scientific Discovery Agent](#scientific-discovery-agent)\n - [Generative Agents Simulation](#generative-agents-simulation)\n - [Proof-of-Concept Examples](#proof-of-concept-examples)\n- [Challenges](#challenges)\n- [Citation](#citation)\n- [References](#references)\n\nBuilding agents with LLM (large language model) as its core controller is a cool concept. Several proof-of-concepts demos, such as [AutoGPT](https://github.com/Significant-Gravitas/Auto-GPT), [GPT-Engineer](https://github.com/AntonOsika/gpt-engineer) and [BabyAGI](https://github.com/yoheinakajima/babyagi), serve as inspiring examples. The potentiality of LLM extends beyond generating well-written copies, stories, essays and programs; it can be framed as a powerful general problem solver.",
0.5715497732162476
],
[
"Challenges [\\#](\\#challenges)\n\nAfter going through key ideas and demos of building LLM-centered agents, I start to see a couple common limitations:\n\n- **Finite context length**: The restricted context capacity limits the inclusion of historical information, detailed instructions, API call context, and responses. The design of the system has to work with this limited communication bandwidth, while mechanisms like self-reflection to learn from past mistakes would benefit a lot from long or infinite context windows. Although vector stores and retrieval can provide access to a larger knowledge pool, their representation power is not as powerful as full attention.\n\n- **Challenges in long-term planning and task decomposition**: Planning over a lengthy history and effectively exploring the solution space remain challenging. LLMs struggle to adjust plans when faced with unexpected errors, making them less robust compared to humans who learn from trial and error.\n\n- **Reliability of natural language interface**: Current agent system relies on natural language as an interface between LLMs and external components such as memory and tools. However, the reliability of model outputs is questionable, as LLMs may make formatting errors and occasionally exhibit rebellious behavior (e.g. refuse to follow an instruction). Consequently, much of the agent demo code focuses on parsing model output.",
0.5009307265281677
]
]
Utiliser LLM pour obtenir une réponse RAG
Convertir les documents récupérés dans un format de chaîne.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Définir les messages-guides du système et de l'utilisateur pour le modèle de langue. Cette invite est assemblée avec les documents récupérés de Milvus.
SYSTEM_PROMPT = """
Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.
"""
USER_PROMPT = f"""
Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.
<context>
{context}
</context>
<question>
{question}
</question>
"""
Utiliser OpenAI ChatGPT pour générer une réponse basée sur les invites.
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
],
)
print(response.choices[0].message.content)
The main components of a LLM-powered autonomous agent system are the Planning, Memory, and Tool use.
1. Planning: The agent breaks down large tasks into smaller, manageable subgoals, and can self-reflect and learn from past mistakes, refining its actions for future steps.
2. Memory: This includes short-term memory, which the model uses for in-context learning, and long-term memory, which allows the agent to retain and recall information over extended periods.
3. Tool use: This component allows the agent to call external APIs for additional information that is not available in the model weights, like current information, code execution capacity, and access to proprietary information sources.