Agent框架核心——规划、工具与编排

Agent如何思考、如何调用工具、如何协同工作。本章将揭开Agent的“大脑”是如何运转的,带您从零实现ReAct模式,设计高可用的工具执行器,并构建多智能体协同系统。

1 ReAct模式实现:Thought -> Action -> Observation

ReAct(Reasoning + Acting)是当前最流行的Agent决策范式。它将推理过程显式化,让模型在“思考”和“行动”之间循环,直到完成任务。

1.1 ReAct的核心循环

一个典型的ReAct循环包含三个步骤:

  1. Thought:模型分析当前状态,思考下一步该做什么。
  2. Action:模型选择要调用的工具(或决定终止),并给出参数。
  3. 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作为独立的微服务,订阅特定主题,发布处理结果。这种架构支持水平扩展,适合大规模生产环境。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