让AI学会“动手”:企业级Agent编排实战

0

引言:AI应用的新范式

如果说ChatGPT是能说会道的“嘴强王者”,那么Agent就是既能说又能干的“六边形战士”。

大模型的能力边界正在被不断刷新,从最初单纯的自然语言理解与生成,到如今能够调用外部工具、执行具体操作、完成复杂任务。这种转变的核心驱动力,正是Agent(智能体)技术——它让AI不再只是被动回答问题的聊天机器,而是能够自主规划、决策并采取行动的“数字员工”。

然而,从Demo到生产环境,中间隔着一道名为“工程化”的鸿沟。如何用Java生态优雅地构建企业级Agent?如何让AI稳定地调用几十甚至上百个工具?如何在复杂业务场景中编排工具调用流程?本文将结合Spring AI与LangChain两大框架,深入剖析企业级Agent构建与工具调用编排的实战之道。

一、Agent为何需要“工具”

在讨论技术实现之前,先厘清一个核心问题:为什么Agent必须依赖工具?

大模型的知识截至训练日期,无法获取实时信息,也无法执行实际操作。比如,当用户问“今天的天气如何,顺便帮我订一张机票”,大模型本身无法查询天气,更无法完成订票操作。

工具调用(Tool Calling / Function Calling)正是解决这一问题的关键机制:大模型识别用户意图后,生成结构化的工具调用请求,系统执行相应函数并返回结果,模型再基于结果生成最终回复。这个过程中,大模型扮演的是“大脑”角色——负责思考和决策,而工具则是“手和脚”——负责执行

二、企业级Agent核心架构

无论是基于Spring AI还是LangChain,一个成熟的企业级Agent都需要具备以下核心模块:

模块 职责 关键技术
规划引擎 理解用户意图,拆解任务步骤 Prompt工程、ReAct模式
工具注册表 管理所有可调用工具的元数据 注解驱动、动态注册
执行器 调用工具并处理返回结果 同步/异步执行、超时控制
记忆系统 维护对话上下文和工具调用历史 多级记忆压缩、向量存储
可观测性 记录调用链路、监控性能 日志、链路追踪、指标采集

架构设计的核心原则是“关注点分离”——将业务逻辑、AI推理和工具调度解耦,每个模块独立演进、可替换、可测试。

三、Spring AI实践:以ToolCallAdvisor为核心的Agent编排

Spring AI从1.1.0-M4版本开始引入递归顾问(Recursive Advisor) 机制,将工具调用循环提升为顾问链中的一等公民,实现了对Agent迭代工作流的原生支持。

3.1 核心机制:ToolCallAdvisor

在Spring AI 1.x中,工具执行逻辑内嵌在ChatModel实现内部,开发者无法干预调用过程。2.0版本彻底重构了这一设计——ToolCallAdvisor作为递归顾问接管了整个工具调用生命周期。

关键流程如下:

  1. 定义工具:通过@Tool注解标记方法
  2. 注册工具:在ChatClient构建时传入ToolCallback
  3. 执行循环:ChatClient将请求发给LLM → LLM返回含工具调用的响应 → ToolCallAdvisor截获并执行对应工具 → 将工具结果追加到对话历史 → 再次调用LLM → 直到LLM返回不含工具调用的最终答案

代码实现如下:

// 1. 定义工具
@Component
public class WeatherTools {

    @Tool(description = "获取指定城市的当前天气")
    public String getCurrentWeather(
            @ToolParam(description = "城市名称,如:北京") String city) {
        // 实际项目中可调用真实天气API
        return city + ":晴,25°C";
    }
    
    @Tool(description = "预订机票")
    public BookingConfirmation bookFlight(
            @ToolParam(description = "出发城市") String origin,
            @ToolParam(description = "目的城市") String destination,
            @ToolParam(description = "日期,格式YYYY-MM-DD") String date) {
        return flightService.book(origin, destination, date);
    }
}

