mirror of
https://github.com/amithkoujalgi/ollama4j.git
synced 2025-05-15 03:47:13 +02:00
Support for structured output
Added support for structured output
This commit is contained in:
parent
e62a7511db
commit
b9b18271a1
8
Makefile
8
Makefile
@ -7,13 +7,19 @@ dev:
|
|||||||
pre-commit install --install-hooks
|
pre-commit install --install-hooks
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
mvn -B clean install -Dgpg.skip=true
|
||||||
|
|
||||||
|
full-build:
|
||||||
mvn -B clean install
|
mvn -B clean install
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
mvn clean test -Punit-tests
|
mvn clean test -Punit-tests
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
mvn clean verify -Pintegration-tests
|
export USE_EXTERNAL_OLLAMA_HOST=false && mvn clean verify -Pintegration-tests
|
||||||
|
|
||||||
|
integration-tests-local:
|
||||||
|
export USE_EXTERNAL_OLLAMA_HOST=true && export OLLAMA_HOST=http://localhost:11434 && mvn clean verify -Pintegration-tests -Dgpg.skip=true
|
||||||
|
|
||||||
doxygen:
|
doxygen:
|
||||||
doxygen Doxyfile
|
doxygen Doxyfile
|
||||||
|
@ -209,6 +209,9 @@ pip install pre-commit
|
|||||||
|
|
||||||
#### Setup dev environment
|
#### Setup dev environment
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> If you're on Windows, install [Chocolatey Package Manager for Windows](https://chocolatey.org/install) and then install `make` by running `choco install make`. Just a little tip - run the command with administrator privileges if installation faiils.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
@ -13,7 +13,7 @@ with [extra parameters](https://github.com/jmorganca/ollama/blob/main/docs/model
|
|||||||
Refer
|
Refer
|
||||||
to [this](/apis-extras/options-builder).
|
to [this](/apis-extras/options-builder).
|
||||||
|
|
||||||
## Try asking a question about the model.
|
## Try asking a question about the model
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.github.ollama4j.OllamaAPI;
|
import io.github.ollama4j.OllamaAPI;
|
||||||
@ -87,7 +87,7 @@ You will get a response similar to:
|
|||||||
> The capital of France is Paris.
|
> The capital of France is Paris.
|
||||||
> Full response: The capital of France is Paris.
|
> Full response: The capital of France is Paris.
|
||||||
|
|
||||||
## Try asking a question from general topics.
|
## Try asking a question from general topics
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.github.ollama4j.OllamaAPI;
|
import io.github.ollama4j.OllamaAPI;
|
||||||
@ -135,7 +135,7 @@ You'd then get a response from the model:
|
|||||||
> semi-finals. The tournament was
|
> semi-finals. The tournament was
|
||||||
> won by the England cricket team, who defeated New Zealand in the final.
|
> won by the England cricket team, who defeated New Zealand in the final.
|
||||||
|
|
||||||
## Try asking for a Database query for your data schema.
|
## Try asking for a Database query for your data schema
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.github.ollama4j.OllamaAPI;
|
import io.github.ollama4j.OllamaAPI;
|
||||||
@ -161,6 +161,7 @@ public class Main {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
_Note: Here I've used
|
_Note: Here I've used
|
||||||
a [sample prompt](https://github.com/ollama4j/ollama4j/blob/main/src/main/resources/sample-db-prompt-template.txt)
|
a [sample prompt](https://github.com/ollama4j/ollama4j/blob/main/src/main/resources/sample-db-prompt-template.txt)
|
||||||
containing a database schema from within this library for demonstration purposes._
|
containing a database schema from within this library for demonstration purposes._
|
||||||
@ -172,4 +173,125 @@ SELECT customers.name
|
|||||||
FROM sales
|
FROM sales
|
||||||
JOIN customers ON sales.customer_id = customers.customer_id
|
JOIN customers ON sales.customer_id = customers.customer_id
|
||||||
GROUP BY customers.name;
|
GROUP BY customers.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Generate structured output
|
||||||
|
|
||||||
|
### With response as a `Map`
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.github.ollama4j.OllamaAPI;
|
||||||
|
import io.github.ollama4j.utils.Utilities;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatRequest;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatResult;
|
||||||
|
import io.github.ollama4j.models.response.OllamaResult;
|
||||||
|
import io.github.ollama4j.types.OllamaModelType;
|
||||||
|
|
||||||
|
public class StructuredOutput {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String host = "http://localhost:11434/";
|
||||||
|
|
||||||
|
OllamaAPI api = new OllamaAPI(host);
|
||||||
|
|
||||||
|
String chatModel = "qwen2.5:0.5b";
|
||||||
|
api.pullModel(chatModel);
|
||||||
|
|
||||||
|
String prompt = "Ollama is 22 years old and is busy saving the world. Respond using JSON";
|
||||||
|
Map<String, Object> format = new HashMap<>();
|
||||||
|
format.put("type", "object");
|
||||||
|
format.put("properties", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("age", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("type", "integer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
put("available", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("type", "boolean");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
format.put("required", Arrays.asList("age", "available"));
|
||||||
|
|
||||||
|
OllamaResult result = api.generate(chatModel, prompt, format);
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With response mapped to specified class type
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.github.ollama4j.OllamaAPI;
|
||||||
|
import io.github.ollama4j.utils.Utilities;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatRequest;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
|
||||||
|
import io.github.ollama4j.models.chat.OllamaChatResult;
|
||||||
|
import io.github.ollama4j.models.response.OllamaResult;
|
||||||
|
import io.github.ollama4j.types.OllamaModelType;
|
||||||
|
|
||||||
|
public class StructuredOutput {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String host = Utilities.getFromConfig("host");
|
||||||
|
|
||||||
|
OllamaAPI api = new OllamaAPI(host);
|
||||||
|
|
||||||
|
int age = 28;
|
||||||
|
boolean available = false;
|
||||||
|
|
||||||
|
String prompt = "Batman is " + age + " years old and is " + (available ? "available" : "not available")
|
||||||
|
+ " because he is busy saving Gotham City. Respond using JSON";
|
||||||
|
|
||||||
|
Map<String, Object> format = new HashMap<>();
|
||||||
|
format.put("type", "object");
|
||||||
|
format.put("properties", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("age", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("type", "integer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
put("available", new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("type", "boolean");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
format.put("required", Arrays.asList("age", "available"));
|
||||||
|
|
||||||
|
OllamaResult result = api.generate(CHAT_MODEL_QWEN_SMALL, prompt, format);
|
||||||
|
|
||||||
|
Person person = result.as(Person.class);
|
||||||
|
System.out.println(person.getAge());
|
||||||
|
System.out.println(person.getAvailable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
class Person {
|
||||||
|
private int age;
|
||||||
|
private boolean available;
|
||||||
|
}
|
||||||
```
|
```
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,26 @@
|
|||||||
package io.github.ollama4j.models.response;
|
package io.github.ollama4j.models.response;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import static io.github.ollama4j.utils.Utils.getObjectMapper;
|
import static io.github.ollama4j.utils.Utils.getObjectMapper;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import java.util.HashMap;
|
||||||
import lombok.Data;
|
import java.util.Map;
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/** The type Ollama result. */
|
/** The type Ollama result. */
|
||||||
@Getter
|
@Getter
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@Data
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class OllamaResult {
|
public class OllamaResult {
|
||||||
/**
|
/**
|
||||||
* -- GETTER --
|
* -- GETTER --
|
||||||
* Get the completion/response text
|
* Get the completion/response text
|
||||||
*
|
*
|
||||||
* @return String completion/response text
|
* @return String completion/response text
|
||||||
*/
|
*/
|
||||||
@ -21,7 +28,7 @@ public class OllamaResult {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* -- GETTER --
|
* -- GETTER --
|
||||||
* Get the response status code.
|
* Get the response status code.
|
||||||
*
|
*
|
||||||
* @return int - response status code
|
* @return int - response status code
|
||||||
*/
|
*/
|
||||||
@ -29,7 +36,7 @@ public class OllamaResult {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* -- GETTER --
|
* -- GETTER --
|
||||||
* Get the response time in milliseconds.
|
* Get the response time in milliseconds.
|
||||||
*
|
*
|
||||||
* @return long - response time in milliseconds
|
* @return long - response time in milliseconds
|
||||||
*/
|
*/
|
||||||
@ -44,9 +51,68 @@ public class OllamaResult {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
|
Map<String, Object> responseMap = new HashMap<>();
|
||||||
|
responseMap.put("response", this.response);
|
||||||
|
responseMap.put("httpStatusCode", this.httpStatusCode);
|
||||||
|
responseMap.put("responseTime", this.responseTime);
|
||||||
|
return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(responseMap);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the structured response if the response is a JSON object.
|
||||||
|
*
|
||||||
|
* @return Map - structured response
|
||||||
|
* @throws IllegalArgumentException if the response is not a valid JSON object
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getStructuredResponse() {
|
||||||
|
String responseStr = this.getResponse();
|
||||||
|
if (responseStr == null || responseStr.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Response is empty or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if the response is a valid JSON
|
||||||
|
if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("[")) ||
|
||||||
|
(!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) {
|
||||||
|
throw new IllegalArgumentException("Response is not a valid JSON object");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> response = getObjectMapper().readValue(responseStr,
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to parse response as JSON: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the structured response mapped to a specific class type.
|
||||||
|
*
|
||||||
|
* @param <T> The type of class to map the response to
|
||||||
|
* @param clazz The class to map the response to
|
||||||
|
* @return An instance of the specified class with the response data
|
||||||
|
* @throws IllegalArgumentException if the response is not a valid JSON or is empty
|
||||||
|
* @throws RuntimeException if there is an error mapping the response
|
||||||
|
*/
|
||||||
|
public <T> T as(Class<T> clazz) {
|
||||||
|
String responseStr = this.getResponse();
|
||||||
|
if (responseStr == null || responseStr.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Response is empty or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if the response is a valid JSON
|
||||||
|
if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("[")) ||
|
||||||
|
(!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) {
|
||||||
|
throw new IllegalArgumentException("Response is not a valid JSON object");
|
||||||
|
}
|
||||||
|
return getObjectMapper().readValue(responseStr, clazz);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to parse response as JSON: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package io.github.ollama4j.models.response;
|
||||||
|
|
||||||
|
import static io.github.ollama4j.utils.Utils.getObjectMapper;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class OllamaStructuredResult {
|
||||||
|
private String response;
|
||||||
|
|
||||||
|
private int httpStatusCode;
|
||||||
|
|
||||||
|
private long responseTime = 0;
|
||||||
|
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
public OllamaStructuredResult(String response, long responseTime, int httpStatusCode) {
|
||||||
|
this.response = response;
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
this.httpStatusCode = httpStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the structured response if the response is a JSON object.
|
||||||
|
*
|
||||||
|
* @return Map - structured response
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getStructuredResponse() {
|
||||||
|
try {
|
||||||
|
Map<String, Object> response = getObjectMapper().readValue(this.getResponse(),
|
||||||
|
new TypeReference<Map<String, Object>>() {
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the structured response mapped to a specific class type.
|
||||||
|
*
|
||||||
|
* @param <T> The type of class to map the response to
|
||||||
|
* @param clazz The class to map the response to
|
||||||
|
* @return An instance of the specified class with the response data
|
||||||
|
* @throws RuntimeException if there is an error mapping the response
|
||||||
|
*/
|
||||||
|
public <T> T getStructuredResponse(Class<T> clazz) {
|
||||||
|
try {
|
||||||
|
return getObjectMapper().readValue(this.getResponse(), clazz);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user