第13章 旅游规划优化,监控,部署
13-1 敏感资源处理方案
从配置中读取敏感资源,不能使用istatic
在resources下创建.env私有信息文件
properties
ALIBABA_DASHCOPE_KEY=
MODEL_NAME =
BAIDU_MAP_KEY=
BAIDU_MAP_ADDR=在application配置文件中配置私有信息动态配置
yaml
spring:
config:
import: optional:file:.env[.properties]
agent:
modeL_name: ${MODEL_NAME}
alibaba_dashscope_key: ${ALIBABA_DASHCOPE_KEY}
mcp:
baidu_map_addr: ${BAIDU_MAP_ADDR}
baidu_map_key: ${BAIDU_MAP_KEY}在创建配置工具类,是注入Bean中的,哪里需要使用私有信息就注入该配置类即可。
java
@Configuration
@Getter
public class Properties {
//大模型名称
@Value("${agent.model_name}")
private String modelName;
//阿云DashScope Key
@Value("${agent.alibaba_dashscope_key}")
private String alibabaDashscopeKey;
//百度地图MCPKey
@Value("${mcp.baidu_map_addr}")
private String baiduMapAddr;
@Value("${mcp.baidu_map_key}")
private String baiduMapKey;
}13-2 主管Agent暴露和用户交互的接口
用户与主管Agent交互,就是创建一个controller,将主管Agent注入到Bean中,controller调用即可
java
@Component
public class ManagerAgent {}
@RestController
public class ManagerAgentController {
@Resource
private ManagerAgent managerAgent;
//用户提交旅游规划的PromptI
@RequestMapping(value = "/trip", method = RequestMethod.POST)
public void tripPlan() {
ReActAgent manager = managerAgent.getManagerAgent();
// managerAgent.run();
}
}13-3 docker搭建Agent跟踪和Token消费分析
大模型数据观察和分析平台Langfuse
下载: git clone https://github.com/langfuse/langfuse.git
启动:sudo docker compose run
13-4 旅游规划Agent回队集成Agent追綜观测
xml
<observation-extension.version>1.1.2.2</observation-extension.version>
<!-- ObservationAPI:能调用LangFuse 进行Agent数据观测 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-observation-extension</artifactId>
<version>${observation-extension.version}</version>
</dependency>敏感信息
properties
LANGFUSE_ADDRE=http://127.0.0.1:3000
LANGFUSE_SECRET_KEY=Yours Langfuse secret key
LANGFUSE_PUBLIC_KEY=Yours Langfuse public key配置文件中
yaml
spring:
config:
import: optional:file:.env[.properties]
ai:
# LangFuse Agent数据观测observation:
langfuse:
#LangFuse 服务地址
endpoint: ${LANGFUSE_ADDR}
#私钥
secret-key: ${LANGFUSE_SECRET_KEY}
#公钥
public-key: ${LANGFUSE_PUBLIC_KEY}
#采样率(生产环境建议0.1-0.5)
sampling-rate: 1.0同理配置类中补充这几个属性
再初始化TelemetryTracer
java
@Service
@Slf4j
public class LangFuseUtils{
private TelemetryTracer langfuseTracer;
@Resource
private Properties properties;
//初始化 LangFuse Agent观测
public TelemetryTracer initLangfuseTracing() {
String endpoint = properties.getEndpoint();
String authHeader = getAuthHeader();
// 创建TelemetryTracer并配置Langfuse
TelemetryTracer langfuseTracer = TelemetryTracer.builder()
.endpoint(endpoint)
.addHeader("Authorization", authHeader)
.build();
return langfuseTracer;
}
// 构建LangFuse的认证头@param
private String getAuthHeader() {
String publicKey = properties.getPublicKey();
String secretKey = properties.getSecretKey();
String credentials = publicKey + ":" + secretKey;
String authHeader = "Basic" + Base64.getEncoder().encodeToString(credentials.getBytes());
return authHeader;
}
}在主管智能体中注入这个工具类,在进入首行注册到全局即可。调用Agent - 自动追踪到Langfuse。
java
@Component
public class ManagerAgent {
@Resource
private LangFuseUtils langFuseUtils;
public ReActAgent getManagerAgent(){
TelemetryTracer tracer = LangFuseUtils.initLangfuseTracing());
//注册到全局TracerRegistry
TracerRegistry.register(tracer);
...
}
}13-5 测试主管Agent接收Prompt及结构化输出
创建控制器的输入输出对象PromptSchema/ResponseSchema
java
@Getter
public class PromptSchema {
private String prompt;
}
public class ResponseSchema {
//LLM响应
public String response;
// 必须有无参构造函数
public ResponseSchema() {}
}控制器中的接口
java
@Resource
private ManagerAgent managerAgent;
//用户提交旅游规划的Prompt
@RequestMapping(value = "/trip",
produces = "application/json;charset=UTF-8", method = RequestMethod.POST)
public ResponseSchema tripPlan(@RequestBody PromptSchema input) {
ReActAgent manager = managerAgent.getManagerAgent();
ResponseSchema response = managerAgent.run(input.getPrompt());
return response;
}修改主管智能体的输入输出参数
java
/**
* 主管Agent
*/
@Component
public class ManagerAgent {
private final ReActAgent agent;
public ManagerAgent() {
//PlanNotebook
TripPlan plan = new TripPlan();
//计划对象
PlanNotebook planNotebook = plan.getPlan();
this.agent = AgentUtils.getReActAgentBuilder("ManagerAgent", "负责用户需求的解决方案制定,以及任务分发")
// .enablePlan()
.planNotebook(planNotebook)
.hook(new PlanHook(planNotebook)) // 拦截器
.toolkit(new ToolUtils().getToolkit(plan)) // 工具包
.structuredOutputReminder(StructuredOutputReminder.PROMPT) // 结构化输出
.build();
}
public ReActAgent getManagerAgent() {
return this.agent;
}
/**
* description: Agent 运行
*/
public ResponseSchema run(String prompt) {
Flux<Event> stream = AgentUtils.streamResponse(agent, prompt);
ResponseSchema result = stream
//阻塞直到结束
.blockLast()
.getMessage()
.getStructuredData(ResponseSchema.class); // 结构化输出
return result;
}
}13-6 旅游规划打印出Agent的深度思考
根据模型,有的模型支持思考,就需要开启思考模式
java
/**
* description: 创建ReAct Agent Builder
*/
public static ReActAgent.Builder getReActAgentBuilder(String name, String description) {
return ReActAgent.builder()
.name(name)
.description(description)
.model(DashScopeChatModel.builder()
//请求语言大模型的apikey
.apiKey("")
//所使用的语言大模型
.modelName("qwen3-max")
.stream(true)
// 开启思考模式
.enableThinking(true)
.build());
}13-7 测试路线制定专员规划最优驾车路线
用户Prompt构建工具类
java
/**
* 用户Prompt构建工具类
*/
@Slf4j
public class PromptUtils {
public Msg getPrompt(String prompt){
log.info("===构建的Prompt===:{}",prompt);
return Msg.builder()
.role(MsgRole.USER)
.content(TextBlock.builder().text(prompt).build())
.build();
}
}擅长制定最优驾车路线的Agent修改,添加参数
java
@Tool(description = "擅长制定最优驾车路线的Agent")
public String callRouteMakingAgent(@ToolParam(name = "prompt", description = "驾车的起点和终点") String prompt) throws NacosException {
log.info("工具方法:路线制定智能体...正在调用中");
A2aAgent agent = A2aAgent.builder()
.name("RouteMakingAgent")
.agentCardResolver(new NacosAgentCardResolver(NacosUtil.getNacosClient())) //创建 Nacos 的 AgentCardResolver
.build();
log.info("获取到的远程Agent描述:{}", agent.getDescription());
log.info("这个工具方法传入的参数:{}", prompt);
prompt = "调用百度地图MCP,制定最优驾车路线:" + prompt;
//组装Prompt
PromptUtils promptUtils = new PromptUtils();
Msg userMsg = promptUtils.getPrompt(prompt);
//远程Agent运行
Msg remoteAgentResponse = agent.call(userMsg).block();
assert remoteAgentResponse != null;
String content = remoteAgentResponse.getTextContent();
log.info("远程Agent返回:{}", content);
return content;
}