OVHcloud AI Endpoints

Use Context212 search as the retrieval layer for models hosted on OVHcloud AI Endpoints.

OVHcloud AI Endpoints expose hosted open models (Qwen, Mistral, Llama, gpt-oss, and others) through an OpenAI-compatible endpoint, served from OVHcloud's European infrastructure. Pair them with Context212 search to build RAG pipelines where the retrieval stays on Context212's infrastructure and the generation runs on OVHcloud.

The flow is:

  1. Search Context212 for the passages most relevant to the user's question.
  2. Pack those passages into the model's context window.
  3. Call the OVHcloud model to generate an answer grounded in the retrieved content.

Prerequisites

  • A C212_API_KEY, available in the Console → API Keys section.
  • An OVHcloud AI Endpoints access token, available in the OVHcloud Control Panel under Public Cloud → AI Endpoints. Store it as OVH_AI_ENDPOINTS_ACCESS_TOKEN.
  • At least one workspace with indexed documents on Context212.

Installation

npm install openai

The openai package is used here only for its client; OVHcloud's /chat/completions endpoint is fully compatible with it.

Full example

import OpenAI from "openai";

const C212_API_KEY = process.env.C212_API_KEY!;
const OVH_AI_ENDPOINTS_ACCESS_TOKEN = process.env.OVH_AI_ENDPOINTS_ACCESS_TOKEN!;

const ovh = new OpenAI({
  baseURL: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
  apiKey: OVH_AI_ENDPOINTS_ACCESS_TOKEN,
});

async function search(
  query: string,
  workspaceId?: number[],
  maxResults = 5,
): Promise<Record<string, unknown>[]> {
  const payload: Record<string, unknown> = { query, max_results: maxResults };
  if (workspaceId) payload.workspace_id = workspaceId;

  const response = await fetch("https://api.context212.com/api/v1/search", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${C212_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
  if (!response.ok) throw new Error(`Search failed: ${response.status}`);
  return (await response.json()).results;
}

async function answer(
  question: string,
  workspaceId?: number[],
  model = "Meta-Llama-3_3-70B-Instruct",
): Promise<string> {
  const results = await search(question, workspaceId);

  const context = results
    .filter((r: any) => r.content)
    .map(
      (r: any) =>
        `[${r.source.filename}, p.${r.source.page_start}]\n${r.content}`,
    )
    .join("\n\n");

  const completion = await ovh.chat.completions.create({
    model,
    messages: [
      {
        role: "system",
        content:
          "You are a helpful assistant. Answer the user's question using only " +
          "the provided context. If the context does not contain enough information, " +
          "say so.\n\nContext:\n" +
          context,
      },
      { role: "user", content: question },
    ],
  });
  return completion.choices[0].message.content ?? "";
}

console.log(await answer("What is our data retention policy?"));

Context212 search as a tool

Instead of always searching before calling the model, you can expose Context212 search as a tool and let the model decide when to call it. The model issues an context212_search tool call when it needs context; your code executes the search and feeds the results back; the model then produces a final answer.

import OpenAI from "openai";

const C212_API_KEY = process.env.C212_API_KEY!;
const OVH_AI_ENDPOINTS_ACCESS_TOKEN = process.env.OVH_AI_ENDPOINTS_ACCESS_TOKEN!;

const ovh = new OpenAI({
  baseURL: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
  apiKey: OVH_AI_ENDPOINTS_ACCESS_TOKEN,
});

const SEARCH_TOOL = {
  type: "function" as const,
  function: {
    name: "context212_search",
    description:
      "Search the company knowledge base for passages relevant to a query. " +
      "Returns ranked excerpts with their source filename and page numbers.",
    parameters: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Natural-language search query.",
        },
        max_results: {
          type: "integer",
          description: "Number of passages to return (1–50, default 5).",
          default: 5,
        },
      },
      required: ["query"],
    },
  },
};

async function runSearch(query: string, maxResults = 5): Promise<string> {
  const response = await fetch("https://api.context212.com/api/v1/search", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${C212_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, max_results: maxResults }),
  });
  if (!response.ok) throw new Error(`Search failed: ${response.status}`);
  const results = (await response.json()).results;
  const passages = results
    .filter((r: any) => r.content)
    .map(
      (r: any) =>
        `[${r.source.filename}, p.${r.source.page_start}]\n${r.content}`,
    );
  return passages.length ? passages.join("\n\n") : "No results found.";
}

async function answer(
  question: string,
  model = "Meta-Llama-3_3-70B-Instruct",
): Promise<string> {
  const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
    { role: "user", content: question },
  ];

  while (true) {
    const completion = await ovh.chat.completions.create({
      model,
      tools: [SEARCH_TOOL],
      messages,
    });
    const choice = completion.choices[0];

    if (choice.finish_reason === "tool_calls") {
      messages.push(choice.message);
      for (const call of choice.message.tool_calls ?? []) {
        const args = JSON.parse(call.function.arguments);
        const result = await runSearch(args.query, args.max_results ?? 5);
        messages.push({
          role: "tool",
          tool_call_id: call.id,
          content: result,
        });
      }
    } else {
      return choice.message.content ?? "";
    }
  }
}

console.log(await answer("What is our data retention policy?"));

The loop handles the case where the model issues multiple search calls in sequence before producing a final answer.

Scoping retrieval to a workspace

Pass workspace_id to limit search to a specific workspace. This is useful in multi-tenant products where each customer's data lives in a dedicated workspace.

await answer("Summarize the onboarding checklist", [42]);

Choosing a model

OVHcloud's catalog includes several hosted models. Pass the model name to the model parameter:

ModelNotes
Meta-Llama-3_3-70B-InstructStrong reasoning, good default choice
Llama-3.1-8B-InstructFaster and cheaper, suitable for simpler queries
Mistral-Small-3.2-24B-Instruct-2506Compact Mistral, low latency
Qwen3-32BStrong multilingual reasoning model
Qwen2.5-VL-72B-InstructVision-language model, accepts image input

You can list the models available to your token at any time:

console.log((await ovh.models.list()).data.map((m) => m.id));

Check the OVHcloud AI Endpoints documentation for the current model list and regional availability.

Streaming responses

OVHcloud's endpoint supports streaming. Enable it by passing stream: true and iterating over the response:

const stream = await ovh.chat.completions.create({
  model: "Meta-Llama-3_3-70B-Instruct",
  messages: [/* ... */],
  stream: true,
});

for await (const chunk of stream) {
  const delta = chunk.choices[0].delta.content;
  if (delta) process.stdout.write(delta);
}

A note on reasoning models

Some OVHcloud models (for example Qwen3-32B and Qwen3.6-27B) are reasoning models. When called through the raw HTTP API they may return their chain of thought under a reasoning field and the final answer under content. The openai client used in the examples above surfaces the final answer in choices[0].message.content as usual, so no special handling is needed; read message.reasoning only if you want to inspect the thinking trace.

On this page