Skip to content

Commit

Permalink
add sap hana support for sql chains and agents (langchain-ai#2218)
Browse files Browse the repository at this point in the history
* add sap hana support for sql chains and agents

* changes based on feedback

* fix in index

* Added example for SqlToolkit

* removed example dependencies

* updated lock file

* Update docs, lint + format

---------

Co-authored-by: jacoblee93 <[email protected]>
  • Loading branch information
willemi069808 and jacoblee93 authored Aug 15, 2023
1 parent 7b18e21 commit df741af
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 5 deletions.
10 changes: 9 additions & 1 deletion docs/snippets/modules/chains/popular/sqlite.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CodeBlock from "@theme/CodeBlock";
import SqlDBExample from "@examples/chains/sql_db.ts";
import SqlDBSqlOutputExample from "@examples/chains/sql_db_sql_output.ts";
import SqlDBSAPHANAExample from "@examples/chains/sql_db_saphana.ts";
import SqlDBSqlCustomPromptExample from "@examples/chains/sql_db_custom_prompt.ts";

This example uses Chinook database, which is a sample database available for SQL Server, Oracle, MySQL, etc.
Expand All @@ -19,7 +20,8 @@ Then install the dependencies needed for your database. For example, for SQLite:
npm install sqlite3
```

For other databases see https://typeorm.io/#installation
For other databases see https://typeorm.io/#installation. Currently, LangChain.js has default prompts for
Postgres, SQLite, Microsoft SQL Server, MySQL, and SAP HANA.

Finally follow the instructions on https://database.guide/2-sample-databases-sqlite/ to get the sample database for this example.

Expand All @@ -39,6 +41,12 @@ If desired, you can return the used SQL command when calling the chain.

<CodeBlock language="typescript">{SqlDBSqlOutputExample}</CodeBlock>

## SAP Hana

Here's an example of using the chain with a SAP HANA database:

<CodeBlock language="typescript">{SqlDBSAPHANAExample}</CodeBlock>

## Custom prompt

You can also customize the prompt that is used. Here is an example prompting the model to understand that "foobar" is the same as the Employee table:
Expand Down
71 changes: 71 additions & 0 deletions examples/src/agents/sql_sap_hana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { OpenAI } from "langchain/llms/openai";
import { SqlDatabase } from "langchain/sql_db";
import { createSqlAgent, SqlToolkit } from "langchain/agents/toolkits/sql";
import { DataSource } from "typeorm";

/**
* This example uses a SAP HANA Cloud database. You can create a free trial database via https://developers.sap.com/tutorials/hana-cloud-deploying.html
*
* You will need to add the following packages to your package.json as they are required when using typeorm with SAP HANA:
*
* "hdb-pool": "^0.1.6", (or latest version)
* "@sap/hana-client": "^2.17.22" (or latest version)
*
*/
export const run = async () => {
const datasource = new DataSource({
type: "sap",
host: "<ADD_YOURS_HERE>.hanacloud.ondemand.com",
port: 443,
username: "<ADD_YOURS_HERE>",
password: "<ADD_YOURS_HERE>",
schema: "<ADD_YOURS_HERE>",
encrypt: true,
extra: {
sslValidateCertificate: false,
},
});

// A custom SQL_PREFIX is required because we want to be explicit that a schema name is needed when querying HANA fool-proof (line 33)
const custom_SQL_PREFIX = `You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct SAP HANA 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 {top_k} results using the LIMIT clause.
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 a the few relevant columns given the question.
You have access to tools for interacting with the database.
Only use the below tools. Only use the information returned by the below tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.
Always use a schema name when running a query.
DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.
If the question does not seem related to the database, just return "I don't know" as the answer.`;

const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});

const model = new OpenAI({ temperature: 0 });
const toolkit = new SqlToolkit(db, model);
const executor = createSqlAgent(model, toolkit, {
prefix: custom_SQL_PREFIX,
});

const input = `List the total sales per country. Which country's customers spent the most?`;

console.log(`Executing with input "${input}"...`);

const result = await executor.call({ input });

console.log(`Got output ${result.output}`);

console.log(
`Got intermediate steps ${JSON.stringify(
result.intermediateSteps,
null,
2
)}`
);

await datasource.destroy();
};
39 changes: 39 additions & 0 deletions examples/src/chains/sql_db_saphana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DataSource } from "typeorm";
import { OpenAI } from "langchain/llms/openai";
import { SqlDatabase } from "langchain/sql_db";
import { SqlDatabaseChain } from "langchain/chains/sql_db";

/**
* This example uses a SAP HANA Cloud database. You can create a free trial database via https://developers.sap.com/tutorials/hana-cloud-deploying.html
*
* You will need to add the following packages to your package.json as they are required when using typeorm with SAP HANA:
*
* "hdb-pool": "^0.1.6", (or latest version)
* "@sap/hana-client": "^2.17.22" (or latest version)
*
*/
const datasource = new DataSource({
type: "sap",
host: "<ADD_YOURS_HERE>.hanacloud.ondemand.com",
port: 443,
username: "<ADD_YOURS_HERE>",
password: "<ADD_YOURS_HERE>",
schema: "<ADD_YOURS_HERE>",
encrypt: true,
extra: {
sslValidateCertificate: false,
},
});

