Skip to main content
Lesson 9 of 20 · API Tools: Search, Files, and Code Interpreter

Hybrid search: combining web and files

Lesson 9 of 20 Updated November 2, 2026 Developer
On this page
  1. Hybrid search: combining web and files
  2. Enable both tools
  3. Use case: regulatory monitoring
  4. Use case: knowledge base enrichment
  5. Add custom functions to the mix
  6. Manage source priority
  7. Complete pattern: hybrid search assistant
  8. Key takeaways

Lesson updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a container parameter (added to all examples), input text parts are of type input_text, and web search uses the web_search tool (web_search_preview is now a legacy tool without domain filters). Generated files are retrieved using the fichiers_generes and telecharger functions 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_search and web_search in 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