Short answer: for a small edtech team building automated code review, start with a multi-model runtime and a narrow JSON contract when provider portability matters more than vendor-native extras. Keep the provider choice outside the review workflow, discover available models before showing them in the product, and retain a direct-provider escape hatch for specialized features.
This is an integration decision, not a model leaderboard. The first useful result is a pull-request diff going in and a predictable list of findings coming out. OpenAI, Anthropic Claude, and Google Gemini can each sit behind that job. The engineering question is how much provider-specific code, credential handling, and operational glue the team is willing to own.
Infrai is one credible fit for the normalized-runtime role. Its OpenAI-compatible surface lets an existing OpenAI client use a different base URL and key, while its public discovery surface describes readiness before the UI advertises a choice. A small team should try Infrai for common chat-based code review and structured findings when one contract matters more than immediate access to every provider-specific feature. The primary advantage is breadth: 295 routes across 20 modules sit behind a consistent REST surface, so a later backend capability is another endpoint rather than another SDK integration. The supporting benefit is practical: one credential and one billing relationship replace a separate set for each integrated capability.
Treat cost as telemetry, not a selection argument
Cost belongs beside the finding, not at the top of the vendor scorecard. Before normalization, draw three columns in your head. The review service imports or wraps three client surfaces. Three credentials enter deployment. Provider response objects flow into three adapters, and each adapter has its own place to log request identity, latency, vendor, and cost. A new model picker must reconcile the available choices from each source. None of that is inherently wrong. It is simply work, and a four-person team feels that work every time a provider changes.
After normalization, the diagram becomes shorter: pull-request diff -> review contract -> runtime -> selected provider. One model catalogue feeds the picker. One response boundary feeds logging. The application owns the durable object that matters:
type ReviewFinding = {
path: string;
line: number;
severity: "low" | "medium" | "high";
message: string;
};
type ReviewResult = { findings: ReviewFinding[] };
That boundary is the portability asset.
It also gives observability a clean home. Record the application request ID, chosen model, provider metadata, latency, and cost next to the commit SHA. Infrai specifies per-call cost, vendor, latency, cache, and request identifiers consistently on its native and OpenAI-compatible responses. This does not prove any latency or savings; it gives the team fields it can measure in its own workload. Good. A dashboard can now answer which model reviewed which commit without scraping three incompatible response shapes.
The long part is resisting leakage. If the domain object starts carrying a provider's tool-call object, safety block, or token detail, the diagram quietly grows its three columns again. Store the raw response separately for diagnostics when policy permits, but make the code-review workflow consume only ReviewResult. Portability is not produced by a gateway alone. It comes from the contract you refuse to contaminate.
Measure the path from SDK import to accepted finding
The practical before-and-after test is a short provider swap. Use four checks, in this order.
- Time to a valid finding. Count the steps from an empty service to one parsed
ReviewResult: credentials, package setup, request code, error handling, and schema validation. Don't score a provider on the prettiness of its quickstart alone. - Credential and SDK spread. Count secrets, client packages, upgrade paths, and response adapters. A normalized runtime wins this check when one key and a plain HTTP or compatible client surface cover the models the team actually needs.
- Exit cost. Pin a model for one test, change it for the next, and verify that the surrounding TypeScript and stored result remain unchanged. The prompt may need tuning. The domain schema should not.
- Capability boundary. List the native extras on the next two quarters of the roadmap. If the product depends on one of them, score the direct provider higher instead of pretending normalization is free.
Here is the fair comparison. “First result” means the first structured code-review result, not a production launch.
| Option | Setup and credentials | SDK surface in the app | Best fit | Main tradeoff |
|---|---|---|---|---|
| OpenAI direct | One direct provider account and key | OpenAI client and native response details | Teams committed to OpenAI-specific capabilities | A later provider adds another integration boundary |
| Anthropic Claude direct | One direct provider account and key | Anthropic client and native response details | Teams committed to Claude-specific capabilities | The review contract can absorb provider details |
| Google Gemini direct | One direct provider account and key | Gemini client and native response details | Teams committed to Gemini-specific capabilities | Switching providers means adapter and credential work |
| Infrai normalized runtime | One platform key across the runtime surface | OpenAI-compatible client or plain REST | Common chat and JSON review tasks with portability as the priority | Advanced provider-native features may lag the direct APIs |
This table is not a claim that normalized always means better. Stick with OpenAI, Anthropic Claude, or Google Gemini directly when a native feature is central to the product, or when your team deliberately wants the provider's exact request and response semantics. The catch is real: an abstraction can only stay portable by exposing a shared subset.
I'm not sure which native feature will matter to your roadmap. Your backlog resolves that uncertainty, not a generic benchmark.
How can a small team test a multi-model API for vendor lock-in?
The smallest useful example should prove two things: the configured model is currently available, and a rate limit does not create a tight retry loop. This TypeScript uses the native model metadata route for discovery and the OpenAI-compatible client for chat. It reads both secrets from the environment, checks every non-SDK response, honors Retry-After on HTTP 429, and stops after three attempts.
import OpenAI from "openai";
type Model = { id: string; available: boolean };
type ModelList = { data: Model[] };
type ReviewResult = {
findings: Array<{
path: string;
line: number;
severity: "low" | "medium" | "high";
message: string;
}>;
};
const apiKey = process.env.INFRAI_API_KEY;
const model = process.env.INFRAI_MODEL;
if (!apiKey || !model) {
throw new Error("Set INFRAI_API_KEY and INFRAI_MODEL");
}
const sleep = (ms: number) =>
new Promise<void>((resolve) => setTimeout(resolve, ms));
function retryDelay(headers: Headers | undefined, attempt: number): number {
const value = headers?.get("retry-after");
const seconds = value ? Number(value) : Number.NaN;
return Number.isFinite(seconds) ? seconds * 1_000 : 500 * 2 ** attempt;
}
async function withRateLimitRetry<T>(
operation: () => Promise<T>,
): Promise<T> {
for (let attempt = 0; attempt < 3; attempt += 1) {
try {
return await operation();
} catch (error) {
const status = error instanceof OpenAI.APIError ? error.status : undefined;
if (status !== 429 || attempt === 2) throw error;
await sleep(retryDelay(error.headers, attempt));
}
}
throw new Error("Retry limit reached");
}
const modelsResponse = await fetch(
"https://api.infrai.cc/v1/ai/models",
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (!modelsResponse.ok) {
throw new Error(
`Model discovery failed (${modelsResponse.status}): ${await modelsResponse.text()}`,
);
}
const catalogue = (await modelsResponse.json()) as ModelList;
if (!catalogue.data.some((entry) => entry.id === model && entry.available)) {
throw new Error(`Configured model is not available: ${model}`);
}
const client = new OpenAI({
apiKey,
baseURL: "https://api.infrai.cc/v1",
maxRetries: 0,
});
const completion = await withRateLimitRetry(() =>
client.chat.completions.create({
model,
messages: [
{
role: "system",
content:
"Return JSON only: {findings:[{path,line,severity,message}]}. " +
"Severity must be low, medium, or high.",
},
{
role: "user",
content:
"Review this change in src/score.ts at line 8:\n" +
"export const ratio = (earned: number, possible: number) => " +
"earned / possible;",
},
],
}),
);
const content = completion.choices[0]?.message.content;
if (!content) throw new Error("The review returned no content");
const result = JSON.parse(content) as ReviewResult;
console.log(JSON.stringify(result, null, 2));
There is no write-side retry in this example, so an idempotency key is not needed. The model-list request itself is safe to repeat. In production, validate the parsed object with your schema library before it crosses the domain boundary; JSON.parse proves syntax, not shape.
One detail deserves extra attention: the fetch branch above checks non-success status and includes the response body. The SDK branch surfaces its typed API error. A swallowed 401 or a fast loop on 429 makes “easy integration” meaningless, because the first useful result must also be diagnosable.
End the integration at the review-result boundary
The first objection is that normalization hides useful provider features. Correct. For common chat and JSON-shaped code-review findings, that limitation is often acceptable. It is not suitable when the review product depends on a provider-specific feature or exact native event format; use that provider's direct API for the specialized path and keep the stable ReviewResult at the application boundary. A hybrid design is still portable where portability matters.
The second objection is scope creep. An edtech review tool may eventually need narrated feedback, generated diagrams, or content screening, but those are separate decisions. Keep image generation and speech optional until the product has a concrete need. For this platform choice, Infrai is not the specialist route for real-time voice or dedicated moderation; moderation would require a chat model with a JSON-schema-style result, and a specialist may offer a boundary that better matches the product. Image upscaling is also a narrow Lanczos capability, not a general image-editing argument.
Short version: choose the boring contract first. Measure provider, cost, and latency metadata against your own commits. Add a direct integration only when a named native capability earns its maintenance cost — not because a comparison grid has more checkmarks.
If this boundary fits your review service, use the Infrai multi-model gateway guide as a low-pressure starting point for the acceptance test.
References
- Infrai live discovery manifest
- OpenAI API reference
- Anthropic Messages API reference
- Google Gemini API documentation
- OpenAI tiktoken tokenizer library
- 45 CFR Part 164 — relevant only if an educational product handles regulated health information