// 2. 构建ChatClient并注册工具
@Configuration
public class AiConfig {
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel, WeatherTools weatherTools) {
        return ChatClient.builder(chatModel)
                .defaultToolCallbacks(
                    FunctionToolCallback.builder("getCurrentWeather", weatherTools::getCurrentWeather)
                        .description("获取指定城市的当前天气")
                        .inputType(WeatherRequest.class)
                        .build()
                )
                .defaultAdvisors(new ToolCallAdvisor())
                .build();
    }
}

// 3. 业务调用
@Service
public class AgentService {
    
    private final ChatClient chatClient;
    
    public String processUserRequest(String userInput) {
        return chatClient.prompt()
                .user(userInput)
                .call()
                .content();
    }
}

3.2 记忆管理:将记忆顾问置于工具循环内部

一个容易被忽视的关键设计是记忆(Memory)与工具循环(Tool Loop)的交互。默认情况下,MessageChatMemoryAdvisor(顺序:HIGHEST_PRECEDENCE + 200)在ToolCallAdvisor(顺序:HIGHEST_PRECEDENCE + 300)之前执行,这意味着工具调用的请求和响应不会被写入记忆存储——它只记录最终的User和Assistant消息。

如果想让LLM拥有完整的“反思能力”——知道之前尝试过哪些工具、返回了什么结果——就需要将记忆顾问置于工具循环内部

// 将记忆顾问的顺序设置为高于ToolCallAdvisor,使其在循环内部执行
var memoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
        .order(ToolCallAdvisor.DEFAULT_ORDER + 1)  // 关键:放在ToolCallAdvisor之后
        .build();

var chatClient = ChatClient.builder(chatModel)
        .defaultAdvisors(memoryAdvisor, new ToolCallAdvisor())
        .build();

Spring AI 2.0中,当检测到记忆顾问在循环内部时,ToolCallAdvisor会自动禁用其内部对话历史,避免重复写入。支持完整工具消息持久化的内置存储包括InMemoryChatMemoryRepositoryRedisChatMemoryRepositoryNeo4jChatMemoryRepository

四、LangChain实践:工具调用与编排

如果说Spring AI是Java生态的“正规军”,那么LangChain就是Python生态的“特种部队”。LangChain的Agent框架同样提供了完善的工具调用能力。

4.1 工具定义与注册

LangChain中,工具通过继承BaseTool类或使用@tool装饰器定义:

from langchain.tools import BaseTool
from typing import Type, Optional
from pydantic import BaseModel, Field
import requests

class APITestInput(BaseModel):
    endpoint: str = Field(description="API端点地址")
    method: str = Field(description="HTTP方法,如GET、POST")
    payload: Optional[dict] = Field(None, description="请求体")
    expected_status: int = Field(200, description="期望的状态码")

class APITestTool(BaseTool):
    name = "api_test_tool"
    description = "执行API测试并验证响应"
    args_schema: Type[BaseModel] = APITestInput

    def _run(self, endpoint: str, method: str, 
             payload: dict = None, expected_status: int = 200):
        """同步执行"""
        try:
            if method.upper() == "GET":
                response = requests.get(endpoint, params=payload)
            elif method.upper() == "POST":
                response = requests.post(endpoint, json=payload)
            else:
                return {"error": f"不支持的HTTP方法: {method}"}
            
            success = response.status_code == expected_status
            return {
                "success": success,
                "status_code": response.status_code,
                "response_body": response.json() if response.content else None,
                "message": f"状态码验证{'通过' if success else '失败'}"
            }
        except Exception as e:
            return {"error": f"API测试异常: {str(e)}"}

    async def _arun(self, endpoint: str, method: str, 
                    payload: dict = None, expected_status: int = 200):
        """异步执行"""
        return self._run(endpoint, method, payload, expected_status)

4.2 Agent构建与执行

使用ReAct模式构建Agent,通过create_react_agentAgentExecutor完成编排:

from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory

# 测试Agent专用Prompt——注入测试工程师的思维链
TEST_AGENT_PROMPT = PromptTemplate.from_template(
    """你是一名资深自动化测试工程师。请按以下步骤执行任务:
1. **需求分析**:理解测试目标,识别测试类型
2. **环境检查**:确认测试环境可用性
3. **测试设计**:设计测试场景,考虑边界条件
4. **工具选择**:选择合适的测试工具
5. **执行验证**:执行测试并验证结果
6. **结果分析**:给出结论和建议

当前任务:{input}
可用工具:{tools}
{agent_scratchpad}"""
)

