001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.agent.route; 017 018import com.agentsflex.core.agent.IAgent; 019import com.agentsflex.core.message.AiMessage; 020import com.agentsflex.core.message.Message; 021import com.agentsflex.core.model.chat.ChatModel; 022import com.agentsflex.core.model.chat.ChatOptions; 023import com.agentsflex.core.prompt.MemoryPrompt; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import java.util.List; 028 029/** 030 * RouteAgent:负责路由用户输入到最合适的 IAgent。 031 * - 不实现 IAgent 接口 032 * - route() 方法返回 IAgent 实例,或 null(表示无匹配) 033 * - 不处理 Direct Answer,一律返回 null 034 * - 支持关键字快速匹配 + LLM 智能路由 035 */ 036public class RoutingAgent { 037 038 private static final Logger log = LoggerFactory.getLogger(RoutingAgent.class); 039 040 private static final String DEFAULT_ROUTING_PROMPT_TEMPLATE = 041 "你是一个智能路由助手,请严格按以下规则响应:\n" + 042 "\n" + 043 "可用处理模块(Agent)及其能力描述:\n" + 044 "{agent_descriptions}\n" + 045 "\n" + 046 "规则:\n" + 047 "1. 如果用户问题属于某个模块的能力范围,请输出:Route: [模块名]\n" + 048 "2. 如果问题可以直接回答(如问候、常识、简单对话),请输出:Direct: [你的自然语言回答]\n" + 049 "3. 如果问题涉及多个模块,选择最核心的一个。\n" + 050 "4. 不要解释、不要输出其他内容,只输出上述两种格式之一。\n" + 051 "\n" + 052 "当前对话上下文(最近几轮):\n" + 053 "{conversation_context}\n" + 054 "\n" + 055 "用户最新问题:\n" + 056 "{user_input}"; 057 058 private final ChatModel chatModel; 059 private final RoutingAgentRegistry routingAgentRegistry; 060 private final String userQuery; 061 private final MemoryPrompt memoryPrompt; 062 063 private String routingPromptTemplate = DEFAULT_ROUTING_PROMPT_TEMPLATE; 064 private ChatOptions chatOptions; 065 private boolean enableKeywordRouting = true; 066 private boolean enableLlmRouting = true; 067 068 public RoutingAgent(ChatModel chatModel, RoutingAgentRegistry routingAgentRegistry, 069 String userQuery, MemoryPrompt memoryPrompt) { 070 this.chatModel = chatModel; 071 this.routingAgentRegistry = routingAgentRegistry; 072 this.userQuery = userQuery; 073 this.memoryPrompt = memoryPrompt; 074 } 075 076 /** 077 * 路由用户输入,返回匹配的 IAgent 实例。 078 * 仅当明确路由到某个 Agent 时才返回 IAgent,否则返回 null。 079 * 080 * @return IAgent 实例(仅 Route:xxx 场景),或 null(包括 Direct:、无匹配、异常等) 081 */ 082 public IAgent route() { 083 try { 084 // 1. 关键字快速匹配 085 if (enableKeywordRouting) { 086 String agentName = routingAgentRegistry.findAgentByKeyword(userQuery); 087 if (agentName != null) { 088 log.debug("关键字匹配命中 Agent: {}", agentName); 089 return createAgent(agentName); 090 } 091 } 092 093 // 2. LLM 智能路由 094 if (enableLlmRouting) { 095 String contextSummary = buildContextSummary(memoryPrompt); 096 String agentDescriptions = routingAgentRegistry.getAgentDescriptions(); 097 String prompt = routingPromptTemplate 098 .replace("{agent_descriptions}", agentDescriptions) 099 .replace("{conversation_context}", contextSummary) 100 .replace("{user_input}", userQuery); 101 102 String decision = chatModel.chat(prompt, chatOptions); 103 104 if (decision != null && decision.startsWith("Route:")) { 105 String agentName = decision.substring("Route:".length()).trim(); 106 return createAgent(agentName); 107 } 108 } 109 110 // 3. 无有效 Route,返回 null 111 log.debug("RouteAgent 未匹配到可路由的 Agent,返回 null。Query: {}", userQuery); 112 return null; 113 114 } catch (Exception e) { 115 log.error("RouteAgent 路由异常,返回 null", e); 116 return null; 117 } 118 } 119 120 private IAgent createAgent(String agentName) { 121 RoutingAgentFactory factory = routingAgentRegistry.getAgentFactory(agentName); 122 if (factory == null) { 123 log.warn("Agent 不存在: {}, 返回 null", agentName); 124 return null; 125 } 126 return factory.create(chatModel, userQuery, memoryPrompt); 127 } 128 129 private String buildContextSummary(MemoryPrompt history) { 130 List<Message> messages = history.getMessages(); 131 if (messages == null || messages.isEmpty()) { 132 return "(无历史对话)"; 133 } 134 135 int start = Math.max(0, messages.size() - 4); 136 StringBuilder sb = new StringBuilder(); 137 for (int i = start; i < messages.size(); i++) { 138 Message msg = messages.get(i); 139 String role = msg instanceof AiMessage ? "AI" : "User"; 140 String content = msg.getTextContent() != null ? msg.getTextContent() : ""; 141 sb.append(role).append(": ").append(content.trim()).append("\n"); 142 } 143 return sb.toString().trim(); 144 } 145 146 147 public void setEnableKeywordRouting(boolean enable) { 148 this.enableKeywordRouting = enable; 149 } 150 151 public void setEnableLlmRouting(boolean enable) { 152 this.enableLlmRouting = enable; 153 } 154 155 public void setRoutingPromptTemplate(String routingPromptTemplate) { 156 if (routingPromptTemplate != null && !routingPromptTemplate.trim().isEmpty()) { 157 this.routingPromptTemplate = routingPromptTemplate; 158 } 159 } 160 161 public void setChatOptions(ChatOptions chatOptions) { 162 if (chatOptions != null) { 163 this.chatOptions = chatOptions; 164 } 165 } 166}