mirror of
https://github.com/langchain-ai/docs.git
synced 2026-08-27 21:00:00 -04:00
814 lines
27 KiB
Plaintext
814 lines
27 KiB
Plaintext
<CodeGroup>
|
|
```ts Google
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "google-genai:gemini-3.6-flash",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts OpenAI
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "openai:gpt-5.5",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts Anthropic
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "anthropic:claude-sonnet-4-6",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts OpenRouter
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "openrouter:openrouter:z-ai/glm-5.2",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts Fireworks
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "fireworks:accounts/fireworks/models/glm-5p2",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts Baseten
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "baseten:zai-org/GLM-5.2",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
|
|
```ts Ollama
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import sqlite3 from "sqlite3";
|
|
import { SystemMessage, createAgent, tool } from "langchain";
|
|
import * as z from "zod";
|
|
|
|
const url =
|
|
"https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db";
|
|
const localPath = path.resolve("Chinook.db");
|
|
|
|
async function resolveDbPath() {
|
|
try {
|
|
await fs.access(localPath);
|
|
return localPath;
|
|
} catch {
|
|
// Chinook.db not present locally; download it.
|
|
}
|
|
const resp = await fetch(url);
|
|
if (!resp.ok)
|
|
throw new Error(`Failed to download DB. Status code: ${resp.status}`);
|
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
await fs.writeFile(localPath, buf);
|
|
return localPath;
|
|
}
|
|
|
|
// Below are minimal tools for demonstration purposes.
|
|
async function runQuery(query: string): Promise<Record<string, unknown>[]> {
|
|
const dbPath = await resolveDbPath();
|
|
const db = new sqlite3.Database(dbPath);
|
|
return new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows as Record<string, unknown>[]);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSchema() {
|
|
const tables = await runQuery(
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';",
|
|
);
|
|
return tables.map((row) => String(row.sql)).join("\n\n");
|
|
}
|
|
|
|
const DENY_RE =
|
|
/\b(INSERT|UPDATE|DELETE|ALTER|DROP|CREATE|REPLACE|TRUNCATE)\b/i;
|
|
const HAS_LIMIT_TAIL_RE = /\blimit\b\s+\d+(\s*,\s*\d+)?\s*;?\s*$/i;
|
|
|
|
function sanitizeSqlQuery(q: string) {
|
|
let query = String(q ?? "").trim();
|
|
|
|
const semis = [...query].filter((c) => c === ";").length;
|
|
if (semis > 1 || (query.endsWith(";") && query.slice(0, -1).includes(";"))) {
|
|
throw new Error("multiple statements are not allowed.");
|
|
}
|
|
query = query.replace(/;+\s*$/g, "").trim();
|
|
|
|
if (!query.toLowerCase().startsWith("select")) {
|
|
throw new Error("Only SELECT statements are allowed");
|
|
}
|
|
if (DENY_RE.test(query)) {
|
|
throw new Error("DML/DDL detected. Only read-only queries are permitted.");
|
|
}
|
|
|
|
if (!HAS_LIMIT_TAIL_RE.test(query)) {
|
|
query += " LIMIT 5";
|
|
}
|
|
return query;
|
|
}
|
|
|
|
const executeSql = tool(
|
|
async ({ query }) => {
|
|
const q = sanitizeSqlQuery(query);
|
|
try {
|
|
const result = await runQuery(q);
|
|
return JSON.stringify(result, null, 2);
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
throw new Error(message);
|
|
}
|
|
},
|
|
{
|
|
name: "execute_sql",
|
|
description: "Execute a READ-ONLY SQLite SELECT query and return results.",
|
|
schema: z.object({
|
|
query: z.string().describe("SQLite SELECT query to execute (read-only)."),
|
|
}),
|
|
},
|
|
);
|
|
|
|
const getSystemPrompt = async () =>
|
|
new SystemMessage(`You are a careful SQLite analyst.
|
|
|
|
Authoritative schema (do not invent columns/tables):
|
|
${await getSchema()}
|
|
|
|
Rules:
|
|
- Think step-by-step.
|
|
- When you need data, call the tool \`execute_sql\` with ONE SELECT query.
|
|
- Read-only; no INSERT/UPDATE/DELETE/ALTER/DROP/CREATE/REPLACE/TRUNCATE.
|
|
- Limit to 5 rows unless user explicitly asks otherwise.
|
|
- If the tool returns 'Error:', revise the SQL and try again.
|
|
- Limit the number of attempts to 5.
|
|
- If you are not successful after 5 attempts, return a note to the user.
|
|
- Prefer explicit column lists; avoid SELECT *.
|
|
`);
|
|
|
|
export const agent = createAgent({
|
|
model: "ollama:north-mini-code-1.0",
|
|
tools: [executeSql],
|
|
systemPrompt: await getSystemPrompt(),
|
|
});
|
|
```
|
|
</CodeGroup>
|