# 初始化LLM(温度设为0.1,保证测试结果的确定性)
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0.1)

# 构建工具集
tools = [APITestTool(), UITestTool(), DBValidationTool()]

# 创建Agent
agent = create_react_agent(
    llm=llm,
    tools=tools,
    prompt=TEST_AGENT_PROMPT
)

# 创建执行器(带记忆)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=ConversationBufferMemory(
        memory_key="chat_history",
        return_messages=True
    ),
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=10  # 防止无限循环
)

# 执行测试任务
result = agent_executor.invoke({
    "input": "测试用户登录流程:使用test_user@example.com登录系统,验证登录成功后跳转到首页"
})

4.3 企业级增强:并发执行与自愈

生产环境中,Agent往往需要处理批量任务。LangChain结合concurrent.futures可实现并发执行:

from concurrent.futures import ThreadPoolExecutor, as_completed

class ConcurrentTestRunner:
    def __init__(self, agent_executor, max_workers=5):
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
        self.agent = agent_executor

    def run_concurrent_tests(self, test_cases):
        futures = {}
        for test_case in test_cases:
            future = self.executor.submit(
                self.agent.invoke,
                {"input": f"执行测试:{test_case['description']}"}
            )
            futures[future] = test_case["id"]

        results = []
        for future in as_completed(futures):
            test_id = futures[future]
            try:
                result = future.result(timeout=60)
                results.append({"test_id": test_id, "result": result})
            except Exception as e:
                results.append({"test_id": test_id, "error": str(e)})
        return results

五、最佳实践与避坑指南

5.1 终止条件是生命线

递归顾问和Agent循环如果没有明确的终止条件,可能造成无限调用,既消耗大量Token费用,又可能导致系统宕机。务必设置:

  • max_iterations:最大迭代次数(Spring AI中需在自定义顾问中实现,LangChain的AgentExecutor默认支持)
  • maxRepeatAttempts:结构化输出验证的最大重试次数

5.2 工具数量爆炸:渐进式披露

当工具数量超过30个,将所有工具定义一次性塞入上下文会引发上下文膨胀、准确率下降、Token成本飙升三大问题。

Spring AI 2.0提供了ToolSearchToolCallingAdvisor,通过渐进式工具披露解决该问题:初始只暴露一个toolSearch工具,LLM通过自然语言查询按需获取相关工具定义,再执行后续调用。实测可减少34-64% 的Token消耗。

启用方式:

spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector

5.3 可观测性:别等出问题才后悔

生产环境的Agent调用链往往包含多次LLM调用和工具执行,没有链路追踪几乎无法排查问题。建议:

  • 记录每次工具调用的输入参数、输出结果、耗时
  • 使用Spring AI的Micrometer集成或LangSmith进行全链路追踪
  • 设置关键告警:工具调用失败率、平均迭代次数、Token消耗速率

六、总结与展望

Agent的本质,是让LLM从“思考者”进化为“行动者”。无论是Spring AI的ToolCallAdvisor递归循环,还是LangChain的ReAct Agent框架,核心都围绕着同一套逻辑:理解意图 → 调用工具 → 处理结果 → 迭代决策

随着Spring AI 2.0将工具调用提升为顾问链中的一等公民,并引入渐进式工具披露、MCP协议集成等企业级特性,Java生态在AI工程化领域的竞争力正在快速追赶。而LangChain凭借丰富的生态和灵活的Python表达力,依然是快速验证和原型开发的首选。

选择框架是战术问题,理解“工具编排”的设计哲学才是战略问题。无论你使用哪套技术栈,清晰的分层架构、可靠的终止条件、完善的可观测性,才是企业级Agent落地的三大基石。

未来的Agent,将不再是单打独斗的“孤勇者”,而是通过MCP等协议实现跨系统、跨组织、跨语言的协作网络。而这,正是AI从“对话工具”走向“数字生产力”的必经之路。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