@sassoftware/sas-score-mcp-serverjs 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.babelrc +6 -0
  2. package/.env +13 -0
  3. package/.env.http +29 -0
  4. package/CHANGES.md +2 -0
  5. package/CONTRIBUTING.md +14 -0
  6. package/ContributorAgreement.txt +56 -0
  7. package/LICENSE +205 -0
  8. package/LICENSES.json +105 -0
  9. package/QUICK_REFERENCE.md +378 -0
  10. package/README.md +267 -0
  11. package/SECURITY.md +31 -0
  12. package/SUPPORT.md +3 -0
  13. package/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
  14. package/TOOL_UPDATES_SUMMARY.md +208 -0
  15. package/cli.js +214 -0
  16. package/labs/.subclass.json +13 -0
  17. package/labs/README.md +4 -0
  18. package/mcpConfigurations/README.md +3 -0
  19. package/mcpConfigurations/http.json +8 -0
  20. package/mcpConfigurations/stdio.json +20 -0
  21. package/mcpConfigurations/stdiodev.json +20 -0
  22. package/mcpserver.png +0 -0
  23. package/openApi.json +106 -0
  24. package/openApi.yaml +84 -0
  25. package/package.json +72 -0
  26. package/sas-mcp-tools-reference.md +600 -0
  27. package/sasCode/sas-sql-query.sas +33 -0
  28. package/sasCode/sas_sql_tool.json +237 -0
  29. package/scripts/getViyaca.sh +8 -0
  30. package/src/core.js +19 -0
  31. package/src/coreSSE.js +14 -0
  32. package/src/corehttp.js +335 -0
  33. package/src/createHttpTransport.js +26 -0
  34. package/src/createMcpServer.js +76 -0
  35. package/src/db/scrModels.js +23 -0
  36. package/src/toolSet/devaScore.js +69 -0
  37. package/src/toolSet/findJob.js +90 -0
  38. package/src/toolSet/findJobdef.js +95 -0
  39. package/src/toolSet/findLibrary.js +100 -0
  40. package/src/toolSet/findModel.js +83 -0
  41. package/src/toolSet/findTable.js +94 -0
  42. package/src/toolSet/getEnv.js +72 -0
  43. package/src/toolSet/listJobdefs.js +96 -0
  44. package/src/toolSet/listJobs.js +110 -0
  45. package/src/toolSet/listLibraries.js +90 -0
  46. package/src/toolSet/listModels.js +83 -0
  47. package/src/toolSet/listTables.js +95 -0
  48. package/src/toolSet/makeTools.js +75 -0
  49. package/src/toolSet/mcp server .png +0 -0
  50. package/src/toolSet/modelInfo.js +87 -0
  51. package/src/toolSet/modelScore.js +131 -0
  52. package/src/toolSet/readTable.js +104 -0
  53. package/src/toolSet/runCasProgram.js +118 -0
  54. package/src/toolSet/runJob.js +81 -0
  55. package/src/toolSet/runJobdef.js +85 -0
  56. package/src/toolSet/runMacro.js +82 -0
  57. package/src/toolSet/runProgram.js +145 -0
  58. package/src/toolSet/sasQuery.js +126 -0
  59. package/src/toolSet/sasQueryTemplate.js +148 -0
  60. package/src/toolSet/sasQueryTemplate2.js +140 -0
  61. package/src/toolSet/scrInfo.js +55 -0
  62. package/src/toolSet/scrScore.js +71 -0
  63. package/src/toolSet/searchAssets.js +52 -0
  64. package/src/toolSet/setContext.js +98 -0
  65. package/src/toolSet/superstat.js +60 -0
  66. package/src/toolSet/tableInfo.js +102 -0
  67. package/src/toolhelpers/_catalogSearch.js +87 -0
  68. package/src/toolhelpers/_getEnv.js +10 -0
  69. package/src/toolhelpers/_itemsData.js +28 -0
  70. package/src/toolhelpers/_jobSubmit.js +78 -0
  71. package/src/toolhelpers/_listJobdefs.js +59 -0
  72. package/src/toolhelpers/_listJobs.js +63 -0
  73. package/src/toolhelpers/_listLibrary.js +56 -0
  74. package/src/toolhelpers/_listModels.js +41 -0
  75. package/src/toolhelpers/_listTables.js +52 -0
  76. package/src/toolhelpers/_masDescribe.js +27 -0
  77. package/src/toolhelpers/_masScoring.js +64 -0
  78. package/src/toolhelpers/_readTable.js +69 -0
  79. package/src/toolhelpers/_scrInfo.js +32 -0
  80. package/src/toolhelpers/_scrScore.js +49 -0
  81. package/src/toolhelpers/_submitCasl.js +34 -0
  82. package/src/toolhelpers/_submitCode.js +96 -0
  83. package/src/toolhelpers/_submitMacro.js +24 -0
  84. package/src/toolhelpers/_tableColumns.js +61 -0
  85. package/src/toolhelpers/_tableInfo.js +72 -0
  86. package/src/toolhelpers/deleteSession.js +13 -0
  87. package/src/toolhelpers/getLogonPayload.js +100 -0
  88. package/src/toolhelpers/getOpts.js +43 -0
  89. package/src/toolhelpers/getOptsViya.js +38 -0
  90. package/src/toolhelpers/getStoreOpts.js +18 -0
  91. package/src/toolhelpers/getToken.js +40 -0
  92. package/src/toolhelpers/refreshToken.js +48 -0
  93. package/test/README.md +63 -0
  94. package/test/listLibraries.test.js +245 -0
  95. package/tool-developer-guide.md +80 -0
  96. package/types.js +25 -0
