diff --git a/erupt-ai-web/src/App.vue b/erupt-ai-web/src/App.vue
index e889abb86..73ff20a8f 100644
--- a/erupt-ai-web/src/App.vue
+++ b/erupt-ai-web/src/App.vue
@@ -92,6 +92,14 @@ const fetchMessages = (chatId: number, toBottom: boolean, after?: () => void) =>
bubbles.value.scrollTop = bubbles.value.scrollHeight; // 滚动到最新消息
}, 100);
}
+ }, err => {
+ messages.value.push({
+ content: err.message || err.data || String(err),
+ senderType: 'MODEL',
+ id: 0,
+ createTime: "",
+ loading: false
+ })
}).finally(() => {
loadingMoreMessages.value = false; // 加载完成
});
diff --git a/erupt-ai/pom.xml b/erupt-ai/pom.xml
index fec8f8eaf..faf8efd8b 100644
--- a/erupt-ai/pom.xml
+++ b/erupt-ai/pom.xml
@@ -14,6 +14,10 @@
../pom.xml
+
+ 1.11.0
+
+
${erupt.groupId}
@@ -37,9 +41,29 @@
true
- com.squareup.okhttp3
- okhttp
- 4.12.0
+ dev.langchain4j
+ langchain4j-open-ai
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-anthropic
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-google-ai-gemini
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-ollama
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j
+ ${langchain4j.version}
org.freemarker
diff --git a/erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiParam.java b/erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiToolbox.java
similarity index 52%
rename from erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiParam.java
rename to erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiToolbox.java
index 40a144f73..f9cee4683 100644
--- a/erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiParam.java
+++ b/erupt-ai/src/main/java/xyz/erupt/ai/annotation/AiToolbox.java
@@ -3,13 +3,11 @@
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.FIELD)
+@Target(ElementType.TYPE)
@Documented
@Inherited
-public @interface AiParam {
+public @interface AiToolbox {
- String description();
-
- boolean required() default true;
+ String value() default "";
}
diff --git a/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionCall.java b/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionCall.java
deleted file mode 100644
index 7e3b89882..000000000
--- a/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionCall.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package xyz.erupt.ai.call;
-
-/**
- * @author YuePeng
- * date 2025/3/14 23:30
- */
-public interface AiFunctionCall {
-
- default boolean mcpCall() {
- return true;
- }
-
- default String name() {
- return this.getClass().getSimpleName();
- }
-
- String description();
-
- String call(String userPrompt);
-
-}
diff --git a/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionManager.java b/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionManager.java
deleted file mode 100644
index 552eda8c6..000000000
--- a/erupt-ai/src/main/java/xyz/erupt/ai/call/AiFunctionManager.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package xyz.erupt.ai.call;
-
-import com.google.gson.reflect.TypeToken;
-import lombok.Getter;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.core.type.filter.AssignableTypeFilter;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.stereotype.Component;
-import xyz.erupt.ai.annotation.AiParam;
-import xyz.erupt.ai.constants.ResponseFormat;
-import xyz.erupt.ai.core.LlmCore;
-import xyz.erupt.ai.core.LlmRequest;
-import xyz.erupt.ai.model.LLM;
-import xyz.erupt.ai.pojo.ChatCompletionMessage;
-import xyz.erupt.ai.util.MarkDownUtil;
-import xyz.erupt.core.config.GsonFactory;
-import xyz.erupt.core.exception.EruptWebApiRuntimeException;
-import xyz.erupt.core.service.EruptApplication;
-import xyz.erupt.core.util.EruptSpringUtil;
-
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * @author YuePeng
- * date 2025/3/14 23:42
- */
-@Component
-@Order(100)
-@Slf4j
-public class AiFunctionManager implements ApplicationRunner {
-
- @Getter
- private static final Map aiFunctions = new HashMap<>();
-
- @Override
- public void run(ApplicationArguments args) {
- EruptSpringUtil.scannerPackage(EruptApplication.getScanPackage(),
- new TypeFilter[]{new AssignableTypeFilter(AiFunctionCall.class)}, clazz -> {
- AiFunctionCall functionCall = (AiFunctionCall) EruptSpringUtil.getBean(clazz);
- aiFunctions.put(functionCall.name(), functionCall);
- });
- }
-
- public String getFunctionCallPrompt() {
- StringBuilder sb = new StringBuilder("""
- Below is a mapping of available Function Calls.
-
- Please decide whether a function is clearly required after understanding the user's question.
-
- Rules:
- 1. Call a function IF AND ONLY IF the user's intent clearly and directly matches the function description,
- and calling the function will significantly improve the accuracy of the response.
- 2. If a function is triggered, STRICTLY output ONLY the function name.
- Do NOT output explanations, symbols, punctuation, or any extra text.
- 3. If no function is required, IGNORE this entire instruction block
- and respond to the user normally.
-
- Available functions:
- """);
- for (Map.Entry entry : aiFunctions.entrySet()) {
- sb.append("- ")
- .append(entry.getKey())
- .append(": ")
- .append(entry.getValue().description())
- .append("\n");
- }
-
- return sb.toString();
- }
-
- public boolean exist(String key) {
- return aiFunctions.containsKey(key);
- }
-
- @SneakyThrows
- public String call(String key, LLM llm, String userMessage, List userContext) {
- AiFunctionCall aiFunctionCall = aiFunctions.get(key);
- Map params = new HashMap<>();
- for (Field field : aiFunctionCall.getClass().getDeclaredFields()) {
- Optional.ofNullable(field.getAnnotation(AiParam.class)).ifPresent(it -> {
- params.put(field.getName(), field);
- });
- }
- if (params.isEmpty()) {
- return aiFunctionCall.call(userMessage);
- } else {
- Map promptTemplateMap = getStringParamPromptTemplateMap(params);
- StringBuilder prompt = new StringBuilder();
- prompt.append(userMessage).append("\n\n");
- prompt.append("根据上面的内容,自动识别并填充下面JSON的val字段,此JSON中的每个value都是具体的生成要求,将不同key的识别结果放到对应val字段内\n");
- prompt.append("请严格按照以下JSON格式返回,不要返回其他任何多余的内容或解释,请确保只返回纯JSON:\n\n");
- prompt.append(GsonFactory.getGson().toJson(promptTemplateMap));
- LlmRequest llmRequest = llm.toLlmRequest();
- llmRequest.setResponseFormat(ResponseFormat.json_object);
- String llmRes = LlmCore.getLLM(llm).chat(llm.toLlmRequest(), prompt.toString(), userContext).getMessageStr();
- llmRes = MarkDownUtil.extractCodeBlock(llmRes);
- try {
- Map res = GsonFactory.getGson().fromJson(llmRes, new TypeToken