eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
82 lines (63 loc) ⢠3.19 kB
text/mdx
---
title: "Query Sample Data"
description: "Part 3 of the Build an Agent tutorial. Add a run_sql tool over the bundled sample dataset and watch the tool loop."
---
The analytics assistant can hold a conversation, but it can't see a single row of data. Give it a tool. A tool is the action primitive. Typed input goes in, your code runs, structured output comes back. The name the model sees is the filename, so `agent/tools/run_sql.ts` becomes the tool `run_sql`.
## A tiny sample dataset
To make the first query work without setup, bundle a small in-memory dataset under `agent/lib/`. Keep it tiny. This is throwaway scaffolding, not the real warehouse (that comes in Step 4).
```ts title="agent/lib/sample-db.ts"
// A toy SQLite-in-memory stand-in. Swap for your real warehouse in Step 4.
import initSqlJs from "sql.js";
const SEED = `
CREATE TABLE orders (id INTEGER, customer_id INTEGER, amount_cents INTEGER, created_at TEXT);
INSERT INTO orders VALUES
(1, 10, 4200, '2026-05-01'), (2, 10, 1500, '2026-05-03'),
(3, 11, 9900, '2026-05-04'), (4, 12, 800, '2026-05-06');
CREATE TABLE customers (id INTEGER, name TEXT, plan TEXT);
INSERT INTO customers VALUES
(10, 'Acme', 'pro'), (11, 'Globex', 'enterprise'), (12, 'Initech', 'free');
`;
let dbPromise: Promise<import("sql.js").Database> | null = null;
async function db() {
dbPromise ??= initSqlJs().then((SQL) => {
const database = new SQL.Database();
database.run(SEED);
return database;
});
return dbPromise;
}
export async function runReadOnlySql(sql: string) {
const database = await db();
const [result] = database.exec(sql);
if (!result) return { columns: [], rows: [] as unknown[][] };
return { columns: result.columns, rows: result.values };
}
```
## Define the run_sql tool
```ts title="agent/tools/run_sql.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";
import { runReadOnlySql } from "../lib/sample-db.js";
export default defineTool({
description:
"Run a read-only SQL query against the analytics tables (orders, customers) " +
"and return the columns and rows.",
inputSchema: z.object({
sql: z.string().describe("A single read-only SELECT statement."),
}),
async execute({ sql }) {
const { columns, rows } = await runReadOnlySql(sql);
// Bound the output so a wide query can't flood the model's context.
return { columns, rows: rows.slice(0, 500), truncated: rows.length > 500 };
},
});
```
Tools run in your app runtime with full `process.env`, not in the sandbox. The `inputSchema` both validates the call and types the `input` you get inside `execute`. For output bounding, `toModelOutput`, and authorization, see [Tools](../tools).
## Watch the tool loop
Restart the dev server with `npm run dev` and ask:
```text
Which customer has spent the most, and how much?
```
Watch the loop play out in the TUI. The model emits a `run_sql` call, eve runs your `execute`, and the rows come back as a tool result. The model reads them and answers with a real number. eve drove the whole loop. All you supplied was the tool.
ā Next: [Connect a warehouse](./connect-a-warehouse)
Learn more: [Tools](../tools)