@@ -0,0 +1,82 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import _submitCode from '../toolHelpers/_submitCode.js';
8
+
9
+
10
+ function runMacro(_appContext) {
11
+ let description = `
12
+ ## run-macro
13
+
14
+ Submit and execute a SAS macro on a SAS Viya server by generating and sending SAS code.
15
+
16
+ Inputs
17
+ - macro (string, required): The name of the macro to execute (provide the macro identifier without the leading "%").
18
+ - scenario (string, optional): Parameters or SAS setup code to send before invoking the macro. Accepted formats:
19
+ - Comma-separated key=value pairs (e.g. "x=1, y=abc") — converted to SAS %let statements.
20
+ - Raw SAS macro code (e.g. "%let x=1; %let y=abc;") — passed through unchanged when it already contains SAS macro syntax.
21
+
22
+ Behavior
23
+ - If \`scenario\` contains SAS macro syntax (contains \`%let\` or other macro markers), it is sent unchanged.
24
+ - Otherwise the tool converts comma-separated parameters into \`%let\` statements (e.g. "x=1,y=abc" → "%let x=1; %let y=abc;") and appends a macro invocation \`%<macro>;\`.
25
+ - The resulting SAS code is submitted via the internal \`_submitCode\` helper and the submission result is returned.
26
+
27
+ Output
28
+ - Returns the response produced by \`_submitCode\`, typically including ods, log, list of tables created by the macro
29
+
30
+ Usage notes
31
+ - Ensure the target SAS environment has the macro defined
32
+ - This helper does not perform advanced input validation or type coercion — validate parameters before calling when needed.
33
+
34
+ Examples
35
+ - run macro \`abc\` with scenario \`x=1, y=2\`
36
+ - run macro \`summarize\` with raw SAS code \`%let x=1; %let y=2;\` (the helper will pass it through unchanged)
37
+ `;
38
+
39
+ let spec = {
40
+ name: 'run-macro',
41
+ aliases: ['runMacro','run macro','run_macro'],
42
+ description: description,
43
+
44
+ schema: {
45
+ macro: z.string(),
46
+ scenario: z.string()
47
+ },
48
+ required: ['macro'],
49
+ handler: async (params) => {
50
+ const scenarioRaw = (params.scenario || '').trim();
51
+ let setup = '';
52
+ if (scenarioRaw) {
53
+ // If the scenario already contains macro syntax, send it through unchanged
54
+ const hasMacroSyntax = /%let\b|%[a-zA-Z_]\w*\s*\(|%[a-zA-Z_]\w*\s*;/.test(scenarioRaw) || scenarioRaw.includes('%');
55
+ if (hasMacroSyntax) {
56
+ setup = scenarioRaw;
57
+ } else {
58
+ // Convert "x=1,y=abc" -> "%let x=1; %let y=abc;"
59
+ setup = scenarioRaw.split(',')
60
+ .map(p => p.trim())
61
+ .filter(Boolean)
62
+ .map(p => {
63
+ const [k, ...rest] = p.split('=');
64
+ if (!k) return '';
65
+ const key = k.trim();
66
+ const val = rest.join('=').trim();
67
+ return `%let ${key}=${val};`;
68
+ })
69
+ .filter(Boolean)
70
+ .join(' ');
71
+ }
72
+ }
73
+ const src = `${setup} %${params.macro};`;
74
+ params.src = src;
75
+ let r = await _submitCode(params);
76
+ return r;
77
+ }
78
+ }
79
+ return spec;
80
+ }
81
+
82
+ export default runMacro;
@@ -0,0 +1,145 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import _submitCode from '../toolHelpers/_submitCode.js';
8
+
9
+ function runProgram(_appContext) {
10
+ let description = `
11
+ ## run-sas-program — execute SAS code or programs on SAS Viya server
12
+
13
+ LLM Invocation Guidance (When to use)
14
+ Use THIS tool when the user wants to run a SAS program on the server:
15
+ - "run program 'data a; x=1; run;'"
16
+ - "execute this SAS code: proc print data=sashelp.cars; run;"
17
+ - "program 'data work.a; x=1; run;' output=a limit=50"
18
+ - "run sas program sample folder=/Public/models output=A limit=50"
19
+ - "run sas program with parameters name=John, age=45"
20
+ - "execute SAS file myprogram.sas from /Public folder"
21
+
22
+ Do NOT use this tool when the user wants:
23
+ - run macro -> use run-macro
24
+ - run job -> use run-job
25
+ - run jobdef -> use run-jobdef
26
+ - list jobs -> use list-jobs
27
+ - list jobdefs -> use list-jobdefs
28
+ - find job -> use find-job
29
+ - find jobdef -> use find-jobdef
30
+ - find model -> use find-model
31
+ - Query tables with SQL -> use sas-query
32
+ - Read table data -> use read-table
33
+
34
+ Purpose
35
+ Execute SAS code or programs on a SAS Viya server. Can run inline SAS code or execute .sas files stored on the server. Supports parameter passing via scenario values and can return output tables.
36
+
37
+ Parameters
38
+ - src (string, required): The SAS program to execute. Can be either:
39
+ - Inline SAS code as a string (e.g., "data a; x=1; run;")
40
+ - Name of a .sas file stored on the server (used with folder parameter)
41
+ - folder (string, default ''): Server folder path where the .sas file is located (e.g., "/Public/models")
42
+ - scenario (string | object, optional): Input parameter values. Accepts:
43
+ - Comma-separated key=value string (e.g., "x=1, y=2")
44
+ - JSON object with field names and values (recommended)
45
+ - output (string, default ''): Table name to return in response (case-sensitive)
46
+ - limit (number, default 100): Maximum number of rows to return from output table
47
+
48
+ Response Contract
49
+ Returns a JSON object containing:
50
+ - log: Execution log from SAS
51
+ - ods: ODS output if generated
52
+ - tables: Array of table names created during execution
53
+ - data: If output parameter specified, returns the table data (up to limit rows)
54
+ - error: Error message if execution fails
55
+ - Empty content if no results produced
56
+
57
+ Disambiguation & Clarification
58
+ - Missing SAS code: ask "What SAS program or code would you like to execute?"
59
+ - Inline code vs file unclear: ask "Do you want to run inline SAS code or execute a .sas file from the server?"
60
+ - If output table not returned: clarify "The output table name is case-sensitive. Did you specify the exact name?"
61
+ - If unclear about parameters: ask "What parameter values should I pass to the program?"
62
+
63
+ Examples (→ mapped params)
64
+ - "run program 'data a; x=1; run;'" → { src: "data a; x=1; run;", folder: "", output: "", limit: 100 }
65
+ - "program 'data work.a; x=1; run;' output=a limit=50" → { src: "data work.a; x=1; run;", folder: "", output: "a", limit: 50 }
66
+ - "run sas program sample folder=/Public/models output=A limit=50" → { src: "sample", folder: "/Public/models", output: "A", limit: 50 }
67
+ - "run program mycode.sas in /Public with name=John, age=45" → { src: "mycode", folder: "/Public", scenario: { name: "John", age: 45 }, output: "", limit: 100 }
68
+
69
+ Negative Examples (should NOT call run-sas-program)
70
+ - "run macro summarize" (use run-macro instead)
71
+ - "run job monthly_report" (use run-job instead)
72
+ - "run jobdef etl_process" (use run-jobdef instead)
73
+ - "how many customers by region in Public.customers" (use sas-query instead)
74
+ - "read 10 rows from cars" (use read-table instead)
75
+
76
+ Behavior & Usage Notes
77
+ - Sends the supplied src verbatim to the SAS execution helper
78
+ - For .sas files, specify the file name (with or without .sas extension) and the folder path
79
+ - For invoking pre-defined SAS macros, prefer run-macro which handles %let statements automatically
80
+ - Scenario values are converted to macro variables when provided
81
+ - Be cautious when executing arbitrary code in production environments
82
+
83
+ Related Tools
84
+ - run-macro — for executing predefined SAS macros with parameters
85
+ - run-job — for executing registered Job Execution assets
86
+ - sas-query — for natural language SQL queries
87
+ - read-table — for simple data retrieval
88
+ `;
89
+
90
+ let spec = {
91
+ name: 'run-sas-program',
92
+ aliases: ['Program','run program'],
93
+ description: description,
94
+ schema: {
95
+ src: z.string(),
96
+ scenario: z.any().default(''),
97
+ output: z.string().default(''),
98
+ folder: z.string().default(''),
99
+ limit: z.number().default(100)
100
+ },
101
+ // NOTE: Previously 'required' incorrectly listed 'program' which does not
102
+ // exist in the schema. This prevented execution in some orchestrators that
103
+ // enforce required parameter presence, causing only descriptions to appear.
104
+ // Corrected to 'src'.
105
+ required: ['src'],
106
+ handler: async (params) => {
107
+ let {src, folder, scenario, _appContext} = params;
108
+ // figure out src
109
+ let isrc = src;
110
+ if (folder != null && folder.trim().length > 0) {
111
+ if (isrc.indexOf('.sas') < 0) {
112
+ isrc = isrc + '.sas';
113
+ }
114
+ isrc = `
115
+ filename mcptemp filesrvc folderpath="${folder}";
116
+ %include mcptemp("${isrc}");
117
+ filename mcptemp clear;
118
+ `;
119
+ }
120
+ // figure out macros
121
+
122
+ if (typeof scenario === 'string' && scenario.includes('=')) {
123
+ scenario = scenario.split(',').reduce((acc, pair) => {
124
+ const [k, ...rest] = pair.split('=');
125
+ if (!k) return acc;
126
+ acc[k.trim()] = rest.join('=').trim();
127
+ return acc;
128
+ }, {});
129
+ }
130
+ let iparms = {
131
+ args: scenario,
132
+ output: params.output,
133
+ limit: params.limit,
134
+ src: isrc,
135
+ _appContext: _appContext
136
+ }
137
+ // console.error('iparms', iparms);
138
+ let r = await _submitCode(iparms);
139
+ return r;
140
+ }
141
+ }
142
+ return spec;
143
+ }
144
+
145
+ export default runProgram;
@@ -0,0 +1,126 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import {z} from 'zod';
6
+ import _jobSubmit from '../toolHelpers/_jobSubmit.js';
7
+
8
+
9
+ function sasQuery() {
10
+
11
+ let description = `
12
+ ## sas-query — convert natural language questions into SQL queries and execute them
13
+
14
+ LLM Invocation Guidance (When to use)
15
+ Use THIS tool when:
16
+ - User asks a natural language question about table data: "how many customers by region?"
17
+ - User wants aggregated analytics: "show total sales by year"
18
+ - User needs complex filtering: "find all orders over $1000 from last month"
19
+ - User requests joined data: "show products with their category names"
20
+ - User wants statistical summaries: "average, min, max salary by department"
21
+ - User asks for specific calculations: "percentage of customers by state"
22
+
23
+ Do NOT use this tool for:
24
+ - Reading raw table data without filtering (use read-table)
25
+ - Getting table structure or column info (use table-info)
26
+ - Running pre-written SAS programs (use run-sas-program)
27
+ - Running jobs or job definitions (use run-job or run-jobdef)
28
+ - Executing macros (use run-macro)
29
+ - Simple table reads with no aggregation (use read-table)
30
+
31
+ Purpose
32
+ Convert natural language queries into SAS PROC SQL SELECT statements and execute them to retrieve analyzed data. The LLM generates the SQL from the natural language query, and this tool executes it against the specified table.
33
+
34
+ Parameters
35
+ - table (string, required): Table name in lib.table format (e.g., "Public.cars", "sashelp.class")
36
+ - query (string, required): Natural language description of what data you want
37
+ - sql (string, optional): Pre-generated SQL SELECT statement (LLM should generate this from the query)
38
+ - job (string, default 'program'): Job name to execute the query (default is 'program')
39
+
40
+ Behavior & Processing
41
+ - LLM converts the natural language query into a valid SAS PROC SQL SELECT statement
42
+ - Do not add semicolons to the end of SQL statements
43
+ - SQL reference: https://go.documentation.sas.com/doc/en/pgmsascdc/v_067/sqlproc/n0w2pkrm208upln11i9r4ogwyvow.htm
44
+ - Tool executes the generated SQL against the specified table
45
+ - Returns data in JSON format
46
+
47
+ Response Contract
48
+ Returns a JSON object containing:
49
+ - rows: Array of row objects with query results
50
+ - columns: Column metadata from the query result
51
+ - log: Execution log if available
52
+ - If error: structured error message
53
+ - If more than 10 rows: only first 10 displayed (ask user if they want more)
54
+
55
+ Disambiguation & Clarification
56
+ - If table missing: ask "Which table should I query (format: lib.tablename)?"
57
+ - If query too vague: ask "Can you be more specific about what data or calculation you want?"
58
+ - If table format unclear: ask "Please specify table as library.tablename (e.g., Public.cars)"
59
+ - If ambiguous calculation: ask for clarification on what to aggregate or filter
60
+
61
+ Examples (→ mapped params)
62
+ - "how many cars by make in sashelp.cars" → { table: "sashelp.cars", query: "how many cars by make", sql: "SELECT make, COUNT(*) AS count FROM sashelp.cars GROUP BY make" }
63
+ - "average horsepower by origin" → { table: "sashelp.cars", query: "average horsepower by origin", sql: "SELECT origin, AVG(horsepower) AS avg_hp FROM sashelp.cars GROUP BY origin" }
64
+ - "total sales over $1000 by region" → { table: "mylib.sales", query: "total sales over $1000 by region", sql: "SELECT region, SUM(amount) AS total FROM mylib.sales WHERE amount > 1000 GROUP BY region" }
65
+ - "percentage of students by year in Public.students" → { table: "Public.students", query: "percentage by year", sql: "SELECT year, COUNT(*) * 100.0 / (SELECT COUNT(*) FROM Public.students) AS pct FROM Public.students GROUP BY year" }
66
+
67
+ Negative Examples (should NOT call sas-query)
68
+ - "read table cars from sashelp" (use read-table instead)
69
+ - "show me 10 rows from customers" (use read-table instead)
70
+ - "what columns are in the sales table?" (use table-info instead)
71
+ - "run this SAS code: proc sql; select * from..." (use run-sas-program instead)
72
+ - "execute job monthly_report" (use run-job instead)
73
+ - "run macro summarize_data" (use run-macro instead)
74
+
75
+ Usage Tips
76
+ - Ensure table is specified in lib.tablename format
77
+ - Be specific in natural language queries for better SQL generation
78
+ - Use table-info first to understand column names and types
79
+ - For simple reads without filtering/aggregation, prefer read-table
80
+
81
+ Related Tools
82
+ - read-table — for simple data reading without SQL queries
83
+ - table-info — to inspect table schema before querying
84
+ - run-sas-program — for executing pre-written SAS/SQL code
85
+ - find-table — to verify table exists before querying
86
+ `;
87
+
88
+
89
+ let spec = {
90
+ name: 'sas-query',
91
+ aliases: ['sasQuery','sas query','sas_query'],
92
+ description: description,
93
+ schema: {
94
+ query: z.string(),
95
+ table: z.string(),
96
+ sql: z.string().optional(),
97
+ job: z.string().default('program')
98
+ },
99
+ required: ['query', 'table'],
100
+ handler: async (params) => {
101
+ let {table,query, sql, job, _appContext} = params;
102
+ let sqlinput = (sql == null) ? ' ' : sql.replaceAll(';', ' ').replaceAll('\n', ' ').replaceAll('\r', ' ');
103
+
104
+ let iparams = {
105
+ scenario: {
106
+ table: table,
107
+ prompt: query,
108
+ sql: sqlinput
109
+ },
110
+ name: (job == null) ? 'program' : job,
111
+ type: 'job',
112
+ query: true,
113
+ _appContext: _appContext
114
+ };
115
+ if (sql == null || sql.trim().length === 0) {
116
+ return { content: [{ type: 'text', text: 'Error: The SQL statement generated is blank. Please provide a valid natural language query that can be converted to SQL.' }] };
117
+ }
118
+
119
+ let r = await _jobSubmit(iparams);
120
+ return r;
121
+
122
+ }
123
+ }
124
+ return spec;
125
+ }
126
+ export default sasQuery;
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { z } from 'zod';
6
+ import _jobSubmit from '../toolHelpers/_jobSubmit.js';
7
+ import _tableColumns from '../toolHelpers/_tableColumns.js';
8
+
9
+
10
+ async function sasQueryTemplate(uparams) {
11
+
12
+ let toolname = uparams.name;
13
+ let uTable = uparams.table;
14
+ let selectColumns = uparams.selectColumns;
15
+ let selectColumns2 = (selectColumns == null) ? '_All_' : selectColumns.replace(',', ' ');
16
+ let uJob = (uparams.job == null) ? 'run_sql_query' : uparams.job;
17
+
18
+ // let allColumns = await _tableColumns(uTable, uparams.server);
19
+
20
+ let selcols = (selectColumns == null) ? ' ' : 'and the columns ${selectColumns}';
21
+ let instruction1 = 'The valid columns in the table ${uTable} are: ${allColumns}. ';
22
+
23
+ let description = `
24
+ You are an expert at translating natural language filter into SAS PROC SQL SELECT statement for the table ${uTable}.
25
+ You will then convert the user's query to a valid SAS PROC SQL SELECT statement and pass this string
26
+ as the query.
27
+
28
+ Instructions to LLM:
29
+
30
+ If the user does not select any columns assume they want to select these columns ${selectColumns}.
31
+ The handler will return the data that was queried from the table.
32
+
33
+ User can optionally specify a SAS job to run the query on the SAS Server. If not specified, the default job 'run_sql_query' will be used.
34
+
35
+ The desired flow:
36
+ Instructions to LLM: Show the progress of how you are thinking about converting the natural language query to SQL.
37
+ Step 1: User provides the following prompt:
38
+ ${toolname} <natural language query>
39
+
40
+ Step 2: Convert the user's natural language query into a valid SAS PROC SQL SELECT statement. A good reference for
41
+ the SQL syntax is this [link](https://go.documentation.sas.com/doc/en/pgmsascdc/v_067/sqlproc/n0w2pkrm208upln11i9r4ogwyvow.htm)
42
+ Instructions to LLM:
43
+ - Do not add a semicolon to the end of the SQL statement.
44
+ - Use only the columns from the table ${uTable} in the SELECT and WHERE clauses.
45
+ - If the user query is ambiguous, ask for clarification instead of guessing.
46
+
47
+ Step 3: Pass these as parameters to the handler:
48
+ { table: ${uTable},
49
+ query: <user's natural language query>,
50
+ sql: <generated SQL query> }
51
+
52
+ If the user specified a job, include that in the parameters:
53
+ { table: ${uTable},
54
+ query: <user's natural language query>,
55
+ sql: <generated SQL query>,
56
+ job: <job name> }
57
+
58
+ Example 1:
59
+
60
+ Step 1: User prompt:
61
+
62
+ User prompt:
63
+ ${toolname} Total paid amount, unique patients, and unique claims by procedure code for diagnosis code Z1100, Z10119, Z1020
64
+
65
+ Step 2: Convert the query to a SQL Select statement
66
+
67
+ sql = "
68
+ SELECT prcdr_cd, SUM(pd_amt) AS total_paid_amount, COUNT(DISTINCT mdcd_id) AS unique_patients, COUNT(DISTINCT icn) AS unique_claims
69
+ FROM ${uTable}
70
+ WHERE diag_cd IN ('Z1100', 'Z10119', 'Z1020')
71
+ GROUP BY prcdr_cd
72
+ "
73
+
74
+ Step 3: Pass these to the handler
75
+ { table: ${uTable},
76
+ query: "Total paid amount, unique patients, and unique claims by procedure code for diagnosis code Z1100, Z10119, Z1020",
77
+ sql: "SELECT prcdr_cd, SUM(pd_amt) AS total_paid_amount, COUNT(DISTINCT mdcd_id) AS unique_patients, COUNT(DISTINCT icn) AS unique_claims
78
+ FROM ${uTable}
79
+ WHERE diag_cd IN ('Z1100', 'Z10119', 'Z1020')
80
+ GROUP BY prcdr_cd",
81
+ selectColumns: ${selectColumns}
82
+ }
83
+
84
+ Step 4: Handler returns the results of the query to the user. The output has a json representation of the table.
85
+
86
+ Example 2:
87
+ Input: ${toolname} How many students are in each year and show me in percentage
88
+
89
+
90
+ The parameters passed to the handler are:
91
+ {
92
+ table: ${uTable},
93
+ query: "How many students are in each year and show me in percentage"
94
+ sql: "SELECT year,
95
+ COUNT(DISTINCT student_id) AS number_of_students,
96
+ COUNT(*) / (SELECT COUNT(DISTINCT student_id) FROM ${uTable}) AS Percent FORMAT=percent8.2
97
+ FROM ${uTable}
98
+ GROUP BY year",
99
+ selectColumns: ${selectColumns}
100
+ }
101
+
102
+ ## Desired Output Display Format
103
+ If the query is successful and returns rows, display the rows as a markdown table.
104
+
105
+ `;
106
+
107
+ let spec = {
108
+ name: toolname,
109
+ description: description,
110
+
111
+ schema: {
112
+ query: z.string(),
113
+ table: z.string().default(uTable),
114
+ sql: z.string().optional()
115
+ },
116
+ required: ['query', 'table'],
117
+ handler: async (params) => {
118
+
119
+
120
+ let { table, query, sql, _appContext} = params;
121
+ let sqlinput = (sql == null) ? ' ' : sql.replaceAll(';', ' ').replaceAll('\n', ' ').replaceAll('\r', ' ');
122
+
123
+ let iparams = {
124
+ scenario: {
125
+ table: table,
126
+ prompt: query,
127
+ sql: sqlinput,
128
+ selectcolumns: `${selectColumns2}`
129
+ },
130
+ name: `${uJob}`,
131
+ type: 'job',
132
+ _appContext: _appContext
133
+ };
134
+
135
+ if (sql == null || sql.trim().length === 0) {
136
+ return { content: [{ type: 'text', text: 'Error: The SQL statement generated is blank. Please provide a valid natural language query that can be converted to SQL.' }] };
137
+ }
138
+
139
+ let r = await _jobSubmit(iparams);
140
+ return r;
141
+
142
+ }
143
+ };
144
+ return spec;
145
+
146
+
147
+ }
148
+ export default sasQueryTemplate;
@@ -0,0 +1,140 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { z } from 'zod';
6
+ import _jobSubmit from '../toolHelpers/_jobSubmit.js';
7
+ import _tableColumns from '../toolHelpers/_tableColumns.js';
8
+
9
+ async function sasQueryTemplate2(uparams) {
10
+
11
+ let toolname = uparams.name;
12
+ let uTable = uparams.table;
13
+ let selectColumns = uparams.selectColumns;
14
+ let selectColumns2 = (selectColumns == null) ? '_All_' : selectColumns.replaceAll(',', ' ');
15
+ let uJob = (uparams.job == null) ? 'run_sql_query' : uparams.job;
16
+
17
+ let allColumns = await _tableColumns(uTable, uparams.server);
18
+
19
+ let selcols = (selectColumns == null) ? ' ' : 'and the columns ${selectColumns}';
20
+ let instruction1 = 'The valid columns in the table ${uTable} are: ${allColumns}. ';
21
+
22
+ let description = `
23
+ You are an expert in SAS Viya PROC SQL syntax.
24
+ You are tasked with creating a SAS PROC SQL SELECT statement based on the user's natural language filter for the table ${uTable}.
25
+ The Select statement you create will have the following syntax:
26
+
27
+ SELECT ${selectColumns} from ${uTable} AS <SQL expressions>
28
+
29
+ You will then convert the user's query to a valid SAS PROC SQL SELECT expressions
30
+
31
+ The valid non-columns in the table ${uTable} are: ${allColumns}. If others are specified inform the user of the error.
32
+
33
+ Example:
34
+
35
+ User query: Origin ='Asia'
36
+ SQL expressions: WHERE Origin = 'Asia'
37
+ sql: SELECT ${selectColumns} from ${uTable} AS WHERE Origin = 'Asia'
38
+
39
+ as the query to be executed on the server.
40
+
41
+
42
+ User can optionally specify a SAS job to run the query on the SAS Server. If not specified, the default job 'run_sql_query' will be used.
43
+
44
+ The desired flow:
45
+ Instructions to LLM: Show the progress of how you are thinking about converting the natural language query to SQL.
46
+ Step 1: User provides the following prompt:
47
+ ${toolname} <natural language query>
48
+
49
+ Step 2: Convert the user's natural language query into a valid SAS PROC SQL SELECT expression to complete the following sql :
50
+
51
+ SELECT ${selectColumns} from ${uTable} AS <sql expression the LLM generated>
52
+
53
+ A good reference for
54
+ the SQL syntax is this [link](https://go.documentation.sas.com/doc/en/pgmsascdc/v_067/sqlproc/n0w2pkrm208upln11i9r4ogwyvow.htm)
55
+
56
+ Instructions to LLM:
57
+ - Do not add a semicolon to the end of the SQL statement.
58
+ - Use only the columns from the table ${uTable} in the SELECT and SQL expression.
59
+ - If the user query is ambiguous, ask for clarification instead of guessing.
60
+
61
+ Step 3: Pass these as parameters to the handler:
62
+ { table: ${uTable},
63
+ query: <user's natural language query>,
64
+ sql: <generated SQL>
65
+ }
66
+
67
+ If the user specified a job, include that in the parameters:
68
+ { table: ${uTable},
69
+ query: <user's natural language query>,
70
+ sql: <generated SQL query>,
71
+ job: <job name> }
72
+
73
+ Example 1:
74
+
75
+ Step 1: User prompt:
76
+
77
+ User prompt:
78
+ ${toolname} origin='Asia' and make='Toyota'
79
+
80
+ Step 2: Convert the query to a SQL Select statement
81
+
82
+ sql = "
83
+ SELECT ${selectColumns} from ${uTable} AS WHERE origin='Asia' and make='Toyota'
84
+ "
85
+
86
+ Step 3: Pass these to the handler
87
+ { table: ${uTable},
88
+ query: "origin='Asia' and make='Toyota",
89
+ sql: "SELECT ${selectColumns} from ${uTable} AS WHERE origin='Asia' and make='Toyota'"
90
+
91
+ }
92
+
93
+ Step 4: Handler returns the results of the query to the user. The output has a json representation of the table.
94
+
95
+
96
+ ## Desired Output Display Format
97
+ If the query is successful and returns rows, display the rows as a markdown table.
98
+
99
+ `;
100
+
101
+ let spec = {
102
+ name: toolname,
103
+ description: description,
104
+
105
+ schema: {
106
+ query: z.string(),
107
+ table: z.string().default(uTable),
108
+ sql: z.string().optional(),
109
+
110
+ },
111
+ required: ['query', 'table'],
112
+ handler: async (params) => {
113
+
114
+ let { table, query, sql } = params;
115
+ let sqlinput = (sql == null) ? ' ' : sql.replaceAll(';', ' ').replaceAll('\n', ' ').replaceAll('\r', ' ');
116
+
117
+ let iparams = {
118
+ scenario: {
119
+ table: table,
120
+ prompt: query,
121
+ sql: sqlinput,
122
+ selectcolumns: `${selectColumns2}`
123
+ },
124
+ name: `${uJob}`,
125
+ type: 'job'
126
+ };
127
+
128
+ if (sql == null || sql.trim().length === 0) {
129
+ return { content: [{ type: 'text', text: 'Error: The SQL statement generated is blank. Please provide a valid natural language query that can be converted to SQL.' }] };
130
+ }
131
+ let r = await _jobSubmit(iparams);
132
+ return r;
133
+
134
+ }
135
+ };
136
+ return spec;
137
+
138
+
139
+ }
140
+ export default sasQueryTemplate2;