feat: Enhance chat completion with tool calling capabilities
- Added support for tools in ChatCompletionRequest, including tool choice and parallel tool calls. - Introduced ChatCompletionTool and ChatCompletionFunction classes to define tool specifications. - Updated ChatMessage to include tool calls and tool call IDs. - Created ChatToolCall and ChatToolFunction classes for managing tool call details. - Modified TextCompletionClient to support tool calling. - Updated Grader and Grader2 classes to handle new tool-related logic. - Enhanced Agent and AgentTest classes to incorporate tool calling in agent workflows. - Added tests for tool calling functionality in CachedClientTest and CompletionClientTest.
This commit is contained in:
@@ -8,13 +8,15 @@ import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
|
||||
public interface Client {
|
||||
|
||||
Completion conversation(ModelCategory modelCategory, Conversation conversation, boolean skipCache);
|
||||
|
||||
Completion conversation(ModelProfile modelProfile, Conversation conversation, boolean skipCache);
|
||||
Completion conversation(ModelCategory modelCategory, Conversation conversation, ToolCallData toolCallData, boolean skipCache);
|
||||
|
||||
Completion streamConversation(ModelCategory modelCategory, Conversation conversation, Consumer<Chunk> chunkConsumer, boolean skipCache);
|
||||
Completion conversation(ModelProfile modelProfile, Conversation conversation, ToolCallData toolCallData, boolean skipCache);
|
||||
|
||||
Completion streamConversation(ModelCategory modelCategory, Conversation conversation, Consumer<Chunk> chunkConsumer, ToolCallData toolCallData, boolean skipCache);
|
||||
|
||||
Completion streamConversation(ModelProfile modelProfile, Conversation conversation, Consumer<Chunk> chunkConsumer, boolean skipCache);
|
||||
Completion streamConversation(ModelProfile modelProfile, Conversation conversation, Consumer<Chunk> chunkConsumer, ToolCallData toolCallData, boolean skipCache);
|
||||
|
||||
ModelProvider getModelProvider();
|
||||
|
||||
boolean supportsToolCalling();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ public class ClientService {
|
||||
final var modelProfile = this.modelProfileService.getModelProfile(category.getModelProfile(), category);
|
||||
return this.getClient(modelProfile);
|
||||
}
|
||||
|
||||
public boolean supportsToolCalling(final ModelCategory category) {
|
||||
return this.getClient(category).supportsToolCalling();
|
||||
}
|
||||
|
||||
@Cacheable("clients")
|
||||
public Client getClient(final ModelProfile modelProfile) {
|
||||
@@ -41,6 +45,10 @@ public class ClientService {
|
||||
.map(client -> this.completionInterceptors == null || this.completionInterceptors.isEmpty() ? client : new InterceptingClient(client, this.modelProfileService, this.completionInterceptors))
|
||||
.orElseThrow(() -> new IllegalStateException("No client found for model profile " + modelProfile.modelName()));
|
||||
}
|
||||
|
||||
public boolean supportsToolCalling(final ModelProfile modelProfile) {
|
||||
return this.getClient(modelProfile).supportsToolCalling();
|
||||
}
|
||||
|
||||
private static class InterceptingClient implements Client {
|
||||
|
||||
@@ -57,24 +65,24 @@ public class ClientService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation_, final boolean skipCache) {
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation_, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
|
||||
return this.conversation(modelProfile, conversation_, skipCache);
|
||||
return this.conversation(modelProfile, conversation_, toolCallData, skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
|
||||
return this.streamConversation(modelProfile, conversation_, chunkConsumer, skipCache);
|
||||
return this.streamConversation(modelProfile, conversation_, chunkConsumer, toolCallData, skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation_, final boolean skipCache) {
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation_, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
Conversation conversation = conversation_;
|
||||
for (final var interceptor : this.completionInterceptors) {
|
||||
conversation = interceptor.before(modelProfile, conversation);
|
||||
}
|
||||
var completion = this.client.conversation(modelProfile, conversation, skipCache);
|
||||
var completion = this.client.conversation(modelProfile, conversation, toolCallData, skipCache);
|
||||
for (final var interceptor : this.completionInterceptors) {
|
||||
completion = interceptor.after(modelProfile, conversation, completion);
|
||||
}
|
||||
@@ -82,12 +90,12 @@ public class ClientService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation_, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
Conversation conversation = conversation_;
|
||||
for (final var interceptor : this.completionInterceptors) {
|
||||
conversation = interceptor.before(modelProfile, conversation);
|
||||
}
|
||||
var completion = this.client.streamConversation(modelProfile, conversation, chunkConsumer, skipCache);
|
||||
var completion = this.client.streamConversation(modelProfile, conversation, chunkConsumer, toolCallData, skipCache);
|
||||
for (final var interceptor : this.completionInterceptors) {
|
||||
completion = interceptor.after(modelProfile, conversation, completion);
|
||||
}
|
||||
@@ -98,6 +106,11 @@ public class ClientService {
|
||||
public ModelProvider getModelProvider() {
|
||||
return this.client.getModelProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsToolCalling() {
|
||||
return this.client.supportsToolCalling();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
public final record Completion(String response, String reasoning, CompletionFinishReason finishReason, CompletionStatistics statistics) {
|
||||
import java.util.List;
|
||||
|
||||
public final record Completion(String response, String reasoning, CompletionFinishReason finishReason, CompletionStatistics statistics, List<ToolCall> toolCalls) {
|
||||
|
||||
public Completion {
|
||||
toolCalls = toolCalls == null ? List.of() : List.copyOf(toolCalls);
|
||||
}
|
||||
|
||||
public Completion(final String response, final String reasoning, final CompletionFinishReason finishReason, final CompletionStatistics statistics) {
|
||||
this(response, reasoning, finishReason, statistics, List.of());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ public enum CompletionFinishReason {
|
||||
/**
|
||||
* Completion was stopped because the content was filtered.
|
||||
*/
|
||||
CONTENT_FILTERED
|
||||
CONTENT_FILTERED,
|
||||
|
||||
/**
|
||||
* Completion stopped because the model emitted one or more tool calls.
|
||||
*/
|
||||
TOOL_CALLS
|
||||
|
||||
}
|
||||
@@ -80,10 +80,18 @@ public final class Conversation {
|
||||
}
|
||||
|
||||
public enum TurnType {
|
||||
SYSTEM, USER, ASSISTANT
|
||||
SYSTEM, USER, ASSISTANT, TOOL
|
||||
}
|
||||
|
||||
public record Turn(TurnType type, String text) {
|
||||
public record Turn(TurnType type, String text, List<ToolCall> toolCalls, String toolCallId) {
|
||||
|
||||
public Turn {
|
||||
toolCalls = toolCalls == null ? List.of() : List.copyOf(toolCalls);
|
||||
}
|
||||
|
||||
public Turn(final TurnType type, final String text) {
|
||||
this(type, text, List.of(), null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
5
src/main/java/de/extio/lmlib/client/ToolCall.java
Normal file
5
src/main/java/de/extio/lmlib/client/ToolCall.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
public record ToolCall(String id, String type, String name, String arguments) {
|
||||
|
||||
}
|
||||
34
src/main/java/de/extio/lmlib/client/ToolCallData.java
Normal file
34
src/main/java/de/extio/lmlib/client/ToolCallData.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public record ToolCallData(List<ToolDefinition> tools, String toolChoice, String forcedToolName, Boolean parallelToolCalls) {
|
||||
|
||||
public static final String TOOL_CHOICE_AUTO = "auto";
|
||||
|
||||
public static final String TOOL_CHOICE_REQUIRED = "required";
|
||||
|
||||
public static final String TOOL_CHOICE_NONE = "none";
|
||||
|
||||
public ToolCallData {
|
||||
tools = tools == null ? List.of() : List.copyOf(tools);
|
||||
}
|
||||
|
||||
public static ToolCallData auto(final Collection<ToolDefinition> tools) {
|
||||
return new ToolCallData(List.copyOf(tools), TOOL_CHOICE_AUTO, null, null);
|
||||
}
|
||||
|
||||
public static ToolCallData required(final Collection<ToolDefinition> tools) {
|
||||
return new ToolCallData(List.copyOf(tools), TOOL_CHOICE_REQUIRED, null, null);
|
||||
}
|
||||
|
||||
public static ToolCallData force(final Collection<ToolDefinition> tools, final String forcedToolName) {
|
||||
return new ToolCallData(List.copyOf(tools), null, forcedToolName, null);
|
||||
}
|
||||
|
||||
public boolean hasTools() {
|
||||
return !this.tools.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/de/extio/lmlib/client/ToolDefinition.java
Normal file
26
src/main/java/de/extio/lmlib/client/ToolDefinition.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record ToolDefinition(String name, String description, Map<String, Object> parameters, Boolean strict) {
|
||||
|
||||
public ToolDefinition {
|
||||
if (name == null || name.isBlank()) {
|
||||
throw new IllegalArgumentException("Tool name cannot be null or blank");
|
||||
}
|
||||
parameters = parameters == null ? Map.of() : Map.copyOf(parameters);
|
||||
}
|
||||
|
||||
public ToolDefinition(final String name, final String description, final Map<String, Object> parameters) {
|
||||
this(name, description, parameters, null);
|
||||
}
|
||||
|
||||
public ToolDefinition(final String name, final String description, final ToolParameters parameters, final Boolean strict) {
|
||||
this(name, description, parameters != null ? parameters.map() : null, strict);
|
||||
}
|
||||
|
||||
public ToolDefinition(final String name, final String description, final ToolParameters parameters) {
|
||||
this(name, description, parameters != null ? parameters.map() : null, null);
|
||||
}
|
||||
|
||||
}
|
||||
83
src/main/java/de/extio/lmlib/client/ToolParameters.java
Normal file
83
src/main/java/de/extio/lmlib/client/ToolParameters.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
public final class ToolParameters {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
|
||||
|
||||
private ToolParameters() {
|
||||
}
|
||||
|
||||
public static ToolParameters create() {
|
||||
return new ToolParameters();
|
||||
}
|
||||
|
||||
public static ToolParameters create(final Map<String, String> properties) {
|
||||
final var schemaProperties = ToolParameters.create();
|
||||
for (final var entry : properties.entrySet()) {
|
||||
schemaProperties.add(entry.getKey(), ToolParameters.create()
|
||||
.add("type", "string")
|
||||
.add("description", entry.getValue()));
|
||||
}
|
||||
|
||||
return new ToolParameters()
|
||||
.add("type", "object")
|
||||
.add("properties", schemaProperties)
|
||||
.add("required", List.copyOf(properties.keySet()))
|
||||
.add("additionalProperties", false);
|
||||
}
|
||||
|
||||
public ToolParameters add(final String key, final Object value) {
|
||||
this.values.put(key, this.normalize(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> map() {
|
||||
return Map.copyOf(this.values);
|
||||
}
|
||||
|
||||
public String json() {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(this.values);
|
||||
}
|
||||
catch (final Exception e) {
|
||||
throw new IllegalStateException("Cannot serialize tool parameters", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object normalize(final Object value) {
|
||||
return switch (value) {
|
||||
case null -> null;
|
||||
case final ToolParameters toolParameters -> toolParameters.map();
|
||||
case final Map<?, ?> map -> this.normalizeMap(map);
|
||||
case final Collection<?> collection -> this.normalizeCollection(collection);
|
||||
default -> value;
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> normalizeMap(final Map<?, ?> map) {
|
||||
final var normalized = new LinkedHashMap<String, Object>();
|
||||
for (final var entry : map.entrySet()) {
|
||||
normalized.put(String.valueOf(entry.getKey()), this.normalize(entry.getValue()));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private List<Object> normalizeCollection(final Collection<?> collection) {
|
||||
final var normalized = new ArrayList<Object>(collection.size());
|
||||
for (final var item : collection) {
|
||||
normalized.add(this.normalize(item));
|
||||
}
|
||||
return List.copyOf(normalized);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,18 +9,22 @@ import java.util.HexFormat;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.extio.lmlib.client.Chunk;
|
||||
import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.CompletionStatistics;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.ToolCallData;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
import de.extio.lmlib.profile.ModelProfileService;
|
||||
|
||||
public class CachedClient implements Client {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private final CachedClientRepository cacheRepository;
|
||||
|
||||
@@ -35,18 +39,18 @@ public class CachedClient implements Client {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final boolean skipCache) {
|
||||
return this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.conversation(modelCategory, conversation, skipCache), conversation.getConversation().toString());
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
return this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.conversation(modelCategory, conversation, toolCallData, skipCache), conversation.getConversation().toString(), this.serializeToolCallData(toolCallData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final boolean skipCache) {
|
||||
return this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.conversation(modelProfile, conversation, skipCache), conversation.getConversation().toString());
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
return this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.conversation(modelProfile, conversation, toolCallData, skipCache), conversation.getConversation().toString(), this.serializeToolCallData(toolCallData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
final var response = this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.streamConversation(modelCategory, conversation, chunkConsumer, skipCache), conversation.getConversation().toString());
|
||||
public Completion streamConversation(final ModelCategory modelCategory, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
final var response = this.getCachedResponse(modelCategory, null, skipCache, () -> this.client.streamConversation(modelCategory, conversation, chunkConsumer, toolCallData, skipCache), conversation.getConversation().toString(), this.serializeToolCallData(toolCallData));
|
||||
if (response.statistics().cached()) {
|
||||
chunkConsumer.accept(new Chunk(response.response(), response.reasoning()));
|
||||
}
|
||||
@@ -54,8 +58,8 @@ public class CachedClient implements Client {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
final var response = this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.streamConversation(modelProfile, conversation, chunkConsumer, skipCache), conversation.getConversation().toString());
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
final var response = this.getCachedResponse(null, modelProfile, skipCache, () -> this.client.streamConversation(modelProfile, conversation, chunkConsumer, toolCallData, skipCache), conversation.getConversation().toString(), this.serializeToolCallData(toolCallData));
|
||||
if (response.statistics().cached()) {
|
||||
chunkConsumer.accept(new Chunk(response.response(), response.reasoning()));
|
||||
}
|
||||
@@ -66,21 +70,38 @@ public class CachedClient implements Client {
|
||||
public ModelProvider getModelProvider() {
|
||||
return this.client.getModelProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsToolCalling() {
|
||||
return this.client.supportsToolCalling();
|
||||
}
|
||||
|
||||
private Completion getCachedResponse(final ModelCategory modelCategory, final ModelProfile modelProfile, final boolean skipCache, final Supplier<Completion> supplier, final String... keys) {
|
||||
final var key = this.createCacheKey(modelCategory, modelProfile, keys);
|
||||
|
||||
final var cachedCompletion = skipCache ? null : this.cacheRepository.get(key);
|
||||
if (cachedCompletion != null) {
|
||||
return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.cachedInTokens(), cachedCompletion.outTokens(), cachedCompletion.reasoningOutTokens(), BigDecimal.ZERO, true));
|
||||
return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), cachedCompletion.finishReason(), new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.cachedInTokens(), cachedCompletion.outTokens(), cachedCompletion.reasoningOutTokens(), BigDecimal.ZERO, true), cachedCompletion.toolCalls());
|
||||
}
|
||||
|
||||
final var completion = supplier.get();
|
||||
if (completion != null) {
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.reasoning(), completion.finishReason(), completion.statistics().inTokens(), completion.statistics().cachedInTokens(), completion.statistics().outTokens(), completion.statistics().reasoningOutTokens(), OffsetDateTime.now()));
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.reasoning(), completion.finishReason(), completion.toolCalls(), completion.statistics().inTokens(), completion.statistics().cachedInTokens(), completion.statistics().outTokens(), completion.statistics().reasoningOutTokens(), OffsetDateTime.now()));
|
||||
}
|
||||
return completion;
|
||||
}
|
||||
|
||||
private String serializeToolCallData(final ToolCallData toolCallData) {
|
||||
if (toolCallData == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(toolCallData);
|
||||
}
|
||||
catch (final Exception e) {
|
||||
return toolCallData.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String createCacheKey(ModelCategory modelCategory, ModelProfile modelProfile, final String... keys) {
|
||||
if (modelProfile == null) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package de.extio.lmlib.client.cached;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.ToolCall;
|
||||
|
||||
public record CachedCompletion(String response, String reasoning, CompletionFinishReason finishReason, long inTokens, long cachedInTokens, long outTokens, long reasoningOutTokens, OffsetDateTime created) {
|
||||
public record CachedCompletion(String response, String reasoning, CompletionFinishReason finishReason, List<ToolCall> toolCalls, long inTokens, long cachedInTokens, long outTokens, long reasoningOutTokens, OffsetDateTime created) {
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.ToolCallData;
|
||||
import de.extio.lmlib.client.oai.ModelNameSupplier;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
@@ -82,37 +83,38 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final boolean skipCache) {
|
||||
return this.streamConversation(modelCategory, conversation, null, skipCache);
|
||||
public Completion conversation(final ModelCategory modelCategory, final Conversation conversation, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
return this.streamConversation(modelCategory, conversation, null, toolCallData, skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final boolean skipCache) {
|
||||
return this.streamConversation(modelProfile, conversation, null, skipCache);
|
||||
public Completion conversation(final ModelProfile modelProfile, final Conversation conversation, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
return this.streamConversation(modelProfile, conversation, null, toolCallData, skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelCategory modelCategory_, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
public Completion streamConversation(final ModelCategory modelCategory_, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
final ModelCategory modelCategory = (modelCategory_ == null) ? ModelCategory.MEDIUM : modelCategory_;
|
||||
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile(), modelCategory);
|
||||
return this.streamConversation(modelProfile, conversation, chunkConsumer, skipCache);
|
||||
return this.streamConversation(modelProfile, conversation, chunkConsumer, toolCallData, skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final boolean skipCache) {
|
||||
public Completion streamConversation(final ModelProfile modelProfile, final Conversation conversation, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData, final boolean skipCache) {
|
||||
if (modelProfile == null || (modelProfile.modelProvider() != ModelProvider.OAI_TEXT_COMPLETION && modelProfile.modelProvider() != ModelProvider.OAI_CHAT_COMPLETION)) {
|
||||
throw new IllegalArgumentException("Invalid ModelProfile");
|
||||
}
|
||||
|
||||
return this.requestCompletion(conversation, modelProfile, chunkConsumer);
|
||||
return this.requestCompletion(conversation, modelProfile, chunkConsumer, toolCallData);
|
||||
}
|
||||
|
||||
protected abstract Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer);
|
||||
protected abstract Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData);
|
||||
|
||||
protected CompletionFinishReason mapFinishReason(final String finishReason) {
|
||||
return switch (finishReason) {
|
||||
case FinishReasons.FINISH_REASON_CONTENT_FILTER -> CompletionFinishReason.CONTENT_FILTERED;
|
||||
case FinishReasons.FINISH_REASON_LENGTH -> CompletionFinishReason.TOKEN_LIMIT_REACHED;
|
||||
case FinishReasons.FINISH_REASON_TOOL_CALLS -> CompletionFinishReason.TOOL_CALLS;
|
||||
default -> CompletionFinishReason.DONE;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@ public final class FinishReasons {
|
||||
public static final String FINISH_REASON_LENGTH = "length";
|
||||
|
||||
public static final String FINISH_REASON_CONTENT_FILTER = "content_filter";
|
||||
|
||||
public static final String FINISH_REASON_TOOL_CALLS = "tool_calls";
|
||||
|
||||
}
|
||||
|
||||
@@ -4,23 +4,24 @@ import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
|
||||
import de.extio.lmlib.client.Chunk;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.ToolCall;
|
||||
import de.extio.lmlib.client.ToolCallData;
|
||||
import de.extio.lmlib.client.ToolDefinition;
|
||||
import de.extio.lmlib.client.Conversation.Turn;
|
||||
import de.extio.lmlib.client.oai.completion.AbstractCompletionClient;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
@@ -34,13 +35,18 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsToolCalling() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelProvider getModelProvider() {
|
||||
return ModelProvider.OAI_CHAT_COMPLETION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer) {
|
||||
protected Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData) {
|
||||
final var chat = createChats(conversation, modelProfile);
|
||||
|
||||
final var request = new ChatCompletionRequest();
|
||||
@@ -76,6 +82,11 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
throw new IllegalArgumentException("Invalid reasoning summary details in the model profile: " + modelProfile.reasoningSummaryDetails(), e);
|
||||
}
|
||||
}
|
||||
if (toolCallData != null && toolCallData.hasTools()) {
|
||||
request.setTools(this.createTools(toolCallData.tools()));
|
||||
request.setToolChoice(this.createToolChoice(toolCallData));
|
||||
request.setParallelToolCalls(toolCallData.parallelToolCalls());
|
||||
}
|
||||
|
||||
String requestBody;
|
||||
try {
|
||||
@@ -112,6 +123,7 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
chatCompletionResponse.setChoices(List.of(new ChatChoice()));
|
||||
final StringBuilder contentStringBuilder = new StringBuilder();
|
||||
final StringBuilder reasoningStringBuilder = new StringBuilder();
|
||||
final List<ChatToolCall> toolCalls = new ArrayList<>();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientResponse.getBody(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
@@ -162,6 +174,9 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
if (content != null || reasoning != null) {
|
||||
chunkConsumer.accept(new Chunk(content, reasoning));
|
||||
}
|
||||
if (streamResponse.getChoices().getFirst().getMessage().getToolCalls() != null && !streamResponse.getChoices().getFirst().getMessage().getToolCalls().isEmpty()) {
|
||||
this.mergeToolCalls(toolCalls, streamResponse.getChoices().getFirst().getMessage().getToolCalls());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,6 +186,7 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
}
|
||||
}
|
||||
chatCompletionResponse.getChoices().getFirst().setMessage(new ChatMessage("assistant", contentStringBuilder.toString(), reasoningStringBuilder.toString()));
|
||||
chatCompletionResponse.getChoices().getFirst().getMessage().setToolCalls(List.copyOf(toolCalls));
|
||||
return chatCompletionResponse;
|
||||
}
|
||||
});
|
||||
@@ -181,65 +197,118 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
final var choice = response.getChoices().getFirst();
|
||||
final var content = response.getChoices().getFirst().getMessage().getContent();
|
||||
final var reasoning = response.getChoices().getFirst().getMessage().getReasoningContent();
|
||||
final var toolCalls = this.toToolCalls(response.getChoices().getFirst().getMessage().getToolCalls());
|
||||
if ((content == null || content.isBlank()) && toolCalls.isEmpty()) {
|
||||
throw new IllegalStateException("No parseable response content from server");
|
||||
}
|
||||
final var finishReason = this.mapFinishReason(choice.getFinishReason());
|
||||
final var statistics = createCompletionStatistics(modelProfile, start, response.getUsage(), response.getTimings(), null, content, reasoning);
|
||||
return new Completion(content, reasoning, finishReason, statistics);
|
||||
return new Completion(content, reasoning, finishReason, statistics, toolCalls);
|
||||
}
|
||||
|
||||
private List<ChatCompletionRequest.ChatCompletionTool> createTools(final List<ToolDefinition> toolDefinitions) {
|
||||
return toolDefinitions.stream().map(toolDefinition -> {
|
||||
final var function = new ChatCompletionRequest.ChatCompletionFunction();
|
||||
function.setName(toolDefinition.name());
|
||||
function.setDescription(toolDefinition.description());
|
||||
function.setParameters(toolDefinition.parameters());
|
||||
function.setStrict(toolDefinition.strict());
|
||||
|
||||
final var tool = new ChatCompletionRequest.ChatCompletionTool();
|
||||
tool.setType("function");
|
||||
tool.setFunction(function);
|
||||
return tool;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private Object createToolChoice(final ToolCallData toolCallData) {
|
||||
if (toolCallData == null) {
|
||||
return null;
|
||||
}
|
||||
if (toolCallData.forcedToolName() != null && !toolCallData.forcedToolName().isBlank()) {
|
||||
final var function = new ChatCompletionRequest.ChatCompletionNamedToolChoiceFunction();
|
||||
function.setName(toolCallData.forcedToolName());
|
||||
|
||||
final var namedToolChoice = new ChatCompletionRequest.ChatCompletionNamedToolChoice();
|
||||
namedToolChoice.setType("function");
|
||||
namedToolChoice.setFunction(function);
|
||||
return namedToolChoice;
|
||||
}
|
||||
return toolCallData.toolChoice();
|
||||
}
|
||||
|
||||
private void mergeToolCalls(final List<ChatToolCall> currentToolCalls, final List<ChatToolCall> deltaToolCalls) {
|
||||
for (final var deltaToolCall : deltaToolCalls) {
|
||||
final int index = deltaToolCall.getIndex() != null ? deltaToolCall.getIndex() : currentToolCalls.size();
|
||||
while (currentToolCalls.size() <= index) {
|
||||
currentToolCalls.add(new ChatToolCall());
|
||||
}
|
||||
final var currentToolCall = currentToolCalls.get(index);
|
||||
if (deltaToolCall.getId() != null && !deltaToolCall.getId().isBlank()) {
|
||||
currentToolCall.setId(deltaToolCall.getId());
|
||||
}
|
||||
if (deltaToolCall.getType() != null && !deltaToolCall.getType().isBlank()) {
|
||||
currentToolCall.setType(deltaToolCall.getType());
|
||||
}
|
||||
if (deltaToolCall.getFunction() != null) {
|
||||
if (currentToolCall.getFunction() == null) {
|
||||
currentToolCall.setFunction(new ChatToolFunction());
|
||||
}
|
||||
if (deltaToolCall.getFunction().getName() != null && !deltaToolCall.getFunction().getName().isBlank()) {
|
||||
currentToolCall.getFunction().setName(deltaToolCall.getFunction().getName());
|
||||
}
|
||||
if (deltaToolCall.getFunction().getArguments() != null && !deltaToolCall.getFunction().getArguments().isEmpty()) {
|
||||
final var existingArguments = currentToolCall.getFunction().getArguments();
|
||||
currentToolCall.getFunction().setArguments((existingArguments == null ? "" : existingArguments) + deltaToolCall.getFunction().getArguments());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ToolCall> toToolCalls(final List<ChatToolCall> chatToolCalls) {
|
||||
if (chatToolCalls == null || chatToolCalls.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return chatToolCalls.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(toolCall -> new ToolCall(toolCall.getId(), toolCall.getType(), toolCall.getFunction() != null ? toolCall.getFunction().getName() : null, toolCall.getFunction() != null ? toolCall.getFunction().getArguments() : null))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<ChatMessage> createChats(final Conversation conversation, final ModelProfile modelProfile) {
|
||||
final List<ChatMessage> chat = new ArrayList<>();
|
||||
|
||||
final Deque<Turn> turns = new ArrayDeque<>(conversation.getConversation());
|
||||
Turn systemTurn = null;
|
||||
Turn userTurn = null;
|
||||
Turn assistantTurn = null;
|
||||
boolean first = true;
|
||||
|
||||
final AtomicInteger tokens = new AtomicInteger();
|
||||
|
||||
while (!turns.isEmpty()) {
|
||||
final Turn turn = turns.pop();
|
||||
|
||||
for (final Turn turn : conversation.getConversation()) {
|
||||
switch (turn.type()) {
|
||||
case SYSTEM:
|
||||
systemTurn = turn;
|
||||
chat.add(this.createChatMessage("system", turn));
|
||||
break;
|
||||
case USER:
|
||||
userTurn = turn;
|
||||
if (this.addChatRequestMessage("user", turn, tokens, chat, modelProfile)) {
|
||||
return chat;
|
||||
}
|
||||
break;
|
||||
case ASSISTANT:
|
||||
assistantTurn = turn;
|
||||
if (this.addChatRequestMessage("assistant", turn, tokens, chat, modelProfile)) {
|
||||
return chat;
|
||||
}
|
||||
break;
|
||||
case TOOL:
|
||||
if (this.addChatRequestMessage("tool", turn, tokens, chat, modelProfile)) {
|
||||
return chat;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown turn type: " + turn.type());
|
||||
}
|
||||
|
||||
if (first && userTurn != null) {
|
||||
if (systemTurn != null) {
|
||||
chat.add(new ChatMessage("system", systemTurn.text(), null));
|
||||
}
|
||||
if (this.addChatRequestMessage("user", userTurn.text(), tokens, chat, modelProfile)) {
|
||||
break;
|
||||
}
|
||||
userTurn = null;
|
||||
first = false;
|
||||
}
|
||||
else if (!first && assistantTurn != null && userTurn != null) {
|
||||
if (this.addChatRequestMessage("assistant", assistantTurn.text(), tokens, chat, modelProfile)) {
|
||||
break;
|
||||
}
|
||||
if (this.addChatRequestMessage("user", userTurn.text(), tokens, chat, modelProfile)) {
|
||||
break;
|
||||
}
|
||||
userTurn = null;
|
||||
assistantTurn = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return chat;
|
||||
}
|
||||
|
||||
private boolean addChatRequestMessage(final String role, final String text, final AtomicInteger tokens, final List<ChatMessage> chat, final ModelProfile modelProfile) {
|
||||
var prompt = text;
|
||||
private boolean addChatRequestMessage(final String role, final Turn turn, final AtomicInteger tokens, final List<ChatMessage> chat, final ModelProfile modelProfile) {
|
||||
var prompt = turn.text() != null ? turn.text() : "";
|
||||
var trimmed = false;
|
||||
|
||||
final var textTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
@@ -252,9 +321,30 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
trimmed = true;
|
||||
}
|
||||
|
||||
chat.add(new ChatMessage(role, prompt, null));
|
||||
final var message = this.createChatMessage(role, turn);
|
||||
message.setContent(prompt);
|
||||
chat.add(message);
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
private ChatMessage createChatMessage(final String role, final Turn turn) {
|
||||
final var message = new ChatMessage(role, turn.text() != null ? turn.text() : "", null);
|
||||
if (!turn.toolCalls().isEmpty()) {
|
||||
message.setToolCalls(turn.toolCalls().stream().map(toolCall -> {
|
||||
final var function = new ChatToolFunction();
|
||||
function.setName(toolCall.name());
|
||||
function.setArguments(toolCall.arguments());
|
||||
|
||||
final var chatToolCall = new ChatToolCall();
|
||||
chatToolCall.setId(toolCall.id());
|
||||
chatToolCall.setType(toolCall.type());
|
||||
chatToolCall.setFunction(function);
|
||||
return chatToolCall;
|
||||
}).toList());
|
||||
}
|
||||
message.setToolCallId(turn.toolCallId());
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.extio.lmlib.client.oai.completion.chat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
|
||||
@@ -13,6 +14,12 @@ final class ChatCompletionRequest extends AbstractCompletionRequest {
|
||||
private Integer maxCompletionTokens;
|
||||
|
||||
private ChatCompletionsRequestReasoning reasoning;
|
||||
|
||||
private List<ChatCompletionTool> tools;
|
||||
|
||||
private Object toolChoice;
|
||||
|
||||
private Boolean parallelToolCalls;
|
||||
|
||||
@JsonProperty("messages")
|
||||
public List<ChatMessage> getMessages() {
|
||||
@@ -41,6 +48,33 @@ final class ChatCompletionRequest extends AbstractCompletionRequest {
|
||||
this.reasoning = reasoning;
|
||||
}
|
||||
|
||||
@JsonProperty("tools")
|
||||
public List<ChatCompletionTool> getTools() {
|
||||
return this.tools;
|
||||
}
|
||||
|
||||
public void setTools(final List<ChatCompletionTool> tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
@JsonProperty("tool_choice")
|
||||
public Object getToolChoice() {
|
||||
return this.toolChoice;
|
||||
}
|
||||
|
||||
public void setToolChoice(final Object toolChoice) {
|
||||
this.toolChoice = toolChoice;
|
||||
}
|
||||
|
||||
@JsonProperty("parallel_tool_calls")
|
||||
public Boolean getParallelToolCalls() {
|
||||
return this.parallelToolCalls;
|
||||
}
|
||||
|
||||
public void setParallelToolCalls(final Boolean parallelToolCalls) {
|
||||
this.parallelToolCalls = parallelToolCalls;
|
||||
}
|
||||
|
||||
final static class ChatCompletionsRequestReasoning {
|
||||
|
||||
private ChatCompletionsRequestReasoningEffort effort;
|
||||
@@ -75,4 +109,119 @@ final class ChatCompletionRequest extends AbstractCompletionRequest {
|
||||
auto, concise, detailed
|
||||
}
|
||||
|
||||
static final class ChatCompletionTool {
|
||||
|
||||
private String type;
|
||||
|
||||
private ChatCompletionFunction function;
|
||||
|
||||
@JsonProperty("type")
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(final String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonProperty("function")
|
||||
public ChatCompletionFunction getFunction() {
|
||||
return this.function;
|
||||
}
|
||||
|
||||
public void setFunction(final ChatCompletionFunction function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ChatCompletionFunction {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private Map<String, Object> parameters;
|
||||
|
||||
private Boolean strict;
|
||||
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonProperty("description")
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@JsonProperty("parameters")
|
||||
public Map<String, Object> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
public void setParameters(final Map<String, Object> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@JsonProperty("strict")
|
||||
public Boolean getStrict() {
|
||||
return this.strict;
|
||||
}
|
||||
|
||||
public void setStrict(final Boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ChatCompletionNamedToolChoice {
|
||||
|
||||
private String type;
|
||||
|
||||
private ChatCompletionNamedToolChoiceFunction function;
|
||||
|
||||
@JsonProperty("type")
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(final String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonProperty("function")
|
||||
public ChatCompletionNamedToolChoiceFunction getFunction() {
|
||||
return this.function;
|
||||
}
|
||||
|
||||
public void setFunction(final ChatCompletionNamedToolChoiceFunction function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ChatCompletionNamedToolChoiceFunction {
|
||||
|
||||
private String name;
|
||||
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package de.extio.lmlib.client.oai.completion.chat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
|
||||
final class ChatMessage {
|
||||
@@ -9,6 +11,10 @@ final class ChatMessage {
|
||||
private String content;
|
||||
|
||||
private String reasoningContent;
|
||||
|
||||
private List<ChatToolCall> toolCalls;
|
||||
|
||||
private String toolCallId;
|
||||
|
||||
public ChatMessage() {
|
||||
}
|
||||
@@ -45,4 +51,22 @@ final class ChatMessage {
|
||||
public void setReasoningContent(final String reasoningContent) {
|
||||
this.reasoningContent = reasoningContent;
|
||||
}
|
||||
|
||||
@JsonProperty("tool_calls")
|
||||
public List<ChatToolCall> getToolCalls() {
|
||||
return this.toolCalls;
|
||||
}
|
||||
|
||||
public void setToolCalls(final List<ChatToolCall> toolCalls) {
|
||||
this.toolCalls = toolCalls;
|
||||
}
|
||||
|
||||
@JsonProperty("tool_call_id")
|
||||
public String getToolCallId() {
|
||||
return this.toolCallId;
|
||||
}
|
||||
|
||||
public void setToolCallId(final String toolCallId) {
|
||||
this.toolCallId = toolCallId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.extio.lmlib.client.oai.completion.chat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
final class ChatToolCall {
|
||||
|
||||
private Integer index;
|
||||
|
||||
private String id;
|
||||
|
||||
private String type;
|
||||
|
||||
private ChatToolFunction function;
|
||||
|
||||
@JsonProperty("index")
|
||||
public Integer getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
public void setIndex(final Integer index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@JsonProperty("id")
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(final String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@JsonProperty("type")
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(final String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonProperty("function")
|
||||
public ChatToolFunction getFunction() {
|
||||
return this.function;
|
||||
}
|
||||
|
||||
public void setFunction(final ChatToolFunction function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.extio.lmlib.client.oai.completion.chat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
final class ChatToolFunction {
|
||||
|
||||
private String name;
|
||||
|
||||
private String arguments;
|
||||
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonProperty("arguments")
|
||||
public String getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
public void setArguments(final String arguments) {
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
|
||||
@@ -21,6 +20,7 @@ import de.extio.lmlib.client.Chunk;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.ToolCallData;
|
||||
import de.extio.lmlib.client.Conversation.Turn;
|
||||
import de.extio.lmlib.client.oai.completion.AbstractCompletionClient;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
@@ -41,7 +41,12 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer) {
|
||||
public boolean supportsToolCalling() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Completion requestCompletion(final Conversation conversation, final ModelProfile modelProfile, final Consumer<Chunk> chunkConsumer, final ToolCallData toolCallData) {
|
||||
final var promptStrategy = this.promptStrategyFactory.getStrategy(modelProfile.prompt());
|
||||
if (promptStrategy == null) {
|
||||
throw new IllegalArgumentException("Prompt strategy not found: " + modelProfile.prompt());
|
||||
|
||||
Reference in New Issue
Block a user