A task contract for giving OpenAI agents reliable web tools

When an OpenAI-powered agent needs current information, the model call is only one part of the system. The agent may also need to search the web, retrieve pages, extract structured fields, render JavaScript, or interact with a browser.

A common implementation starts by connecting one search or scraping API directly to a function tool. This works until the provider changes its response shape, misses a required capability, exceeds the latency budget, or needs a fallback.

The problem is not only provider reliability. The agent and the external API may disagree about what successful completion means. An HTTP 200 response can contain too few search results, stale pages, invalid URLs, incomplete content, or fields that do not match the tool schema.

A small task contract between the agent and the provider adapter makes these failures explicit.

```json
{
  "capability": "web_search",
  "input": {
    "query": "current browser automation API pricing"
  },
  "constraints": {
    "language": "en",
    "freshness_days": 30
  },
  "output": {
    "required_fields": ["title", "url", "snippet"]
  },
  "validation": {
    "minimum_results": 5,
    "require_https": true
  },
  "budgets": {
    "maximum_latency_ms": 3000,
    "maximum_cost_usd": 0.01,
    "maximum_attempts": 2
  }
}
```

The OpenAI tool definition can remain stable while provider-specific adapters translate this contract into native request formats. Each adapter maps its response back to a common envelope:

```json
{
  "status": "satisfied",
  "data": [],
  "validation": {
    "result_count": 8,
    "invalid_urls": 0
  },
  "usage": {
    "latency_ms": 842,
    "cost_usd": 0.003
  },
  "attempt": {
    "provider": "provider-a",
    "number": 1
  }
}
```


The numbers above are illustrative. The important part is the boundary: the model chooses the task, while deterministic code decides whether the external tool satisfied it.

Keep capabilities separate

“Search the web” and “extract these fields from this page” should not be aliases for the same generic web tool. They have different inputs, outputs, and failure conditions.

A search task may require a minimum number of recent results with valid source URLs. An extraction task may require fields that pass a JSON Schema. A browser task may need proof that a particular page state was reached.

Separating these capabilities also prevents misleading provider comparisons. Two APIs may both advertise search, but one returns ranked links, another generates a cited answer, and another returns full page content. The correct choice depends on the contract the agent needs to satisfy.

Validate the result, not the request

Transport success is useful operational data, but it is not task success. Validation should happen before the result returns to the agent.

For web search, validation might check result count, URL format, domain constraints, freshness, and required fields. For scraping, it might check content length, title presence, clean Markdown, or whether JavaScript-rendered content appeared. For extraction, schema validation should run in code rather than asking the model to decide whether its own input was valid.

When validation fails, return a structured reason such as `too_few_results`, `schema_mismatch`, `stale_sources`, or `budget_exceeded`. This gives the agent more useful information than a generic exception.

Make fallbacks capability-aware

A fallback should not mean “try the next API in a static list.” The next provider must support the same capability and output contract.

The routing layer can filter candidates using four questions:

1. Does the provider support the requested capability?
2. Has it passed the relevant validation corpus?
3. Does it fit the latency and cost budgets?
4. Can it return the required output without weakening the contract?

The retry loop should also have a stopping condition. Maximum attempts, cumulative latency, and cumulative cost belong in the task contract so that a failing tool cannot consume an open-ended budget.

Measure providers with fixed tasks

Provider selection improves when the evaluation corpus uses the same versioned tasks for every eligible provider. Keep quality, latency, and cost visible as separate measurements. A composite score can help with sorting, but it should not hide missing capabilities or failed validation cases.

We use this approach in NativePort’s [public evaluation methodology]( How we measure — NativePort ), which documents task corpora, grading, normalization, confidence rules, and evaluation versioning.

The pattern does not depend on a particular agent framework. It applies anywhere an OpenAI model selects tools and external systems perform the work. The useful design boundary is between probabilistic task selection and deterministic result validation.

How are people representing these requirements in their OpenAI agent systems today: separate function tools, JSON Schema metadata, or instructions embedded in the prompt?

Disclosure: I am affiliated with NativePort. This topic was prepared with AI assistance and reviewed by a human for technical accuracy and source quality.