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 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<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="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
......@@ -19,6 +53,9 @@
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{
&quot;isMigrated&quot;: true
......@@ -34,53 +71,57 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Application.LLMClient.executor&quot;: &quot;Run&quot;,
&quot;Maven. [org.apache.maven.plugins:maven-archetype-plugin:RELEASE:generate].executor&quot;: &quot;Run&quot;,
&quot;Maven.api-llm [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.api-llm [package].executor&quot;: &quot;Run&quot;,
&quot;Maven.common-llm [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.common-llm [compile].executor&quot;: &quot;Run&quot;,
&quot;Maven.common-llm [install].executor&quot;: &quot;Run&quot;,
&quot;Maven.common-llm [package].executor&quot;: &quot;Run&quot;,
&quot;Maven.large-language-model [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.large-language-model [package].executor&quot;: &quot;Run&quot;,
&quot;Maven.llm-api [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.llm-api [compile].executor&quot;: &quot;Run&quot;,
&quot;Maven.llm-api [package].executor&quot;: &quot;Run&quot;,
&quot;Maven.llm-api [test].executor&quot;: &quot;Run&quot;,
&quot;Maven.service-llm [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.service-llm [package].executor&quot;: &quot;Run&quot;,
&quot;Maven.service-llm-text [clean].executor&quot;: &quot;Run&quot;,
&quot;Maven.service-llm-text [compile].executor&quot;: &quot;Run&quot;,
&quot;Maven.service-llm-text [package].executor&quot;: &quot;Run&quot;,
&quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;,
&quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;,
&quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;,
&quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;Spring Boot.LLMApiApplication.executor&quot;: &quot;Run&quot;,
&quot;Spring Boot.ServiceLlmApplication.executor&quot;: &quot;Run&quot;,
&quot;com.codeium.enabled&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/wangyinuo/workspace/code/java/large-language-model/api-llm/src/main/resources&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.0&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;build.tools&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Application.LLMClient.executor": "Run",
"Maven. [org.apache.maven.plugins:maven-archetype-plugin:RELEASE:generate].executor": "Run",
"Maven.api-llm [clean].executor": "Run",
"Maven.api-llm [package].executor": "Run",
"Maven.common-llm [clean].executor": "Run",
"Maven.common-llm [compile].executor": "Run",
"Maven.common-llm [install].executor": "Run",
"Maven.common-llm [package].executor": "Run",
"Maven.large-language-model [clean].executor": "Run",
"Maven.large-language-model [package].executor": "Run",
"Maven.llm-api [clean].executor": "Run",
"Maven.llm-api [compile].executor": "Run",
"Maven.llm-api [package].executor": "Run",
"Maven.llm-api [test].executor": "Run",
"Maven.service-llm [clean].executor": "Run",
"Maven.service-llm [package].executor": "Run",
"Maven.service-llm-text [clean].executor": "Run",
"Maven.service-llm-text [compile].executor": "Run",
"Maven.service-llm-text [package].executor": "Run",
"RequestMappingsPanelOrder0": "0",
"RequestMappingsPanelOrder1": "1",
"RequestMappingsPanelWidth0": "75",
"RequestMappingsPanelWidth1": "75",
"RunOnceActivity.ShowReadmeOnStart": "true",
"Spring Boot.LLMApiApplication.executor": "Run",
"Spring Boot.ServiceLlmApplication.executor": "Debug",
"com.codeium.enabled": "true",
"git-widget-placeholder": "main",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "/Users/wangyinuo/workspace/code/java/large-language-model/api-llm/src/main/resources",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"project.structure.last.edited": "Modules",
"project.structure.proportion": "0.0",
"project.structure.side.proportion": "0.0",
"settings.editor.selected.configurable": "build.tools",
"vue.rearranger.settings.migration": "true"
}
}</component>
}]]></component>
<component name="ReactorSettings">
<option name="notificationShown" value="true" />
</component>
<component name="RecentsManager">
<key name="CreateClassDialog.RecentsKey">
<recent name="com.coolook.common.llm.response" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/api-llm/src/main/resources" />
<recent name="$PROJECT_DIR$/service-llm/src/main/resources" />
......@@ -89,11 +130,11 @@
<recent name="$PROJECT_DIR$/common-llm/src/main/java/com/coolook/common/llm/utils" />
</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.text" />
<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>
</component>
<component name="RunDashboard">
......@@ -193,13 +234,25 @@
<workItem from="1741920343491" duration="1645000" />
<workItem from="1741931944414" duration="27842000" />
<workItem from="1742177794559" duration="6136000" />
<workItem from="1742193641040" duration="4084000" />
<workItem from="1742193641040" duration="7021000" />
<workItem from="1742201439685" duration="34459000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</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">
<breakpoint-manager>
<breakpoints>
......@@ -207,11 +260,6 @@
<properties class="java.lang.NullPointerException" package="java.lang" />
<option name="timeStamp" value="1" />
</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>
</breakpoint-manager>
<watches-manager>
......
package com.coolook.llm.api.remote;
import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.response.ResponseResult;
import com.coolook.common.llm.request.OpenAiTextRequest;
import com.coolook.common.llm.request.text.TextRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
......@@ -11,7 +10,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
import java.util.Map;
/**
* @author: Wang Yinuo
......@@ -22,8 +20,8 @@ import java.util.Map;
public interface ServiceLLMClient {
@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();
}
......@@ -6,9 +6,8 @@ import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.constant.AiStatus;
import com.coolook.common.llm.dto.Message;
import com.coolook.common.llm.dto.ModelsDto;
import com.coolook.common.llm.request.OpenAiTextRequest;
import com.coolook.common.llm.request.TextContent;
import com.coolook.common.llm.request.TextMessage;
import com.coolook.common.llm.request.text.TextRequest;
import com.coolook.common.llm.request.text.TextMessage;
import com.coolook.common.llm.response.ResponseResult;
import com.coolook.common.llm.utils.SecurityUtils;
import com.coolook.llm.api.remote.ServiceLLMClient;
......@@ -22,7 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author: WangYinuo
......@@ -61,7 +59,6 @@ public class AiServiceImpl implements IAiService {
return ResponseResult.success(error);
}
@Override
public ResponseResult<Message> ask(Message msg) {
String msgId = msg.getMsgId().replace("_forword", "");;
......@@ -84,17 +81,20 @@ public class AiServiceImpl implements IAiService {
HttpEntity<List<ModelsDto>> availableModelEntity = serviceLlmClient.getAvailableModelList();
List<ModelsDto> textModelList = availableModelEntity.getBody();
OpenAiTextRequest openAiTextRequest = new OpenAiTextRequest();
TextRequest openAiTextRequest = new TextRequest();
openAiTextRequest.setModel(textModelList.get(0).getId());
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);
ResponseEntity<String> responseMsg = serviceLlmClient.chat(openAiTextRequest);
String content = parseResponseEntity(responseMsg.getBody());
log.info("responseMsg:{}", responseMsg);
if (responseMsg != null) {
Message aiMessage = new Message();
......
......@@ -8,4 +8,4 @@ spring:
cloud:
nacos:
discovery:
server-addr: 158.178.244.87
\ No newline at end of file
server-addr: 158.178.244.87:8848
\ No newline at end of file
server:
port: 8900
port: 9009
servlet:
# 应用的访问路径
context-path: /ai-server
......@@ -8,7 +8,7 @@ spring:
application:
name: llm-api
profiles:
active: local
active: global
app:
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.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import lombok.Data;
/**
......@@ -8,9 +11,11 @@ import lombok.Data;
* @create: 2025-03-03:PM4:11
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ImageContent {
public String type;
public String text;
private String type;
private String text;
// 使用自定义的 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.NoArgsConstructor;
import java.util.List;
......@@ -10,6 +13,6 @@ import java.util.List;
*/
@Data
public class ImageMessage {
public String role;
public List<ImageContent> content;
private String role;
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.text;
import com.coolook.common.llm.request.base.BaseMessage;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author: Wang Yinuo
* @create: 2025-03-03:PM3:21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TextMessage {
public class TextMessage extends BaseMessage {
private String role;
//private List<TextContent> 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;
/**
......@@ -11,7 +12,9 @@ import java.util.List;
*/
@Data
public class OpenAiTextRequest implements Serializable {
public class TextRequest {
private String model;
private Boolean stream;
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;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.RestController;
*/
@RestController
public class SearchConfigController {
public class ConfigController {
@Autowired
private SearchConfigService searchConfigService;
private ConfigService searchConfigService;
@GetMapping("/get-search-config")
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;
@RestController
@Slf4j
public class DataSyncController {
public class ModelManagerController {
@Autowired
private ModelDataService modelDataService;
......@@ -43,4 +43,9 @@ public class DataSyncController {
log.info("updateUrl: {}", urlRequestList);
return modelDataService.saveOrUpdateUrl(urlRequestList);
}
@PostMapping("/add-model")
public ResponseResult addModel(@RequestBody ModelsRequest[] content) {
return modelDataService.addModel(content);
}
}
package com.coolook.service.llm.remote;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.coolook.common.llm.constant.ModelConstant;
import com.coolook.common.llm.dto.Models;
import com.coolook.common.llm.request.*;
import com.coolook.common.llm.response.ResponseStandardModelError;
import com.fasterxml.jackson.core.type.TypeReference;
import com.coolook.common.llm.request.base.BaseMessage;
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.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -92,7 +92,7 @@ public class LLMClient {
return checkedModel;
}
public ResponseEntity<Map<String, Object>> chat(Models model, OpenAiTextRequest content) {
public JSONObject chat(Models model, TextRequest content) {
try {
StringBuilder sb = new StringBuilder();
......@@ -108,16 +108,17 @@ public class LLMClient {
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<>();
messageMap.put("role", message.getRole());
//为实现标准协议
// List<TextContent> contentList = message.getContent();
// messageMap.put("content", contentList);
messageMap.put("content", message.getContent());
messageMap.put("role", msg.getRole());
messageMap.put("content", msg.getContent());
messages.add(messageMap);
}
requestBody.put("messages", messages);
requestBody.put("stream", content.getStream());
// 使用 Jackson 序列化
ObjectMapper objectMapper = new ObjectMapper();
......@@ -128,25 +129,21 @@ public class LLMClient {
ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class);
log.info("body:{}", response.getBody());
// String responseContent = parseResponseEntity(response.getBody());
// log.info("resp:{}", responseContent);
// return ResponseResult.success(responseContent);
Map<String, Object> responseBody = objectMapper.readValue(response.getBody(), new TypeReference<Map<String, Object>>() {});
JSONObject jsonObject = JSONObject.parseObject(response.getBody());
return ResponseEntity.ok(responseBody);
// return ResponseEntity.ok(response.getBody());
return jsonObject;
} catch (Exception e) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", "Exception");
errorResponse.put("message", e.getMessage());
errorResponse.put("status", 500);
errorResponse.put("model", content.getModel());
JSONObject jsonObject = new JSONObject();
jsonObject.put("error", e.getMessage());
jsonObject.put("code", 500);
jsonObject.put("type", "error");
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 {
StringBuilder sb = new StringBuilder();
sb.append(model.getUrl());
......@@ -155,79 +152,26 @@ public class LLMClient {
HttpHeaders headers = new HttpHeaders();
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 序列化
ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(requestBody);
String jsonBody = objectMapper.writeValueAsString(content);
log.info("body:{}", jsonBody);
// 创建 HttpEntity
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(sb.toString(), requestEntity, String.class);
// return ResponseResult.success(response);
return ResponseEntity.ok(response.getBody());
log.info("body:{}", response.getBody());
return JSONObject.parseObject(response.getBody());
} catch (Exception e) {
ResponseStandardModelError error = new ResponseStandardModelError(
e.getMessage(),
"Exception",
request.getModel(),
"500"
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("error", e.getMessage());
jsonObject.put("code", 500);
jsonObject.put("type", "error");
jsonObject.put("param", content.getModel());
public String parseResponseEntity(String responseBody) {
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 jsonObject;
}
return null;
}
}
......@@ -11,7 +11,7 @@ import org.springframework.stereotype.Service;
*/
@Service
public class SearchConfigService {
public class ConfigService {
@Autowired
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;
@Slf4j
@Service
@EnableScheduling
public class InitializerService {
public class InitTaskService {
@Autowired
private ModelMapper modelMapper;
......
......@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
......@@ -135,4 +136,26 @@ public class ModelDataService {
}
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.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.dto.Models;
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.common.llm.dto.ModelsDto;
import com.coolook.common.llm.request.image.ImageRequest;
import com.coolook.common.llm.utils.RedisUtil;
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.IImageModelService;
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.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
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
* @create: 2025-03-18:PM12:48
*/
@Service
@Slf4j
public class ModelService {
@Service
public class ImageModelServiceImpl implements IImageModelService {
@Autowired
private LLMClient llmClient;
......@@ -61,19 +53,22 @@ public class ModelService {
private static Map<String, Object> unavailableModel = new ConcurrentHashMap<>();
public HttpEntity<List<ModelsDto>> getTextModelList() {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_TEXT);
@Override
public List getPictureModelList() {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE);
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("model_type", ModelConstant.MODEL_TYPE_IMAGE);
queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE);
queryWrapper.last("LIMIT 500");
queryWrapper.last("LIMIT 10");
List<Models> list = modelMapper.selectList(queryWrapper);
log.info("redis list:{}", 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<?>) {
......@@ -108,97 +103,37 @@ public class ModelService {
}
}
}
return ResponseEntity.ok(nameList);
return nameList;
}
public ResponseResult<List<String>> getPictureModelList() {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE);
List<String> nameList = new ArrayList<>();
Object obj = redisTemplate.opsForValue().get(redisKey);
if (obj == null) {
QueryWrapper<Models> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("model_type", ModelConstant.MODEL_TYPE_IMAGE);
queryWrapper.eq("is_active", ModelConstant.MODEL_IS_ACTIVE);
queryWrapper.last("LIMIT 10");
List<Models> list = modelMapper.selectList(queryWrapper);
log.info("redis list:{}", list);
obj = list;
redisTemplate.opsForValue().set(RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE), 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();
if (!nameList.contains(modelName)) {
nameList.add(modelName);
@Override
public JSONObject pictureAndReturnStream(ImageRequest content) {
JSONObject picture = picture(content);
if (picture != null && !picture.containsKey("error")) {
JSONArray choicesArray = picture.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 ResponseResult.success(nameList);
return picture;
}
public ResponseEntity<?> chat(OpenAiTextRequest 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);
}
}
}
}
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\"}");
}
@Override
public ResponseEntity pictureAndReturnJson(ImageRequest content) {
JSONObject picture = picture(content);
return ResponseEntity.ok(picture);
}
public ResponseEntity<?> picture(OpenAiImageRequest content) {
public JSONObject picture(ImageRequest content) {
String redisKey = RedisUtil.getRedisKey(ModelConstant.MODEL_TYPE_IMAGE);
String modelFullName = content.getModel();
String modelName = modelFullName.split(":")[0];
......@@ -232,26 +167,15 @@ public class ModelService {
}
}
}
ResponseStandardModelError error = new ResponseStandardModelError(
"model:" + content.getModel() + "not found",
"model not found",
content.getModel(),
"500"
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
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 ResponseEntity updateAndSendRequestChat(Models model, OpenAiTextRequest 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) {
public JSONObject updateAndSendRequestPic(Models model, ImageRequest content) {
String key = model.getUrl() + model.getModel();
log.info("updateAndSendRequestPic, modelName;{}", key);
try {
......@@ -260,28 +184,4 @@ public class ModelService {
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 @@
server:
port: 8700
spring:
application:
name: service-llm
......@@ -11,7 +10,6 @@ spring:
profiles:
active: local
search:
config:
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