Agent框架核心——规划、工具与编排
Agent框架核心——规划、工具与编排
Agent如何思考、如何调用工具、如何协同工作。本章将揭开Agent的“大脑”是如何运转的,带您从零实现ReAct模式,设计高可用的工具执行器,并构建多智能体协同系统。
1 ReAct模式实现:Thought -> Action -> Observation
ReAct(Reasoning + Acting)是当前最流行的Agent决策范式。它将推理过程显式化,让模型在“思考”和“行动”之间循环,直到完成任务。
1.1 ReAct的核心循环
一个典型的ReAct循环包含三个步骤:
- Thought:模型分析当前状态,思考下一步该做什么。
- Action:模型选择要调用的工具(或决定终止),并给出参数。
- Observation:执行工具,将结果返回给模型。
循环直到模型输出Final Answer为止。
1.2 Java实现一个基础的ReAct Agent
我们先从最简单的版本开始,理解其本质。
步骤1:设计提示词模板
ReAct的关键在于提示词格式。我们需要告诉模型如何输出Thought、Action和Observation。
public class ReActPrompt {
public static final String SYSTEM_TEMPLATE = """
你是一个能够使用工具的智能助手。你可以使用以下工具:
{tools}
请严格按照以下格式回答:
Thought: 你的思考过程
Action: 工具名称,参数必须是JSON格式,如:getWeather {"city":"北京"}
Observation: 工具返回的结果
...(重复Thought/Action/Observation)
Thought: 现在我知道最终答案了
Final Answer: 对用户的最终回复
""";
public static final String USER_TEMPLATE = "用户问题:{question}";
}
步骤2:定义工具接口
public interface Tool {
String name();
String description();
String execute(String jsonParams);
}
步骤3:实现工具注册与执行
public class ToolRegistry {
private final Map<String, Tool> tools = new HashMap<>();
public void register(Tool tool) {
tools.put(tool.name(), tool);
}
public Tool get(String name) {
return tools.get(name);
}
public String getToolsDescription() {
return tools.values().stream()
.map(t -> t.name() + ": " + t.description())
.collect(Collectors.joining("\n"));
}
}
步骤4:解析模型输出
模型返回的文本中可能包含多轮Thought/Action/Observation。我们需要一个解析器提取Action。
public class ReActParser {
// 提取Action行,返回工具名和参数JSON
public Optional<Action> parseAction(String text) {
Pattern pattern = Pattern.compile("Action:\\s*(\\w+)\\s*(\\{.*\\})?");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
String toolName = matcher.group(1);
String params = matcher.group(2) != null ? matcher.group(2) : "{}";
return Optional.of(new Action(toolName, params));
}
return Optional.empty();
}
// 提取Final Answer
public Optional<String> parseFinalAnswer(String text) {
Pattern pattern = Pattern.compile("Final Answer:\\s*(.*)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
return Optional.of(matcher.group(1).trim());
}
return Optional.empty();
}
record Action(String toolName, String params) {}
}
步骤5:组装ReAct Agent
public class ReActAgent {
private final ChatLanguageModel model;
private final ToolRegistry toolRegistry;
private final ReActParser parser;
public ReActAgent(ChatLanguageModel model, ToolRegistry toolRegistry) {
this.model = model;
this.toolRegistry = toolRegistry;
this.parser = new ReActParser();
}
public String execute(String userQuestion) {
List<ChatMessage> messages = new ArrayList<>();
messages.add(new SystemMessage(buildSystemPrompt()));
messages.add(new UserMessage("用户问题:" + userQuestion));
int maxIterations = 10;
for (int i = 0; i < maxIterations; i++) {
// 调用模型
String response = model.generate(messages).content().text();
messages.add(new AiMessage(response));
// 检查是否有Final Answer
Optional<String> finalAnswer = parser.parseFinalAnswer(response);
if (finalAnswer.isPresent()) {
return finalAnswer.get();
}
// 解析Action
Optional<Action> action = parser.parseAction(response);
if (action.isEmpty()) {
// 没有找到Action,让模型重试
messages.add(new UserMessage("请按照指定格式输出Thought/Action/Observation或Final Answer"));
continue;
}
// 执行工具
Tool tool = toolRegistry.get(action.get().toolName());
if (tool == null) {
String error = "未知工具:" + action.get().toolName();
messages.add(new UserMessage("Observation: " + error));
continue;
}
String observation = tool.execute(action.get().params());
messages.add(new UserMessage("Observation: " + observation));
}
return "抱歉,处理超时,请稍后再试。";
}
private String buildSystemPrompt() {
return ReActPrompt.SYSTEM_TEMPLATE
.replace("{tools}", toolRegistry.getToolsDescription());
}
}
1.3 利用LangChain4j简化实现
实际上,LangChain4j已经内置了ReAct Agent。我们可以直接使用:
// 创建工具
ToolSpecification weatherTool = ToolSpecification.builder()
.name("getWeather")
.description("获取城市天气")
.addParameter("city", type("string"))
.build();
// 创建ReAct Agent
ReActAgent agent = ReActAgent.builder()
.chatLanguageModel(model)
.tools(weatherTool)
.maxIterations(5)
.build();
// 执行
String answer = agent.execute("北京天气怎么样?");
2 工具注册与执行器:线程池隔离与安全控制
工具是Agent的“手脚”,但调用工具可能涉及耗时操作、外部API、甚至危险操作(如删除数据)。我们需要设计健壮的工具执行器。
2.1 工具注解式注册
通过注解将任意Java方法暴露为工具,降低接入成本。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tool {
String name() default "";
String description() default "";
}
@Component
public class WeatherTools {
@Tool(description = "获取指定城市的天气")
public String getWeather(@ToolParam(description = "城市名称") String city) {
// 调用天气API
return "晴天,22°C";
}
}
工具注册器扫描Spring容器中的Bean,收集所有带@Tool注解的方法。
@Component
public class ToolScanner implements ApplicationContextAware {
private final Map<String, ToolMethod> tools = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext context) {
Map<String, Object> beans = context.getBeansWithAnnotation(Component.class);
for (Object bean : beans.values()) {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
Tool tool = method.getAnnotation(Tool.class);
if (tool != null) {
String name = tool.name().isEmpty() ? method.getName() : tool.name();
tools.put(name, new ToolMethod(bean, method, tool.description()));
}
}
}
}
public String execute(String toolName, String jsonParams) {
ToolMethod tm = tools.get(toolName);
if (tm == null) {
throw new IllegalArgumentException("Tool not found: " + toolName);
}
// 使用Jackson解析参数并调用
// ...
}
}
2.2 线程池隔离与超时控制
工具执行可能耗时较长,若阻塞Agent的主线程,会影响整体响应。应使用独立的线程池,并为每个工具调用设置超时。
@Configuration
public class ToolExecutorConfig {
@Bean("toolExecutor")
public ExecutorService toolExecutor() {
return Executors.newFixedThreadPool(20, new ThreadFactoryBuilder()
.setNameFormat("tool-executor-%d")
.setDaemon(true)
.build());
}
@Bean
public ToolInvoker toolInvoker(@Qualifier("toolExecutor") ExecutorService executor) {
return new ToolInvoker(executor, Duration.ofSeconds(30));
}
}
public class ToolInvoker {
private final ExecutorService executor;
private final Duration timeout;
public ToolInvoker(ExecutorService executor, Duration timeout) {
this.executor = executor;
this.timeout = timeout;
}
public String invoke(ToolMethod tool, String jsonParams) {
Future<String> future = executor.submit(() -> tool.execute(jsonParams));
try {
return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
return "工具执行超时,请稍后再试。";
} catch (Exception e) {
return "工具执行失败:" + e.getMessage();
}
}
}
2.3 安全控制:权限与审计
对于敏感操作(如删除数据、支付),必须进行权限校验和操作审计。
@Component
public class SecureToolInvoker {
private final SecurityContext securityContext;
private final AuditLogger auditLogger;
public String invoke(ToolMethod tool, String jsonParams) {
// 检查权限
if (tool.requiresPermission() && !securityContext.hasPermission(tool.permission())) {
auditLogger.log("权限不足", securityContext.getUserId(), tool.name(), jsonParams);
return "抱歉,您没有权限执行此操作。";
}
// 执行工具
String result = tool.execute(jsonParams);
// 记录审计日志
auditLogger.log("工具调用", securityContext.getUserId(), tool.name(), jsonParams, result);
return result;
}
}
3 多智能体协同:Supervisor与Worker架构
单个Agent无法处理复杂业务,我们需要多个Agent分工协作。常见的模式是“监督者(Supervisor) + 工作者(Worker)”。
3.1 模式简介
- Supervisor Agent:负责理解用户意图,将任务分解,分发给合适的Worker,并汇总结果。
- Worker Agent:各司其职,如订单处理Agent、售后Agent、知识库Agent等。
3.2 实现一个简单的多Agent编排
我们使用LangChain4j的AI Service来定义各个Agent。
// 定义Worker接口
public interface OrderWorker {
@SystemMessage("你是一个订单处理专家。你可以查询订单、修改订单地址、取消订单。")
String handle(@UserMessage String query);
}
public interface ReturnWorker {
@SystemMessage("你是一个退货处理专家。你可以创建退货单、查询退货进度。")
String handle(@UserMessage String query);
}
public interface KnowledgeWorker {
@SystemMessage("你是一个产品知识库专家,回答产品相关问题。")
String handle(@UserMessage String query);
}
// 定义Supervisor
public interface Supervisor {
@SystemMessage("""
你是客服主管。请分析用户问题,决定由哪个专家处理。
可选专家:OrderWorker, ReturnWorker, KnowledgeWorker
只输出专家名称,不要其他内容。
""")
String selectWorker(@UserMessage String query);
}
编排逻辑:
@Service
public class MultiAgentOrchestrator {
private final Supervisor supervisor;
private final Map<String, Object> workers;
public MultiAgentOrchestrator(Supervisor supervisor,
OrderWorker orderWorker,
ReturnWorker returnWorker,
KnowledgeWorker knowledgeWorker) {
this.supervisor = supervisor;
this.workers = Map.of(
"OrderWorker", orderWorker,
"ReturnWorker", returnWorker,
"KnowledgeWorker", knowledgeWorker
);
}
public String handle(String userQuery) {
String workerName = supervisor.selectWorker(userQuery);
Object worker = workers.get(workerName);
if (worker == null) {
return "抱歉,我暂时无法处理您的问题。";
}
// 通过反射或接口调用worker的handle方法
return ((Function<String, String>) worker::handle).apply(userQuery);
}
}
3.3 进阶:带记忆的协同
如果Worker需要共享上下文(如订单号),可以在Supervisor层面提取关键信息,传递给Worker。
public interface SupervisorWithContext {
@SystemMessage("""
你是客服主管。请分析用户问题,提取关键实体(订单号、商品名称等),
并以JSON格式输出,包含worker和context字段。
""")
SupervisorDecision decide(@UserMessage String query);
}
record SupervisorDecision(String worker, Map<String, String> context) {}
Supervisor调用后,将context传递给Worker:
public String handle(String userQuery) {
SupervisorDecision decision = supervisor.decide(userQuery);
Object worker = workers.get(decision.worker());
// 将context注入Worker的提示词或工具参数中
// ...
}
3.4 多Agent的通信与协调
对于更复杂的场景(如多个Agent需要协作完成一个任务),可以采用事件驱动架构,通过消息队列(如Kafka)传递Agent间的消息。
每个Agent作为独立的微服务,订阅特定主题,发布处理结果。这种架构支持水平扩展,适合大规模生产环境。
更多推荐



所有评论(0)