Hybrid search: combining web and files
On this page
Lesson updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a
containerparameter (added to all examples), input text parts are of typeinput_text, and web search uses theweb_searchtool (web_search_previewis now a legacy tool without domain filters). Generated files are retrieved using thefichiers_generesandtelechargerfunctions from Lesson 10. Sources: Code Interpreter · Web search.
Hybrid search: combining web and files
The two search tools answer different questions, and used in isolation, each remains incomplete. File Search knows what you have written; Web Search knows what the world has published. Many useful questions sit exactly at the intersection of the two: Is our procedure still compliant? Is our documentation up to date? Where do we stand relative to the market?
These questions share a common property that explains their value: no one can answer them without reading both sides. It is this crossover, not the search itself, that makes this lesson valuable.
Enable both tools
Declare both tools simultaneously in the tools array:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
input="Compare our remote work policy with current market trends",
tools=[
{
"type": "file_search",
"vector_store_ids": ["vs_abc123"],
"max_num_results": 10
},
{
"type": "web_search",
"search_context_size": "medium"
}
]
)
print(response.output_text)
The model decides on its own when to use each tool. For a question about your internal documents, it will use File Search. For public data, it will use Web Search. For a comparison, it will use both.
Use case: regulatory monitoring
Regulatory monitoring is the textbook example because the gap between the two sources is precisely what we are looking for. Explicitly ask for differences, not a summary of each side: a model asked to summarize will produce two correct syntheses and leave you to do the comparison, which is the only difficult part of the job.
def audit_conformite(sujet: str, vs_processus: str) -> dict:
"""Compares internal processes with current regulations."""
response = client.responses.create(
model="gpt-5.6-terra",
instructions=(
"You are a compliance auditor. For each point, you must: "
"1. Search internal documents for what is currently in place "
"2. Search the web for current applicable regulations "
"3. Identify gaps between the two "
"4. Propose corrective actions"
),
input=f"Audit the compliance of our processes on the topic: {sujet}",
tools=[
{
"type": "file_search",
"vector_store_ids": [vs_processus],
"max_num_results": 15
},
{
"type": "web_search",
"search_context_size": "high"
}
]
)
return {
"rapport": response.output_text,
"tokens_utilises": response.usage.total_tokens
}
# Usage
rapport = audit_conformite(
"personal data protection and GDPR",
"vs_processus_internes"
)
Use case: knowledge base enrichment
The same mechanism, reversed, serves to age-check your own documents: instead of verifying compliance, you ask what has changed since they were written. This is the most cost-effective use of hybrid search on an old document base—no one has time to reread a hundred pages to find the three that have aged.
def detecter_obsolescence(vs_id: str, sujets: list[str]) -> list[dict]:
"""Identifies potentially obsolete information."""
resultats = []
for sujet in sujets:
response = client.responses.create(
model="gpt-5.6-terra",
instructions=(
"Compare what our internal documents say with the most recent "
"information from the web. Identify factual gaps, outdated figures, "
"and obsolete procedures."
),
input=f"Check the currency of our documents on: {sujet}",
tools=[
{"type": "file_search", "vector_store_ids": [vs_id]},
{"type": "web_search"}
]
)
resultats.append({
"sujet": sujet,
"analyse": response.output_text
})
return resultats
Add custom functions to the mix
Adding your functions to the mix shifts the workflow from analysis to action: the model spots a gap, then opens a ticket. However, keep a clear boundary—search and analysis can remain automatic, but actions that commit resources (creating, sending, modifying) deserve human validation until you have measured the reliability of the flow over several weeks.
tools = [
# Search your documents
{
"type": "file_search",
"vector_store_ids": ["vs_contrats"],
"max_num_results": 10
},
# Web search
{
"type": "web_search",
"search_context_size": "medium"
},
# Business function: query the CRM
{
"type": "function",
"name": "get_client_info",
"description": "Retrieve client information from the CRM",
"parameters": {
"type": "object",
"properties": {
"client_id": {"type": "string"},
"champs": {
"type": "array",
"items": {"type": "string"},
"description": "List of fields to return"
}
},
"required": ["client_id"],
"additionalProperties": False
},
"strict": True
},
# Business function: create an alert
{
"type": "function",
"name": "creer_alerte",
"description": "Create a follow-up alert for a contract or client",
"parameters": {
"type": "object",
"properties": {
"titre": {"type": "string"},
"priorite": {"type": "string", "enum": ["basse", "moyenne", "haute", "critique"]},
"description": {"type": "string"},
"assignee": {"type": "string"}
},
"required": ["titre", "priorite", "description", "assignee"],
"additionalProperties": False
},
"strict": True
}
]
# The model can now:
# 1. Search for a contract in File Search
# 2. Verify a clause on the web
# 3. Retrieve client info from the CRM
# 4. Create an alert if a problem is detected
response = client.responses.create(
model="gpt-5.6-terra",
input="Check if client CLI-42567's contract complies with the new European "
"standards. If not, create a high-priority alert.",
tools=tools
)
Manage source priority
Without instructions, the model treats both sources equally. This is rarely what you want: for internal facts—figures, procedures, decisions—your documents are authoritative and the web is just plausible noise; for news and regulations, the reverse is true. Write this hierarchy into the system prompt, and require that any conflict between the two be flagged rather than silently arbitrated.
response = client.responses.create(
model="gpt-5.6-terra",
instructions=(
"Source priority rules: "
"1. For internal facts (processes, policies, contracts) -> File Search "
"2. For external facts (laws, market, trends) -> Web Search "
"3. In case of conflict between internal and external sources, flag the gap "
"4. NEVER answer based solely on your own knowledge "
),
input="What is our standard payment term and is it compliant with the law?",
tools=[
{"type": "file_search", "vector_store_ids": [vs_id]},
{"type": "web_search"}
]
)
Complete pattern: hybrid search assistant
class AssistantRecherche:
def __init__(self, vector_store_ids: list[str]):
self.client = OpenAI()
self.vs_ids = vector_store_ids
self.historique = []
def rechercher(self, question: str, mode: str = "hybride") -> dict:
tools = []
if mode in ("hybride", "interne"):
tools.append({
"type": "file_search",
"vector_store_ids": self.vs_ids,
"max_num_results": 10
})
if mode in ("hybride", "web"):
tools.append({
"type": "web_search",
"search_context_size": "medium"
})
response = self.client.responses.create(
model="gpt-5.6-terra",
instructions="Always cite your sources. Clearly distinguish "
"internal information from public information.",
input=question,
tools=tools
)
resultat = {
"question": question,
"reponse": response.output_text,
"mode": mode,
"tokens": response.usage.total_tokens
}
self.historique.append(resultat)
return resultat
# Usage
assistant = AssistantRecherche(["vs_contrats", "vs_procedures"])
r = assistant.rechercher("Are our GTCs up to date with European law?")
print(r["reponse"])
Key takeaways
- Combine
file_searchandweb_searchin a single call - The model automatically chooses the right tool for each need
- The system prompt guides priority between internal and external sources
- Add custom functions for complete workflows
- Hybrid search is ideal for compliance audits and monitoring