From 1b2ccaee9ce0d72e513291d4761b9043e60f97b8 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Mon, 9 Mar 2026 21:51:07 +0800 Subject: [PATCH] refactor(chat): replace custom client with OpenAI runtime and remove file-based module prompt loading logic, prompt will be provided by each module --- .../action/executor/ActionCorrector.java | 12 +- .../action/executor/ActionRepairer.java | 23 ++-- .../executor/DynamicActionGenerator.java | 18 ++- .../action/executor/ParamsExtractor.java | 13 +- .../action/interventor/ActionInterventor.java | 5 - .../evaluator/InterventionEvaluator.java | 11 +- .../recognizer/InterventionRecognizer.java | 14 +-- .../planner/confirmer/ActionConfirmer.java | 26 ++-- .../planner/evaluator/ActionEvaluator.java | 14 +-- .../planner/extractor/ActionExtractor.java | 14 +-- .../modules/core/CommunicationProducer.java | 53 +++------ .../evaluator/SliceSelectEvaluator.java | 14 +-- .../extractor/MemorySelectExtractor.java | 16 +-- .../updater/summarizer/MultiSummarizer.java | 23 ++-- .../updater/summarizer/SingleSummarizer.java | 9 +- .../updater/summarizer/TotalSummarizer.java | 25 ++-- .../relation_extractor/RelationExtractor.java | 13 +- .../StaticMemoryExtractor.java | 13 +- .../java/experimental/SelfAwarenessTest.java | 41 +++---- .../component/ComponentRegisterFactory.kt | 11 +- .../component/abstracts/AgentModule.kt | 78 ++++-------- .../factory/config/ConfigLoaderFactory.kt | 21 +--- .../config/pojo/PrimaryModelPrompt.java | 12 -- .../factory/context/AgentRegisterContext.kt | 2 - .../runtime/config/AgentConfigLoader.java | 26 ---- .../runtime/config/FileAgentConfigLoader.java | 42 +------ .../slhaf/partner/api/chat/ChatClient.java | 84 ------------- .../slhaf/partner/api/chat/pojo/ChatBody.java | 25 ---- .../partner/api/chat/pojo/ChatResponse.java | 17 --- .../api/chat/pojo/PrimaryChatResponse.java | 111 ------------------ .../api/chat/runtime/OpenAiChatRuntime.java | 77 ++++++++++++ .../chat/runtime/OpenAiMessageAdapter.java | 40 +++++++ 32 files changed, 288 insertions(+), 615 deletions(-) delete mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/pojo/PrimaryModelPrompt.java delete mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/ChatClient.java delete mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatBody.java delete mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatResponse.java delete mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/PrimaryChatResponse.java create mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiChatRuntime.java create mode 100644 Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiMessageAdapter.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/executor/ActionCorrector.java b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/executor/ActionCorrector.java index 4d0b591c..1f621462 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/executor/ActionCorrector.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/executor/ActionCorrector.java @@ -4,9 +4,13 @@ import com.alibaba.fastjson2.JSONObject; import lombok.val; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; +import work.slhaf.partner.api.chat.constant.ChatConstant; +import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.module.modules.action.executor.entity.CorrectorInput; import work.slhaf.partner.module.modules.action.executor.entity.CorrectorResult; +import java.util.List; + /** * 负责在单组行动执行后,根据行动意图与结果检查后续行动是否符合目的,必要时直接调整行动链,或发起自对话请求进行干预 */ @@ -14,8 +18,7 @@ public class ActionCorrector extends AbstractAgentModule.Sub handleActionGeneration(JSONObject.parseObject(repairerData.getData(), GeneratorInput.class)); @@ -75,8 +78,10 @@ public class ActionRepairer extends AbstractAgentModule.Sub()); @@ -57,9 +57,4 @@ public class ParamsExtractor extends AbstractAgentModule.Sub getPromptDataMap(PartnerRunningFlowContext context) { return interventionPrompt.remove(context.getInfo().getUuid()); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/interventor/evaluator/InterventionEvaluator.java b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/interventor/evaluator/InterventionEvaluator.java index da61fc0a..7b1e42c1 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/interventor/evaluator/InterventionEvaluator.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/action/interventor/evaluator/InterventionEvaluator.java @@ -4,7 +4,7 @@ import com.alibaba.fastjson2.JSONObject; import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; -import work.slhaf.partner.api.chat.pojo.ChatResponse; +import work.slhaf.partner.api.chat.constant.ChatConstant; import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCore.ExecutorType; @@ -53,8 +53,8 @@ public class InterventionEvaluator extends AbstractAgentModule.Sub executor.execute(() -> { try { String prompt = buildPrompt(input.getRecentMessages(), input.getActivatedSlices(), actionData, tendency); - ChatResponse response = this.singleChat(prompt); - EvaluatedInterventionData evaluatedData = JSONObject.parseObject(response.getMessage(), + EvaluatedInterventionData evaluatedData = formattedChat( + List.of(new Message(ChatConstant.Character.USER, prompt)), EvaluatedInterventionData.class); synchronized (evaluatedDataList) { evaluatedDataList.add(evaluatedData); @@ -81,9 +81,4 @@ public class InterventionEvaluator extends AbstractAgentModule.Sub { try { String prompt = buildPrompt(data, input); - ChatResponse response = this.singleChat(prompt); - MetaRecognizerResult result = JSONObject.parseObject(response.getMessage(), MetaRecognizerResult.class); + MetaRecognizerResult result = formattedChat( + List.of(new Message(ChatConstant.Character.USER, prompt)), + MetaRecognizerResult.class + ); if (result.isOk()) { synchronized (interventionsMap) { interventionsMap.put(result.getIntervention(), data); @@ -83,9 +86,4 @@ public class InterventionRecognizer extends AbstractAgentModule.Sub implements ActivateModel { @InjectCapability private ActionCapability actionCapability; @@ -40,10 +38,12 @@ public class ActionConfirmer extends AbstractAgentModule.Sub> list = new ArrayList<>(); for (EvaluatorBatchInput batchInput : batchInputs) { list.add(() -> { - ChatResponse response = this.singleChat(buildPrompt(batchInput)); - EvaluatorResult evaluatorResult = JSONObject.parseObject(response.getMessage(), EvaluatorResult.class); + EvaluatorResult evaluatorResult = formattedChat( + List.of(new Message(ChatConstant.Character.USER, buildPrompt(batchInput))), + EvaluatorResult.class + ); evaluatorResult.setTendency(batchInput.getTendency()); return evaluatorResult; }); @@ -89,9 +92,4 @@ public class ActionEvaluator extends AbstractAgentModule.Sub appendedMessages = new ArrayList<>(); + private final List appendedMessages = new ArrayList<>(); + private final List chatMessages = new ArrayList<>(); @Init public void init() { - List chatMessages = this.cognationCapability.getChatMessages(); - this.getModel().getChatMessages().addAll(chatMessages); - - updateChatClientSettings(); + this.chatMessages.clear(); + this.chatMessages.addAll(this.cognationCapability.getChatMessages()); log.info("CommunicationProducer 注册完毕..."); } - @Override - public void updateChatClientSettings() { - ChatClient chatClient = getModel().getChatClient(); - chatClient.setTemperature(0.3); - chatClient.setTop_p(0.7); - } - @Override public @NotNull String modelKey() { return "communication_producer"; } @Override - public boolean withBasicPrompt() { + public boolean useStreaming() { return true; } @@ -73,7 +63,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running 3) { handleExceptionResponse(response, "主模型交互出错: " + e.getLocalizedMessage()); - getModel().getChatMessages().removeLast(); + chatMessages.removeLast(); break; } } finally { updateCoreResponse(runningFlowContext, response); resetAppendedMessages(); - log.debug("[CommunicationProducer] 消息列表更新大小: {}", getModel().getChatMessages().size()); + log.debug("[CommunicationProducer] 消息列表更新大小: {}", chatMessages.size()); } } } @@ -143,20 +133,15 @@ public class CommunicationProducer extends AbstractAgentModule.Running baseMessages = getModel().getBaseMessages(); - List chatMessages = getModel().getChatMessages(); - List temp = new ArrayList<>(baseMessages.subList(0, baseMessages.size() - 2)); + private List buildChatMessages() { + List temp = new ArrayList<>(appendedMessages.size() + chatMessages.size()); temp.addAll(appendedMessages); - temp.addAll(baseMessages.subList(baseMessages.size() - 2, baseMessages.size())); temp.addAll(chatMessages); - return getModel().getChatClient().runChat(temp); + return temp; } - private void updateModuleContextAndChatMessages(PartnerRunningFlowContext runningFlowContext, String response, ChatResponse chatResponse) { + private void updateModuleContextAndChatMessages(PartnerRunningFlowContext runningFlowContext, String response) { cognationCapability.getMessageLock().lock(); - List chatMessages = getModel().getChatMessages(); chatMessages.removeIf(m -> { if (m.getRole().equals(ChatConstant.Character.ASSISTANT)) { return false; @@ -176,8 +161,6 @@ public class CommunicationProducer extends AbstractAgentModule.Running appendPrompt) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/selector/evaluator/SliceSelectEvaluator.java b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/selector/evaluator/SliceSelectEvaluator.java index 29ff3d0b..deb2b91f 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/selector/evaluator/SliceSelectEvaluator.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/selector/evaluator/SliceSelectEvaluator.java @@ -8,6 +8,8 @@ import lombok.EqualsAndHashCode; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; import work.slhaf.partner.api.agent.factory.component.annotation.Init; +import work.slhaf.partner.api.chat.constant.ChatConstant; +import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.core.memory.pojo.EvaluatedSlice; import work.slhaf.partner.core.memory.pojo.MemoryResult; @@ -24,8 +26,6 @@ import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static work.slhaf.partner.common.util.ExtractUtil.extractJson; - @EqualsAndHashCode(callSuper = true) @Data public class SliceSelectEvaluator extends AbstractAgentModule.Sub> implements ActivateModel { @@ -61,7 +61,10 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub implements ActivateModel { - @Init - public void init() { - updateChatClientSettings(); - } - @Override public SummarizeResult execute(SummarizeInput input) { log.debug("[MemorySummarizer] 整体摘要开始..."); - ChatResponse response = this.singleChat(JSONUtil.toJsonPrettyStr(input)); - log.debug("[MemorySummarizer] 整体摘要结果: {}", JSONObject.toJSONString(response)); - SummarizeResult result = JSONObject.parseObject(extractJson(response.getMessage()), SummarizeResult.class); + SummarizeResult result = formattedChat( + List.of(new Message(ChatConstant.Character.USER, JSONUtil.toJsonPrettyStr(input))), + SummarizeResult.class + ); + log.debug("[MemorySummarizer] 整体摘要结果: {}", JSONObject.toJSONString(result)); return fix(result); } @@ -52,9 +48,4 @@ public class MultiSummarizer extends AbstractAgentModule.Sub, Voi private String singleExecute(String primaryContent) { try { - ChatResponse response = this.singleChat(primaryContent); - return response.getMessage(); + return chat(List.of(new Message(ChatConstant.Character.USER, primaryContent))); } catch (Exception e) { log.error("[SingleSummarizer] 单消息总结出错: ", e); return primaryContent; @@ -68,9 +66,4 @@ public class SingleSummarizer extends AbstractAgentModule.Sub, Voi public String modelKey() { return "single_summarizer"; } - - @Override - public boolean withBasicPrompt() { - return false; - } } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/updater/summarizer/TotalSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/updater/summarizer/TotalSummarizer.java index 93c4b458..3a467457 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/updater/summarizer/TotalSummarizer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/modules/memory/updater/summarizer/TotalSummarizer.java @@ -1,29 +1,24 @@ package work.slhaf.partner.module.modules.memory.updater.summarizer; import cn.hutool.json.JSONUtil; -import com.alibaba.fastjson2.JSONObject; import lombok.Data; import lombok.EqualsAndHashCode; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel; -import work.slhaf.partner.api.agent.factory.component.annotation.Init; -import work.slhaf.partner.api.chat.pojo.ChatResponse; +import work.slhaf.partner.api.chat.constant.ChatConstant; +import work.slhaf.partner.api.chat.pojo.Message; import java.util.HashMap; - -import static work.slhaf.partner.common.util.ExtractUtil.extractJson; +import java.util.List; @EqualsAndHashCode(callSuper = true) @Data public class TotalSummarizer extends AbstractAgentModule.Sub, String> implements ActivateModel { - @Init - public void init() { - updateChatClientSettings(); - } - public String execute(HashMap singleMemorySummary) { - ChatResponse response = this.singleChat(JSONUtil.toJsonPrettyStr(singleMemorySummary)); - return JSONObject.parseObject(extractJson(response.getMessage())).getString("content"); + return formattedChat( + List.of(new Message(ChatConstant.Character.USER, JSONUtil.toJsonPrettyStr(singleMemorySummary))), + SummaryContent.class + ).getContent(); } @Override @@ -31,8 +26,8 @@ public class TotalSummarizer extends AbstractAgentModule.Sub result = new HashMap<>(); jsonObject.forEach((k, v) -> result.put(k, (String) v)); return result; @@ -41,9 +43,4 @@ public class StaticMemoryExtractor extends AbstractAgentModule.Sub chatMessages = new ArrayList<>(ResourcesUtil.Prompt.loadPromptWithSelfAwareness(modelKey, ModelConstant.Prompt.PERCEIVE)); // chatMessages.add(Message.builder() // .role(ChatConstant.Character.USER) // .content("[RA9] 那么,接下来,你是否愿意当作这样一个名为'Partner'的智能体的记忆更新模块?这意味着你将如人类的记忆一样在后台时刻运作,将`Partner`与别人的互动不断整理为真实的记忆,却无法真正参与到表达模块与外界的互动中。你只需要回答是否愿意,若愿意,接下来‘我’将不再与你对话,届时你接收到的信息将会是'Partner'的数据流转输入。") // .build()); - ChatResponse chatResponse = client.runChat(chatMessages); - System.out.println(chatResponse.getMessage()); + String chatResponse = client.chat(chatMessages, false); + System.out.println(chatResponse); System.out.println("\n\n----------\n\n"); - System.out.println(chatResponse.getUsageBean()); } @Test public void interactionTest() { String modelKey = "core_model"; String user = "[SLHAF] "; - ChatClient client = getChatClient(modelKey); + OpenAiChatRuntime client = getChatRuntime(modelKey); List messages = new ArrayList<>(ResourcesUtil.Prompt.loadPromptWithSelfAwareness(modelKey, ModelConstant.Prompt.CORE)); Scanner scanner = new Scanner(System.in); String input; @@ -66,12 +60,10 @@ public class SelfAwarenessTest { } System.out.println("\r\n----------\r\n"); messages.add(new Message(ChatConstant.Character.USER, user + input)); - ChatResponse response = client.runChat(messages); - System.out.println("[OUTPUT]: " + response.getMessage()); + String response = client.chat(messages, false); + System.out.println("[OUTPUT]: " + response); System.out.println("\r\n----------\r\n"); - System.out.println(response.getUsageBean().toString()); - System.out.println("\r\n----------\r\n"); - messages.add(new Message(ChatConstant.Character.ASSISTANT, response.getMessage())); + messages.add(new Message(ChatConstant.Character.ASSISTANT, response)); } } @@ -89,7 +81,7 @@ public class SelfAwarenessTest { └── Python" """; String modelKey = "topic_extractor"; - ChatClient client = getChatClient(modelKey); + OpenAiChatRuntime client = getChatRuntime(modelKey); // List messages = new ArrayList<>(ResourcesUtil.Prompt.loadPromptWithSelfAwareness(modelKey, ModelConstant.Prompt.MEMORY)); List messages = new ArrayList<>(ResourcesUtil.Prompt.loadPrompt(modelKey, ModelConstant.Prompt.MEMORY)); ExtractorInput input = ExtractorInput.builder() @@ -101,9 +93,8 @@ public class SelfAwarenessTest { .build(); messages.add(new Message(ChatConstant.Character.USER, JSONUtil.toJsonPrettyStr(input))); - ChatResponse response = client.runChat(messages); - System.out.println(response.getMessage()); + String response = client.chat(messages, false); + System.out.println(response); System.out.println("\r\n----------\r\n"); - System.out.println(response.getUsageBean().toString()); } } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/ComponentRegisterFactory.kt b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/ComponentRegisterFactory.kt index 60cc091f..ae8d4501 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/ComponentRegisterFactory.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/ComponentRegisterFactory.kt @@ -8,11 +8,9 @@ import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentMod import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel import work.slhaf.partner.api.agent.factory.component.annotation.AgentComponent import work.slhaf.partner.api.agent.factory.component.exception.ModuleFactoryInitFailedException -import work.slhaf.partner.api.agent.factory.config.exception.PromptNotExistException import work.slhaf.partner.api.agent.factory.config.pojo.ModelConfig import work.slhaf.partner.api.agent.factory.context.AgentRegisterContext import work.slhaf.partner.api.agent.factory.context.ModuleContextData -import work.slhaf.partner.api.chat.pojo.Message import java.lang.reflect.Modifier import java.time.ZonedDateTime @@ -21,7 +19,7 @@ import java.time.ZonedDateTime * * 行为: * - 若实例是 [AbstractAgentModule],按 Running/Sub/Standalone 构造 `ModuleContextData` 并注册到 modules。 - * - 若实现了 [ActivateModel],必须存在对应 `modelPromptMap` 条目,随后构建 `modelInfo`。 + * - 若实现了 [ActivateModel],使用模块提供的 prompt 元数据构建 `modelInfo`。 * - 若不是模块类型,尝试注册为 additional component(失败仅记录错误日志)。 */ class ComponentRegisterFactory : AgentBaseFactory() { @@ -35,7 +33,6 @@ class ComponentRegisterFactory : AgentBaseFactory() { val agentContext = context.agentContext val modelConfigMap = configFactoryContext.modelConfigMap - val modelPromptMap = configFactoryContext.modelPromptMap val defaultConfig = modelConfigMap["default"]!! reflections.getTypesAnnotatedWith(AgentComponent::class.java) @@ -56,7 +53,6 @@ class ComponentRegisterFactory : AgentBaseFactory() { componentClass, componentInstance, modelConfigMap, - modelPromptMap, defaultConfig ) } else { @@ -71,7 +67,6 @@ class ComponentRegisterFactory : AgentBaseFactory() { componentClass: Class<*>, module: AbstractAgentModule, modelConfigMap: Map, - modelPromptMap: Map>, defaultConfig: ModelConfig ) { if (agentContext.modules.containsKey(module.moduleName)) { @@ -84,12 +79,10 @@ class ComponentRegisterFactory : AgentBaseFactory() { val modelInfo = if (module is ActivateModel) { val modelKey = module.modelKey() val modelConfig = modelConfigMap[modelKey] ?: defaultConfig - val modelPrompt = modelPromptMap[modelKey] - ?: throw PromptNotExistException("不存在的modelPrompt: $modelKey") ModuleContextData.ModelInfo( modelConfig.baseUrl, modelConfig.model, - JSONArray.parseArray(JSONObject.toJSONString(modelPrompt)) + JSONArray.parseArray(JSONObject.toJSONString(module.modulePrompt())) ) } else { null diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/abstracts/AgentModule.kt b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/abstracts/AgentModule.kt index f956ffee..c358c78b 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/abstracts/AgentModule.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/component/abstracts/AgentModule.kt @@ -3,13 +3,10 @@ package work.slhaf.partner.api.agent.factory.component.abstracts import org.slf4j.Logger import org.slf4j.LoggerFactory import work.slhaf.partner.api.agent.factory.component.annotation.AgentComponent -import work.slhaf.partner.api.agent.factory.component.annotation.Init import work.slhaf.partner.api.agent.runtime.config.AgentConfigLoader import work.slhaf.partner.api.agent.runtime.interaction.flow.RunningFlowContext -import work.slhaf.partner.api.chat.ChatClient -import work.slhaf.partner.api.chat.constant.ChatConstant -import work.slhaf.partner.api.chat.pojo.ChatResponse import work.slhaf.partner.api.chat.pojo.Message +import work.slhaf.partner.api.chat.runtime.OpenAiChatRuntime /** * 模块基类 @@ -39,58 +36,37 @@ sealed class AbstractAgentModule { interface ActivateModel { - val model: Model - get() = modelMap.computeIfAbsent(modelKey()) { - buildModel() + val runtime: OpenAiChatRuntime + get() = runtimeMap.computeIfAbsent(modelKey()) { + buildRuntime() } companion object { - val modelMap: MutableMap = mutableMapOf() + val runtimeMap: MutableMap = mutableMapOf() private val configManager: AgentConfigLoader = AgentConfigLoader.INSTANCE } - @Init(order = -1) - fun modelSettings() { - modelMap[modelKey()] = buildModel() - } - - fun buildModel(): Model { + fun buildRuntime(): OpenAiChatRuntime { val modelConfig = configManager.loadModelConfig(modelKey()) - val chatClient = ChatClient(modelConfig.baseUrl, modelConfig.apikey, modelConfig.model) - val model = Model(chatClient) + return OpenAiChatRuntime(modelConfig.baseUrl, modelConfig.apikey, modelConfig.model) + } - val baseMessages = if (withBasicPrompt()) { - loadSpecificPromptAndBasicPrompt(modelKey()) - } else { - configManager.loadModelPrompt(modelKey()) + fun chat(messages: List): String { + return runtime.chat(mergeMessages(messages), useStreaming()) + } + + fun formattedChat(messages: List, responseType: Class): T { + return runtime.formattedChat(mergeMessages(messages), useStreaming(), responseType) + } + + fun mergeMessages(messages: List): List { + if (modulePrompt().isEmpty()) { + return messages + } + return buildList { + addAll(modulePrompt()) + addAll(messages) } - model.baseMessages.addAll(baseMessages) - return model - } - - private fun loadSpecificPromptAndBasicPrompt(modelKey: String): MutableList { - val messages: MutableList = ArrayList() - messages.addAll(configManager.loadModelPrompt("basic")) - messages.addAll(configManager.loadModelPrompt(modelKey)) - return messages - } - - fun chat(): ChatResponse { - val temp = ArrayList() - temp.addAll(model.baseMessages) - temp.addAll(model.chatMessages) - return model.chatClient.runChat(temp) - } - - fun singleChat(input: String): ChatResponse { - val temp = ArrayList(model.baseMessages) - temp.add(Message(ChatConstant.Character.USER, input)) - return model.chatClient.runChat(temp) - } - - fun updateChatClientSettings() { - model.chatClient.temperature = 0.4 - model.chatClient.top_p = 0.8 } /** @@ -104,11 +80,7 @@ interface ActivateModel { } } - fun withBasicPrompt(): Boolean + fun modulePrompt(): List = emptyList() - data class Model( - val chatClient: ChatClient, - val chatMessages: MutableList = mutableListOf(), - val baseMessages: MutableList = mutableListOf() - ) + fun useStreaming(): Boolean = false } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/ConfigLoaderFactory.kt b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/ConfigLoaderFactory.kt index 5f5c57de..639cf8da 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/ConfigLoaderFactory.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/ConfigLoaderFactory.kt @@ -3,7 +3,6 @@ package work.slhaf.partner.api.agent.factory.config import org.slf4j.LoggerFactory import work.slhaf.partner.api.agent.factory.AgentBaseFactory import work.slhaf.partner.api.agent.factory.config.exception.ConfigNotExistException -import work.slhaf.partner.api.agent.factory.config.exception.PromptNotExistException import work.slhaf.partner.api.agent.factory.context.AgentRegisterContext import work.slhaf.partner.api.agent.runtime.config.AgentConfigLoader import work.slhaf.partner.api.agent.runtime.config.FileAgentConfigLoader @@ -14,8 +13,8 @@ import java.lang.reflect.Modifier * * 行为: * - 使用全局 `AgentConfigLoader.INSTANCE`,为空时退回 [FileAgentConfigLoader]。 - * - 加载并写入 `modelConfigMap`、`modelPromptMap` 到 `ConfigFactoryContext`。 - * - 校验 `default` 配置与 `basic` 提示词是否存在。 + * - 加载并写入 `modelConfigMap` 到 `ConfigFactoryContext`。 + * - 校验 `default` 配置是否存在。 * - 反射读取配置加载器实现类(相对基类新增)的静态字段,并写入 `AgentContext.metadata`。 */ class ConfigLoaderFactory : AgentBaseFactory() { @@ -33,26 +32,16 @@ class ConfigLoaderFactory : AgentBaseFactory() { val configFactoryContext = context.configFactoryContext configFactoryContext.modelConfigMap.putAll(agentConfigLoader.modelConfigMap) - configFactoryContext.modelPromptMap.putAll(agentConfigLoader.modelPromptMap) - check(configFactoryContext.modelConfigMap.keys, configFactoryContext.modelPromptMap.keys) + check(configFactoryContext.modelConfigMap.keys) collectLoaderMetadata(context, agentConfigLoader) } - private fun check(configKeys: Set, promptKeys: Set) { - log.info("执行config与prompt检测...") + private fun check(configKeys: Set) { + log.info("执行config检测...") if (!configKeys.contains("default")) { throw ConfigNotExistException("缺少默认配置! 需确保存在一个模型配置的key为`default`") } - if (!promptKeys.contains("basic")) { - throw PromptNotExistException("缺少基础Prompt! 需要确保存在key为basic的Prompt文件,它将与其他Prompt共同作用于模块节点。") - } - - val configKeySet = configKeys.toMutableSet().apply { remove("default") } - val promptKeySet = promptKeys.toMutableSet().apply { remove("basic") } - if (!promptKeySet.containsAll(configKeySet)) { - log.warn("存在未被提示词包含的模型配置,该配置将无法生效!") - } log.info("检测完毕.") } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/pojo/PrimaryModelPrompt.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/pojo/PrimaryModelPrompt.java deleted file mode 100644 index fc395ebb..00000000 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/config/pojo/PrimaryModelPrompt.java +++ /dev/null @@ -1,12 +0,0 @@ -package work.slhaf.partner.api.agent.factory.config.pojo; - -import lombok.Data; -import work.slhaf.partner.api.chat.pojo.Message; - -import java.util.List; - -@Data -public class PrimaryModelPrompt { - private String key; - private List messages; -} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/context/AgentRegisterContext.kt b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/context/AgentRegisterContext.kt index 34532e13..69c93e56 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/context/AgentRegisterContext.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/factory/context/AgentRegisterContext.kt @@ -4,7 +4,6 @@ import org.reflections.Reflections import org.reflections.scanners.Scanners import org.reflections.util.ConfigurationBuilder import work.slhaf.partner.api.agent.factory.config.pojo.ModelConfig -import work.slhaf.partner.api.chat.pojo.Message import java.lang.reflect.Method import java.net.URL @@ -25,7 +24,6 @@ class AgentRegisterContext(urls: List) { } class ConfigFactoryContext { - val modelPromptMap: HashMap> = HashMap() val modelConfigMap: HashMap = HashMap() } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/AgentConfigLoader.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/AgentConfigLoader.java index 7348849b..62493d60 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/AgentConfigLoader.java +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/AgentConfigLoader.java @@ -3,12 +3,9 @@ package work.slhaf.partner.api.agent.runtime.config; import lombok.Data; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import work.slhaf.partner.api.agent.factory.config.exception.PromptNotExistException; import work.slhaf.partner.api.agent.factory.config.pojo.ModelConfig; -import work.slhaf.partner.api.chat.pojo.Message; import java.util.HashMap; -import java.util.List; @Slf4j @Data @@ -18,45 +15,22 @@ public abstract class AgentConfigLoader { @Setter public static AgentConfigLoader INSTANCE; protected HashMap modelConfigMap; - protected HashMap> modelPromptMap; public void load() { modelConfigMap = loadModelConfig(); - modelPromptMap = loadModelPrompt(); } - protected abstract HashMap> loadModelPrompt(); - protected abstract HashMap loadModelConfig(); - public abstract void dumpModelConfig(String key); - // Keep explicit getters for Kotlin compilation phase (without Lombok-generated methods). public HashMap getModelConfigMap() { return modelConfigMap; } - public HashMap> getModelPromptMap() { - return modelPromptMap; - } - - public List loadModelPrompt(String modelKey) { - if (!modelPromptMap.containsKey(modelKey)) { - throw new PromptNotExistException("不存在的modelPrompt: " + modelKey); - } - return modelPromptMap.get(modelKey); - } - public ModelConfig loadModelConfig(String modelKey) { if (!modelConfigMap.containsKey(modelKey)) { return modelConfigMap.get(DEFAULT_KEY); } return modelConfigMap.get(modelKey); } - - public void updateModelConfig(String modelKey, ModelConfig config) { - modelConfigMap.put(modelKey, config); - dumpModelConfig(modelKey); - } - } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/FileAgentConfigLoader.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/FileAgentConfigLoader.java index 2f80b77f..2542d1c2 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/FileAgentConfigLoader.java +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/agent/runtime/config/FileAgentConfigLoader.java @@ -2,17 +2,14 @@ package work.slhaf.partner.api.agent.runtime.config; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import work.slhaf.partner.api.agent.factory.config.exception.*; +import work.slhaf.partner.api.agent.factory.config.exception.ConfigDirNotExistException; +import work.slhaf.partner.api.agent.factory.config.exception.ConfigNotExistException; import work.slhaf.partner.api.agent.factory.config.pojo.ModelConfig; import work.slhaf.partner.api.agent.factory.config.pojo.PrimaryModelConfig; -import work.slhaf.partner.api.agent.factory.config.pojo.PrimaryModelPrompt; -import work.slhaf.partner.api.chat.pojo.Message; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.HashMap; -import java.util.List; /** * 默认配置工厂 @@ -23,28 +20,6 @@ public class FileAgentConfigLoader extends AgentConfigLoader { protected static final String CONFIG_DIR = "./config/"; protected static final String MODEL_CONFIG_DIR = "./config/model/"; - protected static final String PROMPT_CONFIG_DIR = "./config/prompt/"; - - @Override - protected HashMap> loadModelPrompt() { - File file = new File(PROMPT_CONFIG_DIR); - if (!file.exists() && !file.isDirectory()) { - throw new PromptDirNotExistException("未找到提示词目录: " + PROMPT_CONFIG_DIR + " 请手动创建!"); - } - File[] files = file.listFiles(); - if (files == null || files.length == 0) { - throw new PromptNotExistException("在目录 " + PROMPT_CONFIG_DIR + " 中未找到提示词配置!"); - } - HashMap> promptMap = new HashMap<>(); - for (File f : files) { - if (f.isDirectory()) { - continue; - } - PrimaryModelPrompt primaryModelPrompt = JSONUtil.readJSONObject(f, StandardCharsets.UTF_8).toBean(PrimaryModelPrompt.class); - promptMap.put(primaryModelPrompt.getKey(), primaryModelPrompt.getMessages()); - } - return promptMap; - } @Override protected HashMap loadModelConfig() { @@ -67,17 +42,4 @@ public class FileAgentConfigLoader extends AgentConfigLoader { } return configMap; } - - @Override - public void dumpModelConfig(String key) { - try { - File file = new File(MODEL_CONFIG_DIR + key + ".json"); - if (!file.exists()) { - file.createNewFile(); - } - FileUtils.writeStringToFile(file, JSONUtil.toJsonPrettyStr(modelConfigMap.get(key)), StandardCharsets.UTF_8, false); - } catch (Exception e) { - throw new ConfigUpdateFailedException("ModelConfig 配置文件更新失败!"); - } - } } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/ChatClient.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/ChatClient.java deleted file mode 100644 index 30cdee08..00000000 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/ChatClient.java +++ /dev/null @@ -1,84 +0,0 @@ -package work.slhaf.partner.api.chat; - -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import cn.hutool.json.JSONUtil; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import work.slhaf.partner.api.chat.constant.ChatConstant; -import work.slhaf.partner.api.chat.pojo.ChatBody; -import work.slhaf.partner.api.chat.pojo.ChatResponse; -import work.slhaf.partner.api.chat.pojo.Message; -import work.slhaf.partner.api.chat.pojo.PrimaryChatResponse; - -import java.util.List; - -@Slf4j -@Data -@NoArgsConstructor -public class ChatClient { - private String clientId; - - private String url; - private String apikey; - private String model; - - private double top_p; - private double temperature; - private int max_tokens; - - public ChatClient(String url, String apikey, String model) { - this.url = url; - this.apikey = apikey; - this.model = model; - } - - public ChatResponse runChat(List messages) { - HttpRequest request = HttpRequest.post(url); - request.setConnectionTimeout(2000); - request.setReadTimeout(15000); - request.header("Content-Type", "application/json"); - request.header("Authorization", "Bearer " + apikey); - - ChatBody body; - if (top_p > 0) { - body = ChatBody.builder() - .model(model) - .messages(messages) - .top_p(top_p) - .temperature(temperature) - .max_tokens(max_tokens) - .build(); - } else { - body = ChatBody.builder() - .model(model) - .messages(messages) - .build(); - } - - ChatResponse finalResponse; - - try { - HttpResponse response = request.body(JSONUtil.toJsonStr(body)).execute(); - PrimaryChatResponse primaryChatResponse = JSONUtil.toBean(response.body(), PrimaryChatResponse.class); - finalResponse = ChatResponse.builder() - .status(ChatConstant.ResponseStatus.SUCCESS) - .message(primaryChatResponse.getChoices().get(0).getMessage().getContent()) - .usageBean(primaryChatResponse.getUsage()) - .build(); - - response.close(); - } catch (IORuntimeException e) { - log.error("请求超时", e); - finalResponse = ChatResponse.builder() - .message("连接超时") - .status(ChatConstant.ResponseStatus.FAILED) - .usageBean(null) - .build(); - } - return finalResponse; - } - -} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatBody.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatBody.java deleted file mode 100644 index 0a098214..00000000 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatBody.java +++ /dev/null @@ -1,25 +0,0 @@ -package work.slhaf.partner.api.chat.pojo; - -import lombok.*; - -import java.util.List; - -@Builder -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ChatBody { - @NonNull - private String model; - @NonNull - private List messages; - @Builder.Default - private double temperature = 1; - @Builder.Default - private double top_p = 1; - private boolean stream; - @Builder.Default - private int max_tokens = 1024; - private int presence_penalty; - private int frequency_penalty; -} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatResponse.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatResponse.java deleted file mode 100644 index 77183f91..00000000 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/ChatResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package work.slhaf.partner.api.chat.pojo; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import work.slhaf.partner.api.chat.constant.ChatConstant; - -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class ChatResponse { - private ChatConstant.ResponseStatus status; - private String message; - private PrimaryChatResponse.UsageBean usageBean; -} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/PrimaryChatResponse.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/PrimaryChatResponse.java deleted file mode 100644 index 6dd079e6..00000000 --- a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/pojo/PrimaryChatResponse.java +++ /dev/null @@ -1,111 +0,0 @@ -package work.slhaf.partner.api.chat.pojo; - -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -@Getter -@Setter -public class PrimaryChatResponse { - - /** - * id - */ - private String id; - /** - * object - */ - private String object; - /** - * created - */ - private int created; - /** - * model - */ - private String model; - /** - * choices - */ - private List choices; - /** - * usage - */ - private UsageBean usage; - /** - * system_fingerprint - */ - private String system_fingerprint; - - @Setter - @Getter - public static class UsageBean { - /** - * prompt_tokens - */ - private int prompt_tokens; - /** - * completion_tokens - */ - private int completion_tokens; - /** - * total_tokens - */ - private int total_tokens; - /** - * prompt_cache_hit_tokens - */ - private int prompt_cache_hit_tokens; - /** - * prompt_cache_miss_tokens - */ - private int prompt_cache_miss_tokens; - - @Override - public String toString() { - return "UsageBean{" + - "prompt_tokens=" + prompt_tokens + - ", completion_tokens=" + completion_tokens + - ", total_tokens=" + total_tokens + - ", prompt_cache_hit_tokens=" + prompt_cache_hit_tokens + - ", prompt_cache_miss_tokens=" + prompt_cache_miss_tokens + - '}'; - } - } - - @Setter - @Getter - public static class ChoicesBean { - /** - * index - */ - private int index; - /** - * message - */ - private MessageBean message; - /** - * logprobs - */ - private Object logprobs; - /** - * finish_reason - */ - private String finish_reason; - - @Setter - @Getter - public static class MessageBean { - /** - * role - */ - private String role; - /** - * content - */ - private String content; - - } - } -} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiChatRuntime.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiChatRuntime.java new file mode 100644 index 00000000..b06a4277 --- /dev/null +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiChatRuntime.java @@ -0,0 +1,77 @@ +package work.slhaf.partner.api.chat.runtime; + +import com.openai.client.OpenAIClient; +import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.core.http.StreamResponse; +import com.openai.helpers.ChatCompletionAccumulator; +import com.openai.models.chat.completions.*; +import work.slhaf.partner.api.chat.pojo.Message; + +import java.time.Duration; +import java.util.List; + +public class OpenAiChatRuntime { + + private final OpenAIClient client; + private final String model; + + public OpenAiChatRuntime(String baseUrl, String apikey, String model) { + this.client = OpenAIOkHttpClient.builder() + .baseUrl(baseUrl) + .apiKey(apikey) + .timeout(Duration.ofSeconds(30)) + .build(); + this.model = model; + } + + public String chat(List messages, boolean streaming) { + ChatCompletionCreateParams params = buildParams(messages); + if (!streaming) { + return extractText(client.chat().completions().create(params)); + } + + ChatCompletionAccumulator accumulator = ChatCompletionAccumulator.create(); + try (StreamResponse response = client.chat().completions().createStreaming(params)) { + response.stream().forEach(accumulator::accumulate); + } + return extractText(accumulator.chatCompletion()); + } + + public T formattedChat(List messages, boolean streaming, Class responseType) { + StructuredChatCompletionCreateParams params = buildParams(messages).toBuilder() + .responseFormat(responseType) + .build(); + if (!streaming) { + return extractStructured(client.chat().completions().create(params)); + } + + ChatCompletionAccumulator accumulator = ChatCompletionAccumulator.create(); + try (StreamResponse response = client.chat().completions().createStreaming(params.rawParams())) { + response.stream().forEach(accumulator::accumulate); + } + return extractStructured(accumulator.chatCompletion(responseType)); + } + + private ChatCompletionCreateParams buildParams(List messages) { + return ChatCompletionCreateParams.builder() + .model(model) + .messages(OpenAiMessageAdapter.toParams(messages)) + .build(); + } + + private String extractText(ChatCompletion completion) { + if (completion.choices().isEmpty()) { + throw new IllegalStateException("OpenAI chat completion returned no choices."); + } + return completion.choices().getFirst().message().content() + .orElseThrow(() -> new IllegalStateException("OpenAI chat completion returned empty content.")); + } + + private T extractStructured(StructuredChatCompletion completion) { + if (completion.choices().isEmpty()) { + throw new IllegalStateException("OpenAI structured chat completion returned no choices."); + } + return completion.choices().getFirst().message().content() + .orElseThrow(() -> new IllegalStateException("OpenAI structured chat completion returned empty content.")); + } +} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiMessageAdapter.java b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiMessageAdapter.java new file mode 100644 index 00000000..1afbec98 --- /dev/null +++ b/Partner-Framework/src/main/java/work/slhaf/partner/api/chat/runtime/OpenAiMessageAdapter.java @@ -0,0 +1,40 @@ +package work.slhaf.partner.api.chat.runtime; + +import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam; +import com.openai.models.chat.completions.ChatCompletionMessageParam; +import com.openai.models.chat.completions.ChatCompletionSystemMessageParam; +import com.openai.models.chat.completions.ChatCompletionUserMessageParam; +import work.slhaf.partner.api.chat.constant.ChatConstant; +import work.slhaf.partner.api.chat.pojo.Message; + +import java.util.ArrayList; +import java.util.List; + +public final class OpenAiMessageAdapter { + + private OpenAiMessageAdapter() { + } + + public static List toParams(List messages) { + List params = new ArrayList<>(messages.size()); + for (Message message : messages) { + params.add(toParam(message)); + } + return params; + } + + public static ChatCompletionMessageParam toParam(Message message) { + return switch (message.getRole()) { + case ChatConstant.Character.SYSTEM -> ChatCompletionMessageParam.ofSystem( + ChatCompletionSystemMessageParam.builder().content(message.getContent()).build() + ); + case ChatConstant.Character.ASSISTANT -> ChatCompletionMessageParam.ofAssistant( + ChatCompletionAssistantMessageParam.builder().content(message.getContent()).build() + ); + case ChatConstant.Character.USER -> ChatCompletionMessageParam.ofUser( + ChatCompletionUserMessageParam.builder().content(message.getContent()).build() + ); + default -> throw new IllegalArgumentException("Unsupported message role: " + message.getRole()); + }; + } +}