mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-31 05:24:26 +02:00
[voice] Add REST endpoint to get LLM tools (#5654)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.rest.voice.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* A DTO that is used on the REST API to provide details about LLM tools.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
*/
|
||||
@Schema(name = "LLMTool")
|
||||
@NonNullByDefault
|
||||
public record LLMToolDTO(@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String id,
|
||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String label, String description) {
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.rest.voice.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.voice.text.interpreter.llm.LLMTool;
|
||||
|
||||
/**
|
||||
* Mapper class that maps {@link LLMTool} instances to their respective DTOs.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LLMToolMapper {
|
||||
|
||||
/**
|
||||
* Maps a {@link LLMTool} to a {@link LLMToolDTO}.
|
||||
*
|
||||
* @param tool the LLM tool
|
||||
* @param locale the locale to use for the DTO
|
||||
*
|
||||
* @return the corresponding DTO
|
||||
*/
|
||||
public static LLMToolDTO map(LLMTool tool, @Nullable Locale locale) {
|
||||
return new LLMToolDTO(tool.getUID(), tool.getLabel(locale), tool.getDescription(locale));
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -52,6 +52,8 @@ import org.openhab.core.voice.text.InterpretationArguments;
|
||||
import org.openhab.core.voice.text.InterpretationException;
|
||||
import org.openhab.core.voice.text.conversation.Conversation;
|
||||
import org.openhab.core.voice.text.conversation.ConversationManager;
|
||||
import org.openhab.core.voice.text.interpreter.llm.LLMTool;
|
||||
import org.openhab.core.voice.text.interpreter.llm.LLMToolRegistry;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
@@ -95,17 +97,20 @@ public class VoiceResource implements RESTResource {
|
||||
private final AudioManager audioManager;
|
||||
private final VoiceManager voiceManager;
|
||||
private final ConversationManager conversationManager;
|
||||
private final LLMToolRegistry llmToolRegistry;
|
||||
|
||||
@Activate
|
||||
public VoiceResource( //
|
||||
final @Reference LocaleService localeService, //
|
||||
final @Reference AudioManager audioManager, //
|
||||
final @Reference VoiceManager voiceManager, //
|
||||
final @Reference ConversationManager conversationManager) {
|
||||
final @Reference ConversationManager conversationManager, //
|
||||
final @Reference LLMToolRegistry llmToolRegistry) {
|
||||
this.localeService = localeService;
|
||||
this.audioManager = audioManager;
|
||||
this.voiceManager = voiceManager;
|
||||
this.conversationManager = conversationManager;
|
||||
this.llmToolRegistry = llmToolRegistry;
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -247,6 +252,20 @@ public class VoiceResource implements RESTResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/llmtools")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getLLMTools", summary = "Get the list of all LLM tools.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LLMToolDTO.class)))) })
|
||||
public Response getLLMTools(
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
List<LLMToolDTO> dtos = llmToolRegistry.getAll().stream()
|
||||
.sorted(java.util.Comparator.comparing(LLMTool::getUID)).map(tool -> LLMToolMapper.map(tool, locale))
|
||||
.toList();
|
||||
return Response.ok(dtos).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/voices")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
||||
+37
-1
@@ -27,6 +27,8 @@ import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.voice.VoiceManager;
|
||||
import org.openhab.core.voice.text.conversation.Conversation;
|
||||
import org.openhab.core.voice.text.conversation.ConversationManager;
|
||||
import org.openhab.core.voice.text.interpreter.llm.LLMTool;
|
||||
import org.openhab.core.voice.text.interpreter.llm.LLMToolRegistry;
|
||||
|
||||
/**
|
||||
* Test class for {@link VoiceResource}.
|
||||
@@ -40,9 +42,10 @@ public class VoiceResourceTest {
|
||||
private AudioManager audioManager = mock(AudioManager.class);
|
||||
private VoiceManager voiceManager = mock(VoiceManager.class);
|
||||
private ConversationManager conversationManager = mock(ConversationManager.class);
|
||||
private LLMToolRegistry llmToolRegistry = mock(LLMToolRegistry.class);
|
||||
|
||||
private VoiceResource voiceResource = new VoiceResource(localeService, audioManager, voiceManager,
|
||||
conversationManager);
|
||||
conversationManager, llmToolRegistry);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@@ -76,4 +79,37 @@ public class VoiceResourceTest {
|
||||
assertEquals(Instant.ofEpochMilli(3000), dto2.created());
|
||||
assertEquals(Instant.ofEpochMilli(4000), dto2.lastUpdated());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testGetLLMTools() {
|
||||
LLMTool tool1 = mock(LLMTool.class);
|
||||
when(tool1.getUID()).thenReturn("tool-1");
|
||||
when(tool1.getLabel(any())).thenReturn("Tool 1");
|
||||
when(tool1.getDescription(any())).thenReturn("Description of tool 1");
|
||||
|
||||
LLMTool tool2 = mock(LLMTool.class);
|
||||
when(tool2.getUID()).thenReturn("tool-2");
|
||||
when(tool2.getLabel(any())).thenReturn("Tool 2");
|
||||
when(tool2.getDescription(any())).thenReturn("Description of tool 2");
|
||||
|
||||
when(llmToolRegistry.getAll()).thenReturn(List.of(tool1, tool2));
|
||||
|
||||
Response response = voiceResource.getLLMTools(null);
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
|
||||
List<LLMToolDTO> entity = (List<LLMToolDTO>) response.getEntity();
|
||||
assertNotNull(entity);
|
||||
assertEquals(2, entity.size());
|
||||
|
||||
LLMToolDTO dto1 = entity.get(0);
|
||||
assertEquals("tool-1", dto1.id());
|
||||
assertEquals("Tool 1", dto1.label());
|
||||
assertEquals("Description of tool 1", dto1.description());
|
||||
|
||||
LLMToolDTO dto2 = entity.get(1);
|
||||
assertEquals("tool-2", dto2.id());
|
||||
assertEquals("Tool 2", dto2.label());
|
||||
assertEquals("Description of tool 2", dto2.description());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user