---
title: Agents and LLMs
url: https://docs.schemalabs.ai/integrations/agents
description: Wrap a quick run or an endpoint as a tool for agent frameworks and function calling, and narrate the bundle with your own LLM.
---

# Agents and LLMs

> Wrap a quick run or an endpoint as a tool for agent frameworks and function calling, and narrate the bundle with your own LLM.

An endpoint is a stable REST microservice with its own OpenAPI description at `https://api.schemalabs.ai/v2/openapi/{endpoint_id}` ([reference](https://docs.schemalabs.ai/api-reference/endpoints#endpoint-openapi)): POST data, get the full bundle. That is the universal path for agent frameworks, function calling, and LLM tool wrappers. A stateless quick run (`POST /v2/run`) is the same call without state, for agents that meet a new source mid-task with nobody available to explain it.

## The pattern

A data agent wraps the endpoint's REST API; the orchestrator routes data tasks to it; it returns compact JSON (the bundle) that flows to reasoning and action agents. The agent reasons over structured JSON, not raw rows: sector, column profile, entity matches with confidences, filled values, predictions with probabilities.

The lifecycle: **quick run** to understand a new source (nothing persists), **create an endpoint** when the same understanding should persist and carry a held-out score (a vertical product typically creates one per customer at onboarding), **refresh and upgrade** to keep it current. Branch on confidence at every step: act above your threshold, queue the rest for review, and mask columns flagged `pii` before they leave the agent.

```text
 orchestrator ──► data agent ──► POST /v2/serve/:id  (or /v2/run)
                       ▲                │
                       └── the bundle ◄─┘
                           sector · profile · map · imputation · predictions
```

## Function-calling tool definition

Give your LLM one tool. The schema below is what the model needs to call `run`; for a live endpoint, fix the URL and drop `base`.

```json
{
  "name": "schema_run",
  "description": "Sector, roles, PII, matches, imputation, prediction on raw tables.",
  "parameters": {
    "type": "object",
    "properties": {
      "tables": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "id": { "type": "string" },
            "columns": { "type": "array", "items": { "type": "string" } },
            "rows": { "type": "array", "items": { "type": "array" } }
          },
          "required": ["id", "columns", "rows"]
        }
      },
      "target": {
        "type": "object",
        "properties": { "mode": { "enum": ["auto", "none"] },
                        "column": { "type": "string" } }
      }
    },
    "required": ["tables"]
  }
}
```

The tool implementation is a `POST /v2/run` with the arguments as the body and `Authorization: Bearer $SCHEMA_API_KEY`. Return the bundle (or a trimmed view of it) as the tool result.

## Frameworks

| Framework | How |
|---|---|
| LLM function calling, any provider | The tool definition above; the tool body calls the API |
| LangChain, LlamaIndex, CrewAI, AutoGen | Wrap the endpoint as a tool from its OpenAPI spec (`https://api.schemalabs.ai/v2/openapi/{endpoint_id}`); observability layers such as LangSmith trace it like any tool call |
| MCP-style tool servers | Expose `run` and `serve` as tools; the bundle is the tool result |

## Narration

Pass the bundle to your LLM with the endpoint's `system_prompts` (active fragments, in order) as the system prompt; read them from the endpoint object (`GET /v2/endpoints/:id`, which needs the `read` scope; a `serve`-only key covers the serve call alone). See [System prompts](https://docs.schemalabs.ai/endpoints#system-prompts).

Python:

```python
import os, json, requests

SCHEMA_API_KEY = os.environ["SCHEMA_API_KEY"]
HEADERS = {"Authorization": f"Bearer {SCHEMA_API_KEY}", "Content-Type": "application/json"}
EP_ID = "{endpoint_id}"
SERVE_URL = f"https://api.schemalabs.ai/v2/serve/{EP_ID}"
# llm.chat(): your own model call

bundle = requests.post(SERVE_URL, headers=HEADERS, json={"tables": tables}).json()
ep_url = f"https://api.schemalabs.ai/v2/endpoints/{EP_ID}"
ep = requests.get(ep_url, headers=HEADERS).json()
fragments = sorted(ep["system_prompts"], key=lambda f: f["order"])
system = "\n\n".join(f["body"] for f in fragments if f["active"])
user = f"Question: {q}\n\nSchema bundle:\n{json.dumps(bundle)}"
answer = llm.chat(system=system, user=user)
```
JavaScript:

```javascript
const SCHEMA_API_KEY = process.env.SCHEMA_API_KEY;
const HEADERS = { Authorization: `Bearer ${SCHEMA_API_KEY}`, 'Content-Type': 'application/json' };
const EP_ID = '{endpoint_id}';
const SERVE_URL = `https://api.schemalabs.ai/v2/serve/${EP_ID}`;
// llm.chat(): your own model call

const res = await fetch(SERVE_URL, { method: 'POST', headers: HEADERS,
  body: JSON.stringify({ tables }) });
const bundle = await res.json();
const epUrl = `https://api.schemalabs.ai/v2/endpoints/${EP_ID}`;
const ep = await (await fetch(epUrl, { headers: HEADERS })).json();
const system = ep.system_prompts.filter(f => f.active)
  .sort((a, b) => a.order - b.order).map(f => f.body).join('\n\n');
const answer = await llm.chat({ system,
  user: `Question: ${q}\n\nSchema bundle:\n${JSON.stringify(bundle)}` });
```

## Keys for agents

Give the agent a `run` key for quick runs, or a `serve`-only key for production; a leaked agent key then cannot create, change, or delete anything. See [Authentication](https://docs.schemalabs.ai/authentication). Pin datasets an agent re-queries so unchanged rows bill the cached rate ([Data and pinning](https://docs.schemalabs.ai/data#pinning)); elect batch for offline sweeps.
