From 77455690e79664a059f80aba69d7428d0000febb Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:46:18 +0100 Subject: [PATCH 1/2] feat(examples): Add sql_agent example --- examples/sql-agent/package.json | 21 ++ examples/sql-agent/sql_agent.ts | 401 +++++++++++++++++++++++++++++++ examples/sql-agent/tsconfig.json | 15 ++ yarn.lock | 292 ++++++++++++++++++++-- 4 files changed, 715 insertions(+), 14 deletions(-) create mode 100644 examples/sql-agent/package.json create mode 100644 examples/sql-agent/sql_agent.ts create mode 100644 examples/sql-agent/tsconfig.json diff --git a/examples/sql-agent/package.json b/examples/sql-agent/package.json new file mode 100644 index 000000000..fa1b95527 --- /dev/null +++ b/examples/sql-agent/package.json @@ -0,0 +1,21 @@ +{ + "name": "@examples/sql-agent", + "type": "module", + "private": true, + "scripts": { + "start": "tsx -r dotenv/config sql_agent.ts" + }, + "devDependencies": { + "@langchain/classic": "^1.0.0", + "@langchain/core": "^1.0.1", + "@langchain/langgraph": "workspace:*", + "@langchain/openai": "^1.0.0", + "better-sqlite3": "^11.0.0", + "dotenv": "^16.4.5", + "langchain": "^1.0.0-alpha", + "reflect-metadata": "^0.2.2", + "tsx": "^4.19.3", + "typeorm": "^0.3.0", + "zod": "^3.23.8" + } +} diff --git a/examples/sql-agent/sql_agent.ts b/examples/sql-agent/sql_agent.ts new file mode 100644 index 000000000..63f74824e --- /dev/null +++ b/examples/sql-agent/sql_agent.ts @@ -0,0 +1,401 @@ +/** + * Custom SQL Agent with LangGraph + * + * This example demonstrates building a custom SQL agent using LangGraph primitives. + * Unlike higher-level agent abstractions, building directly in LangGraph provides + * fine-grained control over agent behavior through: + * - Dedicated nodes for specific tool-calls (list tables, get schema, run query) + * - Custom prompts for each step (query generation, query checking) + * - Enforced workflow with conditional edges + * - Human-in-the-loop review before executing queries + * + * The agent follows a ReAct-style pattern: + * 1. List available database tables + * 2. Retrieve schema for relevant tables + * 3. Generate a SQL query based on the user's question + * 4. Check the query for common mistakes + * 5. Execute the query (with optional human review) + * 6. Return results to the user + * + * Security Note: + * This example includes human-in-the-loop review to mitigate risks of executing + * model-generated SQL queries. Always scope database permissions narrowly. + */ + +import { SqlDatabase } from "@langchain/classic/sql_db"; +import { + AIMessage, + HumanMessage, + SystemMessage, + ToolMessage, +} from "@langchain/core/messages"; +import { RunnableConfig } from "@langchain/core/runnables"; +import { tool } from "@langchain/core/tools"; +import { + Command, + END, + interrupt, + MemorySaver, + MessagesAnnotation, + START, + StateGraph, +} from "@langchain/langgraph"; +import { ToolNode } from "@langchain/langgraph/prebuilt"; +import { ChatOpenAI } from "@langchain/openai"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { DataSource } from "typeorm"; +import { z } from "zod"; + +// Download and setup database +const url = + "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"; +const localPath = path.resolve("Chinook.db"); + +async function resolveDbPath() { + const exists = await fs + .access(localPath) + .then(() => true) + .catch(() => false); + if (exists) { + console.log(`${localPath} already exists, skipping download.`); + return localPath; + } + 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); + console.log(`File downloaded and saved as ${localPath}`); + return localPath; +} + +const dbPath = await resolveDbPath(); +const datasource = new DataSource({ + type: "better-sqlite3", + database: dbPath, +}); +const db = await SqlDatabase.fromDataSourceParams({ + appDataSource: datasource, +}); + +console.log(`Dialect: ${db.appDataSourceOptions.type}`); +const tableNames = db.allTables.map((t) => t.tableName); +console.log(`Available tables: ${tableNames.join(", ")}`); +const sampleResults = await db.run("SELECT * FROM Artist LIMIT 5;"); +console.log(`Sample output: ${sampleResults}`); + +// Initialize LLM +const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }); + +// Create tools +const listTablesTool = tool( + async () => { + const tableNames = db.allTables.map((t) => t.tableName); + return tableNames.join(", "); + }, + { + name: "sql_db_list_tables", + description: + "Input is an empty string, output is a comma-separated list of tables in the database.", + schema: z.object({}), + } +); + +const getSchemaTool = tool( + async ({ table_names }) => { + const tables = table_names.split(",").map((t) => t.trim()); + return await db.getTableInfo(tables); + }, + { + name: "sql_db_schema", + description: + "Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3", + schema: z.object({ + table_names: z.string().describe("Comma-separated list of table names"), + }), + } +); + +const queryTool = tool( + async ({ query }) => { + try { + const result = await db.run(query); + return typeof result === "string" ? result : JSON.stringify(result); + } catch (error: any) { + return `Error: ${error.message}`; + } + }, + { + name: "sql_db_query", + description: + "Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again.", + schema: z.object({ + query: z.string().describe("SQL query to execute"), + }), + } +); + +const tools = [listTablesTool, getSchemaTool, queryTool]; + +// Create tool nodes +const getSchemaNode = new ToolNode([getSchemaTool]); +const runQueryNode = new ToolNode([queryTool]); + +// Define node functions +async function listTables(state: typeof MessagesAnnotation.State) { + const toolCall = { + name: "sql_db_list_tables", + args: {}, + id: "abc123", + type: "tool_call" as const, + }; + const toolCallMessage = new AIMessage({ + content: "", + tool_calls: [toolCall], + }); + + const toolMessage = await listTablesTool.invoke({}); + const response = new AIMessage(`Available tables: ${toolMessage}`); + + return { + messages: [ + toolCallMessage, + new ToolMessage({ content: toolMessage, tool_call_id: "abc123" }), + response, + ], + }; +} + +async function callGetSchema(state: typeof MessagesAnnotation.State) { + const llmWithTools = llm.bindTools([getSchemaTool], { + tool_choice: "any", + }); + const response = await llmWithTools.invoke(state.messages); + + return { messages: [response] }; +} + +const topK = 5; + +const generateQuerySystemPrompt = ` +You are an agent designed to interact with a SQL database. +Given an input question, create a syntactically correct ${db.appDataSourceOptions.type} +query to run, then look at the results of the query and return the answer. Unless +the user specifies a specific number of examples they wish to obtain, always limit +your query to at most ${topK} results. + +You can order the results by a relevant column to return the most interesting +examples in the database. Never query for all the columns from a specific table, +only ask for the relevant columns given the question. + +DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. +`; + +async function generateQuery(state: typeof MessagesAnnotation.State) { + const systemMessage = new SystemMessage(generateQuerySystemPrompt); + const llmWithTools = llm.bindTools([queryTool]); + const response = await llmWithTools.invoke([ + systemMessage, + ...state.messages, + ]); + + return { messages: [response] }; +} + +const checkQuerySystemPrompt = ` +You are a SQL expert with a strong attention to detail. +Double check the sqlite query for common mistakes, including: +- Using NOT IN with NULL values +- Using UNION when UNION ALL should have been used +- Using BETWEEN for exclusive ranges +- Data type mismatch in predicates +- Properly quoting identifiers +- Using the correct number of arguments for functions +- Casting to the correct data type +- Using the proper columns for joins + +If there are any of the above mistakes, rewrite the query. If there are no mistakes, +just reproduce the original query. + +You will call the appropriate tool to execute the query after running this check. +`; + +async function checkQuery(state: typeof MessagesAnnotation.State) { + const systemMessage = new SystemMessage(checkQuerySystemPrompt); + + const lastMessage = state.messages[state.messages.length - 1]; + if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { + throw new Error("No tool calls found in the last message"); + } + const toolCall = lastMessage.tool_calls[0]; + const userMessage = new HumanMessage(toolCall.args.query); + const llmWithTools = llm.bindTools([queryTool], { + tool_choice: "any", + }); + const response = await llmWithTools.invoke([systemMessage, userMessage]); + response.id = lastMessage.id; + + return { messages: [response] }; +} + +// Build the graph +function shouldContinue( + state: typeof MessagesAnnotation.State +): "check_query" | typeof END { + const messages = state.messages; + const lastMessage = messages[messages.length - 1]; + if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { + return END; + } else { + return "check_query"; + } +} + +const builder = new StateGraph(MessagesAnnotation) + .addNode("list_tables", listTables) + .addNode("call_get_schema", callGetSchema) + .addNode("get_schema", getSchemaNode) + .addNode("generate_query", generateQuery) + .addNode("check_query", checkQuery) + .addNode("run_query", runQueryNode) + .addEdge(START, "list_tables") + .addEdge("list_tables", "call_get_schema") + .addEdge("call_get_schema", "get_schema") + .addEdge("get_schema", "generate_query") + .addConditionalEdges("generate_query", shouldContinue) + .addEdge("check_query", "run_query") + .addEdge("run_query", "generate_query"); + +const agent = builder.compile(); + +// Run the agent +const question = "Which genre on average has the longest tracks?"; + +console.log("\n=== Running SQL Agent ===\n"); + +const stream = await agent.stream( + { messages: [{ role: "user", content: question }] }, + { streamMode: "values" } +); + +for await (const step of stream) { + const lastMessage = step.messages[step.messages.length - 1]; + console.log(lastMessage.toFormattedString()); +} + +console.log("\n=== Agent completed ===\n"); + +// ===== Human-in-the-loop implementation ===== + +console.log("\n=== Setting up agent with human-in-the-loop ===\n"); + +// Create a tool with interrupt for human review +const queryToolWithInterrupt = tool( + async (input, config: RunnableConfig) => { + const request = { + action: queryTool.name, + args: input, + description: "Please review the tool call", + }; + const response = interrupt([request]); + // approve the tool call + if (response.type === "accept") { + const toolResponse = await queryTool.invoke(input, config); + return toolResponse; + } + // update tool call args + else if (response.type === "edit") { + const editedInput = response.args.args; + const toolResponse = await queryTool.invoke(editedInput, config); + return toolResponse; + } + // respond to the LLM with user feedback + else if (response.type === "response") { + const userFeedback = response.args; + return userFeedback; + } else { + throw new Error(`Unsupported interrupt response type: ${response.type}`); + } + }, + { + name: queryTool.name, + description: queryTool.description, + schema: queryTool.schema, + } +); + +// Modified shouldContinue for human-in-the-loop version +function shouldContinueWithHuman( + state: typeof MessagesAnnotation.State +): "run_query" | typeof END { + const messages = state.messages; + const lastMessage = messages[messages.length - 1]; + if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { + return END; + } else { + return "run_query"; + } +} + +// Create tool node with interrupt +const runQueryNodeWithInterrupt = new ToolNode([queryToolWithInterrupt]); + +// Build graph with human-in-the-loop +const builderWithHuman = new StateGraph(MessagesAnnotation) + .addNode("list_tables", listTables) + .addNode("call_get_schema", callGetSchema) + .addNode("get_schema", getSchemaNode) + .addNode("generate_query", generateQuery) + .addNode("run_query", runQueryNodeWithInterrupt) + .addEdge(START, "list_tables") + .addEdge("list_tables", "call_get_schema") + .addEdge("call_get_schema", "get_schema") + .addEdge("get_schema", "generate_query") + .addConditionalEdges("generate_query", shouldContinueWithHuman) + .addEdge("run_query", "generate_query"); + +const checkpointer = new MemorySaver(); +const agentWithHuman = builderWithHuman.compile({ checkpointer }); + +// Run the agent with human-in-the-loop +const config = { configurable: { thread_id: "1" } }; + +console.log("\n=== Running SQL Agent with Human-in-the-Loop ===\n"); + +const streamWithHuman = await agentWithHuman.stream( + { messages: [{ role: "user", content: question }] }, + { ...config, streamMode: "values" } +); + +for await (const step of streamWithHuman) { + if (step.messages && step.messages.length > 0) { + const lastMessage = step.messages[step.messages.length - 1]; + console.log(lastMessage.toFormattedString()); + } +} + +// Check for interrupts +const state = await agentWithHuman.getState(config); +if (state.next.length > 0) { + console.log("\nINTERRUPTED:"); + console.log(JSON.stringify(state.tasks[0].interrupts[0], null, 2)); + + // Resume with approval + console.log("\n=== Resuming with approval ===\n"); + + const resumeStream = await agentWithHuman.stream( + new Command({ resume: { type: "accept" } }), + // new Command({ resume: { type: "edit", args: { query: "..." } } }), + { ...config, streamMode: "values" } + ); + + for await (const step of resumeStream) { + if (step.messages && step.messages.length > 0) { + const lastMessage = step.messages[step.messages.length - 1]; + console.log(lastMessage.toFormattedString()); + } + } + + console.log("\n=== Agent with human-in-the-loop completed ===\n"); +} diff --git a/examples/sql-agent/tsconfig.json b/examples/sql-agent/tsconfig.json new file mode 100644 index 000000000..5c50df65d --- /dev/null +++ b/examples/sql-agent/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/recommended", + "compilerOptions": { + "outDir": "dist", + "lib": ["ES2021", "ES2022.Object", "ES2022.Error", "DOM"], + "target": "ES2021", + "module": "nodenext", + "allowSyntheticDefaultImports": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedParameters": true, + "useDefineForClassFields": true, + "strictPropertyInitialization": false + } +} diff --git a/yarn.lock b/yarn.lock index e58e5f0ac..6dfde6f4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1251,6 +1251,24 @@ __metadata: languageName: unknown linkType: soft +"@examples/sql-agent@workspace:examples/sql-agent": + version: 0.0.0-use.local + resolution: "@examples/sql-agent@workspace:examples/sql-agent" + dependencies: + "@langchain/classic": "npm:^1.0.0" + "@langchain/core": "npm:^1.0.1" + "@langchain/langgraph": "workspace:*" + "@langchain/openai": "npm:^1.0.0" + better-sqlite3: "npm:^11.0.0" + dotenv: "npm:^16.4.5" + langchain: "npm:^1.0.0-alpha" + reflect-metadata: "npm:^0.2.2" + tsx: "npm:^4.19.3" + typeorm: "npm:^0.3.0" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + "@examples/ui-react-transport@workspace:examples/ui-react-transport": version: 0.0.0-use.local resolution: "@examples/ui-react-transport@workspace:examples/ui-react-transport" @@ -1588,6 +1606,40 @@ __metadata: languageName: unknown linkType: soft +"@langchain/classic@npm:^1.0.0": + version: 1.0.0 + resolution: "@langchain/classic@npm:1.0.0" + dependencies: + "@langchain/openai": "npm:1.0.0-alpha.3" + "@langchain/textsplitters": "npm:1.0.0" + handlebars: "npm:^4.7.8" + js-yaml: "npm:^4.1.0" + jsonpointer: "npm:^5.0.1" + langsmith: "npm:^0.3.64" + openapi-types: "npm:^12.1.3" + p-retry: "npm:4" + uuid: "npm:^10.0.0" + yaml: "npm:^2.2.1" + zod: "npm:^3.25.76 || ^4" + peerDependencies: + "@langchain/core": ^1.0.0 + cheerio: "*" + peggy: ^3.0.2 + typeorm: "*" + dependenciesMeta: + langsmith: + optional: true + peerDependenciesMeta: + cheerio: + optional: true + peggy: + optional: true + typeorm: + optional: true + checksum: 10/a1d7efe8856f66c2d08246607743f59182ae8760201335f0ae3133e2c7056c193bf8b7899858081457f98d4e746b36e716b901ae01a6a62c5a31739762c0add5 + languageName: node + linkType: hard + "@langchain/core@npm:^1.0.0, @langchain/core@npm:^1.0.1": version: 1.0.1 resolution: "@langchain/core@npm:1.0.1" @@ -2184,6 +2236,19 @@ __metadata: languageName: node linkType: hard +"@langchain/openai@npm:1.0.0-alpha.3": + version: 1.0.0-alpha.3 + resolution: "@langchain/openai@npm:1.0.0-alpha.3" + dependencies: + js-tiktoken: "npm:^1.0.12" + openai: "npm:^6.3.0" + zod: "npm:^3.25.76 || ^4" + peerDependencies: + "@langchain/core": ^1.0.0-alpha.6 + checksum: 10/e89717b24f520b87596361c71652ab246fa69eea2f3d3b94f2def5590ccdf6134cfb80fa608c0e67caf97bdc497d62d6d7f24144f207f977f528a2231ff31288 + languageName: node + linkType: hard + "@langchain/openai@npm:^1.0.0": version: 1.0.0 resolution: "@langchain/openai@npm:1.0.0" @@ -2231,25 +2296,25 @@ __metadata: languageName: node linkType: hard -"@langchain/textsplitters@npm:1.0.0-alpha.1": - version: 1.0.0-alpha.1 - resolution: "@langchain/textsplitters@npm:1.0.0-alpha.1" +"@langchain/textsplitters@npm:1.0.0, @langchain/textsplitters@npm:^1.0.0": + version: 1.0.0 + resolution: "@langchain/textsplitters@npm:1.0.0" dependencies: js-tiktoken: "npm:^1.0.12" peerDependencies: - "@langchain/core": ^1.0.0-alpha.1 <2.0.0 - checksum: 10/af39820a5292361a2eb0322f48b5dd9115cf20787a753b3c23a2c19994420362b429ebb6af2ec1048e2db2a25b8b9dee56095ae6b93742abd7e4c819fb55d272 + "@langchain/core": ^1.0.0 + checksum: 10/09ef6ad7c36d5420438b841291ad549f92f398c552dd2c0d758a465411aa6ca605a05cb7baf2b9692ccd44d1250d107b7e6ae18cfd1444d77ecadc8c1071113d languageName: node linkType: hard -"@langchain/textsplitters@npm:^1.0.0": - version: 1.0.0 - resolution: "@langchain/textsplitters@npm:1.0.0" +"@langchain/textsplitters@npm:1.0.0-alpha.1": + version: 1.0.0-alpha.1 + resolution: "@langchain/textsplitters@npm:1.0.0-alpha.1" dependencies: js-tiktoken: "npm:^1.0.12" peerDependencies: - "@langchain/core": ^1.0.0 - checksum: 10/09ef6ad7c36d5420438b841291ad549f92f398c552dd2c0d758a465411aa6ca605a05cb7baf2b9692ccd44d1250d107b7e6ae18cfd1444d77ecadc8c1071113d + "@langchain/core": ^1.0.0-alpha.1 <2.0.0 + checksum: 10/af39820a5292361a2eb0322f48b5dd9115cf20787a753b3c23a2c19994420362b429ebb6af2ec1048e2db2a25b8b9dee56095ae6b93742abd7e4c819fb55d272 languageName: node linkType: hard @@ -3337,6 +3402,13 @@ __metadata: languageName: node linkType: hard +"@sqltools/formatter@npm:^1.2.5": + version: 1.2.5 + resolution: "@sqltools/formatter@npm:1.2.5" + checksum: 10/ce9335025cd033f8f1ac997d290af22d5a5cdbd5f04cbf0fa18d5388871e980a4fc67875037821799b356032f851732dee1017b2ee7de84f5c2a2b8bfd5604f5 + languageName: node + linkType: hard + "@swc/core-darwin-arm64@npm:1.4.16": version: 1.4.16 resolution: "@swc/core-darwin-arm64@npm:1.4.16" @@ -5004,6 +5076,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^3.17.0": + version: 3.17.0 + resolution: "ansis@npm:3.17.0" + checksum: 10/6fd6bc4d1187b894d9706f4c141c81b788e90766426617385486dae38f8b2f5a1726d8cc754939e44265f92a9db4647d5136cb1425435c39ac42b35e3acf4f3d + languageName: node + linkType: hard + "ansis@npm:^4.0.0, ansis@npm:^4.1.0": version: 4.1.0 resolution: "ansis@npm:4.1.0" @@ -5011,6 +5090,13 @@ __metadata: languageName: node linkType: hard +"app-root-path@npm:^3.1.0": + version: 3.1.0 + resolution: "app-root-path@npm:3.1.0" + checksum: 10/b4cdab5f7e51ec43fa04c97eca2adedf8e18d6c3dd21cd775b70457c5e71f0441c692a49dcceb426f192640b7393dcd41d85c36ef98ecb7c785a53159c912def + languageName: node + linkType: hard + "archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": version: 5.0.2 resolution: "archiver-utils@npm:5.0.2" @@ -5358,6 +5444,17 @@ __metadata: languageName: node linkType: hard +"better-sqlite3@npm:^11.0.0": + version: 11.10.0 + resolution: "better-sqlite3@npm:11.10.0" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10/5e4c7437c4fe6033335a79c82974d7ab29f33c51c36f48b73e87e087d21578468575de1c56a7badd4f76f17255e25abefddaeacf018e5eeb9e0cb8d6e3e4a5e1 + languageName: node + linkType: hard + "better-sqlite3@npm:^11.7.0": version: 11.8.1 resolution: "better-sqlite3@npm:11.8.1" @@ -6714,6 +6811,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:^1.11.13": + version: 1.11.18 + resolution: "dayjs@npm:1.11.18" + checksum: 10/7d29a90834cf4da2feb437c2f34b8235c3f94493a06d2f1bf9f506f1fa49eadf796f26e1d685b9fe8cb5e75ce6ee067825115e196f1af3d07b3552ff857bfc39 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -6794,6 +6898,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.6.0": + version: 1.7.0 + resolution: "dedent@npm:1.7.0" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10/c902f3e7e828923bd642c12c1d8996616ff5588f8279a2951790bd7c7e479fa4dd7f016b55ce2c9ea1aa2895fc503e7d6c0cde6ebc95ca683ac0230f7c911fd7 + languageName: node + linkType: hard + "deep-eql@npm:^5.0.1": version: 5.0.2 resolution: "deep-eql@npm:5.0.2" @@ -8462,7 +8578,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.3.7": +"glob@npm:^10.0.0, glob@npm:^10.3.7, glob@npm:^10.4.5": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -8598,6 +8714,24 @@ __metadata: languageName: node linkType: hard +"handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10/bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -10116,7 +10250,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f @@ -10450,6 +10584,13 @@ __metadata: languageName: node linkType: hard +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 + languageName: node + linkType: hard + "node-abi@npm:^3.3.0": version: 3.60.0 resolution: "node-abi@npm:3.60.0" @@ -12033,6 +12174,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.14.0": version: 0.14.1 resolution: "regenerator-runtime@npm:0.14.1" @@ -12656,7 +12804,7 @@ __metadata: languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.8": +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": version: 2.4.12 resolution: "sha.js@npm:2.4.12" dependencies: @@ -12902,6 +13050,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff + languageName: node + linkType: hard + "sourcemap-codec@npm:^1.4.8": version: 1.4.8 resolution: "sourcemap-codec@npm:1.4.8" @@ -12939,6 +13094,13 @@ __metadata: languageName: node linkType: hard +"sql-highlight@npm:^6.0.0": + version: 6.1.0 + resolution: "sql-highlight@npm:6.1.0" + checksum: 10/6cd92e7ca3046563f3daf2086adc4c2e1ce43784e59827a12bb9e569bf915eace1d800713f4d2798fc7d475f64852bf08001dca8dd409e9895ba5e0e170b94ff + languageName: node + linkType: hard + "ssh-remote-port-forward@npm:^1.0.4": version: 1.0.4 resolution: "ssh-remote-port-forward@npm:1.0.4" @@ -13721,7 +13883,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.4.0, tslib@npm:^2.8.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -14002,6 +14164,83 @@ __metadata: languageName: node linkType: hard +"typeorm@npm:^0.3.0": + version: 0.3.27 + resolution: "typeorm@npm:0.3.27" + dependencies: + "@sqltools/formatter": "npm:^1.2.5" + ansis: "npm:^3.17.0" + app-root-path: "npm:^3.1.0" + buffer: "npm:^6.0.3" + dayjs: "npm:^1.11.13" + debug: "npm:^4.4.0" + dedent: "npm:^1.6.0" + dotenv: "npm:^16.4.7" + glob: "npm:^10.4.5" + sha.js: "npm:^2.4.12" + sql-highlight: "npm:^6.0.0" + tslib: "npm:^2.8.1" + uuid: "npm:^11.1.0" + yargs: "npm:^17.7.2" + peerDependencies: + "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 + "@sap/hana-client": ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.1 || ^11.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + reflect-metadata: ^0.1.14 || ^0.2.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + "@google-cloud/spanner": + optional: true + "@sap/hana-client": + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + bin: + typeorm: cli.js + typeorm-ts-node-commonjs: cli-ts-node-commonjs.js + typeorm-ts-node-esm: cli-ts-node-esm.js + checksum: 10/9155a8573a3f7976bc7f9312294d15c7c6fd68746a11c6b95184f8c4157f31916e32b9a17dd2da136fc75deda7ae2b74947990982fc6109c44531d418ad58dcd + languageName: node + linkType: hard + "typescript@npm:5.6.1-rc": version: 5.6.1-rc resolution: "typescript@npm:5.6.1-rc" @@ -14049,6 +14288,15 @@ __metadata: languageName: node linkType: hard +"uglify-js@npm:^3.1.4": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10/6b9639c1985d24580b01bb0ab68e78de310d38eeba7db45bec7850ab4093d8ee464d80ccfaceda9c68d1c366efbee28573b52f95e69ac792354c145acd380b11 + languageName: node + linkType: hard + "ulid@npm:^2.3.0": version: 2.4.0 resolution: "ulid@npm:2.4.0" @@ -14261,6 +14509,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 + languageName: node + linkType: hard + "uuid@npm:^9.0.0": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -14648,6 +14905,13 @@ __metadata: languageName: node linkType: hard +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10/497d40beb2bdb08e6d38754faa17ce20b0bf1306327f80cb777927edb23f461ee1f6bc659b3c3c93f26b08e1cf4b46acc5bae8fda1f0be3b5ab9a1a0211034cd + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" From 0beba1269922d65d6c881b9228f8d723f3ececb1 Mon Sep 17 00:00:00 2001 From: Hunter Lovell <40191806+hntrl@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:32:33 -0800 Subject: [PATCH 2/2] Update examples/sql-agent/sql_agent.ts Co-authored-by: Christian Bromann --- examples/sql-agent/sql_agent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sql-agent/sql_agent.ts b/examples/sql-agent/sql_agent.ts index 63f74824e..5460df173 100644 --- a/examples/sql-agent/sql_agent.ts +++ b/examples/sql-agent/sql_agent.ts @@ -314,9 +314,9 @@ const queryToolWithInterrupt = tool( else if (response.type === "response") { const userFeedback = response.args; return userFeedback; - } else { - throw new Error(`Unsupported interrupt response type: ${response.type}`); } + + throw new Error(`Unsupported interrupt response type: ${response.type}`); }, { name: queryTool.name,