# Tool and Connector Context

Course: [Context Engineering](https://www.devtutorials.ai/learn/context-engineering) · Section: Memory, Media and Tools
Source: https://www.devtutorials.ai/learn/context-engineering/memory-media-tools/tool-connector-context

> How tool calls work, why tool definitions and results are context, how to check arguments and shape results with status, source and time, and how tools, APIs and connectors differ.

When a model calls a tool (like a calculator or a database), the tool gives back a result. That result must go into the context for the next step. But raw results from tools are often messy: too much data, unclear labels, wrong format.

You need to **shape** the result: pick the important parts, label them clearly, and put them in a form the model understands.

**The main idea:** when a tool returns data, clean it up, label it, put it in the right place, and tell the model what it means.

## A Real-World Example

Think about a weather app.

- You ask: "Is it raining?"
- The app calls a weather service.
- The service returns: thousands of data points (temperature every minute, wind gusts, UV index, etc).
- The app shows you: "Rain chance: 60% for the next 3 hours."

The service did the work. The app shaped the answer so you understand it.

**In simple words:** tools do the work, but you shape the answer.

## What You Will Learn

In this lesson, you will learn:

1. How the tool loop works: ask, call, get result, shape it, send to model.
2. What a tool definition looks like, and what arguments the model needs.
3. How to check arguments before the tool runs.
4. How to shape results so the model understands them.
5. How to format results from databases, APIs, and other sources.
6. How shaping saves time and tokens.

This lesson goes with Lesson 11 in the course. Lesson 11 shows the tool loop from the model's side. This lesson shows it from the system's side.

## The Tool Loop

### Step 1: Tool Definition

Tell the model what tools it can use and what they do:

```js runnable title="A tool definition the model understands"
const toolDef = {
  name: "export_status",
  description: "Get the status of an export job",
  parameters: {
    required: ["job_id"],
    properties: {
      job_id: { type: "string", description: "The export job ID" },
    },
  },
};

console.log("Tool: " + toolDef.name);
console.log("Description: " + toolDef.description);
console.log("Needs: " + toolDef.parameters.required.join(", "));
```

Expected output:

```text
Tool: export_status
Description: Get the status of an export job
Needs: job_id
```

The model reads this and says: "I can call export_status if I need to know the status of a job."

### Step 2: Model Asks

The model looks at the context and decides it needs to call a tool. It sends back a request:

```
Tool call: export_status
Arguments: { "job_id": "EXP-12345" }
```

### Step 3: Call the Tool

Your system receives the call, checks the arguments, and calls the tool:

```js runnable title="Call the tool with the arguments"
function exportStatus(jobId) {
  return {
    job_id: jobId,
    status: "complete",
    rows: 15000,
    file_url: "https://example.com/exp-12345.csv",
    created_at: "2026-01-15T10:30:00Z",
  };
}

const result = exportStatus("EXP-12345");
console.log("Tool returned:");
console.log(JSON.stringify(result, null, 2));
```

Expected output:

```text
Tool returned:
{
  "job_id": "EXP-12345",
  "status": "complete",
  "rows": 15000,
  "file_url": "https://example.com/exp-12345.csv",
  "created_at": "2026-01-15T10:30:00Z"
}
```

The tool did its job. Now the result must be shaped.

### Step 4: Shape the Result

Clean up the result, add labels, and remove what the model doesn't need:

```js runnable title="Shape a tool result for the model"
const raw = {
  job_id: "EXP-12345",
  status: "complete",
  rows: 15000,
  file_url: "https://example.com/exp-12345.csv",
  created_at: "2026-01-15T10:30:00Z",
  internal_id: "5f8a9c3e",
  cached: false,
  retry_count: 0,
};

const shaped = {
  status: "complete",
  rows: 15000,
  file_url: "https://example.com/exp-12345.csv",
};

console.log("Raw result size:", JSON.stringify(raw).length, "chars");
console.log("Shaped result size:", JSON.stringify(shaped).length, "chars");
console.log("Shaped result:", JSON.stringify(shaped));
```

Expected output:

```text
Raw result size: 192 chars
Shaped result size: 98 chars
Raw result: {"status":"complete","rows":15000,"file_url":"https://example.com/exp-12345.csv"}
```

Half the size. The model gets the facts it needs: status, row count, and the file link. The system details (internal_id, cache flag, retry count) are gone.

### Step 5: Send to Model

Put the shaped result in the context for the next step:

```
Tool result: export_status(EXP-12345)
{
  "status": "complete",
  "rows": 15000,
  "file_url": "https://example.com/exp-12345.csv"
}
```

Now the model reads this and says, "The export is done, 15,000 rows, here's the link to download."

## Tool Definitions

### Schema Format

A tool definition uses a **schema** to describe what it does and what arguments it needs:

```js runnable title="A complete tool definition"
const tools = [
  {
    name: "invoice_lookup",
    description: "Get an invoice by customer ID",
    parameters: {
      type: "object",
      required: ["customer_id"],
      properties: {
        customer_id: {
          type: "string",
          description: "The customer's ID, like C-1042",
        },
        include_items: {
          type: "boolean",
          description: "Include line items (default: false)",
        },
      },
    },
  },
];

console.log("Tool: " + tools[0].name);
console.log("Required arguments: " + tools[0].parameters.required.join(", "));
console.log("Optional arguments: include_items");
```

Expected output:

```text
Tool: invoice_lookup
Required arguments: customer_id
Optional arguments: include_items
```

### Argument Validation

Before calling a tool, check that the arguments are right:

```js runnable title="Validate arguments before the tool runs"
const call = { tool: "invoice_lookup", args: { customer_id: "C-1042", include_items: true } };

function validate(toolCall) {
  if (toolCall.tool === "invoice_lookup") {
    if (!toolCall.args.customer_id) return "error: customer_id is required";
    if (typeof toolCall.args.customer_id !== "string") return "error: customer_id must be a string";
  }
  return "valid";
}

console.log("Validation:", validate(call));
console.log("Safe to call:", call.tool + "(" + call.args.customer_id + ")");
```

Expected output:

```text
Validation: valid
Safe to call: invoice_lookup(C-1042)
```

Bad arguments are caught before the tool runs.

A tool that can change or delete things must never run just because the model asked. Check the arguments, check the user's permissions, and ask a person to approve risky actions.

## Shaping Results

### From Databases

A database query might return many columns. Shape it to the few the model needs:

```js runnable title="Get a row, keep only what matters"
const rawRow = {
  customer_id: "C-1042",
  name: "Alice Smith",
  email: "alice@example.com",
  phone: "555-1234",
  internal_flags: "VIP,nomail,audit",
  created_at: "2020-01-15T08:00:00Z",
  updated_at: "2026-01-14T16:30:00Z",
  row_version: 42,
  deleted: false,
};

const shaped = {
  customer_id: "C-1042",
  name: "Alice Smith",
  email: "alice@example.com",
};

console.log("Raw size:", JSON.stringify(rawRow).length, "chars");
console.log("Shaped size:", JSON.stringify(shaped).length, "chars");
console.log("Shaped result:", JSON.stringify(shaped));
```

Expected output:

```text
Raw size: 252 chars
Shaped size: 73 chars
Shaped result: {"customer_id":"C-1042","name":"Alice Smith","email":"alice@example.com"}
```

The system fields (row_version, deleted, created_at, updated_at, flags) are dropped. The model gets only name, email, and ID.

### From APIs

An API might return lots of metadata. Pull out the useful data:

```js runnable title="Shape an API response"
const apiResponse = {
  status_code: 200,
  request_id: "req-5f8a9c3e",
  timestamp: "2026-01-15T10:30:00Z",
  cache_hit: false,
  latency_ms: 145,
  data: {
    plan: "Pro",
    monthly_cost: 20,
    seats: 5,
  },
};

const shaped = apiResponse.data;

console.log("Raw response keys:", Object.keys(apiResponse).join(", "));
console.log("Shaped response keys:", Object.keys(shaped).join(", "));
console.log("What the model sees:", JSON.stringify(shaped));
```

Expected output:

```text
Raw response keys: status_code, request_id, timestamp, cache_hit, latency_ms, data
Shaped response keys: plan, monthly_cost, seats
What the model sees: {"plan":"Pro","monthly_cost":20,"seats":5}
```

The metadata (status, latency, cache info) is system info. The model only needs the data.

### Labeling Results

Add labels so the model knows what the data is:

```js runnable title="Add labels to shaped results"
const shaped = {
  status: "complete",
  rows: 15000,
  file_url: "https://example.com/exp-12345.csv",
};

const labeled = {
  source: "export_status",
  tool_call: "export_status(EXP-12345)",
  result: shaped,
  fetched_at: "2026-01-15T10:30:00Z",
};

console.log("<tool_result source=\"export_status\">");
console.log(JSON.stringify(labeled.result));
console.log("</tool_result>");
```

Expected output:

```text
<tool_result source="export_status">
{"status":"complete","rows":15000,"file_url":"https://example.com/exp-12345.csv"}
</tool_result>
```

Now the model sees: "This is a tool result, from export_status, and this is the data."

## Formatting Common Results

### Search Results

A search returns many results with scores. Shape it to the top items:

```js runnable title="Ranked search results"
const allResults = [
  { id: "doc-1", score: 0.95, title: "Free plan limits" },
  { id: "doc-2", score: 0.91, title: "Pro plan pricing" },
  { id: "doc-3", score: 0.78, title: "API keys" },
  { id: "doc-4", score: 0.42, title: "Company history" },
];

const topK = 2;
const shaped = allResults.slice(0, topK).map((r) => ({ id: r.id, title: r.title }));

console.log("Returned " + allResults.length + " results, kept top " + topK);
for (const r of shaped) {
  console.log("- " + r.id + ": " + r.title);
}
```

Expected output:

```text
Returned 4 results, kept top 2
- doc-1: Free plan limits
- doc-2: Pro plan pricing
```

The scores are gone. The model gets the best docs, not all of them.

### Time Series Data

A time series (like daily sales) might have many data points. Shape it:

```js runnable title="Summarize time series data"
const timeSeries = [
  { date: "2026-01-01", sales: 1200 },
  { date: "2026-01-02", sales: 1250 },
  { date: "2026-01-03", sales: 1180 },
  { date: "2026-01-04", sales: 1400 },
  { date: "2026-01-05", sales: 1350 },
];

const summary = {
  period: "2026-01-01 to 2026-01-05",
  total: timeSeries.reduce((sum, d) => sum + d.sales, 0),
  average: Math.round(timeSeries.reduce((sum, d) => sum + d.sales, 0) / timeSeries.length),
  min: Math.min(...timeSeries.map((d) => d.sales)),
  max: Math.max(...timeSeries.map((d) => d.sales)),
};

console.log("Time series summary:");
for (const [key, value] of Object.entries(summary)) {
  console.log(key + ": " + value);
}
```

Expected output:

```text
Time series summary:
period: 2026-01-01 to 2026-01-05
total: 6380
average: 1276
min: 1180
max: 1400
```

Instead of 5 data points, the model gets: total, average, min, max. Much smaller, much clearer.

### List Results

If a tool returns a list, limit the size and pick the fields:

```js runnable title="Shape a list result"
const rawList = [
  { id: "user-1", name: "Alice", email: "alice@example.com", phone: "555-1234", role: "admin", status: "active" },
  { id: "user-2", name: "Bob", email: "bob@example.com", phone: "555-5678", role: "user", status: "active" },
  { id: "user-3", name: "Carol", email: "carol@example.com", phone: "555-9012", role: "user", status: "inactive" },
];

const shaped = rawList.slice(0, 2).map((u) => ({ id: u.id, name: u.name, role: u.role }));

console.log("Returned " + rawList.length + " users, shaped " + shaped.length);
for (const u of shaped) {
  console.log("- " + u.name + " (" + u.role + ")");
}
```

Expected output:

```text
Returned 3 users, shaped 2
- Alice (admin)
- Bob (user)
```

Limit to the top 2. Drop email and phone (not needed now). Keep id, name, role.

## Status and Source Labels

### Status Labels

Mark the status of the result so the model knows if it's complete:

```js runnable title="Mark status on tool results"
const results = [
  { status: "success", data: { rows: 1000 } },
  { status: "error", error: "Invalid customer ID" },
  { status: "timeout", error: "Tool took longer than 5 seconds" },
  { status: "empty", data: [] },
];

for (const r of results) {
  console.log(r.status.toUpperCase() + ": " + (r.data ? "result ready" : r.error));
}
```

Expected output:

```text
SUCCESS: result ready
ERROR: Invalid customer ID
TIMEOUT: Tool took longer than 5 seconds
EMPTY: result ready
```

Success, error, timeout, empty. The model reads the status and reacts.

### Source and Time Labels

Mark where the data came from and when:

```js runnable title="Label the source and time"
const labeled = {
  source: "database",
  tool: "customer_lookup",
  call_args: { customer_id: "C-1042" },
  fetched_at: "2026-01-15T10:30:00Z",
  cached: false,
  result: { plan: "Pro", status: "active" },
};

console.log("Source: " + labeled.source);
console.log("Tool: " + labeled.tool);
console.log("Fetched at: " + labeled.fetched_at);
console.log("Result: " + JSON.stringify(labeled.result));
```

Expected output:

```text
Source: database
Tool: customer_lookup
Fetched at: 2026-01-15T10:30:00Z
Result: {"plan":"Pro","status":"active"}
```

Now the model knows: this came from the database via customer_lookup, just now, and it's not a cached result.

**Easy Way to Remember:** every tool result needs **status, source, time, and only the fields that matter**.

## Optimization: Parallel Calls

When you call many tools at once (in parallel), you can return results faster:

```js runnable title="Time saved with parallel tool calls"
function timeSerial(calls) {
  return calls.length * 100; // 100 ms per call
}

function timeParallel(calls) {
  return 100; // All at once = same time as 1 call
}

const calls = [
  { tool: "export_status", args: { job_id: "EXP-1" } },
  { tool: "invoice_lookup", args: { customer_id: "C-1" } },
  { tool: "customer_lookup", args: { customer_id: "C-1" } },
];

console.log("Serial: " + timeSerial(calls) + " ms");
console.log("Parallel: " + timeParallel(calls) + " ms");
console.log("Speedup: " + (timeSerial(calls) / timeParallel(calls)) + "x faster");
```

Expected output:

```text
Serial: 300 ms
Parallel: 100 ms
Speedup: 3x faster
```

Running 3 tools one at a time takes 300 ms. Running them together takes 100 ms. The user waits 3x less.

## Comparing Tools vs APIs vs Connectors

A **tool** is a function your system knows: export_status, customer_lookup.

An **API** is outside: weather service, payment processor, translation API.

A **connector** is a bridge to outside systems: your database, a Salesforce account, a Google Workspace.

All three return results. Shape them the same way:

```js runnable title="Tools, APIs, connectors: shape them all"
const sources = [
  { type: "tool", name: "export_status", result: { status: "complete", rows: 1000 } },
  { type: "api", name: "weather", result: { temp: 72, condition: "sunny" } },
  { type: "connector", name: "salesforce", result: { lead_id: "L-42", stage: "qualified" } },
];

for (const s of sources) {
  console.log(s.type.toUpperCase() + " (" + s.name + "): " + JSON.stringify(s.result));
}
```

Expected output:

```text
TOOL (export_status): {"status":"complete","rows":1000}
API (weather): {"temp":72,"condition":"sunny"}
CONNECTOR (salesforce): {"lead_id":"L-42","stage":"qualified"}
```

One result, three sources. Shape them the same way so the model treats them the same.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Raw results | "Send the whole response, the model will pick what it needs." | Shape it, keep user data only. | The model gets confused by system fields. |
| No status | "The model will assume it worked." | Mark success, error, timeout, empty. | Wrong results are used as if they were right. |
| All results | "More results are better." | Return top-k items. | Key data is buried in weak results. |
| Serial calls | "One tool at a time is safer." | Call parallel when safe. | The user waits much longer. |
| No labels | "Where it came from is obvious." | Label source, status, time. | The model doesn't know if data is fresh or old. |
| Time series all points | "Show all data." | Summarize: average, min, max. | The model gets lost in hundreds of points. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- **Tool loop**: define, call, get result, shape it, send to model.
- **Shape**: drop system fields, keep user data, label source and status.
- **Validate** arguments before tools run.
- **Limit** results: top-k items, not all.
- **Summarize** time series: total, average, min, max.
- **Parallel calls** are faster than serial calls.

Next: **Caching and Cost**, how to reuse work and cut expenses.
