Commit cc2771a5 by 王一诺

文本和图片接口都是用了openai 标准接口,返回值提供stream 和 json 两种形式

parent fc19145a
Showing with 715 additions and 564 deletions
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
...@@ -4,7 +4,41 @@ ...@@ -4,7 +4,41 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="6879e488-b2d7-4692-a578-ccef01aca622" name="Changes" comment="" /> <list default="true" id="6879e488-b2d7-4692-a578-ccef01aca622" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/constant/ContentDeserializer.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/base/BaseMessage.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/base/BaseRequest.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/image/ImageRequest.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/ModelController.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/IImageModelService.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ITextModelService.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/impl/ImageModelServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/api-llm/src/main/java/com/coolook/llm/api/remote/ServiceLLMClient.java" beforeDir="false" afterPath="$PROJECT_DIR$/api-llm/src/main/java/com/coolook/llm/api/remote/ServiceLLMClient.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/api-llm/src/main/java/com/coolook/llm/api/service/impl/AiServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/api-llm/src/main/java/com/coolook/llm/api/service/impl/AiServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/api-llm/src/main/resources/application-global.yml" beforeDir="false" afterPath="$PROJECT_DIR$/api-llm/src/main/resources/application-global.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/api-llm/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/api-llm/src/main/resources/application.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/dto/Choices.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/dto/Usage.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/ImageContent.java" beforeDir="false" afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/image/ImageContent.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/ImageMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/image/ImageMessage.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/ImageUrl.java" beforeDir="false" afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/image/ImageUrl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/OpenAiImageRequest.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/OpenAiTextRequest.java" beforeDir="false" afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/text/TextRequest.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/TextContent.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/TextMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/request/text/TextMessage.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/response/ResponseStandardModel.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/response/ResponseStandardModelError.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/CustomModelController.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/DataSyncController.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/ModelManagerController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/SearchConfigController.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/controller/ConfigController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/remote/LLMClient.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/remote/LLMClient.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/InitializerService.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/InitTaskService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ModelDataService.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ModelDataService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ModelService.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/impl/TextModelServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/SearchConfigService.java" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ConfigService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/resources/application-global.yml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/service-llm/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/service-llm/src/main/resources/application.yml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
...@@ -19,6 +53,9 @@ ...@@ -19,6 +53,9 @@
</list> </list>
</option> </option>
</component> </component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="KubernetesApiPersistence">{}</component> <component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{ <component name="KubernetesApiProvider">{
&quot;isMigrated&quot;: true &quot;isMigrated&quot;: true
...@@ -34,53 +71,57 @@ ...@@ -34,53 +71,57 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;Application.LLMClient.executor&quot;: &quot;Run&quot;, "Application.LLMClient.executor": "Run",
&quot;Maven. [org.apache.maven.plugins:maven-archetype-plugin:RELEASE:generate].executor&quot;: &quot;Run&quot;, "Maven. [org.apache.maven.plugins:maven-archetype-plugin:RELEASE:generate].executor": "Run",
&quot;Maven.api-llm [clean].executor&quot;: &quot;Run&quot;, "Maven.api-llm [clean].executor": "Run",
&quot;Maven.api-llm [package].executor&quot;: &quot;Run&quot;, "Maven.api-llm [package].executor": "Run",
&quot;Maven.common-llm [clean].executor&quot;: &quot;Run&quot;, "Maven.common-llm [clean].executor": "Run",
&quot;Maven.common-llm [compile].executor&quot;: &quot;Run&quot;, "Maven.common-llm [compile].executor": "Run",
&quot;Maven.common-llm [install].executor&quot;: &quot;Run&quot;, "Maven.common-llm [install].executor": "Run",
&quot;Maven.common-llm [package].executor&quot;: &quot;Run&quot;, "Maven.common-llm [package].executor": "Run",
&quot;Maven.large-language-model [clean].executor&quot;: &quot;Run&quot;, "Maven.large-language-model [clean].executor": "Run",
&quot;Maven.large-language-model [package].executor&quot;: &quot;Run&quot;, "Maven.large-language-model [package].executor": "Run",
&quot;Maven.llm-api [clean].executor&quot;: &quot;Run&quot;, "Maven.llm-api [clean].executor": "Run",
&quot;Maven.llm-api [compile].executor&quot;: &quot;Run&quot;, "Maven.llm-api [compile].executor": "Run",
&quot;Maven.llm-api [package].executor&quot;: &quot;Run&quot;, "Maven.llm-api [package].executor": "Run",
&quot;Maven.llm-api [test].executor&quot;: &quot;Run&quot;, "Maven.llm-api [test].executor": "Run",
&quot;Maven.service-llm [clean].executor&quot;: &quot;Run&quot;, "Maven.service-llm [clean].executor": "Run",
&quot;Maven.service-llm [package].executor&quot;: &quot;Run&quot;, "Maven.service-llm [package].executor": "Run",
&quot;Maven.service-llm-text [clean].executor&quot;: &quot;Run&quot;, "Maven.service-llm-text [clean].executor": "Run",
&quot;Maven.service-llm-text [compile].executor&quot;: &quot;Run&quot;, "Maven.service-llm-text [compile].executor": "Run",
&quot;Maven.service-llm-text [package].executor&quot;: &quot;Run&quot;, "Maven.service-llm-text [package].executor": "Run",
&quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;, "RequestMappingsPanelOrder0": "0",
&quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;, "RequestMappingsPanelOrder1": "1",
&quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;, "RequestMappingsPanelWidth0": "75",
&quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;, "RequestMappingsPanelWidth1": "75",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;Spring Boot.LLMApiApplication.executor&quot;: &quot;Run&quot;, "Spring Boot.LLMApiApplication.executor": "Run",
&quot;Spring Boot.ServiceLlmApplication.executor&quot;: &quot;Run&quot;, "Spring Boot.ServiceLlmApplication.executor": "Debug",
&quot;com.codeium.enabled&quot;: &quot;true&quot;, "com.codeium.enabled": "true",
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, "git-widget-placeholder": "main",
&quot;last_opened_file_path&quot;: &quot;/Users/wangyinuo/workspace/code/java/large-language-model/api-llm/src/main/resources&quot;, "kotlin-language-version-configured": "true",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "last_opened_file_path": "/Users/wangyinuo/workspace/code/java/large-language-model/api-llm/src/main/resources",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;, "nodejs_package_manager_path": "npm",
&quot;project.structure.proportion&quot;: &quot;0.0&quot;, "project.structure.last.edited": "Modules",
&quot;project.structure.side.proportion&quot;: &quot;0.0&quot;, "project.structure.proportion": "0.0",
&quot;settings.editor.selected.configurable&quot;: &quot;build.tools&quot;, "project.structure.side.proportion": "0.0",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "settings.editor.selected.configurable": "build.tools",
"vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="ReactorSettings"> <component name="ReactorSettings">
<option name="notificationShown" value="true" /> <option name="notificationShown" value="true" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CreateClassDialog.RecentsKey">
<recent name="com.coolook.common.llm.response" />
</key>
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/api-llm/src/main/resources" /> <recent name="$PROJECT_DIR$/api-llm/src/main/resources" />
<recent name="$PROJECT_DIR$/service-llm/src/main/resources" /> <recent name="$PROJECT_DIR$/service-llm/src/main/resources" />
...@@ -89,11 +130,11 @@ ...@@ -89,11 +130,11 @@
<recent name="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/utils" /> <recent name="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/utils" />
</key> </key>
<key name="CopyClassDialog.RECENTS_KEY"> <key name="CopyClassDialog.RECENTS_KEY">
<recent name="com.coolook.service.llm.controller" />
<recent name="com.coolook.service.llm.service" />
<recent name="com.coolook.common.llm.request" /> <recent name="com.coolook.common.llm.request" />
<recent name="com.coolook.common.llm.request.text" />
<recent name="com.coolook.common.llm.dto" /> <recent name="com.coolook.common.llm.dto" />
<recent name="com.coolook.common.llm.response" />
<recent name="com.coolook.service.llm.controller" />
<recent name="com.coolook.common.llm.pojo" />
</key> </key>
</component> </component>
<component name="RunDashboard"> <component name="RunDashboard">
...@@ -193,13 +234,25 @@ ...@@ -193,13 +234,25 @@
<workItem from="1741920343491" duration="1645000" /> <workItem from="1741920343491" duration="1645000" />
<workItem from="1741931944414" duration="27842000" /> <workItem from="1741931944414" duration="27842000" />
<workItem from="1742177794559" duration="6136000" /> <workItem from="1742177794559" duration="6136000" />
<workItem from="1742193641040" duration="4084000" /> <workItem from="1742193641040" duration="7021000" />
<workItem from="1742201439685" duration="34459000" />
</task> </task>
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
<breakpoints> <breakpoints>
...@@ -207,11 +260,6 @@ ...@@ -207,11 +260,6 @@
<properties class="java.lang.NullPointerException" package="java.lang" /> <properties class="java.lang.NullPointerException" package="java.lang" />
<option name="timeStamp" value="1" /> <option name="timeStamp" value="1" />
</breakpoint> </breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/service-llm/src/main/java/com/coolook/service/llm/service/ModelService.java</url>
<line>101</line>
<option name="timeStamp" value="2" />
</line-breakpoint>
</breakpoints> </breakpoints>
</breakpoint-manager> </breakpoint-manager>
<watches-manager> <watches-manager>
......
package com.coolook.llm.api.remote; package com.coolook.llm.api.remote;
import com.coolook.common.llm.dto.ModelsDto; import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.response.ResponseResult; import com.coolook.common.llm.request.text.TextRequest;
import com.coolook.common.llm.request.OpenAiTextRequest;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
...@@ -11,7 +10,6 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -11,7 +10,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author: Wang Yinuo * @author: Wang Yinuo
...@@ -22,8 +20,8 @@ import java.util.Map; ...@@ -22,8 +20,8 @@ import java.util.Map;
public interface ServiceLLMClient { public interface ServiceLLMClient {
@RequestMapping(method = RequestMethod.POST, value ="/text/v1/chat/completions") @RequestMapping(method = RequestMethod.POST, value ="/text/v1/chat/completions")
public ResponseEntity<String> chat(@RequestBody OpenAiTextRequest content); public ResponseEntity<String> chat(@RequestBody TextRequest content);
@RequestMapping(method = RequestMethod.GET, value ="/v1/models") @RequestMapping(method = RequestMethod.GET, value ="/text/v1/models")
public HttpEntity<List<ModelsDto>> getAvailableModelList(); public HttpEntity<List<ModelsDto>> getAvailableModelList();
} }
...@@ -6,9 +6,8 @@ import com.alibaba.fastjson2.JSONObject; ...@@ -6,9 +6,8 @@ import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.constant.AiStatus; import com.coolook.common.llm.constant.AiStatus;
import com.coolook.common.llm.dto.Message; import com.coolook.common.llm.dto.Message;
import com.coolook.common.llm.dto.ModelsDto; import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.request.OpenAiTextRequest; import com.coolook.common.llm.request.text.TextRequest;
import com.coolook.common.llm.request.TextContent; import com.coolook.common.llm.request.text.TextMessage;
import com.coolook.common.llm.request.TextMessage;
import com.coolook.common.llm.response.ResponseResult; import com.coolook.common.llm.response.ResponseResult;
import com.coolook.common.llm.utils.SecurityUtils; import com.coolook.common.llm.utils.SecurityUtils;
import com.coolook.llm.api.remote.ServiceLLMClient; import com.coolook.llm.api.remote.ServiceLLMClient;
...@@ -22,7 +21,6 @@ import lombok.extern.slf4j.Slf4j; ...@@ -22,7 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author: WangYinuo * @author: WangYinuo
...@@ -61,7 +59,6 @@ public class AiServiceImpl implements IAiService { ...@@ -61,7 +59,6 @@ public class AiServiceImpl implements IAiService {
return ResponseResult.success(error); return ResponseResult.success(error);
} }
@Override @Override
public ResponseResult<Message> ask(Message msg) { public ResponseResult<Message> ask(Message msg) {
String msgId = msg.getMsgId().replace("_forword", "");; String msgId = msg.getMsgId().replace("_forword", "");;
...@@ -84,17 +81,20 @@ public class AiServiceImpl implements IAiService { ...@@ -84,17 +81,20 @@ public class AiServiceImpl implements IAiService {
HttpEntity<List<ModelsDto>> availableModelEntity = serviceLlmClient.getAvailableModelList(); HttpEntity<List<ModelsDto>> availableModelEntity = serviceLlmClient.getAvailableModelList();
List<ModelsDto> textModelList = availableModelEntity.getBody(); List<ModelsDto> textModelList = availableModelEntity.getBody();
OpenAiTextRequest openAiTextRequest = new OpenAiTextRequest(); TextRequest openAiTextRequest = new TextRequest();
openAiTextRequest.setModel(textModelList.get(0).getId()); openAiTextRequest.setModel(textModelList.get(0).getId());
List<TextMessage> messageList = new ArrayList(); List<TextMessage> messageList = new ArrayList();
messageList.add(new TextMessage("user", msg.getAiQuestion()));
TextMessage textMessage = new TextMessage();
textMessage.setContent(msg.getAiQuestion());
textMessage.setRole("user");
messageList.add(textMessage);
openAiTextRequest.setMessages(messageList); openAiTextRequest.setMessages(messageList);
ResponseEntity<String> responseMsg = serviceLlmClient.chat(openAiTextRequest); ResponseEntity<String> responseMsg = serviceLlmClient.chat(openAiTextRequest);
String content = parseResponseEntity(responseMsg.getBody()); String content = parseResponseEntity(responseMsg.getBody());
log.info("responseMsg:{}", responseMsg); log.info("responseMsg:{}", responseMsg);
if (responseMsg != null) { if (responseMsg != null) {
Message aiMessage = new Message(); Message aiMessage = new Message();
......
...@@ -8,4 +8,4 @@ spring: ...@@ -8,4 +8,4 @@ spring:
cloud: cloud:
nacos: nacos:
discovery: discovery:
server-addr: 158.178.244.87 server-addr: 158.178.244.87:8848
\ No newline at end of file \ No newline at end of file
server: server:
port: 8900 port: 9009
servlet: servlet:
# 应用的访问路径 # 应用的访问路径
context-path: /ai-server context-path: /ai-server
...@@ -8,7 +8,7 @@ spring: ...@@ -8,7 +8,7 @@ spring:
application: application:
name: llm-api name: llm-api
profiles: profiles:
active: local active: global
app: app:
displacementDictionary: "aigmzp79Y6FOodkI4etj5XbfxGrRSHEZcAw0UDPn8LBhV1yqMuvKWsJlCN3T2Q" displacementDictionary: "aigmzp79Y6FOodkI4etj5XbfxGrRSHEZcAw0UDPn8LBhV1yqMuvKWsJlCN3T2Q"
package com.coolook.common.llm.constant;
import com.coolook.common.llm.request.image.ImageContent;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-18:PM4:38
*/
public class ContentDeserializer extends JsonDeserializer<Object> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
// 判断 content 是 String 还是 List
if (node.isTextual()) {
// 解析为字符串(文本请求)
return node.asText();
} else if (node.isArray()) {
return objectMapper.readValue(node.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, ImageContent.class));
}
return null;
}
}
package com.coolook.common.llm.dto;
import lombok.Data;
/**
* @author: Wang Yinuo
* @create: 2025-03-14:PM2:44
*/
@Data
public class Choices {
private Message message;
private String logprobs;
private String finish_reason;
private Integer index;
}
package com.coolook.common.llm.dto;
import lombok.Data;
/**
* @author: Wang Yinuo
* @create: 2025-03-14:PM2:42
*/
@Data
public class Usage {
private int prompt_tokens;
private int completion_tokens;
private int total_tokens;
}
package com.coolook.common.llm.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-03:PM3:20
*/
@Data
public class OpenAiImageRequest {
private String model;
private List<ImageMessage> messages;
private double temperature;
@JsonProperty("max_tokens")
private int maxTokens;
@JsonProperty("top_p")
private double topP;
@JsonProperty("frequency_penalty")
private double frequencyPenalty;
@JsonProperty("presence_penalty")
private double presencePenalty;
}
package com.coolook.common.llm.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author: Wang Yinuo
* @create: 2025-03-03:PM4:11
*/
@Data
public class TextContent {
public String type;
public String text;
}
package com.coolook.common.llm.request.base;
import com.coolook.common.llm.constant.ContentDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Data;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-18:PM2:26
*/
@Data
public class BaseMessage {
private String role;
@JsonDeserialize(using = ContentDeserializer.class)
private Object content;
}
package com.coolook.common.llm.request.base;
import lombok.*;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-18:PM2:23
*/
@Data
public class BaseRequest {
private String model;
private List<BaseMessage> messages;
private Boolean stream;
}
package com.coolook.common.llm.request; package com.coolook.common.llm.request.image;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import lombok.Data; import lombok.Data;
/** /**
...@@ -8,9 +11,11 @@ import lombok.Data; ...@@ -8,9 +11,11 @@ import lombok.Data;
* @create: 2025-03-03:PM4:11 * @create: 2025-03-03:PM4:11
*/ */
@Data @Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ImageContent { public class ImageContent {
public String type; private String type;
public String text; private String text;
// 使用自定义的 image_url 类型映射
@JsonProperty("image_url") @JsonProperty("image_url")
public ImageUrl imageUrl; private ImageUrl imageUrl;
} }
package com.coolook.common.llm.request; package com.coolook.common.llm.request.image;
import com.coolook.common.llm.request.base.BaseMessage;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
...@@ -10,6 +13,6 @@ import java.util.List; ...@@ -10,6 +13,6 @@ import java.util.List;
*/ */
@Data @Data
public class ImageMessage { public class ImageMessage {
public String role; private String role;
public List<ImageContent> content; private List<ImageContent> content;
} }
package com.coolook.common.llm.request.image;
import com.coolook.common.llm.request.base.BaseMessage;
import com.coolook.common.llm.request.base.BaseRequest;
import com.coolook.common.llm.request.text.TextMessage;
import lombok.*;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-03:PM3:20
*/
@Data
public class ImageRequest {
private String model;
private List<ImageMessage> messages;
private Boolean stream;
}
package com.coolook.common.llm.request; package com.coolook.common.llm.request.image;
import lombok.Data; import lombok.Data;
......
package com.coolook.common.llm.request; package com.coolook.common.llm.request.text;
import com.coolook.common.llm.request.base.BaseMessage;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
/** /**
* @author: Wang Yinuo * @author: Wang Yinuo
* @create: 2025-03-03:PM3:21 * @create: 2025-03-03:PM3:21
*/ */
@Data @Data
@AllArgsConstructor public class TextMessage extends BaseMessage {
@NoArgsConstructor
public class TextMessage {
private String role; private String role;
//private List<TextContent> content;
private String content; private String content;
} }
package com.coolook.common.llm.request; package com.coolook.common.llm.request.text;
import lombok.Data; import com.coolook.common.llm.request.base.BaseMessage;
import com.coolook.common.llm.request.base.BaseRequest;
import lombok.*;
import java.io.Serializable;
import java.util.List; import java.util.List;
/** /**
...@@ -11,7 +12,9 @@ import java.util.List; ...@@ -11,7 +12,9 @@ import java.util.List;
*/ */
@Data @Data
public class OpenAiTextRequest implements Serializable { public class TextRequest {
private String model; private String model;
private Boolean stream;
private List<TextMessage> messages; private List<TextMessage> messages;
} }
package com.coolook.common.llm.response;
import com.coolook.common.llm.constant.CommonStatusEnum;
import com.coolook.common.llm.dto.Choices;
import com.coolook.common.llm.dto.Usage;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @author: WangYinuo
* @create: 2025-01-03:PM1:39
*/
@Data
public class ResponseStandardModel {
private String id;
private String object;
private Long created;
private String model;
private Usage usage;
private List<Choices> choices;
}
package com.coolook.common.llm.response;
import com.coolook.common.llm.dto.Choices;
import com.coolook.common.llm.dto.Usage;
import lombok.Data;
import java.util.List;
/**
* @author: WangYinuo
* @create: 2025-01-03:PM1:39
*/
@Data
public class ResponseStandardModelError {
private ErrorDetail error;
public ResponseStandardModelError(String message, String type, String param, String code) {
this.error = new ErrorDetail(message, type, param, code);
}
public ErrorDetail getError() {
return error;
}
public static class ErrorDetail {
private String message;
private String type;
private String param;
private String code;
public ErrorDetail(String message, String type, String param, String code) {
this.message = message;
this.type = type;
this.param = param;
this.code = code;
}
public String getMessage() {
return message;
}
public String getType() {
return type;
}
public String getParam() {
return param;
}
public String getCode() {
return code;
}
}
}
package com.coolook.service.llm.controller; package com.coolook.service.llm.controller;
import com.coolook.common.llm.response.ResponseResult; import com.coolook.common.llm.response.ResponseResult;
import com.coolook.service.llm.service.SearchConfigService; import com.coolook.service.llm.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
...@@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.RestController;
*/ */
@RestController @RestController
public class SearchConfigController { public class ConfigController {
@Autowired @Autowired
private SearchConfigService searchConfigService; private ConfigService searchConfigService;
@GetMapping("/get-search-config") @GetMapping("/get-search-config")
public ResponseResult getSearchConfig() { public ResponseResult getSearchConfig() {
......
package com.coolook.service.llm.controller;
import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.response.ResponseResult;
import com.coolook.common.llm.request.ModelsRequest;
import com.coolook.common.llm.request.OpenAiImageRequest;
import com.coolook.common.llm.request.OpenAiTextRequest;
import com.coolook.service.llm.service.ModelService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-02-21:AM11:30
*/
@Slf4j
@RestController
public class CustomModelController {
@Autowired
private ModelService llmService;
@GetMapping("/v1/models")
public HttpEntity<List<ModelsDto>> getAvailableModelList() {
HttpEntity<List<ModelsDto>> textModelList = llmService.getTextModelList();
// ResponseResult<List<String>> pictureModelList = llmService.getPictureModelList();
return textModelList;
}
@PostMapping("/text/v1/chat/completions")
public ResponseEntity<?> chat(@RequestBody OpenAiTextRequest content) throws JsonProcessingException {
ResponseEntity<?> chat = llmService.chat(content);
ObjectMapper objectMapper = new ObjectMapper();
byte[] responseBytes = objectMapper.writeValueAsBytes(chat.getBody());
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(responseBytes.length);
return ResponseEntity.ok()
.headers(headers)
.body(chat.getBody());
}
@PostMapping("/image/v1/chat/completions")
public ResponseEntity<?> picture(@RequestBody OpenAiImageRequest content) {
return llmService.picture(content);
}
@PostMapping("/add-model")
public ResponseResult addModel(@RequestBody ModelsRequest[] content) {
return llmService.addModel(content);
}
}
\ No newline at end of file
package com.coolook.service.llm.controller;
import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.request.base.BaseMessage;
import com.coolook.common.llm.request.base.BaseRequest;
import com.coolook.common.llm.request.image.ImageContent;
import com.coolook.common.llm.request.image.ImageMessage;
import com.coolook.common.llm.request.image.ImageRequest;
import com.coolook.common.llm.request.text.TextMessage;
import com.coolook.common.llm.request.text.TextRequest;
import com.coolook.service.llm.service.IImageModelService;
import com.coolook.service.llm.service.ITextModelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* @author: Wang Yinuo
* @create: 2025-02-21:AM11:30
*/
@Slf4j
@RestController
public class ModelController {
@Autowired
private ITextModelService textModelService;
@Autowired
private IImageModelService imageModelService;
private final ExecutorService executorService = Executors.newCachedThreadPool();
@GetMapping("/v1/models")
public ResponseEntity<?> getAvailableModelList() {
List textModelList = textModelService.getTextModelList();
List pictureModelList = imageModelService.getPictureModelList();
ArrayList<Object> modelList = new ArrayList<>();
modelList.add(textModelList);
modelList.add(pictureModelList);
return ResponseEntity.ok(modelList);
}
@PostMapping("/v1/chat/completions")
public Object chat(@RequestBody BaseRequest content) {
if (content.getMessages().get(0).getContent() instanceof String) {
TextRequest textRequest = new TextRequest();
textRequest.setModel(content.getModel());
textRequest.setStream(content.getStream());
List<BaseMessage> messages = content.getMessages();
List<TextMessage> textMessages = messages.stream().map(message -> {
TextMessage textMessage = new TextMessage();
textMessage.setRole(message.getRole());
textMessage.setContent((String) message.getContent());
return textMessage;
}).collect(Collectors.toList());
textRequest.setMessages(textMessages);
if (Boolean.TRUE.equals(textRequest.getStream())) {
textRequest.setStream(false);
return chatStream(textRequest);
} else {
textRequest.setStream(false);
return textModelService.chatAndReturnJson(textRequest);
}
} else if (content.getMessages().get(0).getContent() instanceof List) {
ImageRequest imageRequest = new ImageRequest();
imageRequest.setModel(content.getModel());
imageRequest.setStream(content.getStream());
List<BaseMessage> messages = content.getMessages();
List<ImageMessage> imageMessages = messages.stream().map(message -> {
ImageMessage imageMessage = new ImageMessage();
imageMessage.setRole(message.getRole());
imageMessage.setContent((List<ImageContent>) message.getContent());
return imageMessage;
}).collect(Collectors.toList());
imageRequest.setMessages(imageMessages);
if (Boolean.TRUE.equals(imageRequest.getStream())) {
imageRequest.setStream(false);
return picStream(imageRequest);
} else {
imageRequest.setStream(false);
return imageModelService.pictureAndReturnJson(imageRequest);
}
}
return null;
}
private SseEmitter chatStream(TextRequest content) {
JSONObject chat = textModelService.chatAndReturnStream(content);
SseEmitter emitter = new SseEmitter(0L);
executorService.submit(() -> {
try {
log.info("data: {}", chat + "\n\n");
emitter.send(chat.toString());
emitter.send("[DONE]\n\n");
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
private SseEmitter picStream(ImageRequest content) {
JSONObject pic = imageModelService.pictureAndReturnStream(content);
SseEmitter emitter = new SseEmitter(0L);
executorService.submit(() -> {
try {
log.info("data: {}", pic + "\n\n");
emitter.send(pic.toString());
emitter.send("[DONE]\n\n");
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
}
\ No newline at end of file
...@@ -17,7 +17,7 @@ import java.util.List; ...@@ -17,7 +17,7 @@ import java.util.List;
@RestController @RestController
@Slf4j @Slf4j
public class DataSyncController { public class ModelManagerController {
@Autowired @Autowired
private ModelDataService modelDataService; private ModelDataService modelDataService;
...@@ -43,4 +43,9 @@ public class DataSyncController { ...@@ -43,4 +43,9 @@ public class DataSyncController {
log.info("updateUrl: {}", urlRequestList); log.info("updateUrl: {}", urlRequestList);
return modelDataService.saveOrUpdateUrl(urlRequestList); return modelDataService.saveOrUpdateUrl(urlRequestList);
} }
@PostMapping("/add-model")
public ResponseResult addModel(@RequestBody ModelsRequest[] content) {
return modelDataService.addModel(content);
}
} }
package com.coolook.service.llm.remote; package com.coolook.service.llm.remote;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.constant.ModelConstant; import com.coolook.common.llm.constant.ModelConstant;
import com.coolook.common.llm.dto.Models; import com.coolook.common.llm.dto.Models;
import com.coolook.common.llm.request.*; import com.coolook.common.llm.request.base.BaseMessage;
import com.coolook.common.llm.response.ResponseStandardModelError; import com.coolook.common.llm.request.image.ImageRequest;
import com.fasterxml.jackson.core.type.TypeReference; import com.coolook.common.llm.request.text.TextMessage;
import com.coolook.common.llm.request.text.TextRequest;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -92,7 +92,7 @@ public class LLMClient { ...@@ -92,7 +92,7 @@ public class LLMClient {
return checkedModel; return checkedModel;
} }
public ResponseEntity<Map<String, Object>> chat(Models model, OpenAiTextRequest content) { public JSONObject chat(Models model, TextRequest content) {
try { try {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
...@@ -108,16 +108,17 @@ public class LLMClient { ...@@ -108,16 +108,17 @@ public class LLMClient {
List<Map<String, Object>> messages = new ArrayList<>(); List<Map<String, Object>> messages = new ArrayList<>();
for (TextMessage message : content.getMessages()) { for (BaseMessage message : content.getMessages()) {
TextMessage msg = (TextMessage) message;
Map<String, Object> messageMap = new HashMap<>(); Map<String, Object> messageMap = new HashMap<>();
messageMap.put("role", message.getRole()); messageMap.put("role", msg.getRole());
//为实现标准协议
// List<TextContent> contentList = message.getContent(); messageMap.put("content", msg.getContent());
// messageMap.put("content", contentList);
messageMap.put("content", message.getContent());
messages.add(messageMap); messages.add(messageMap);
} }
requestBody.put("messages", messages); requestBody.put("messages", messages);
requestBody.put("stream", content.getStream());
// 使用 Jackson 序列化 // 使用 Jackson 序列化
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
...@@ -128,25 +129,21 @@ public class LLMClient { ...@@ -128,25 +129,21 @@ public class LLMClient {
ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class); ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class);
log.info("body:{}", response.getBody()); log.info("body:{}", response.getBody());
// String responseContent = parseResponseEntity(response.getBody()); JSONObject jsonObject = JSONObject.parseObject(response.getBody());
// log.info("resp:{}", responseContent);
// return ResponseResult.success(responseContent);
Map<String, Object> responseBody = objectMapper.readValue(response.getBody(), new TypeReference<Map<String, Object>>() {});
return ResponseEntity.ok(responseBody); return jsonObject;
// return ResponseEntity.ok(response.getBody());
} catch (Exception e) { } catch (Exception e) {
Map<String, Object> errorResponse = new HashMap<>(); JSONObject jsonObject = new JSONObject();
errorResponse.put("error", "Exception"); jsonObject.put("error", e.getMessage());
errorResponse.put("message", e.getMessage()); jsonObject.put("code", 500);
errorResponse.put("status", 500); jsonObject.put("type", "error");
errorResponse.put("model", content.getModel()); jsonObject.put("param", content.getModel());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); return jsonObject;
} }
} }
public ResponseEntity picture(Models model, OpenAiImageRequest request) { public JSONObject picture(Models model, ImageRequest content) {
try { try {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(model.getUrl()); sb.append(model.getUrl());
...@@ -155,79 +152,26 @@ public class LLMClient { ...@@ -155,79 +152,26 @@ public class LLMClient {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);
// 构造请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", request.getModel());
requestBody.put("temperature", request.getTemperature());
requestBody.put("max_tokens", request.getMaxTokens());
requestBody.put("top_p", request.getTopP());
requestBody.put("frequency_penalty", request.getFrequencyPenalty());
requestBody.put("presence_penalty", request.getPresencePenalty());
// 将 "messages" 添加到请求体中
List<Map<String, Object>> messages = new ArrayList<>();
for (ImageMessage msg : request.getMessages()) {
Map<String, Object> messageMap = new HashMap<>();
messageMap.put("role", msg.role);
List<Map<String, Object>> contentList = new ArrayList<>();
for (ImageContent content : msg.content) {
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("type", content.type);
if ("text".equals(content.type)) {
contentMap.put("text", content.text);
} else if ("image_url".equals(content.type) && content.imageUrl != null) {
Map<String, String> imageUrlMap = new HashMap<>();
imageUrlMap.put("url", content.imageUrl.url);
contentMap.put("image_url", imageUrlMap);
}
contentList.add(contentMap);
}
messageMap.put("content", contentList);
messages.add(messageMap);
}
requestBody.put("messages", messages);
// 使用 Jackson 序列化 // 使用 Jackson 序列化
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(requestBody); String jsonBody = objectMapper.writeValueAsString(content);
log.info("body:{}", jsonBody);
// 创建 HttpEntity // 创建 HttpEntity
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers); HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class); ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class);
// return ResponseResult.success(response); // return ResponseResult.success(response);
return ResponseEntity.ok(response.getBody()); log.info("body:{}", response.getBody());
return JSONObject.parseObject(response.getBody());
} catch (Exception e) { } catch (Exception e) {
ResponseStandardModelError error = new ResponseStandardModelError( JSONObject jsonObject = new JSONObject();
e.getMessage(), jsonObject.put("error", e.getMessage());
"Exception", jsonObject.put("code", 500);
request.getModel(), jsonObject.put("type", "error");
"500" jsonObject.put("param", content.getModel());
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
public String parseResponseEntity(String responseBody) { return jsonObject;
JSONObject jsonObject = JSONObject.parseObject(responseBody);
try {
if (jsonObject.containsKey("choices")) {
JSONArray choices = jsonObject.getJSONArray("choices");
for (int i = choices.size() - 1; i >= 0; i--) {
JSONObject obj = choices.getJSONObject(i);
if (obj.containsKey("message")) {
JSONObject message = obj.getJSONObject("message");
if (message.containsKey("role") && "assistant".equals(message.getString("role"))) {
if (message.containsKey("content")) {
return message.getString("content");
}
}
}
}
}
} catch (Exception e) {
log.error("parseResponseEntity error:{}", e.getMessage());
} }
return null;
} }
} }
...@@ -11,7 +11,7 @@ import org.springframework.stereotype.Service; ...@@ -11,7 +11,7 @@ import org.springframework.stereotype.Service;
*/ */
@Service @Service
public class SearchConfigService { public class ConfigService {
@Autowired @Autowired
private SearchConfig searchConfig; private SearchConfig searchConfig;
......
package com.coolook.service.llm.service;
import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.request.image.ImageRequest;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-18:PM12:00
*/
public interface IImageModelService {
public List getPictureModelList();
public JSONObject pictureAndReturnStream(ImageRequest content);
public ResponseEntity pictureAndReturnJson(ImageRequest content);
}
package com.coolook.service.llm.service;
import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.request.text.TextRequest;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-18:PM12:00
*/
public interface ITextModelService {
public List getTextModelList();
public JSONObject chatAndReturnStream(TextRequest content);
public ResponseEntity chatAndReturnJson(TextRequest content);
}
...@@ -32,7 +32,7 @@ import org.springframework.core.task.TaskExecutor; ...@@ -32,7 +32,7 @@ import org.springframework.core.task.TaskExecutor;
@Slf4j @Slf4j
@Service @Service
@EnableScheduling @EnableScheduling
public class InitializerService { public class InitTaskService {
@Autowired @Autowired
private ModelMapper modelMapper; private ModelMapper modelMapper;
......
...@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -135,4 +136,26 @@ public class ModelDataService { ...@@ -135,4 +136,26 @@ public class ModelDataService {
} }
return ResponseResult.success(); return ResponseResult.success();
} }
public ResponseResult addModel(ModelsRequest[] content) {
List<Models> modelList = new ArrayList<>();
for (ModelsRequest modelRequest : content) {
Models models = new Models();
models.setUrl(modelRequest.getUrl());
models.setModel(modelRequest.getModel());
models.setModelType(modelRequest.getModelType());
models.setIsActive(ModelConstant.MODEL_IS_NOT_ACTIVE);
models.setCreateAt(LocalDateTime.now());
models.setLastCheckedAt(LocalDateTime.now());
models.setSuccessCount(0);
models.setFailureCount(0);
models.setTotalCount(0);
models.setConsecutiveFailure(0);
models.setLatestSpeed(0L);
modelList.add(models);
}
modelMapper.insertModelList(modelList);
return ResponseResult.success();
}
} }
package com.coolook.service.llm.service; package com.coolook.service.llm.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.coolook.common.llm.constant.CommonStatusEnum;
import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.response.ResponseStandardModel;
import com.coolook.common.llm.response.ResponseStandardModelError;
import com.coolook.common.llm.utils.RedisUtil;
import com.coolook.common.llm.constant.ModelConstant; import com.coolook.common.llm.constant.ModelConstant;
import com.coolook.common.llm.dto.Models; import com.coolook.common.llm.dto.Models;
import com.coolook.common.llm.response.ResponseResult; import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.request.ModelsRequest; import com.coolook.common.llm.request.image.ImageRequest;
import com.coolook.common.llm.request.OpenAiImageRequest; import com.coolook.common.llm.utils.RedisUtil;
import com.coolook.common.llm.request.OpenAiTextRequest;
import com.coolook.service.llm.config.SearchConfig; import com.coolook.service.llm.config.SearchConfig;
import com.coolook.service.llm.mapper.ModelMapper; import com.coolook.service.llm.mapper.ModelMapper;
import com.coolook.service.llm.remote.LLMClient; import com.coolook.service.llm.remote.LLMClient;
import com.coolook.service.llm.service.IImageModelService;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* @author: Wang Yinuo * @author: Wang Yinuo
* @create: 2025-02-21:AM11:20 * @create: 2025-03-18:PM12:48
*/ */
@Service
@Slf4j @Slf4j
public class ModelService { @Service
public class ImageModelServiceImpl implements IImageModelService {
@Autowired @Autowired
private LLMClient llmClient; private LLMClient llmClient;
...@@ -61,19 +53,22 @@ public class ModelService { ...@@ -61,19 +53,22 @@ public class ModelService {
private static Map<String, Object> unavailableModel = new ConcurrentHashMap<>(); private static Map<String, Object> unavailableModel = new ConcurrentHashMap<>();
public HttpEntity<List<ModelsDto>> getTextModelList() { @Override
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT); public List getPictureModelList() {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE);
List<ModelsDto> nameList = new ArrayList<>(); List<ModelsDto> nameList = new ArrayList<>();
Object obj = redisTemplate.opsForValue().get(redisKey); Object obj = redisTemplate.opsForValue().get(redisKey);
if (obj == null) { if (obj == null) {
QueryWrapper<Models> queryWrapper = new QueryWrapper<>(); QueryWrapper<Models> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("model_type", ModelConstant.MODEL_TYPE_TEXT); queryWrapper.eq("model_type", ModelConstant.MODEL_TYPE_IMAGE);
queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE); queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE);
queryWrapper.last("LIMIT 500"); queryWrapper.last("LIMIT 10");
List<Models> list = modelMapper.selectList(queryWrapper); List<Models> list = modelMapper.selectList(queryWrapper);
log.info("redis list:{}", list);
obj = list; obj = list;
redisTemplate.opsForValue().set(RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT), list, 1, TimeUnit.HOURS); redisTemplate.opsForValue().set(RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE), list, 1, TimeUnit.HOURS);
} }
if (obj instanceof List<?>) { if (obj instanceof List<?>) {
...@@ -108,97 +103,37 @@ public class ModelService { ...@@ -108,97 +103,37 @@ public class ModelService {
} }
} }
} }
return ResponseEntity.ok(nameList); return nameList;
} }
public ResponseResult<List<String>> getPictureModelList() { @Override
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE); public JSONObject pictureAndReturnStream(ImageRequest content) {
List<String> nameList = new ArrayList<>(); JSONObject picture = picture(content);
if (picture != null && !picture.containsKey("error")) {
Object obj = redisTemplate.opsForValue().get(redisKey); JSONArray choicesArray = picture.getJSONArray("choices");
if (obj == null) { for (int i = 0; i < choicesArray.size(); i++) {
QueryWrapper<Models> queryWrapper = new QueryWrapper<>(); JSONObject choiceObj = choicesArray.getJSONObject(i);
queryWrapper.eq("model_type", ModelConstant.MODEL_TYPE_IMAGE); if (choiceObj.containsKey("message")) {
queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE); // 将 message 取出
queryWrapper.last("LIMIT 10"); JSONObject messageNode = choiceObj.getJSONObject("message");
List<Models> list = modelMapper.selectList(queryWrapper);
log.info("redis list:{}", list); // 强制转换为 ObjectNode,以便修改
obj = list; choiceObj.remove("message");
redisTemplate.opsForValue().set(RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE), list, 1, TimeUnit.HOURS); choiceObj.put("delta", messageNode);
}
if (obj instanceof List<?>) {
List<?> rawList = (List<?>) obj;
List<Models> modelList = rawList.stream()
.map(item -> objectMapper.convertValue(item, Models.class))
.collect(Collectors.toList());
for (Models model : modelList) {
String modelName = model.getModel();
if (!nameList.contains(modelName)) {
nameList.add(modelName);
} }
} }
} }
return ResponseResult.success(nameList); return picture;
} }
public ResponseEntity<?> chat(OpenAiTextRequest content) { @Override
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT); public ResponseEntity pictureAndReturnJson(ImageRequest content) {
String modelFullName = content.getModel(); JSONObject picture = picture(content);
String modelName = modelFullName.split(":")[0]; return ResponseEntity.ok(picture);
Object obj = redisTemplate.opsForValue().get(redisKey);
if (obj instanceof List<?>) {
List<?> rawList = (List<?>) obj;
List<Models> modelList = rawList.stream()
.map(item -> objectMapper.convertValue(item, Models.class))
.collect(Collectors.toList());
for (Models model : modelList) {
String name = model.getModel().trim();
Boolean flag = false;
// 根据名字寻找合适的模型
if (name.equals(modelName)) {
flag = true;
} else if (searchConfig.getValue()) {
if (name.contains(modelName)) {
flag = true;
}
} else {
flag = true;
}
if (flag) {
if (unavailableModel.putIfAbsent(model.getUrl() + model.getModel(), true) == null) {
return updateAndSendRequestChat(model, content);
}
}
}
}
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("error", "error");
errorResponse.put("message", "model not found");
errorResponse.put("status", "500");
errorResponse.put("model", content.getModel());
// 将错误响应转换为 JSON 字符串并返回
ObjectMapper objectMapper = new ObjectMapper();
try {
String errorJson = objectMapper.writeValueAsString(errorResponse);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorJson);
} catch (Exception e) {
// 如果序列化失败,返回通用错误消息
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("{\"error\": \"Serialization error\",\"message\": \"Unable to process error response\"}");
}
} }
public ResponseEntity<?> picture(OpenAiImageRequest content) { public JSONObject picture(ImageRequest content) {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE); String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE);
String modelFullName = content.getModel(); String modelFullName = content.getModel();
String modelName = modelFullName.split(":")[0]; String modelName = modelFullName.split(":")[0];
...@@ -232,26 +167,15 @@ public class ModelService { ...@@ -232,26 +167,15 @@ public class ModelService {
} }
} }
} }
ResponseStandardModelError error = new ResponseStandardModelError( JSONObject errorJson = new JSONObject();
"model:" + content.getModel() + "not found", errorJson.put("error", "error");
"model not found", errorJson.put("message", "model not found");
content.getModel(), errorJson.put("code", "500");
"500" errorJson.put("model", content.getModel());
); return errorJson;
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
} }
public ResponseEntity updateAndSendRequestChat(Models model, OpenAiTextRequest content) { public JSONObject updateAndSendRequestPic(Models model, ImageRequest content) {
String key = model.getUrl() + model.getModel();
log.info("updateAndSendRequestChat, modelName;{}", key);
try {
return llmClient.chat(model, content);
} finally {
unavailableModel.remove(key);
}
}
public ResponseEntity updateAndSendRequestPic(Models model, OpenAiImageRequest content) {
String key = model.getUrl() + model.getModel(); String key = model.getUrl() + model.getModel();
log.info("updateAndSendRequestPic, modelName;{}", key); log.info("updateAndSendRequestPic, modelName;{}", key);
try { try {
...@@ -260,28 +184,4 @@ public class ModelService { ...@@ -260,28 +184,4 @@ public class ModelService {
unavailableModel.remove(key); unavailableModel.remove(key);
} }
} }
public ResponseResult addModel(ModelsRequest[] content) {
List<Models> modelList = new ArrayList<>();
for (ModelsRequest modelRequest : content) {
Models models = new Models();
models.setUrl(modelRequest.getUrl());
models.setModel(modelRequest.getModel());
models.setModelType(modelRequest.getModelType());
models.setIsActive(ModelConstant.MODEL_IS_NOT_ACTIVE);
models.setCreateAt(LocalDateTime.now());
models.setLastCheckedAt(LocalDateTime.now());
models.setSuccessCount(0);
models.setFailureCount(0);
models.setTotalCount(0);
models.setConsecutiveFailure(0);
models.setLatestSpeed(0L);
modelList.add(models);
}
modelMapper.insertModelList(modelList);
return ResponseResult.success();
}
} }
package com.coolook.service.llm.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.request.text.TextRequest;
import com.coolook.common.llm.utils.RedisUtil;
import com.coolook.common.llm.constant.ModelConstant;
import com.coolook.common.llm.dto.Models;
import com.coolook.service.llm.config.SearchConfig;
import com.coolook.service.llm.mapper.ModelMapper;
import com.coolook.service.llm.remote.LLMClient;
import com.coolook.service.llm.service.ITextModelService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author: Wang Yinuo
* @create: 2025-02-21:AM11:20
*/
@Service
@Slf4j
public class TextModelServiceImpl implements ITextModelService {
@Autowired
private LLMClient llmClient;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ModelMapper modelMapper;
@Autowired
private SearchConfig searchConfig;
private static Map<String, Object> unavailableModel = new ConcurrentHashMap<>();
@Override
public List getTextModelList() {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT);
List<ModelsDto> nameList = new ArrayList<>();
Object obj = redisTemplate.opsForValue().get(redisKey);
if (obj == null) {
QueryWrapper<Models> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("model_type", ModelConstant.MODEL_TYPE_TEXT);
queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE);
queryWrapper.last("LIMIT 500");
List<Models> list = modelMapper.selectList(queryWrapper);
obj = list;
redisTemplate.opsForValue().set(RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT), list, 1, TimeUnit.HOURS);
}
if (obj instanceof List<?>) {
List<?> rawList = (List<?>) obj;
List<Models> modelList = rawList.stream()
.map(item -> objectMapper.convertValue(item, Models.class))
.collect(Collectors.toList());
for (Models model : modelList) {
String modelName = model.getModel();
String[] modelInfo = modelName.split(":");
String modelType = null;
String modelVersion = null;
if (modelInfo.length > 1) {
modelType = modelInfo[0];
modelVersion = modelInfo[1];
} else {
modelType = modelName;
}
if (modelVersion == null || modelVersion.contains("-") || modelVersion.contains("_")) {
modelVersion = "";
}
modelName = modelType + ":" + modelVersion;
ModelsDto modelDto = new ModelsDto();
modelDto.setId(modelName);
modelDto.setCreated(model.getCreateAt().atZone(ZoneId.of("UTC")).toInstant().toEpochMilli());
if (!nameList.stream().anyMatch(p -> p.getId().equals(modelDto.getId()))) {
nameList.add(modelDto);
}
}
}
return nameList;
}
@Override
public JSONObject chatAndReturnStream(TextRequest content) {
JSONObject chat = chat(content);
if (chat != null && !chat.containsKey("error")) {
JSONArray choicesArray = chat.getJSONArray("choices");
for (int i = 0; i < choicesArray.size(); i++) {
JSONObject choiceObj = choicesArray.getJSONObject(i);
if (choiceObj.containsKey("message")) {
// 将 message 取出
JSONObject messageNode = choiceObj.getJSONObject("message");
// 强制转换为 ObjectNode,以便修改
choiceObj.remove("message");
choiceObj.put("delta", messageNode);
}
}
}
return chat;
}
@Override
public ResponseEntity chatAndReturnJson(TextRequest content) {
JSONObject chat = chat(content);
return ResponseEntity.ok(chat);
}
public JSONObject chat(TextRequest content) {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT);
String modelFullName = content.getModel();
String modelName = modelFullName.split(":")[0];
Object obj = redisTemplate.opsForValue().get(redisKey);
if (obj instanceof List<?>) {
List<?> rawList = (List<?>) obj;
List<Models> modelList = rawList.stream()
.map(item -> objectMapper.convertValue(item, Models.class))
.collect(Collectors.toList());
for (Models model : modelList) {
String name = model.getModel().trim();
Boolean flag = false;
// 根据名字寻找合适的模型
if (name.equals(modelName)) {
flag = true;
} else if (searchConfig.getValue()) {
if (name.contains(modelName)) {
flag = true;
}
} else {
flag = true;
}
if (flag) {
if (unavailableModel.putIfAbsent(model.getUrl() + model.getModel(), true) == null) {
return updateAndSendRequestChat(model, content);
}
}
}
}
JSONObject errorJson = new JSONObject();
errorJson.put("error", "error");
errorJson.put("message", "model not found");
errorJson.put("code", "500");
errorJson.put("model", content.getModel());
return errorJson;
}
public JSONObject updateAndSendRequestChat(Models model, TextRequest content) {
String key = model.getUrl() + model.getModel();
log.info("updateAndSendRequestChat, modelName;{}", key);
try {
return llmClient.chat(model, content);
} finally {
unavailableModel.remove(key);
}
}
}
spring:
cloud:
nacos:
discovery:
server-addr: 132.145.115.85:8848
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.0.20.195:3306/service_llm?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: zwjt123.com
redis:
host: aaaawxqy5yamgkqc5lii6wutfmzocximcbesvikrwqxn6p4i4drafka-p.redis.ap-tokyo-1.oci.oraclecloud.com
port: 6379
database: 8
cache:
type: redis
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
server: server:
port: 8700 port: 8700
spring: spring:
application: application:
name: service-llm name: service-llm
...@@ -11,7 +10,6 @@ spring: ...@@ -11,7 +10,6 @@ spring:
profiles: profiles:
active: local active: local
search: search:
config: config:
value: false value: false
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment