Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,22 @@ for (SearchMatch match : response.getResults()) {
}
```

### Natural Language to SQL (Nsql)

Use `nsql()` to have the runtime's configured LLM translate a natural-language question
into SQL and run it, or `nsqlGenerateSql()` to only generate the SQL without running it.
Requires an LLM model configured in the Spicepod — see the
[text-to-SQL docs](https://docs.spice.ai/features/text-to-sql).

```java
NsqlResponse response = client.nsql(new NsqlRequest("how many taxi trips were there yesterday?"));
System.out.println(response.getSql()); // the SQL the model generated
System.out.println(response.getData()); // the rows it returned, decoded from JSON

// Inspect or edit the generated SQL without running it:
String sql = client.nsqlGenerateSql(new NsqlRequest("how many taxi trips were there yesterday?"));
```

### Logging

The SDK uses SLF4J for logging, allowing you to plug in your preferred logging implementation (Logback, Log4j2, java.util.logging, etc.).
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/ai/spice/NsqlField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2026 The Spice.ai OSS Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package ai.spice;

import com.google.gson.JsonElement;
import com.google.gson.annotations.SerializedName;

/**
* Describes one column of an {@link NsqlResponse}.
*/
public class NsqlField {
@SerializedName("name")
private String name;

@SerializedName("data_type")
private JsonElement dataType;

@SerializedName("nullable")
private boolean nullable;

/**
* The column name.
*
* @return the column name
*/
public String getName() {
return this.name;
}

/**
* The column's Arrow type, in the runtime's raw JSON encoding. Simple
* types encode as a quoted string ({@code "Utf8"}, {@code "Int64"});
* parameterized ones as an object (for example
* {@code {"Timestamp":["Nanosecond",null]}}) — returned as-is rather than
* modeled, since the shape varies by type.
*
* @return the raw data type
*/
public JsonElement getDataType() {
return this.dataType;
}

/**
* Whether the column admits nulls.
*
* @return true if the column is nullable
*/
public boolean isNullable() {
return this.nullable;
}
}
138 changes: 138 additions & 0 deletions src/main/java/ai/spice/NsqlRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright 2026 The Spice.ai OSS Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package ai.spice;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.google.gson.annotations.SerializedName;

/**
* A natural-language query against the runtime's {@code /v1/nsql} endpoint.
*
* <p>
* Only {@code query} is required. The runtime needs an LLM model configured
* in the Spicepod to translate it; when exactly one is configured, {@code
* model} may be left unset and the runtime selects it.
*/
public class NsqlRequest {
@SerializedName("query")
private final String query;

@SerializedName("model")
private String model;

@SerializedName("datasets")
private List<String> datasets;

// Boxed and left null when unset, rather than a primitive boolean, so Gson
// omits this field entirely instead of always sending "sample_data_enabled":
// false — the runtime already defaults it to false, and gospice's equivalent
// field is "omitempty".
@SerializedName("sample_data_enabled")
private Boolean sampleDataEnabled;

@SerializedName("prompt_cache_key")
private String promptCacheKey;

/**
* Creates a request for the given natural-language query.
*
* @param query the question to answer, in natural language
*/
public NsqlRequest(String query) {
this.query = query;
}

/**
* Names the LLM used to generate SQL. When unset, the runtime uses the
* only compatible model configured in the Spicepod, and reports an error
* if there is not exactly one.
*
* @param model the model name
* @return this request
*/
public NsqlRequest withModel(String model) {
this.model = model;
return this;
}

/**
* Hints which datasets to sample when building model context. This is a
* sampling hint only — it does not restrict which tables the generated
* query may reference. When unset, all datasets are used.
*
* @param datasets the dataset names to sample
* @return this request
*/
public NsqlRequest withDatasets(List<String> datasets) {
this.datasets = datasets == null ? null : new ArrayList<>(datasets);
return this;
}

/**
* Includes sample rows in the context given to the model. It improves
* generation on ambiguous schemas at the cost of sending data values to
* the model.
*
* @param sampleDataEnabled whether to include sample data
* @return this request
*/
public NsqlRequest withSampleDataEnabled(boolean sampleDataEnabled) {
this.sampleDataEnabled = sampleDataEnabled;
return this;
}

/**
* A stable key forwarded to the model provider for prompt caching. Reuse
* it across related requests to benefit from it.
*
* @param promptCacheKey the cache key
* @return this request
*/
public NsqlRequest withPromptCacheKey(String promptCacheKey) {
this.promptCacheKey = promptCacheKey;
return this;
}

public String getQuery() {
return this.query;
}

public String getModel() {
return this.model;
}

public List<String> getDatasets() {
return this.datasets == null ? null : Collections.unmodifiableList(this.datasets);
}

public boolean isSampleDataEnabled() {
return Boolean.TRUE.equals(this.sampleDataEnabled);
}

public String getPromptCacheKey() {
return this.promptCacheKey;
}
}
89 changes: 89 additions & 0 deletions src/main/java/ai/spice/NsqlResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright 2026 The Spice.ai OSS Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package ai.spice;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.google.gson.annotations.SerializedName;

/**
* The result of a {@link SpiceClient#nsql(NsqlRequest)} call: the SQL the
* runtime's configured LLM generated from a natural-language query, and the
* rows it produced.
*/
public class NsqlResponse {
@SerializedName("sql")
private String sql;

@SerializedName("row_count")
private int rowCount;

@SerializedName("schema")
private NsqlSchema schema;

@SerializedName("data")
private List<Map<String, Object>> data;

/**
* The query the model generated. Worth logging: a surprising result is
* usually a surprising query.
*
* @return the generated SQL
*/
public String getSql() {
return this.sql;
}

/**
* The number of rows returned.
*
* @return the row count
*/
public int getRowCount() {
return this.rowCount;
}

/**
* The schema describing the columns in {@link #getData()}.
*
* @return the schema, or an empty one when the runtime omitted it
*/
public NsqlSchema getSchema() {
return this.schema == null ? new NsqlSchema() : this.schema;
}

/**
* The rows, each keyed by column name. Values are decoded from JSON, so
* they carry JSON's types rather than the Arrow types named in {@link
* #getSchema()} — numbers arrive as {@code Double}. Use {@link
* SpiceClient#nsqlGenerateSql(NsqlRequest)} with {@code query} or {@code
* queryWithParams} when Arrow-typed results matter.
*
* @return the rows, or an empty list when the runtime returned none
*/
public List<Map<String, Object>> getData() {
return this.data == null ? Collections.emptyList() : this.data;
}
}
50 changes: 50 additions & 0 deletions src/main/java/ai/spice/NsqlSchema.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2026 The Spice.ai OSS Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package ai.spice;

import java.util.Collections;
import java.util.List;

import com.google.gson.annotations.SerializedName;

/**
* The schema of the rows an {@link SpiceClient#nsql(NsqlRequest)} call
* returned.
*
* <p>
* {@link #getFields()} is empty when the generated query returned no rows —
* the runtime omits the schema body in that case.
*/
public class NsqlSchema {
@SerializedName("fields")
private List<NsqlField> fields;

/**
* The columns of the result, in order.
*
* @return the fields, or an empty list when the runtime omitted them
*/
public List<NsqlField> getFields() {
return this.fields == null ? Collections.emptyList() : this.fields;
}
}
Loading
Loading