@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.
- package/.babelrc +6 -0
- package/.env +13 -0
- package/.env.http +29 -0
- package/CHANGES.md +2 -0
- package/CONTRIBUTING.md +14 -0
- package/ContributorAgreement.txt +56 -0
- package/LICENSE +205 -0
- package/LICENSES.json +105 -0
- package/QUICK_REFERENCE.md +378 -0
- package/README.md +267 -0
- package/SECURITY.md +31 -0
- package/SUPPORT.md +3 -0
- package/TOOL_DESCRIPTION_TEMPLATE.md +157 -0
- package/TOOL_UPDATES_SUMMARY.md +208 -0
- package/cli.js +214 -0
- package/labs/.subclass.json +13 -0
- package/labs/README.md +4 -0
- package/mcpConfigurations/README.md +3 -0
- package/mcpConfigurations/http.json +8 -0
- package/mcpConfigurations/stdio.json +20 -0
- package/mcpConfigurations/stdiodev.json +20 -0
- package/mcpserver.png +0 -0
- package/openApi.json +106 -0
- package/openApi.yaml +84 -0
- package/package.json +72 -0
- package/sas-mcp-tools-reference.md +600 -0
- package/sasCode/sas-sql-query.sas +33 -0
- package/sasCode/sas_sql_tool.json +237 -0
- package/scripts/getViyaca.sh +8 -0
- package/src/core.js +19 -0
- package/src/coreSSE.js +14 -0
- package/src/corehttp.js +335 -0
- package/src/createHttpTransport.js +26 -0
- package/src/createMcpServer.js +76 -0
- package/src/db/scrModels.js +23 -0
- package/src/toolSet/devaScore.js +69 -0
- package/src/toolSet/findJob.js +90 -0
- package/src/toolSet/findJobdef.js +95 -0
- package/src/toolSet/findLibrary.js +100 -0
- package/src/toolSet/findModel.js +83 -0
- package/src/toolSet/findTable.js +94 -0
- package/src/toolSet/getEnv.js +72 -0
- package/src/toolSet/listJobdefs.js +96 -0
- package/src/toolSet/listJobs.js +110 -0
- package/src/toolSet/listLibraries.js +90 -0
- package/src/toolSet/listModels.js +83 -0
- package/src/toolSet/listTables.js +95 -0
- package/src/toolSet/makeTools.js +75 -0
- package/src/toolSet/mcp server .png +0 -0
- package/src/toolSet/modelInfo.js +87 -0
- package/src/toolSet/modelScore.js +131 -0
- package/src/toolSet/readTable.js +104 -0
- package/src/toolSet/runCasProgram.js +118 -0
- package/src/toolSet/runJob.js +81 -0
- package/src/toolSet/runJobdef.js +85 -0
- package/src/toolSet/runMacro.js +82 -0
- package/src/toolSet/runProgram.js +145 -0
- package/src/toolSet/sasQuery.js +126 -0
- package/src/toolSet/sasQueryTemplate.js +148 -0
- package/src/toolSet/sasQueryTemplate2.js +140 -0
- package/src/toolSet/scrInfo.js +55 -0
- package/src/toolSet/scrScore.js +71 -0
- package/src/toolSet/searchAssets.js +52 -0
- package/src/toolSet/setContext.js +98 -0
- package/src/toolSet/superstat.js +60 -0
- package/src/toolSet/tableInfo.js +102 -0
- package/src/toolhelpers/_catalogSearch.js +87 -0
- package/src/toolhelpers/_getEnv.js +10 -0
- package/src/toolhelpers/_itemsData.js +28 -0
- package/src/toolhelpers/_jobSubmit.js +78 -0
- package/src/toolhelpers/_listJobdefs.js +59 -0
- package/src/toolhelpers/_listJobs.js +63 -0
- package/src/toolhelpers/_listLibrary.js +56 -0
- package/src/toolhelpers/_listModels.js +41 -0
- package/src/toolhelpers/_listTables.js +52 -0
- package/src/toolhelpers/_masDescribe.js +27 -0
- package/src/toolhelpers/_masScoring.js +64 -0
- package/src/toolhelpers/_readTable.js +69 -0
- package/src/toolhelpers/_scrInfo.js +32 -0
- package/src/toolhelpers/_scrScore.js +49 -0
- package/src/toolhelpers/_submitCasl.js +34 -0
- package/src/toolhelpers/_submitCode.js +96 -0
- package/src/toolhelpers/_submitMacro.js +24 -0
- package/src/toolhelpers/_tableColumns.js +61 -0
- package/src/toolhelpers/_tableInfo.js +72 -0
- package/src/toolhelpers/deleteSession.js +13 -0
- package/src/toolhelpers/getLogonPayload.js +100 -0
- package/src/toolhelpers/getOpts.js +43 -0
- package/src/toolhelpers/getOptsViya.js +38 -0
- package/src/toolhelpers/getStoreOpts.js +18 -0
- package/src/toolhelpers/getToken.js +40 -0
- package/src/toolhelpers/refreshToken.js +48 -0
- package/test/README.md +63 -0
- package/test/listLibraries.test.js +245 -0
- package/tool-developer-guide.md +80 -0
- 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;
|