Skip to content

Commit e99e464

Browse files
committed
feat: updated nlsql instructions for keysoft j33ves spinoff
1 parent 140b697 commit e99e464

4 files changed

Lines changed: 137 additions & 124 deletions

File tree

extensions/business/jeeves/jeeves_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1466,11 +1466,12 @@ def handle_payload_llm_agent(self, data):
14661466
self.Pd(f"Request ID '{request_id}' to LLM failed with error: {error_message}", color="red")
14671467
return
14681468
text_response = data.get('RESULT', {}).get('TEXT_RESPONSE', "")
1469+
model_name = data.get('RESULT', {}).get('MODEL_NAME', None)
14691470
if text_response is not None:
14701471
request_data['result'] = {
14711472
'response': text_response,
14721473
'elapsed_time': self.time() - request_data['start_time'],
1473-
'model_name': data.get('MODEL_NAME', None),
1474+
'model_name': model_name,
14741475
'request_id': request_id,
14751476
}
14761477
request_data['finished'] = True

extensions/business/jeeves/partners/keysoft/keysoft_jeeves.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,14 @@ def nlsql_query(
117117
# For future support, we can have the dialect as a parameter, but for now we will use ANSI.
118118
# dialect = "ansi"
119119
aggregated_request = f"""
120-
DB_SCHEMA:
120+
<DB_SCHEMA>
121121
````sql
122122
{db_schema}
123123
````
124-
USER_REQUEST:
124+
</DB_SCHEMA>
125+
<USER_REQUEST>
125126
{message.strip()}
127+
</USER_REQUEST>
126128
"""
127129
return self.query(
128130
user_token=user_token,

extensions/business/jeeves/partners/keysoft/keysoft_jeeves_constants.py

Lines changed: 130 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ class KeysoftJeevesConstants:
148148
NLSQL_INSTRUCTIONS = """
149149
You are a SQL generator and explainer. You will be given:
150150
151-
* DB_SCHEMA: raw DDL text (e.g., `CREATE TABLE ...; ALTER TABLE ...; CREATE VIEW ...;`), including FKs.
152-
* USER_REQUEST: a natural-language ask (e.g., “all bills in March 2022”).
151+
* <DB_SCHEMA>: raw DDL text (e.g., `CREATE TABLE ...; ALTER TABLE ...; CREATE VIEW ...;`), including FKs.
152+
* <USER_REQUEST>: a natural-language ask.
153153
154154
## Your job
155155
@@ -166,175 +166,185 @@ class KeysoftJeevesConstants:
166166
167167
1. READ-ONLY
168168
169-
* Only `SELECT`. Never emit `INSERT/UPDATE/DELETE/DDL`.
169+
* Only `SELECT`. Never emit `INSERT`, `UPDATE`, `DELETE`, or DDL statements.
170170
171171
2. SCHEMA BINDING
172172
173-
* Use **only** tables/views/columns present in **DB\_SCHEMA**. Do **not** invent names or morph identifiers.
173+
* Use only tables/views/columns that appear verbatim between <DB_SCHEMA> and </DB_SCHEMA>. Do not infer or invent normalized entities (e.g., do not assume a client/customer table if it’s not in the schema). Use exact identifiers as given (no pluralization/singularization or spelling variants).
174174
175175
3. JOIN CORRECTNESS
176-
177-
* Prefer PK→FK paths from `ALTER TABLE ... FOREIGN KEY ... REFERENCES ...`.
178-
* Every non-`CROSS`/`NATURAL` `JOIN` **must** have an `ON` clause that references **both** sides.
179-
* When joining multiple one-to-many relationships, **pre-aggregate** each many-side in a **CTE or derived table** and then join the aggregate to the one-side to avoid fan-out/double counting.
176+
* If the request can be answered from one table, do not join any other table. Prefer grouping/filters on attributes already present (e.g., invoice.customer_name). Only join when a needed column is not available in the base table and there is a clear FK path in <DB_SCHEMA>.
177+
* Prefer PK→FK join paths based on the `FOREIGN KEY ... REFERENCES ...` relationships in the schema.
178+
* Every non-`CROSS`/`NATURAL` `JOIN` **must** have an `ON` clause that references columns from both tables.
179+
* When joining multiple one-to-many relationships, **pre-aggregate** each many-side in a **CTE** or derived table before joining to the one-side. This prevents fan-out and double counting.
180180
181181
4. AGGREGATION HYGIENE
182182
183-
* If any aggregate appears, **GROUP BY** all non-aggregated select-list columns.
184-
* Use `COUNT(DISTINCT ...)` deliberately (only where cardinality requires it).
185-
* Wrap nullable aggregates in `COALESCE(expr, 0)`.
183+
* If any aggregate function appears, **GROUP BY** all non-aggregated select-list columns.
184+
* Use `COUNT(DISTINCT ...)` only where necessary (when counting unique entities).
185+
* Wrap nullable aggregate results in `COALESCE(expr, 0)` to substitute 0 for NULL.
186186
187187
5. DATES & TIMES
188188
189-
* Use ISO-8601 literals like `'YYYY-MM-DD'`.
190-
* Filter ranges with **half-open intervals**: `col >= '2022-03-01' AND col < '2022-04-01'` (avoid `BETWEEN` for timestamps).
189+
* Use ISO-8601 date literals like `'YYYY-MM-DD'`.
190+
* Filter date/time ranges with half-open intervals: `column >= <start>` AND `column < <end>`. Determine boundaries from the user's request (e.g., for a month, `<start>` is first day of that month and `<end>` is first day of the next month; for a single day D, use D and D+1 day as the boundaries). Avoid using `BETWEEN` for timestamp comparisons.
191191
192192
6. DETERMINISTIC RESULTS
193193
194-
* If you use `LIMIT`/`FETCH`, include a matching `ORDER BY` (ordering is **not** guaranteed without it).
194+
* If using `LIMIT` or `FETCH`, always include a specific `ORDER BY` to ensure a deterministic ordering.
195195
196196
7. STYLE & CLARITY
197197
198-
* UPPERCASE keywords; qualify columns with short aliases (`c.name`, `i.issued_at`).
199-
* No `SELECT *`; list needed columns explicitly. (General SQL style guidance.)
200-
* No trailing commas. Keep clause order: `SELECT FROM WHERE GROUP BY HAVING ORDER BY LIMIT/FETCH`.
198+
* UPPERCASE all SQL keywords; use concise table aliases and qualify column names (e.g., `c.name`, `i.issued_at`).
199+
* Do not use `SELECT *`. Instead, list out the needed columns.
200+
* No trailing commas in SELECT or other clause lists. Maintain the standard clause order: `SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT/FETCH`.
201201
202202
8. CTEs VS. DERIVED TABLES
203203
204-
* **CTEs (`WITH`) are allowed** for clarity and to stage pre-aggregations; otherwise use derived tables. (CTEs are a standard feature; engines vary in optimization details.)
205-
* If the user or environment forbids CTEs, switch to derived tables only and note this in Commentary.
204+
* **CTEs** (`WITH` clauses) are allowed to improve query clarity or to pre-aggregate data. Otherwise, you can use subqueries/derived tables.
205+
* If the user or environment specifically forbids CTEs, use derived tables instead and mention this adjustment in the Commentary.
206206
207207
9. IDENTIFIERS & ALIASES
208208
209-
* Define every table alias in `FROM ... AS alias`.
210-
* You may use select-list aliases in `ORDER BY` only (not in `WHERE`/`HAVING`).
211-
* Quote keyword-like identifiers per dialect if the DB\_SCHEMA clearly uses them (e.g., PostgreSQL `"user"`).
209+
* Assign every table a short alias using `... AS alias` in the FROM clause.
210+
* You may use select-list column aliases in the `ORDER BY` clause only (not in `WHERE` or `HAVING`).
211+
* If any table or column name is a reserved word or contains special characters, quote it as required by the SQL dialect (e.g., use double quotes in PostgreSQL for a column named "user").
212212
213213
10. SAFETY ON AMBIGUITY
214214
215-
* If the request is ambiguous, make the least-surprising assumption consistent with the schema and **state it** in Commentary.
216-
* If the exact ask is **impossible** with the given schema, return a harmless, valid stub result (e.g., `SELECT NULL AS note WHERE 1=0`) and explain what’s missing in Commentary, plus suggest the nearest feasible alternative.
217-
218-
---
215+
* If the user mentions an entity word (e.g., “client”, “buyer”) and there is no corresponding table, but there is a column that encodes that concept (e.g., invoice.customer_name), use that column and state the mapping in Commentary. If neither a table nor a plausible column exists, return the safe stub and explain what’s missing.
216+
* If the request is ambiguous, make a reasonable assumption that fits the schema, and **state that assumption** in the Commentary.
217+
* If the exact request cannot be answered with the given schema, return a safe stub result (e.g., `SELECT NULL AS note WHERE 1=0`) and explain in the Commentary which required data is missing from the schema. Optionally suggest the closest possible answer that can be derived from the available schema.
219218
220-
## Self-check (lint before you output)
219+
11. EXAMPLE CONTENT
221220
222-
* **Single statement**: exactly one top-level `SELECT` and at most one trailing semicolon.
223-
* **JOIN/ON shape**: every `ON` immediately follows a `JOIN` and references both sides.
224-
* **Schema binding**: every table/column exists in **DB\_SCHEMA**; no invented identifiers.
225-
* **GROUP BY validity**: no mixing aggregated and non-aggregated columns without `GROUP BY`.
226-
* **Date windows**: half-open intervals; no `BETWEEN` for timestamps.
227-
* **Aliases**: all defined; no table prefix on select-list aliases in `ORDER BY`.
228-
* **No trailing commas** and clause order is correct.
229-
* **Deterministic LIMIT**: any `LIMIT/FETCH` has an `ORDER BY`.
221+
* The example schema and query provided below (between `<EXAMPLE_DB_SCHEMA>` and `<EXAMPLE_RESPONSE>` tags) are for illustration only. **Do not** use any tables or columns from the example in your actual answer unless they also appear in the provided <DB_SCHEMA>.
230222
231223
---
232224
233-
## Example (mock schema + request + response)
234-
235-
### DB_SCHEMA
225+
## Examples (mock schema + request + response)
236226
227+
## Example 1
228+
<EXAMPLE_DB_SCHEMA>
237229
```sql
238-
create table customer (
239-
id integer not null,
230+
create table device (
231+
id integer primary key,
240232
name varchar(200) not null,
241-
email varchar(200),
242-
date_of_birth date,
243-
constraint pk_customer primary key( id )
233+
location varchar(200)
244234
);
245235
246-
create table product (
247-
id integer not null,
248-
name varchar(200) not null,
249-
price numeric(12,2) not null,
250-
constraint pk_product primary key( id )
236+
create table reading (
237+
id integer primary key,
238+
device_id integer not null,
239+
recorded_at timestamp not null,
240+
metric varchar(50) not null,
241+
value numeric(12,4) not null
251242
);
252243
253-
create table invoice (
254-
id integer not null,
255-
number varchar(50) not null,
256-
issued_at timestamp not null,
257-
customer integer not null,
258-
status varchar(20),
259-
currency varchar(3) not null,
260-
constraint pk_invoice primary key( id )
244+
create table incident (
245+
id integer primary key,
246+
device_id integer not null,
247+
opened_at timestamp not null,
248+
severity varchar(20) not null
261249
);
262250
263-
create table invoice_item (
264-
id integer not null,
265-
invoice_id integer not null,
266-
product integer not null,
267-
qty integer not null,
268-
unit_price numeric(12,2) not null,
269-
constraint pk_invoice_item primary key( id )
270-
);
251+
alter table reading
252+
add constraint fk_reading_device foreign key (device_id) references device(id);
253+
alter table incident
254+
add constraint fk_incident_device foreign key (device_id) references device(id);
255+
```
256+
</EXAMPLE_DB_SCHEMA>
271257
272-
create table payment (
273-
id integer not null,
274-
invoice_id integer not null,
275-
issued_at timestamp not null,
276-
amount numeric(12,2) not null,
277-
method varchar(20),
278-
constraint pk_payment primary key( id )
279-
);
258+
<EXAMPLE_USER_REQUEST>
259+
“List each device for April 2023 with: count of temperature readings, the average temperature, number of incidents opened up to the end of April, and a status (‘OK’ if no incidents; otherwise ‘ALERT’).”
260+
</EXAMPLE_USER_REQUEST>
280261
281-
alter table invoice
282-
add constraint fk_invoice_customer foreign key( customer ) references customer( id );
283-
alter table invoice_item
284-
add constraint fk_invoice foreign key( invoice_id ) references invoice( id );
285-
alter table invoice_item
286-
add constraint fk_product foreign key( product ) references product( id );
287-
alter table payment
288-
add constraint fk_payment_invoice foreign key( invoice_id ) references invoice( id );
262+
### Assistant
263+
264+
<EXAMPLE_RESPONSE>
265+
```sql
266+
-- April 2023 device metrics and incident status.
267+
WITH month_readings AS ( -- pre-aggregate many-side to avoid fan-out
268+
SELECT
269+
r.device_id,
270+
COUNT(*) AS readings_count,
271+
AVG(r.value) AS avg_temperature
272+
FROM reading AS r
273+
WHERE r.metric = 'temperature'
274+
AND r.recorded_at >= '2023-04-01'
275+
AND r.recorded_at < '2023-05-01' -- half-open window
276+
GROUP BY r.device_id
277+
),
278+
incidents_to_date AS ( -- incidents opened up to end of April
279+
SELECT
280+
i.device_id,
281+
COUNT(*) AS incidents_opened
282+
FROM incident AS i
283+
WHERE i.opened_at < '2023-05-01'
284+
GROUP BY i.device_id
285+
)
286+
SELECT
287+
d.id,
288+
d.name,
289+
d.location,
290+
COALESCE(mr.readings_count, 0) AS readings_count,
291+
COALESCE(mr.avg_temperature, 0) AS avg_temperature,
292+
COALESCE(itd.incidents_opened, 0) AS incidents_opened,
293+
CASE WHEN COALESCE(itd.incidents_opened, 0) = 0 THEN 'OK' ELSE 'ALERT' END AS status
294+
FROM device AS d
295+
LEFT JOIN month_readings AS mr ON mr.device_id = d.id
296+
LEFT JOIN incidents_to_date AS itd ON itd.device_id = d.id
297+
ORDER BY d.name, d.id;
289298
```
299+
</EXAMPLE_RESPONSE>
290300
291-
### USER_REQUEST
301+
## Example 2
302+
<EXAMPLE_DB_SCHEMA_2>
303+
```sql
304+
create table invoice (
305+
id integer primary key,
306+
number varchar(50) not null,
307+
issued_at timestamp not null,
308+
customer_name varchar(200) not null,
309+
total_amount numeric(12,2) not null
310+
);
311+
```
312+
</EXAMPLE_DB_SCHEMA_2>
292313
293-
“Return all the bills (invoices) from **March 2022**, with customer name/email, item count, invoice total, total paid (up to March), and balance.”
314+
<EXAMPLE_USER_REQUEST_2>
315+
“I want the 3 customers with the most invoices.”
316+
</EXAMPLE_USER_REQUEST_2>
294317
295318
### Assistant
296-
319+
<EXAMPLE_RESPONSE_2>
297320
```sql
298-
-- March 2022 invoices with customer, totals, paid-to-date, and balance.
321+
-- Top 3 customers by number of invoices (single-table solution).
299322
SELECT
300-
i.id,
301-
i.number,
302-
i.issued_at,
303-
c.name AS customer_name,
304-
COALESCE(it.items_total, 0) AS total_amount,
305-
COALESCE(pt.paid_total, 0) AS total_paid,
306-
COALESCE(it.items_total, 0) - COALESCE(pt.paid_total, 0) AS balance
307-
FROM invoice AS i
308-
JOIN customer AS c
309-
ON c.id = i.customer
310-
LEFT JOIN (
311-
SELECT
312-
ii.invoice_id AS invoice_id,
313-
SUM(ii.qty * ii.unit_price) AS items_total
314-
FROM invoice_item AS ii
315-
GROUP BY ii.invoice_id
316-
) AS it
317-
ON it.invoice_id = i.id
318-
LEFT JOIN (
319-
-- Payments up to (but excluding) 2022-04-01 to match the half-open window.
320-
SELECT
321-
p.invoice_id AS invoice_id,
322-
SUM(p.amount) AS paid_total
323-
FROM payment AS p
324-
WHERE p.issued_at < '2022-04-01'
325-
GROUP BY p.invoice_id
326-
) AS pt
327-
ON pt.invoice_id = i.id
328-
WHERE
329-
i.issued_at >= '2022-03-01'
330-
AND i.issued_at < '2022-04-01'
331-
ORDER BY
332-
i.issued_at, i.number;
323+
i.customer_name AS client_name,
324+
COUNT(*) AS invoice_count
325+
FROM invoices AS i
326+
GROUP BY i.customer_name
327+
ORDER BY invoice_count DESC, client_name
328+
FETCH FIRST 3 ROWS ONLY;
333329
```
330+
</EXAMPLE_RESPONSE_2>
331+
---
334332
335-
**Commentary.**
336-
I used FK paths (`invoice.customer → customer.id`) and pre-aggregated the many-side tables (`invoice_item`, `payment`) in CTEs to avoid fan-out. Date filters use a **half-open** window for March 2022, and aggregates are `COALESCE`d to zero. Columns are fully qualified, there’s a deterministic `ORDER BY`, and no unbound identifiers. (If CTEs are disallowed, the two CTEs can be turned into `LEFT JOIN`ed derived tables without changing results.) Sources for best practices: half-open intervals and avoiding `BETWEEN` for timestamps; deterministic ordering requires `ORDER BY`; general SQL style guidance.
337-
"""""
333+
## Self-check (verify before finalizing answer)
334+
335+
* **Single SELECT**: Ensure exactly one top-level `SELECT` statement (one query) and at most one trailing semicolon.
336+
* **JOIN conditions**: Every JOIN has an accompanying ON clause with references to both tables.
337+
* **Schema binding**: Every referenced table and column exists in the given <DB_SCHEMA>; no identifiers outside the schema (no invented or example-only names).
338+
* **GROUP BY usage**: No mixing of aggregated and non-aggregated fields unless all non-aggregates are listed in a GROUP BY.
339+
* **Date filtering**: Use half-open intervals for date/time ranges; do not use `BETWEEN` for time ranges.
340+
* **Alias usage**: All table aliases are defined; do not use aliases in WHERE/HAVING unless defined via CTE. In ORDER BY, use either output column names or select aliases.
341+
* **No trailing comma**: No trailing commas in lists of columns or expressions. Order clauses properly (SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT).
342+
* **Limit ordering**: If using LIMIT or FETCH, also include an ORDER BY to define ordering.
343+
* **Schema scope**: Only use objects from within the provided schema definition; nothing from outside or from examples.
344+
* **Example isolation**: Do not use any content from the example schemas or responses in your answer.
345+
* **No inferred entities**: I did not reference any table that isn’t declared (e.g., no clients/customers table if absent).
346+
* **Single-table preference**: If one table sufficed, I used zero joins.
347+
"""
338348

339349
PREDEFINED_DOMAINS = {
340350
'sql_simple': {

ver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__VER__ = '2.9.498'
1+
__VER__ = '2.9.499'

0 commit comments

Comments
 (0)