Skip to content

Commit 7e91fb6

Browse files
committed
feat: functions new api
1 parent fc1f4c8 commit 7e91fb6

File tree

4 files changed

+11
-162
lines changed

4 files changed

+11
-162
lines changed

backend/src/main/java/ch/xxx/aidoclibchat/adapter/config/FunctionConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.context.annotation.Bean;
2121
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.context.annotation.Description;
2223

2324
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient;
2425

@@ -31,6 +32,7 @@ public FunctionConfig(OpenLibraryClient openLibraryClient) {
3132
}
3233

3334
@Bean
35+
@Description("Search for books by author, title or subject.")
3436
public Function<OpenLibraryClient.Request, OpenLibraryClient.Response> openLibraryClient() {
3537
return this.openLibraryClient::apply;
3638
}

backend/src/main/java/ch/xxx/aidoclibchat/adapter/controller/FunctionController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.springframework.web.bind.annotation.RequestMapping;
1919
import org.springframework.web.bind.annotation.RestController;
2020

21-
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient;
2221
import ch.xxx.aidoclibchat.domain.model.dto.FunctionSearch;
2322
import ch.xxx.aidoclibchat.usecase.service.FunctionService;
2423

@@ -38,7 +37,7 @@ public FunctionController(FunctionService functionService) {
3837
// }
3938

4039
@PostMapping(path="/books", produces = MediaType.APPLICATION_JSON_VALUE)
41-
public OpenLibraryClient.Response postQuestion(@RequestBody FunctionSearch functionSearch) {
40+
public String postQuestion(@RequestBody FunctionSearch functionSearch) {
4241
return this.functionService.functionCall(functionSearch.question(), functionSearch.resultsAmount());
4342
}
4443

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
package ch.xxx.aidoclibchat.domain.client;
22

33
import java.util.List;
4-
import java.util.Map;
54
import java.util.function.Function;
65

7-
import org.springframework.boot.context.properties.bind.ConstructorBinding;
8-
96
import com.fasterxml.jackson.annotation.JsonClassDescription;
107
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
118
import com.fasterxml.jackson.annotation.JsonInclude;
129
import com.fasterxml.jackson.annotation.JsonInclude.Include;
1310
import com.fasterxml.jackson.annotation.JsonProperty;
1411
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
15-
import com.fasterxml.jackson.core.type.TypeReference;
16-
import com.fasterxml.jackson.databind.ObjectMapper;
1712

1813

1914

@@ -36,68 +31,4 @@ record Request(@JsonProperty(required=false, value="author") @JsonPropertyDescri
3631
@JsonProperty(required=false, value="subject") @JsonPropertyDescription("The book subject") String subject) {}
3732
@JsonIgnoreProperties(ignoreUnknown = true)
3833
record Response(Long numFound, Long start, Boolean numFoundExact, List<Book> docs) {}
39-
40-
@JsonInclude(Include.NON_NULL)
41-
record FunctionTool(
42-
@JsonProperty("type") Type type,
43-
@JsonProperty("function") Function function) {
44-
45-
/**
46-
* Create a tool of type 'function' and the given function definition.
47-
* @param function function definition.
48-
*/
49-
@ConstructorBinding
50-
public FunctionTool(Function function) {
51-
this(Type.FUNCTION, function);
52-
}
53-
54-
/**
55-
* Create a tool of type 'function' and the given function definition.
56-
*/
57-
public enum Type {
58-
/**
59-
* Function tool type.
60-
*/
61-
@JsonProperty("function") FUNCTION
62-
}
63-
64-
/**
65-
* Function definition.
66-
*
67-
* @param description A description of what the function does, used by the model to choose when and how to call
68-
* the function.
69-
* @param name The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes,
70-
* with a maximum length of 64.
71-
* @param parameters The parameters the functions accepts, described as a JSON Schema object. To describe a
72-
* function that accepts no parameters, provide the value {"type": "object", "properties": {}}.
73-
*/
74-
public record Function(
75-
@JsonProperty("description") String description,
76-
@JsonProperty("name") String name,
77-
@JsonProperty("parameters") Map<String, Object> parameters) {
78-
79-
/**
80-
* Create tool function definition.
81-
*
82-
* @param description tool function description.
83-
* @param name tool function name.
84-
* @param jsonSchema tool function schema as json.
85-
*/
86-
@ConstructorBinding
87-
public Function(String description, String name, String jsonSchema) {
88-
this(description, name, parseJson(jsonSchema));
89-
}
90-
}
91-
}
92-
93-
static Map<String, Object> parseJson(String jsonSchema) {
94-
try {
95-
return new ObjectMapper().readValue(jsonSchema,
96-
new TypeReference<Map<String, Object>>() {
97-
});
98-
}
99-
catch (Exception e) {
100-
throw new RuntimeException("Failed to parse schema: " + jsonSchema, e);
101-
}
102-
}
10334
}

backend/src/main/java/ch/xxx/aidoclibchat/usecase/service/FunctionService.java

Lines changed: 8 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -12,122 +12,39 @@
1212
*/
1313
package ch.xxx.aidoclibchat.usecase.service;
1414

15-
import java.util.List;
16-
import java.util.Map;
17-
import java.util.concurrent.atomic.AtomicReference;
18-
1915
import org.slf4j.Logger;
2016
import org.slf4j.LoggerFactory;
2117
import org.springframework.ai.chat.client.ChatClient;
2218
import org.springframework.ai.chat.client.ChatClient.Builder;
2319
import org.springframework.beans.factory.annotation.Value;
24-
import org.springframework.boot.context.properties.bind.ConstructorBinding;
2520
import org.springframework.stereotype.Service;
2621

27-
import com.fasterxml.jackson.annotation.JsonProperty;
28-
import com.fasterxml.jackson.core.JsonProcessingException;
29-
import com.fasterxml.jackson.databind.ObjectMapper;
30-
31-
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient;
32-
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient.FunctionTool.Type;
33-
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient.Response;
34-
3522
@Service
3623
public class FunctionService {
3724
private static final Logger LOGGER = LoggerFactory.getLogger(FunctionService.class);
38-
private final ObjectMapper objectMapper;
3925
private final ChatClient chatClient;
40-
private final OpenLibraryClient openLibraryClient;
41-
private final List<String> nullCodes = List.of("none", "string");
4226
private final String promptStr = """
43-
You have access to the following tools:
44-
%s
45-
46-
You must follow these instructions:
47-
Always select one or more of the above tools based on the user query
48-
If a tool is found, you must respond in the JSON format matching the following schema:
49-
{"tools": [{
50-
"tool": "<name of the selected tool>",
51-
"tool_input": "<parameters for the selected tool, matching the tool's JSON schema>"
52-
}]}
53-
Make sure to include all tool parameters in the JSON at tool_input.
54-
If there is no tool that match the user request, you will respond with empty json.
55-
Do not add any additional Notes or Explanations. Respond only with the JSON.
27+
Make sure to have all the parameters when calling a function.
28+
If a parameter is missing ask the user for the parameter.
5629
5730
User Query:
5831
%s
5932
""";
6033

61-
private record Tool(@JsonProperty("tool") String tool, @JsonProperty("tool_input") Map<String, Object> toolInput) {
62-
@ConstructorBinding
63-
public Tool(String tool, String jsonSchema) {
64-
this(tool, OpenLibraryClient.parseJson(jsonSchema));
65-
}
66-
}
67-
68-
private record Tools(@JsonProperty("tools") List<Tool> tools) {
69-
}
70-
7134
@Value("${spring.profiles.active:}")
7235
private String activeProfile;
7336

74-
public FunctionService(ObjectMapper objectMapper, Builder builder, OpenLibraryClient openLibraryClient) {
75-
this.objectMapper = objectMapper;
37+
public FunctionService(Builder builder) {
7638
this.chatClient = builder.build();
77-
this.openLibraryClient = openLibraryClient;
7839
}
7940

80-
public Response functionCall(String question, Long resultsAmount) {
41+
public String functionCall(String question, Long resultsAmount) {
8142
if (!this.activeProfile.contains("ollama")) {
82-
return new Response(0L, 0L, false, List.of());
83-
}
84-
var description = "Search for books by author, title or subject.";
85-
var name = "booksearch";
86-
var aiFunction = new OpenLibraryClient.FunctionTool(Type.FUNCTION, new OpenLibraryClient.FunctionTool.Function(
87-
description, name, Map.of("author", "string", "title", "string", "subject", "string")));
88-
String jsonStr = "";
89-
try {
90-
jsonStr = this.objectMapper.writeValueAsString(aiFunction);
91-
} catch (JsonProcessingException e) {
92-
LOGGER.error("Json Mapping failed.", e);
93-
}
94-
var query = String.format(this.promptStr, jsonStr, question);
95-
int aiCallCounter = 0;
96-
var responseRef = new AtomicReference<Response>(new Response(0L, 0L, false, List.of()));
97-
List<Tool> myToolsList = List.of();
98-
while (aiCallCounter < 3 && myToolsList.isEmpty()) {
99-
aiCallCounter += 1;
100-
var response = this.chatClient.prompt().user(u -> u.text(query)).call().chatResponse().getResult().getOutput().getContent();
101-
try {
102-
response = response.substring(response.indexOf("{"), response.lastIndexOf("}") + 1);
103-
final var atomicResponse = new AtomicReference<String>(response);
104-
this.nullCodes.forEach(myCode -> {
105-
var myResponse = atomicResponse.get();
106-
atomicResponse.set(myResponse.replaceAll(myCode, ""));
107-
});
108-
var myTools = this.objectMapper.readValue(atomicResponse.get(), Tools.class);
109-
// LOGGER.info(myTools.toString());
110-
myToolsList = myTools.tools().stream()
111-
.filter(myTool1 -> myTool1.toolInput().values().stream()
112-
.filter(myValue -> (myValue instanceof String) && !((String) myValue).isBlank())
113-
.findFirst().isPresent())
114-
.toList();
115-
if (myToolsList.isEmpty()) {
116-
throw new RuntimeException("No parameters found.");
117-
}
118-
} catch (Exception e) {
119-
LOGGER.error("Chatresult Json Mapping failed.", e);
120-
LOGGER.error("ChatResponse: {}", response);
121-
}
43+
return "";
12244
}
12345

124-
myToolsList.forEach(myTool -> {
125-
var myRequest = new OpenLibraryClient.Request((String) myTool.toolInput().get("author"),
126-
(String) myTool.toolInput().get("title"), (String) myTool.toolInput().get("subject"));
127-
var myResponse = this.openLibraryClient.apply(myRequest);
128-
// LOGGER.info(myResponse.toString());
129-
responseRef.set(myResponse);
130-
});
131-
return responseRef.get();
46+
var result = this.chatClient.prompt().user(this.promptStr + question).functions("openLibraryClient").call().content();
47+
return result;
13248
}
49+
13350
}

0 commit comments

Comments
 (0)