通过ByteBuddy动态创建Agent
java
package com.zishi.ai.shdemo.code.arch;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.AgenticServices;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import net.bytebuddy.jar.asm.Opcodes;
import java.util.Map;
public class UltraAgentFactory {
/**
* 【全动态智能体孵化核心】
* 在内存中凭空雕刻出带有 LangChain4j 原生注解的"伪预设接口",并托管给官方生态
*
* @param agentName 动态生成的 Agent 接口类名 (例如 "WithdrawAgent")
* @param systemPrompt 大模型的 System Prompt (扮演的角色职责)
* @param userPromptMethod 大模型的 User Prompt 模板 (例如 "从 {{user}} 账户提取 {{amount}} 元")
* @param paramDefinitions 参数定义映射表(必须用 LinkedHashMap 保证参数顺序,Key 为占位符名,Value 为类型)
* @param model LangChain4j 基础大模型实例
* @return LangChain4j 官方代理生成的强类型 Agent 实例
*/
public static Object createDynamicAgent(
String agentName,
String agentDescription,
String systemPrompt,
String userPromptMethod,
Map<String, Class<?>> paramDefinitions,
ChatModel model) {
try {
// 1. 开启 ByteBuddy 纯接口(Interface)雕刻模式
var builder = new ByteBuddy().makeInterface().name("com.dynamic.agent.dynamicinterface." + agentName);
// 2. 将传入的参数 Class 类型提取为数组 (例如 [String.class, Double.class])
Class<?>[] paramTypes = paramDefinitions.values().toArray(new Class<?>[0]);
// 🛠️ 核心修正:不要再叫无名之辈 "execute" 了!
// 我们直接将方法名动态雕刻为更符合业务的名称,例如小写的 agentName("withdrawAgent" -> "withdraw")
// 或者直接约定一个 AgenticServices 绝对放行的通用标准行为方法名。
String methodName = agentName.toLowerCase().replace("agent", "");
if (methodName.isEmpty()) {
methodName = "process"; // 降级兜底名
}
System.out.println("🤖 [框架层注入] 正在为 AgenticServices 雕刻核心触发方法: " + methodName);
// 3. 现场雕刻接口方法(统一硬编码方法名为 "execute",返回值统一为 String 方便上层反射抓取)
// 1. 开启方法定义,并紧跟 .withParameters 指定参数
DynamicType.Builder.MethodDefinition<?> methodDefinition =
builder.defineMethod(methodName, String.class, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT)
.withParameters(paramTypes)
// 2. 🎯 核心修正:在 1.18.x 版本中,直接调用 .annotateMethod 之前,
// 必须先调用 .withoutCode() 表明这是接口的抽象方法。
// 调用后,API 的上下文会切换为 MethodDefinition.ExceptionDeclarable,此时核心注解方法全部现身!
.withoutCode()
// 3. 动态将 LangChain4j 的 @SystemMessage 刻在方法上
.annotateMethod(AnnotationDescription.Builder.ofType(SystemMessage.class)
.defineArray("value", new String[]{systemPrompt}).build())
// 4. 动态将 LangChain4j 的 @UserMessage 刻在方法上
.annotateMethod(AnnotationDescription.Builder.ofType(UserMessage.class)
.defineArray("value", new String[]{userPromptMethod}).build())// ⚡️ 见证奇迹:在这里!把 @Agent 注解稳稳地刻在接口类的头上!
.annotateMethod(AnnotationDescription.Builder.ofType(Agent.class)
.define("value", agentDescription) // 对应 @Agent("xxxx")
.build());
// 4. 遍历参数定义表,将 LangChain4j 的 @V 注解精准刻在对应的参数位置上
int index = 0;
for (String paramName : paramDefinitions.keySet()) {
methodDefinition = methodDefinition
.annotateParameter(index, AnnotationDescription.Builder.ofType(V.class).define("value", paramName).build());
index++;
}
// 5. 将这个纯动态接口编译并加载到当前 JVM 内存中
Class<?> dynamicInterface = methodDefinition.make()
.load(UltraAgentFactory.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
printDetail(dynamicInterface);
// 6. 🎉 天衣无缝的降维打击:直接塞给 LangChain4j 官方工厂
// 官方会认为这是一个你开发期手写的标准接口,高高兴兴地接管了拼装 Prompt 和请求 LLM 的全部脏活
// 6. 🎉 修正 2:切换为 AgenticServices 建造者工厂进行托管
return AgenticServices.agentBuilder(dynamicInterface)
.chatModel(model)
.build();
} catch (Exception e) {
throw new RuntimeException("【架构层异常】全动态接口智能体孵化失败", e);
}
}
public static void printDetail(Class<?> dynamicInterface) {
// ==================== 🛠️ 接口元数据显微镜 (放入工厂中) ====================
System.out.println("\n=======================================================");
System.out.println("🔍 [字节码探测] 动态生成的接口全限定名: " + dynamicInterface.getName());
System.out.println("🔍 [字节码探测] 是否真的是接口: " + dynamicInterface.isInterface());
java.lang.reflect.Method[] methods = dynamicInterface.getDeclaredMethods();
System.out.println("🔍 [字节码探测] 接口内查找到的方法总数: " + methods.length);
for (java.lang.reflect.Method m : methods) {
System.out.println("------------ 方法明细 ------------");
System.out.println("👉 方法名: " + m.getName());
System.out.println("👉 返回值类型: " + m.getReturnType().getName());
// 打印方法上的注解
java.lang.annotation.Annotation[] annos = m.getAnnotations();
System.out.println("👉 方法上的注解数量: " + annos.length);
for (java.lang.annotation.Annotation a : annos) {
System.out.println(" [Method Anno] -> " + a.toString());
}
// 打印参数及参数上的注解
java.lang.reflect.Parameter[] params = m.getParameters();
System.out.println("👉 方法参数数量: " + params.length);
for (int i = 0; i < params.length; i++) {
java.lang.reflect.Parameter p = params[i];
System.out.println(" [Param " + i + "] 类型: " + p.getType().getName() + ", 名字: " + p.getName());
for (java.lang.annotation.Annotation pa : p.getAnnotations()) {
System.out.println(" [Param Anno] -> " + pa.toString());
}
}
}
System.out.println("=======================================================\n");
// ====================================================================
}
}
使用
java
import com.zishi.ai.shdemo.util.ModelUtil;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
public class Main02 {
public static void main(String[] args) throws Exception {
// 0. 初始化底层的原子大模型(此处使用 OpenAI 示例,生产环境可替换为任意模型)
ChatModel baseModel = ModelUtil.BASE_MODEL;
System.out.println("====== 🎯 场景一:运行时动态派生【财务取款 Agent】 ======");
// 1. 动态定义参数字典(!!! 必须使用 LinkedHashMap 确保反射时参数的严格顺序 !!!)
Map<String, Class<?>> withdrawParams = new LinkedHashMap<>();
withdrawParams.put("user", String.class); // 第一个参数叫 user
withdrawParams.put("amount", Double.class); // 第二个参数叫 amount
// 2. 召唤工厂,一键捏出强类型实例
Object withdrawAgent = UltraAgentFactory.createDynamicAgent(
"WithdrawAgent",
"一个从账户中提取美元的银行家", // 👈 这一行会被完美注入到接口头顶的 @Agent 注解中!
"你是一名专业的银行出纳家,只负责从账户中提取美元,拒绝一切其他无聊的闲聊。",
"从 {{user}} 的账户中提取 {{amount}} 美元,并返回新的余额。",
withdrawParams,
baseModel
);
// 3. 运行时反射抓取固定的 "execute" 方法进行调用
// 这一步彻底摆脱了硬编码接口,参数类型在运行时完美对齐
java.lang.reflect.Method coreMethod = withdrawAgent.getClass().getInterfaces()[0].getDeclaredMethods()[0];
//Method withdrawMethod = withdrawAgent.getClass().getMethod("execute", String.class, Double.class);
System.out.println("🔄 正在触发【财务取款 Agent】的 execute 方法...");
String withdrawResult = (String) coreMethod.invoke(withdrawAgent, "张三", 8500.25);
System.out.println("\n🔥 LLM 最终执行响应:");
System.out.println(withdrawResult);
System.out.println("\n\n====== 🎯 场景二:同一套代码,现场捏一个完全不同的【大额合同审计 Agent】 ======");
// 1. 重新定义一套完全不同的业务参数
Map<String, Class<?>> auditParams = new LinkedHashMap<>();
auditParams.put("companyName", String.class);
auditParams.put("contractValue", Integer.class);
auditParams.put("riskLevel", String.class);
// 2. 再次孵化一个新的纯动态接口 Agent
Object contractAuditAgent = UltraAgentFactory.createDynamicAgent(
"ContractAuditAgent",
"你是一名严谨的跨国集团法务风控总监,精通商业合同合规性审查。",
"你是一名严谨的跨国集团法务风控总监,精通商业合同合规性审查。",
"正在审计 [{{companyName}}] 的合同,标的额 {{contractValue}} 万元,风控等级定为 [{{riskLevel}}]。请给出防范意见。",
auditParams,
baseModel
);
// 3. 反射抓取它专属的 execute 方法进行调用(参数变成了 String, Integer, String)
//Method auditMethod = contractAuditAgent.getClass().getMethod("execute", String.class, Integer.class, String.class);
java.lang.reflect.Method auditMethod = contractAuditAgent.getClass().getInterfaces()[0].getDeclaredMethods()[0];
System.out.println("🔄 正在触发【合同审计 Agent】的 execute 方法...");
String auditResult = (String) auditMethod.invoke(contractAuditAgent, "阿里巴巴网络技术有限公司", 5000, "HIGH_RISK");
System.out.println("\n🔥 LLM 最终执行响应:");
System.out.println(auditResult);
}
}