Tool and Connector Context

By the Syncfusion DevTutorials team · Last updated

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

The model asks for a tool. The app checks the call, runs it, shapes the result, and adds it to the context. Then the model goes again.

Step 1: Tool Definition

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

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:

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:

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:

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:

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:

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:

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:

Tool: invoice_lookup
Required arguments: customer_id
Optional arguments: include_items

Argument Validation

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

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:

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:

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:

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:

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:

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:

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:

<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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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

MistakeWrong ideaCorrect ideaWhat 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.