mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
feat(wiki): LLM pipeline step executor via model routing
This commit is contained in:
parent
85f5df394b
commit
15a8b2d73c
@ -0,0 +1,75 @@
|
|||||||
|
package vip.mate.wiki.pipeline;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.messages.SystemMessage;
|
||||||
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
|
import org.springframework.ai.chat.model.ChatModel;
|
||||||
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import vip.mate.wiki.job.WikiModelRoutingService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipeline step executor that calls a chat model. The step config supplies a
|
||||||
|
* {@code prompt} (system instruction) and an optional {@code model_id}; the
|
||||||
|
* previous step's output is passed as the user message so steps compose. The
|
||||||
|
* model is resolved through the existing wiki model routing.
|
||||||
|
*
|
||||||
|
* @author MateClaw Team
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class WikiLlmStepExecutor implements WikiStepExecutor {
|
||||||
|
|
||||||
|
private final WikiModelRoutingService modelRoutingService;
|
||||||
|
|
||||||
|
public WikiLlmStepExecutor(WikiModelRoutingService modelRoutingService) {
|
||||||
|
this.modelRoutingService = modelRoutingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String type() {
|
||||||
|
return "llm";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute(WikiStepContext context) {
|
||||||
|
String prompt = stringConfig(context, "prompt");
|
||||||
|
if (prompt == null || prompt.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("llm step '" + context.stepId() + "' has no prompt");
|
||||||
|
}
|
||||||
|
Long modelId = longConfig(context, "model_id");
|
||||||
|
ChatModel chatModel = modelRoutingService.buildChatModel(modelId);
|
||||||
|
if (chatModel == null) {
|
||||||
|
throw new IllegalStateException("No chat model available for pipeline step " + context.stepId());
|
||||||
|
}
|
||||||
|
String userContent = context.previousOutput() == null || context.previousOutput().isBlank()
|
||||||
|
? "(no prior output)" : context.previousOutput();
|
||||||
|
ChatResponse resp = chatModel.call(new Prompt(List.of(
|
||||||
|
new SystemMessage(prompt), new UserMessage(userContent))));
|
||||||
|
if (resp == null || resp.getResult() == null || resp.getResult().getOutput() == null
|
||||||
|
|| resp.getResult().getOutput().getText() == null) {
|
||||||
|
throw new IllegalStateException("Chat model returned no text for step " + context.stepId());
|
||||||
|
}
|
||||||
|
return resp.getResult().getOutput().getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringConfig(WikiStepContext context, String key) {
|
||||||
|
Object v = context.stepConfig() == null ? null : context.stepConfig().get(key);
|
||||||
|
return v == null ? null : String.valueOf(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longConfig(WikiStepContext context, String key) {
|
||||||
|
Object v = context.stepConfig() == null ? null : context.stepConfig().get(key);
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(String.valueOf(v));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package vip.mate.wiki.pipeline;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||||
|
import org.springframework.ai.chat.model.ChatModel;
|
||||||
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
|
import org.springframework.ai.chat.model.Generation;
|
||||||
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
|
import vip.mate.wiki.job.WikiModelRoutingService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link WikiLlmStepExecutor}: it builds a prompt from the step
|
||||||
|
* config and previous output, calls the routed model, and returns its text.
|
||||||
|
* Model routing is mocked to return a fixed-response ChatModel.
|
||||||
|
*/
|
||||||
|
class WikiLlmStepExecutorTest {
|
||||||
|
|
||||||
|
private WikiLlmStepExecutor executor(String modelReply) {
|
||||||
|
WikiModelRoutingService routing = mock(WikiModelRoutingService.class);
|
||||||
|
ChatModel chat = prompt -> new ChatResponse(List.of(new Generation(new AssistantMessage(modelReply))));
|
||||||
|
when(routing.buildChatModel(any())).thenReturn(chat);
|
||||||
|
return new WikiLlmStepExecutor(routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WikiStepContext ctx(Map<String, Object> config, String previousOutput) {
|
||||||
|
return new WikiStepContext(1L, 42L, "s1", config, previousOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void callsModelAndReturnsText() throws Exception {
|
||||||
|
WikiLlmStepExecutor e = executor("pattern summary");
|
||||||
|
String out = e.execute(ctx(Map.of("prompt", "Summarize the episodes"), "ep1, ep2"));
|
||||||
|
assertEquals("pattern summary", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingPrompt_throws() {
|
||||||
|
WikiLlmStepExecutor e = executor("x");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> e.execute(ctx(Map.of(), "prior")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void typeIsLlm() {
|
||||||
|
assertTrue("llm".equals(executor("x").type()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user