You can fine-tune ColBERT models without manual annotations by using the instructor library and an LLM (like OpenAI) to generate synthetic [query, relevant_passage] pairs. This process involves:
- Extracting structured queries: Use
instructor.patch(OpenAI(...)) and a Pydantic model to force the LLM to return specific query types (e.g., hypothetical questions or search queries) based on document chunks. - Creating training pairs: Map the generated queries to their corresponding document chunks.
- Preparing training data: Use
RAGTrainer.prepare_training_data to combine these pairs with your full corpus to automatically mine hard negatives.
This approach allows for domain adaptation without the cost of human labeling.
import instructor
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List
from ragatouille import RAGTrainer
# 1. Setup Instructor with OpenAI
client = instructor.patch(OpenAI(api_key=os.environ["OPENAI_API_KEY"]))
# 2. Define schema for synthetic query generation
class QueryForPassage(BaseModel):
hypothetical_questions: List[str] = Field(
default_factory=list,
description="A wide variety of hypothetical questions that this document could answer.",
)
hypothetical_queries: List[str] = Field(
default_factory=list,
description="A wide variety of hypothetical queries that this document would be relevant to.",
)
# 3. Generate queries using the LLM
# (Assuming 'relevant_documents' is a list of text chunks)
candidate_queries = []
for doc in relevant_documents:
candidate = client.chat.completions.create(
model="gpt-4-1106-preview",
response_model=QueryForPassage,
messages=[
{"role": "system", "content": "You are an expert AI..."},
{"role": "user", "content": doc},
],
)
candidate_queries.append(candidate)
# 4. Format pairs for RAGatouille
pairs = []
for candidates, doc in zip(candidate_queries, relevant_documents):
candidates_dict = candidates.model_dump()
# Combine different query types into the training pairs
queries = candidates_dict['hypothetical_questions'] + candidates_dict['hypothetical_queries']
for q in queries:
pairs.append([q, doc])
# 5. Prepare training data with hard negative mining
trainer = RAGTrainer(model_name="MyModel", pretrained_model_name="colbert-ir/colbertv2.0")
trainer.prepare_training_data(
raw_data=pairs,
all_documents=documents,
num_new_negatives=10,
mine_hard_negatives=True,
)