const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});

const chain = new SqlDatabaseChain({
llm: new OpenAI({ temperature: 0 }),
database: db,
});

const res = await chain.run("How many tracks are there?");
console.log(res);
// There are 3503 tracks.
1 change: 1 addition & 0 deletions langchain/src/chains/sql_db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export {
SQL_SQLITE_PROMPT,
SQL_MSSQL_PROMPT,
SQL_MYSQL_PROMPT,
SQL_SAP_HANA_PROMPT,
} from "./sql_db_prompt.js";
21 changes: 21 additions & 0 deletions langchain/src/chains/sql_db/sql_db_prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,24 @@ Only use the following tables:
Question: {input}`,
inputVariables: ["dialect", "table_info", "input", "top_k"],
});

export const SQL_SAP_HANA_PROMPT = /*#__PURE__*/ new PromptTemplate({
template: `You are a SAP HANA expert. Given an input question, first create a syntactically correct SAP HANA query to run, then look at the results of the query and return the answer to the input question.
Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the LIMIT clause as per SAP HANA. You can order the results to return the most informative data in the database.
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (") to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.
Always use a schema name when executing a query.
Use the following format:
Question: "Question here"
SQLQuery: "SQL Query to run"
SQLResult: "Result of the SQLQuery"
Answer: "Final answer here"
Only use the following tables:
{table_info}
Question: {input}`,
inputVariables: ["dialect", "table_info", "input", "top_k"],
});
50 changes: 46 additions & 4 deletions langchain/src/util/sql_utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DataSource, DataSourceOptions } from "typeorm";
import {
DEFAULT_SQL_DATABASE_PROMPT,
SQL_SAP_HANA_PROMPT,
SQL_MSSQL_PROMPT,
SQL_MYSQL_PROMPT,
SQL_POSTGRES_PROMPT,
Expand Down Expand Up @@ -189,6 +190,32 @@ export const getTableAndColumnsName = async (
return formatToSqlTable(rep);
}

if (appDataSource.options.type === "sap") {
const schema = appDataSource.options?.schema ?? "public";
sql = `SELECT
TABLE_NAME,
COLUMN_NAME,
DATA_TYPE_NAME AS data_type,
CASE WHEN IS_NULLABLE='TRUE' THEN 'YES' ELSE 'NO' END AS is_nullable
FROM TABLE_COLUMNS
WHERE SCHEMA_NAME='${schema}'`;

const rep: Array<{ [key: string]: string }> = await appDataSource.query(
sql
);

const repLowerCase: Array<RawResultTableAndColumn> = [];
rep.forEach((_rep) =>
repLowerCase.push({
table_name: _rep.TABLE_NAME,
column_name: _rep.COLUMN_NAME,
data_type: _rep.DATA_TYPE,
is_nullable: _rep.IS_NULLABLE,
})
);

return formatToSqlTable(repLowerCase);
}
throw new Error("Database type not implemented yet");
};

Expand Down Expand Up @@ -220,10 +247,15 @@ export const generateTableInfoFromTables = async (
let globalString = "";
for (const currentTable of tables) {
// Add the creation of the table in SQL
const schema =
appDataSource.options.type === "postgres"
? appDataSource.options?.schema ?? "public"
: null;
let schema = null;
if (appDataSource.options.type === "postgres") {
schema = appDataSource.options?.schema ?? "public";
} else if (appDataSource.options.type === "sap") {
schema =
appDataSource.options?.schema ??
appDataSource.options?.username ??
"public";
}
let sqlCreateTableQuery = schema
? `CREATE TABLE "${schema}"."${currentTable.tableName}" (\n`
: `CREATE TABLE ${currentTable.tableName} (\n`;
Expand All @@ -246,6 +278,12 @@ export const generateTableInfoFromTables = async (
sqlSelectInfoQuery = `SELECT * FROM "${schema}"."${currentTable.tableName}" LIMIT ${nbSampleRow};\n`;
} else if (appDataSource.options.type === "mssql") {
sqlSelectInfoQuery = `SELECT TOP ${nbSampleRow} * FROM [${currentTable.tableName}];\n`;
} else if (appDataSource.options.type === "sap") {
const schema =
appDataSource.options?.schema ??
appDataSource.options?.username ??
"public";
sqlSelectInfoQuery = `SELECT * FROM "${schema}"."${currentTable.tableName}" LIMIT ${nbSampleRow};\n`;
} else {
sqlSelectInfoQuery = `SELECT * FROM "${currentTable.tableName}" LIMIT ${nbSampleRow};\n`;
}
Expand Down Expand Up @@ -296,5 +334,9 @@ export const getPromptTemplateFromDataSource = (
return SQL_MSSQL_PROMPT;
}

if (appDataSource.options.type === "sap") {
return SQL_SAP_HANA_PROMPT;
}

return DEFAULT_SQL_DATABASE_PROMPT;
};

0 comments on commit df741af

Please sign in to comment.