Skip to content

translated rag examples #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rag_documents_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

# Now we can use the matches to generate a response
SYSTEM_MESSAGE = """
You are a helpful assistant that answers questions about Maya civilization.
You are a helpful assistant that answers questions about insects.
You must use the data set to answer the questions,
you should not provide any info that is not in the provided sources.
Cite the sources you used to answer the question inside square brackets.
Expand Down
2 changes: 1 addition & 1 deletion rag_documents_hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def hybrid_search(query, limit):

# Now we can use the matches to generate a response
SYSTEM_MESSAGE = """
You are a helpful assistant that answers questions about Maya civilization.
You are a helpful assistant that answers questions about insects.
You must use the data set to answer the questions,
you should not provide any info that is not in the provided sources.
Cite the sources you used to answer the question inside square brackets.
Expand Down
28 changes: 27 additions & 1 deletion spanish/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Este repositorio contiene una colección de scripts en Python que demuestran cómo usar la API de OpenAI para generar completados de chat.

En orden creciente de complejidad, los scripts son:
## Librería OpenAI

Estos scripts demuestran como usar la libreria de OpenAI. En orden creciente de complejidad, los scripts son:

1. [`chat.py`](./chat.py): Un script simple que demuestra cómo usar la API de OpenAI para generar completados de chat.
2. [`chat_stream.py`](./chat_stream.py): Añade `stream=True` a la llamada de API para devolver un generador que transmite el completado mientras se está generando.
Expand All @@ -13,9 +15,33 @@ Además de estos scripts para demostrar características adicionales:

* [`chat_safety.py`](./chat_safety.py): El script simple con manejo de excepciones para errores de filtro de Seguridad de Contenido de Azure AI.
* [`chat_async.py`](./chat_async.py): Utiliza los clientes asíncronos para hacer llamadas asincrónicas, incluyendo un ejemplo de envío de múltiples solicitudes a la vez usando `asyncio.gather`.

## Popular LLM libraries

Estos scripts usan librerías populares para LLMs y demuestran como usarlas con la API de OpenAI:

* [`chat_langchain.py`](./chat_langchain.py): Utiliza el SDK de langchain para generar completados de chat. [Aprende más en la documentación de Langchain](https://python.langchain.com/docs/get_started/quickstart)
* [`chat_llamaindex.py`](./chat_llamaindex.py): Utiliza el SDK de LlamaIndex para generar completados de chat. [Aprende más en la documentación de LlamaIndex](https://docs.llamaindex.ai/en/stable/)
* [`chat_pydanticai.py`](./chat_pydanticai.py): Utiliza el SDK de PydanticAI para general completados de chat. [Aprende más en la documentación de PydanticAI](https://ai.pydantic.dev/)

## Generación Aumentada con Recuperación (RAG)

Estos scripts demuestran cómo usar la API de OpenAI para tareas de Generación Aumentada con Recuperación (RAG), donde el modelo recupera información relevante de una fuente y la utiliza para generar una respuesta.

Primero instala las dependencias de RAG:

```bash
python -m pip install -r requirements-rag.txt
```

Luego ejecuta los scripts (en orden de complejidad creciente):

* [`rag_csv.py`](./rag.py): Recupera resultados coincidentes de un archivo CSV y los utiliza para responder a la pregunta del usuario.
* [`rag_multiturn.py`](./rag_multiturn.py): La misma idea, pero con una interfaz de chat bidireccional usando `input()` que mantiene un registro de mensajes anteriores y los envía con cada llamada de completado de chat.them with each chat completion call.
* [`rag_queryrewrite.py`](./rag_queryrewrite.py): Añade un paso de reescritura de consulta al proceso RAG, donde la pregunta del usuario se reescribe para mejorar los resultados de recuperación.
* [`rag_documents_ingestion.py`](./rag_ingestion.py): Ingesta PDFs usando pymupdf para convertirlos a markdown, luego usa Langchain para dividirlos en fragmentos, después usa OpenAI para incrustar los fragmentos, y finalmente los almacena en un archivo JSON local.
* [`rag_documents_flow.py`](./rag_pdfs.py): Un flujo RAG que recupera resultados coincidentes del archivo JSON local creado por `rag_documents_ingestion.py`.
* [`rag_documents_hybrid.py`](./rag_documents_hybrid.py): Un flujo RAG que implementa una recuperación híbrida con búsqueda vectorial y por palabras clave, fusionando con Reciprocal Rank Fusion (RRF), y reclasificación semántica con un modelo cross-encoder.

## Configuración del entorno

Expand Down
Binary file added spanish/data/Apis_mellifera.pdf
Binary file not shown.
Binary file added spanish/data/Centris_pallida.pdf
Binary file not shown.
Binary file added spanish/data/Syrphidae.pdf
Binary file not shown.
Binary file added spanish/data/Xylocopa_californica.pdf
Binary file not shown.
74 changes: 74 additions & 0 deletions spanish/rag_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import csv
import os

import azure.identity
import openai
from dotenv import load_dotenv
from lunr import lunr

# Setup the OpenAI client to use either Azure, OpenAI.com, or Ollama API
load_dotenv(override=True)
API_HOST = os.getenv("API_HOST")

if API_HOST == "azure":
token_provider = azure.identity.get_bearer_token_provider(
azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
)
client = openai.AzureOpenAI(
api_version=os.environ["AZURE_OPENAI_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
azure_ad_token_provider=token_provider,
)
MODEL_NAME = os.environ["AZURE_OPENAI_DEPLOYMENT"]

elif API_HOST == "ollama":
client = openai.OpenAI(base_url=os.environ["OLLAMA_ENDPOINT"], api_key="nokeyneeded")
MODEL_NAME = os.environ["OLLAMA_MODEL"]

elif API_HOST == "github":
client = openai.OpenAI(base_url="https://models.inference.ai.azure.com", api_key=os.environ["GITHUB_TOKEN"])
MODEL_NAME = os.environ["GITHUB_MODEL"]

else:
client = openai.OpenAI(api_key=os.environ["OPENAI_KEY"])
MODEL_NAME = os.environ["OPENAI_MODEL"]

# Indexamos los datos del CSV
with open("hybridos.csv") as file:
reader = csv.reader(file)
rows = list(reader)
documents = [{"id": (i + 1), "body": " ".join(row)} for i, row in enumerate(rows[1:])]
index = lunr(ref="id", fields=["body"], documents=documents)

# Obteneemos la pregunta del usuario
user_question = "¿qué tan rápido es el Prius v?"

# Buscaamos en el índice la pregunta del usuario
results = index.search(user_question)
matching_rows = [rows[int(result["ref"])] for result in results]

# Formateamos como tabla markdown, ya que los llms entienden markdown
matches_table = " | ".join(rows[0]) + "\n" + " | ".join(" --- " for _ in range(len(rows[0]))) + "\n"
matches_table += "\n".join(" | ".join(row) for row in matching_rows)

print("Found matches:")
print(matches_table)

# Ahora podemos usar los resultados para generar una respuesta
SYSTEM_MESSAGE = """
Eres un asistente útil que responde preguntas sobre automóviles basándote en un conjunto de datos de autos híbridos.
Debes utilizar el conjunto de datos para responder las preguntas, no debes
proporcionar ninguna información que no esté en las fuentes proporcionadas.
"""

response = client.chat.completions.create(
model=MODEL_NAME,
temperature=0.3,
messages=[
{"role": "system", "content": SYSTEM_MESSAGE},
{"role": "user", "content": f"{user_question}\nSources: {matches_table}"},
],
)

print(f"\nResponse from {API_HOST}: \n")
print(response.choices[0].message.content)
70 changes: 70 additions & 0 deletions spanish/rag_documents_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import json
import os

import azure.identity
import openai
from dotenv import load_dotenv
from lunr import lunr

# Setup the OpenAI client to use either Azure, OpenAI.com, or Ollama API
load_dotenv(override=True)
API_HOST = os.getenv("API_HOST")

if API_HOST == "azure":
token_provider = azure.identity.get_bearer_token_provider(
azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
)
client = openai.AzureOpenAI(
api_version=os.environ["AZURE_OPENAI_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
azure_ad_token_provider=token_provider,
)
MODEL_NAME = os.environ["AZURE_OPENAI_DEPLOYMENT"]

elif API_HOST == "ollama":
client = openai.OpenAI(base_url=os.environ["OLLAMA_ENDPOINT"], api_key="nokeyneeded")
MODEL_NAME = os.environ["OLLAMA_MODEL"]

elif API_HOST == "github":
client = openai.OpenAI(base_url="https://models.inference.ai.azure.com", api_key=os.environ["GITHUB_TOKEN"])
MODEL_NAME = os.environ["GITHUB_MODEL"]

else:
client = openai.OpenAI(api_key=os.environ["OPENAI_KEY"])
MODEL_NAME = os.environ["OPENAI_MODEL"]

# Indexar los datos del JSON - cada objeto tiene id, texto y embedding
with open("rag_ingested_chunks.json") as file:
documents = json.load(file)
documents_by_id = {doc["id"]: doc for doc in documents}
index = lunr(ref="id", fields=["text"], documents=documents)

# Obtener la pregunta del usuario
user_question = "¿dónde vive la abeja solitaria?"

# Buscar la pregunta del usuario en el índice
results = index.search(user_question)
retrieved_documents = [documents_by_id[result["ref"]] for result in results]
print(f"Recuperados {len(retrieved_documents)} documentos coincidentes, enviando sólo los primeros 5.")
context = "\n".join([f"{doc['id']}: {doc['text']}" for doc in retrieved_documents[0:5]])

# Ahora podemos usar las coincidencias para generar una respuesta
SYSTEM_MESSAGE = """
Eres un asistente útil que responde preguntas sobre insectos regionales.
Debes utilizar el conjunto de datos para responder las preguntas,
no debes proporcionar ninguna información que no esté en las fuentes proporcionadas.
Cita las fuentes que utilizaste para responder la pregunta entre corchetes.
Las fuentes están en el formato: <id>: <texto>.
"""

response = client.chat.completions.create(
model=MODEL_NAME,
temperature=0.3,
messages=[
{"role": "system", "content": SYSTEM_MESSAGE},
{"role": "user", "content": f"{user_question}\nFuentes: {context}"},
],
)

print(f"\nRespuesta de {MODEL_NAME} en {API_HOST}: \n")
print(response.choices[0].message.content)
143 changes: 143 additions & 0 deletions spanish/rag_documents_hybrid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# pip install sentence-transformers
import json
import os

import azure.identity
import openai
from dotenv import load_dotenv
from lunr import lunr
from sentence_transformers import CrossEncoder

# Setup the OpenAI client to use either Azure, OpenAI.com, or Ollama API
load_dotenv(override=True)
API_HOST = os.getenv("API_HOST")

if API_HOST == "azure":
token_provider = azure.identity.get_bearer_token_provider(
azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
)
client = openai.AzureOpenAI(
api_version=os.environ["AZURE_OPENAI_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
azure_ad_token_provider=token_provider,
)
MODEL_NAME = os.environ["AZURE_OPENAI_DEPLOYMENT"]

elif API_HOST == "ollama":
client = openai.OpenAI(base_url=os.environ["OLLAMA_ENDPOINT"], api_key="nokeyneeded")
MODEL_NAME = os.environ["OLLAMA_MODEL"]

elif API_HOST == "github":
client = openai.OpenAI(base_url="https://models.inference.ai.azure.com", api_key=os.environ["GITHUB_TOKEN"])
MODEL_NAME = os.environ["GITHUB_MODEL"]

else:
client = openai.OpenAI(api_key=os.environ["OPENAI_KEY"])
MODEL_NAME = os.environ["OPENAI_MODEL"]

# Indexar los datos del JSON - cada objeto tiene id, texto y embedding
with open("rag_ingested_chunks.json") as file:
documents = json.load(file)
documents_by_id = {doc["id"]: doc for doc in documents}
index = lunr(ref="id", fields=["text"], documents=documents)


def full_text_search(query, limit):
"""
Realizar una búsqueda de texto completo en los documentos indexados.
"""
results = index.search(query)
retrieved_documents = [documents_by_id[result["ref"]] for result in results[:limit]]
return retrieved_documents


def vector_search(query, limit):
"""
Realizar una búsqueda vectorial en los documentos indexados
utilizando una función simple de similitud de coseno.
"""

def cosine_similarity(a, b):
return sum(x * y for x, y in zip(a, b)) / ((sum(x * x for x in a) ** 0.5) * (sum(y * y for y in b) ** 0.5))

query_embedding = client.embeddings.create(model="text-embedding-3-small", input=query).data[0].embedding
similarities = []
for doc in documents:
doc_embedding = doc["embedding"]
similarity = cosine_similarity(query_embedding, doc_embedding)
similarities.append((doc, similarity))
similarities.sort(key=lambda x: x[1], reverse=True)

retrieved_documents = [doc for doc, _ in similarities[:limit]]
return retrieved_documents


def reciprocal_rank_fusion(text_results, vector_results, alpha=0.5):
"""
Realizar la Fusión de Rango Recíproco en los resultados de búsquedas de texto y vectoriales.
"""
text_ids = {doc["id"] for doc in text_results}
vector_ids = {doc["id"] for doc in vector_results}

combined_results = []
for doc in text_results:
if doc["id"] in vector_ids:
combined_results.append((doc, alpha))
else:
combined_results.append((doc, 1 - alpha))
for doc in vector_results:
if doc["id"] not in text_ids:
combined_results.append((doc, alpha))
combined_results.sort(key=lambda x: x[1], reverse=True)
return [doc for doc, _ in combined_results]


def rerank(query, retrieved_documents):
"""
Reclasificar los resultados utilizando un cross-enconder modelo .
"""
encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
scores = encoder.predict([(query, doc["text"]) for doc in retrieved_documents])
scored_documents = [v for _, v in sorted(zip(scores, retrieved_documents), reverse=True)]
return scored_documents


def hybrid_search(query, limit):
"""
Realizar una búsqueda híbrida utilizando tanto búsqueda de texto completo como vectorial.
"""
text_results = full_text_search(query, limit * 2)
vector_results = vector_search(query, limit * 2)
combined_results = reciprocal_rank_fusion(text_results, vector_results)
combined_results = rerank(query, combined_results)
return combined_results[:limit]


# Obtener la pregunta del usuario
user_question = "gris y solitario"

# Buscar la pregunta del usuario en el índice
retrieved_documents = hybrid_search(user_question, limit=5)
print(f"Recuperados {len(retrieved_documents)} documentos coincidentes.")
context = "\n".join([f"{doc['id']}: {doc['text']}" for doc in retrieved_documents[0:5]])

# Ahora podemos usar las coincidencias para generar una respuesta
SYSTEM_MESSAGE = """
Eres un asistente útil que responde preguntas sobre insectos.
Debes utilizar el conjunto de datos para responder las preguntas,
no debes proporcionar ninguna información que no esté en las fuentes proporcionadas.
Cita las fuentes que utilizaste para responder la pregunta entre corchetes.
Las fuentes están en el formato: <id>: <texto>.
"""

response = client.chat.completions.create(
model=MODEL_NAME,
temperature=0.3,
messages=[
{"role": "system", "content": SYSTEM_MESSAGE},
{"role": "user", "content": f"{user_question}\nFuentes: {context}"},
],
)

print(f"\nRespuesta de {MODEL_NAME} en {API_HOST}: \n")
print(response.choices[0].message.content)
Loading