Harness Engineering 从理论到实战:基于 Spring AI Alibaba 的完整实现指南
一份涵盖七层架构、完整代码和深度解析的实践手册
Harness Engineering 实战:用 Spring AI Alibaba 构建可控的 AI 智能体:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/162488035
基于上述流程。
目录
- [什么是 Harness Engineering?](#什么是 Harness Engineering?)
- [Harness Engineering 的核心架构](#Harness Engineering 的核心架构)
- 环境准备与项目搭建
- 完整代码实现
- 七层架构深度解析
- 运行与测试
- 常见问题与解决方案
- 总结与进阶方向
一、什么是 Harness Engineering?
Harness Engineering(驾驭工程)是 2026 年 AI 工程化领域兴起的一种新范式。它的核心思想是:将重心从优化 AI 模型本身,转移到为 AI 智能体(Agent)构建一个可靠、可控、可维护的运行环境。
核心公式
Agent = Model + Harness
如果把大语言模型比作一匹力量强大但难以预测的野马,那么 Harness(马具) 就是套在它身上用来引导和控制的整套装备。
Harness Engineering 的核心组件
| 组件 | 类型 | 作用 | 类比 |
|---|---|---|---|
| Rules(规则) | 软约束 | 声明式规范,告诉 Agent "什么不能做" | 交通规则 |
| Skills(技能) | 半硬约束 | 步骤化操作手册,告诉 Agent "具体怎么做" | 操作说明书 |
| Gate(门禁) | 硬约束 | 强制校验输出,"不通过就拦截" | 安检闸机 |
| State(状态) | 上下文管理 | 记录 Agent 的进度和上下文 | 记事本 |
| Instructions(指令) | 行为指导 | 告诉 Agent 做什么、按什么顺序做 | 工作清单 |
| Verification(验证) | 质量保证 | 只有通过测试才算任务完成 | 质量检查员 |
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、Harness Engineering 的核心架构
七层架构总览
Harness Engineering 的七层架构是一个从内到外、层层递进的完整体系,其核心目标是将 AI 的不可控性通过工程化手段转化为确定性。
┌─────────────────────────────────────────────────────────────┐
│ 7. 评估与反馈层 (Evaluation & Feedback) │ ← 持续优化闭环
├─────────────────────────────────────────────────────────────┤
│ 6. 多 Agent 架构层 (Multi-Agent Architecture) │ ← 团队协作
├─────────────────────────────────────────────────────────────┤
│ 5. 约束与防护层 (Constraints & Guardrails) │ ← 安全护栏
├─────────────────────────────────────────────────────────────┤
│ 4. 上下文工程层 (Context Engineering) │ ← 记忆与知识
├─────────────────────────────────────────────────────────────┤
│ 3. 项目搭建层 (Project Setup) │ ← 标准化地基
├─────────────────────────────────────────────────────────────┤
│ 2. 工具编排层 (Tool Orchestration) │ ← 外部交互
├─────────────────────────────────────────────────────────────┤
│ 1. 执行主循环层 (Execution Loop) │ ← 大脑与调度
└─────────────────────────────────────────────────────────────┘
各层职责速览
| 层级 | 核心职责 | 简单理解 |
|---|---|---|
| 1. 执行主循环 | Agent 运行的"大脑"与调度核心 | 负责思考、决策、执行和反思的循环 |
| 2. 工具编排 | Agent 与外部世界交互的"手脚" | 安全、可控地调用外部 API 或工具 |
| 3. 项目搭建 | 标准化项目的"地基" | 统一项目结构、依赖和规范 |
| 4. 上下文工程 | Agent 的"记忆"与"知识库" | 管理、优化注入给模型的信息 |
| 5. 约束与防护 | Agent 的"安全护栏" | 硬性校验输入输出,防止失控 |
| 6. 多 Agent 架构 | 团队协作的"管理模式" | 让多个专业 Agent 协同工作 |
| 7. 评估与反馈 | 持续优化的"学习闭环" | 通过测试和反馈不断改进系统 |
三、环境准备与项目搭建
3.1 Windows 本地环境要求
| 软件 | 版本要求 | 下载地址 |
|---|---|---|
| JDK | 17 或更高 | Oracle JDK |
| Maven | 3.8+ | https://maven.apache.org/download.cgi |
| IntelliJ IDEA | 2023.3+ | https://www.jetbrains.com/idea/ |
| curl | 任意版本 | Windows 10/11 内置 |
| DashScope API Key | 阿里云百炼平台获取 | https://bailian.console.aliyun.com/ |
验证安装 :在命令行执行
java -version和mvn -version确认环境变量配置正确。
3.2 项目结构
spring-ai-harness-demo/
├── pom.xml
├── src/main/
│ ├── java/com/badao/ai/
│ │ ├── SpringAiHarnessDemoApplication.java # 启动类
│ │ ├── config/
│ │ │ ├── HarnessAgentConfig.java # 单Agent配置
│ │ │ └── MultiAgentConfig.java # 多Agent配置
│ │ ├── controller/
│ │ │ └── HarnessController.java # REST API
│ │ ├── service/
│ │ │ ├── HarnessAgentService.java # 核心业务服务
│ │ │ └── MultiAgentService.java # 多Agent协同服务
│ │ ├── model/
│ │ │ ├── ContactInfo.java # 联系人POJO
│ │ │ └── ProductReview.java # 商品评价POJO
│ │ ├── harness/
│ │ │ ├── rules/
│ │ │ │ └── ExtractionRules.md # 规则文件(软约束)
│ │ │ ├── skills/
│ │ │ │ └── ReviewAnalysisSkill.java # 评价分析技能
│ │ │ ├── gates/
│ │ │ │ └── OutputValidator.java # 输出门禁(硬约束)
│ │ │ └── tools/
│ │ │ ├── WeatherTool.java # 天气查询工具
│ │ │ └── CalculatorTool.java # 计算器工具
│ │ └── evaluation/
│ │ ├── QualityScorer.java # 质量评分器
│ │ └── FeedbackLogger.java # 反馈日志
│ └── resources/
│ ├── application.yml
│ └── harness/rules/
│ └── ExtractionRules.md
四、完整代码实现
4.1 Maven 项目配置(pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.badao.ai</groupId>
<artifactId>spring-ai-harness-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
<jackson.version>2.17.2</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Alibaba Agent Framework -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- DashScope 模型适配器 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意 :必须显式管理 Jackson 版本,避免版本冲突导致
NoSuchMethodError。
4.2 配置文件(application.yml)
yaml
server:
port: 885
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-max
logging:
level:
com.alibaba.cloud.ai: debug
com.badao.ai: debug
4.3 数据模型(POJO)
ContactInfo.java(简单 POJO)
java
package com.badao.ai.model;
public class ContactInfo {
private String name;
private String email;
private String phone;
public ContactInfo() {}
public ContactInfo(String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
@Override
public String toString() {
return "ContactInfo{name='" + name + "', email='" + email + "', phone='" + phone + "'}";
}
}
ProductReview.java(嵌套 POJO)
java
package com.badao.ai.model;
import java.util.Arrays;
public class ProductReview {
private int rating;
private String sentiment; // "positive", "neutral", "negative"
private String[] keyPoints;
private ReviewDetails details;
public ProductReview() {}
public int getRating() { return rating; }
public void setRating(int rating) { this.rating = rating; }
public String getSentiment() { return sentiment; }
public void setSentiment(String sentiment) { this.sentiment = sentiment; }
public String[] getKeyPoints() { return keyPoints; }
public void setKeyPoints(String[] keyPoints) { this.keyPoints = keyPoints; }
public ReviewDetails getDetails() { return details; }
public void setDetails(ReviewDetails details) { this.details = details; }
public static class ReviewDetails {
private String[] pros;
private String[] cons;
private String summary;
public ReviewDetails() {}
public String[] getPros() { return pros; }
public void setPros(String[] pros) { this.pros = pros; }
public String[] getCons() { return cons; }
public void setCons(String[] cons) { this.cons = cons; }
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
@Override
public String toString() {
return "ReviewDetails{pros=" + Arrays.toString(pros) +
", cons=" + Arrays.toString(cons) +
", summary='" + summary + "'}";
}
}
@Override
public String toString() {
return "ProductReview{rating=" + rating +
", sentiment='" + sentiment + "'" +
", keyPoints=" + Arrays.toString(keyPoints) +
", details=" + details + "}";
}
}
4.4 Harness 核心组件
① 规则层(软约束):ExtractionRules.md
markdown
# 联系人提取规则
## 必填字段
- name: 必须提取完整姓名(中文或英文)
- email: 必须提取完整邮箱地址
- phone: 必须提取完整电话号码(含区号)
## 格式要求
- 所有字段值必须去除首尾空格
- 电话号统一为字符串格式,保留原始格式
## 禁止行为
- 不要编造任何字段值
- 如果某个字段在原文中找不到,设置为空字符串 ""
② 技能层(半硬约束):ReviewAnalysisSkill.java
java
package com.badao.ai.harness.skills;
import com.badao.ai.model.ProductReview;
import org.springframework.stereotype.Component;
@Component
public class ReviewAnalysisSkill {
public String buildPrompt(String reviewText) {
return """
请分析以下商品评价,按标准格式输出 JSON。
【重要约束】
1. 如果评价内容不足以提取优缺点,请基于评价的字面含义,给出合理的推断或直接输出空数组。
2. keyPoints 必须是从评价中提取的具体产品维度(如"音质""价格""外观"),而不是对评价本身的描述。
3. 如果评价少于5个字,你可以推断一个合理的评分,并在 sentiment 中标注 "neutral"。
分析步骤:
- 评分:1-5 整数
- 情感:positive / neutral / negative
- 关键点:至少1个,从评价中抽取具体维度
- 优点/缺点:尽量提取,如果没有则保留空数组
- 总结:一句话概括整体评价
评价文本:
""" + reviewText;
}
public ProductReview postProcess(ProductReview review) {
if (review.getRating() < 1) review.setRating(1);
if (review.getRating() > 5) review.setRating(5);
if (review.getKeyPoints() == null || review.getKeyPoints().length == 0) {
review.setKeyPoints(new String[]{"无关键点"});
}
if (review.getDetails() == null) {
ProductReview.ReviewDetails details = new ProductReview.ReviewDetails();
details.setPros(new String[0]);
details.setCons(new String[0]);
details.setSummary("无总结");
review.setDetails(details);
}
return review;
}
}
③ 门禁层(硬约束):OutputValidator.java
java
package com.badao.ai.harness.gates;
import com.badao.ai.model.ContactInfo;
import com.badao.ai.model.ProductReview;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class OutputValidator {
public List<String> validateContact(ContactInfo contact) {
List<String> errors = new ArrayList<>();
if (contact == null) { errors.add("联系人信息为空"); return errors; }
if (contact.getName() == null || contact.getName().trim().isEmpty())
errors.add("姓名不能为空");
if (contact.getEmail() == null || !contact.getEmail().contains("@"))
errors.add("邮箱格式无效");
if (contact.getPhone() == null || contact.getPhone().trim().isEmpty())
errors.add("电话不能为空");
return errors;
}
public List<String> validateReview(ProductReview review) {
List<String> errors = new ArrayList<>();
if (review == null) { errors.add("评价信息为空"); return errors; }
if (review.getRating() < 1 || review.getRating() > 5)
errors.add("评分必须在 1-5 之间");
String sentiment = review.getSentiment();
if (sentiment == null || !(sentiment.equals("positive") ||
sentiment.equals("neutral") || sentiment.equals("negative")))
errors.add("情感倾向必须是 positive/neutral/negative 之一");
if (review.getKeyPoints() == null || review.getKeyPoints().length == 0)
errors.add("关键点不能为空");
return errors;
}
public boolean isValid(List<String> errors) {
return errors == null || errors.isEmpty();
}
}
④ 工具层:WeatherTool.java
java
package com.badao.ai.harness.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class WeatherTool {
@Tool(description = "根据城市名称查询当前天气")
public String getWeather(@ToolParam(description = "城市名称,如Beijing") String city) {
if ("Beijing".equalsIgnoreCase(city)) {
return "北京:晴,25°C,湿度40%";
} else if ("Shanghai".equalsIgnoreCase(city)) {
return "上海:多云,28°C,湿度65%";
} else {
return city + ":天气未知,请稍后再查";
}
}
}
⑤ 工具层:CalculatorTool.java
java
package com.badao.ai.harness.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class CalculatorTool {
@Tool(description = "执行基本的四则运算")
public double calculate(
@ToolParam(description = "第一个操作数") double a,
@ToolParam(description = "运算符,支持 + - * /") String operator,
@ToolParam(description = "第二个操作数") double b) {
return switch (operator) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> a / b;
default -> throw new IllegalArgumentException("不支持的运算符: " + operator);
};
}
}
4.5 Agent 配置类
HarnessAgentConfig.java(单 Agent 配置)
java
package com.badao.ai.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.badao.ai.model.ContactInfo;
import com.badao.ai.model.ProductReview;
import com.badao.ai.harness.skills.ReviewAnalysisSkill;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Configuration
public class HarnessAgentConfig {
private final ReviewAnalysisSkill reviewAnalysisSkill;
public HarnessAgentConfig(ReviewAnalysisSkill reviewAnalysisSkill) {
this.reviewAnalysisSkill = reviewAnalysisSkill;
}
@Bean
public ReactAgent contactAgent(ChatModel chatModel) throws IOException {
String rules = loadRules("harness/rules/ExtractionRules.md");
String systemPrompt = "你是一个联系人信息提取专家。请严格遵循以下规则:\n" + rules;
return ReactAgent.builder()
.name("contact_extractor")
.model(chatModel)
.systemPrompt(systemPrompt)
.outputType(ContactInfo.class)
.saver(new MemorySaver())
.build();
}
@Bean
public ReactAgent reviewAgent(ChatModel chatModel) {
BeanOutputConverter<ProductReview> converter =
new BeanOutputConverter<>(ProductReview.class);
String schema = converter.getFormat();
return ReactAgent.builder()
.name("review_analyzer")
.model(chatModel)
.systemPrompt("你是一个商品评价分析专家,必须按以下 JSON Schema 格式输出:\n" + schema)
.outputSchema(schema)
.saver(new MemorySaver())
.build();
}
private String loadRules(String path) throws IOException {
ClassPathResource resource = new ClassPathResource(path);
return new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
}
}
MultiAgentConfig.java(多 Agent 协同配置)
java
package com.badao.ai.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.badao.ai.harness.tools.CalculatorTool;
import com.badao.ai.harness.tools.WeatherTool;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MultiAgentConfig {
@Bean
public ReactAgent plannerAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("planner")
.model(chatModel)
.systemPrompt("""
你是一个任务规划专家。用户会给出一个复杂需求,你需要将其拆解为1~3个明确的子任务,
并以JSON数组格式输出,每个子任务包含:description(描述)和 assigned_to(执行角色,只能是"executor")。
示例输出:[{"description":"查询北京天气","assigned_to":"executor"}]
""")
.saver(new MemorySaver())
.build();
}
@Bean
public ReactAgent executorAgent(ChatModel chatModel,
WeatherTool weatherTool,
CalculatorTool calculatorTool) {
return ReactAgent.builder()
.name("executor")
.model(chatModel)
.systemPrompt("你是一个执行专家,负责具体执行用户分配的任务。你可以使用工具完成工作。")
.methodTools(weatherTool, calculatorTool) // 自动扫描 @Tool 注解
.saver(new MemorySaver())
.build();
}
}
4.6 Service 层
HarnessAgentService.java(核心业务服务)
java
package com.badao.ai.service;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.badao.ai.model.ContactInfo;
import com.badao.ai.model.ProductReview;
import com.badao.ai.harness.gates.OutputValidator;
import com.badao.ai.harness.skills.ReviewAnalysisSkill;
import com.badao.ai.evaluation.QualityScorer;
import com.badao.ai.evaluation.FeedbackLogger;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HarnessAgentService {
private final ReactAgent contactAgent;
private final ReactAgent reviewAgent;
private final ReviewAnalysisSkill reviewAnalysisSkill;
private final OutputValidator outputValidator;
private final ObjectMapper objectMapper;
private final QualityScorer qualityScorer;
private final FeedbackLogger feedbackLogger;
public HarnessAgentService(ReactAgent contactAgent, ReactAgent reviewAgent,
ReviewAnalysisSkill reviewAnalysisSkill,
OutputValidator outputValidator,
ObjectMapper objectMapper,
QualityScorer qualityScorer,
FeedbackLogger feedbackLogger) {
this.contactAgent = contactAgent;
this.reviewAgent = reviewAgent;
this.reviewAnalysisSkill = reviewAnalysisSkill;
this.outputValidator = outputValidator;
this.objectMapper = objectMapper;
this.qualityScorer = qualityScorer;
this.feedbackLogger = feedbackLogger;
}
// ========== 联系人提取 ==========
public ContactInfo extractContact(String text, String sessionId) {
RunnableConfig config = RunnableConfig.builder().threadId(sessionId).build();
AssistantMessage response;
try {
response = contactAgent.call(text, config);
} catch (GraphRunnerException e) {
throw new RuntimeException("Agent 执行失败: " + e.getMessage(), e);
}
String json = response.getText();
ContactInfo contact;
try {
contact = objectMapper.readValue(json, ContactInfo.class);
} catch (Exception e) {
throw new RuntimeException("解析结构化输出失败,原始 JSON: " + json, e);
}
// Gate 门禁校验
List<String> errors = outputValidator.validateContact(contact);
if (!outputValidator.isValid(errors)) {
throw new RuntimeException("输出校验失败: " + String.join("; ", errors));
}
return contact;
}
public String extractContactRaw(String text, String sessionId) {
RunnableConfig config = RunnableConfig.builder().threadId(sessionId).build();
try {
AssistantMessage response = contactAgent.call(text, config);
return response.getText();
} catch (GraphRunnerException e) {
throw new RuntimeException("Agent 执行失败: " + e.getMessage(), e);
}
}
// ========== 商品评价分析 ==========
public ProductReview analyzeReview(String reviewText, String sessionId) {
RunnableConfig config = RunnableConfig.builder().threadId(sessionId).build();
String prompt = reviewAnalysisSkill.buildPrompt(reviewText);
AssistantMessage response;
try {
response = reviewAgent.call(prompt, config);
} catch (GraphRunnerException e) {
throw new RuntimeException("Agent 执行失败: " + e.getMessage(), e);
}
String json = response.getText();
ProductReview review;
try {
review = objectMapper.readValue(json, ProductReview.class);
} catch (Exception e) {
throw new RuntimeException("解析结构化输出失败,原始 JSON: " + json, e);
}
// Skill 后处理
review = reviewAnalysisSkill.postProcess(review);
// Gate 门禁校验
List<String> errors = outputValidator.validateReview(review);
if (!outputValidator.isValid(errors)) {
throw new RuntimeException("输出校验失败: " + String.join("; ", errors));
}
return review;
}
// ========== 带评估的评价分析 ==========
public ProductReview analyzeReviewWithEval(String reviewText, String sessionId) {
ProductReview review = analyzeReview(reviewText, sessionId);
// 质量评估
int score = qualityScorer.scoreReview(review);
if (score < 3) {
feedbackLogger.logFailedCase(reviewText, review, "质量评分过低: " + score);
feedbackLogger.triggerHumanReview(reviewText, review);
throw new RuntimeException("质量门禁未通过(得分" + score + "/4),已转人工复核");
}
return review;
}
}
MultiAgentService.java(多 Agent 协同服务)
java
package com.badao.ai.service;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class MultiAgentService {
private final ReactAgent plannerAgent;
private final ReactAgent executorAgent;
private final ObjectMapper objectMapper;
public MultiAgentService(ReactAgent plannerAgent, ReactAgent executorAgent,
ObjectMapper objectMapper) {
this.plannerAgent = plannerAgent;
this.executorAgent = executorAgent;
this.objectMapper = objectMapper;
}
public String executeComplexTask(String userRequest, String sessionId) {
RunnableConfig config = RunnableConfig.builder().threadId(sessionId).build();
// 1. 规划阶段
AssistantMessage planResponse;
try {
planResponse = plannerAgent.call("请拆解以下任务:" + userRequest, config);
} catch (GraphRunnerException e) {
throw new RuntimeException("规划失败: " + e.getMessage(), e);
}
String planJson = planResponse.getText();
// 解析规划结果
List<JsonNode> tasks;
try {
tasks = objectMapper.readValue(planJson, objectMapper.getTypeFactory()
.constructCollectionType(List.class, JsonNode.class));
} catch (Exception e) {
throw new RuntimeException("规划结果解析失败: " + planJson, e);
}
// 2. 执行阶段
List<String> results = new ArrayList<>();
for (JsonNode task : tasks) {
String description = task.get("description").asText();
String assignedTo = task.get("assigned_to").asText();
if (!"executor".equals(assignedTo)) {
results.add("跳过不支持的角色: " + assignedTo);
continue;
}
AssistantMessage execResponse;
try {
execResponse = executorAgent.call(description, config);
} catch (GraphRunnerException e) {
results.add("执行子任务失败: " + description + ",错误: " + e.getMessage());
continue;
}
results.add(execResponse.getText());
}
return String.join("\n", results);
}
}
4.7 评估与反馈组件
QualityScorer.java
java
package com.badao.ai.evaluation;
import com.badao.ai.model.ProductReview;
import org.springframework.stereotype.Component;
@Component
public class QualityScorer {
public int scoreReview(ProductReview review) {
int score = 0;
if (review.getRating() >= 1 && review.getRating() <= 5) score += 1;
if (review.getSentiment() != null) score += 1;
if (review.getKeyPoints() != null && review.getKeyPoints().length >= 2) score += 1;
if (review.getDetails() != null && review.getDetails().getSummary() != null) score += 1;
return score; // 满分4分
}
}
FeedbackLogger.java
java
package com.badao.ai.evaluation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class FeedbackLogger {
private static final Logger logger = LoggerFactory.getLogger(FeedbackLogger.class);
public void logFailedCase(Object input, Object output, String reason) {
logger.warn("失败案例 - 输入: {}, 输出: {}, 原因: {}", input, output, reason);
}
public void triggerHumanReview(Object input, Object output) {
logger.info("人工复核触发 - 输入: {}, 输出: {}", input, output);
}
}
4.8 Controller 层
java
package com.badao.ai.controller;
import com.badao.ai.model.ContactInfo;
import com.badao.ai.model.ProductReview;
import com.badao.ai.service.HarnessAgentService;
import com.badao.ai.service.MultiAgentService;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/harness")
public class HarnessController {
private final HarnessAgentService harnessAgentService;
private final MultiAgentService multiAgentService;
public HarnessController(HarnessAgentService harnessAgentService,
MultiAgentService multiAgentService) {
this.harnessAgentService = harnessAgentService;
this.multiAgentService = multiAgentService;
}
// ===== 联系人提取 =====
@PostMapping("/contact")
public Map<String, Object> extractContact(
@RequestParam String text,
@RequestParam(required = false, defaultValue = "default") String sessionId) {
ContactInfo contact = harnessAgentService.extractContact(text, sessionId);
return Map.of("success", true, "data", contact, "sessionId", sessionId);
}
@PostMapping("/contact/raw")
public Map<String, Object> extractContactRaw(
@RequestParam String text,
@RequestParam(required = false, defaultValue = "default") String sessionId) {
String json = harnessAgentService.extractContactRaw(text, sessionId);
return Map.of("success", true, "json", json, "sessionId", sessionId);
}
// ===== 商品评价分析 =====
@PostMapping("/review")
public Map<String, Object> analyzeReview(
@RequestParam String reviewText,
@RequestParam(required = false, defaultValue = "default") String sessionId) {
ProductReview review = harnessAgentService.analyzeReview(reviewText, sessionId);
return Map.of("success", true, "data", review, "sessionId", sessionId);
}
@PostMapping("/review/eval")
public Map<String, Object> analyzeReviewWithEval(
@RequestParam String reviewText,
@RequestParam(required = false, defaultValue = "default") String sessionId) {
ProductReview review = harnessAgentService.analyzeReviewWithEval(reviewText, sessionId);
return Map.of("success", true, "data", review, "sessionId", sessionId);
}
// ===== 多 Agent 协同 =====
@PostMapping("/complex")
public Map<String, Object> complexTask(
@RequestParam String request,
@RequestParam(required = false, defaultValue = "default") String sessionId) {
String result = multiAgentService.executeComplexTask(request, sessionId);
return Map.of("success", true, "result", result, "sessionId", sessionId);
}
}
4.9 启动类
java
package com.badao.ai;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiHarnessDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiHarnessDemoApplication.class, args);
}
}
五、七层架构深度解析
5.1 执行主循环层
知识点:这是 Agent 运行的"大脑",负责"规划-执行-反思"的 ReAct 循环。
代码体现:
java
// ReactAgent 内置了 ReAct 循环
ReactAgent.builder()
.model(chatModel) // 绑定模型
.saver(new MemorySaver()) // 状态管理,支持错误恢复
.build();
最佳实践:
- 避免无规划的扁平串行循环,必须加入前置规划和后置反思
- 所有 LLM 调用应内置分级重试与降级策略
- 使用
MemorySaver实现状态持久化
5.2 工具编排层
知识点:Agent 与外部世界交互的"手脚",需要建立边界管控、权限校验和结果处理体系。
代码体现:
java
// 使用 @Tool 注解定义工具
@Tool(description = "根据城市名称查询当前天气")
public String getWeather(@ToolParam(description = "城市名称") String city) { ... }
// 在 Agent 中注册工具
.methodTools(weatherTool, calculatorTool)
最佳实践:
- 为每个工具定义清晰的 description,帮助模型决策
- 工具结果应进行结构化处理和异常捕获
- 建立工具黑白名单机制,防止危险操作
5.3 项目搭建层
知识点:标准化项目的"地基",统一项目结构、依赖和规范。
代码体现:
xml
<!-- pom.xml 统一版本管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
</properties>
yaml
# application.yml 环境配置
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY} # 环境变量注入
最佳实践:
- 使用父 POM 统一管理依赖版本
- 敏感信息通过环境变量注入,实现环境隔离
- 建立模板仓库,包含基础依赖、安全策略和监控配置
5.4 上下文工程层
知识点:Agent 的"记忆"与"知识库",管理、优化注入给模型的信息。
代码体现:
java
// 1. 加载规则文件作为静态上下文
String rules = loadRules("harness/rules/ExtractionRules.md");
String systemPrompt = "你是一个联系人信息提取专家。\n" + rules;
// 2. Skill 构建结构化提示词
public String buildPrompt(String reviewText) {
return """
请分析以下商品评价,按标准格式输出 JSON。
分析步骤:
1. 提取评分(1-5星整数)
2. 判断情感倾向:positive / neutral / negative
...
""" + reviewText;
}
最佳实践:
- 使用分层缓存:基础层(项目配置)+ 会话层(当前任务)+ 临时层(用户输入)
- 构建知识图谱,让 Agent 理解项目全貌
- 对上下文进行版本控制,便于追溯
5.5 约束与防护层
知识点:Agent 的"安全护栏",硬性校验输入输出,防止失控。
代码体现:
java
// Gate 门禁校验
public List<String> validateContact(ContactInfo contact) {
List<String> errors = new ArrayList<>();
if (contact.getName() == null || contact.getName().trim().isEmpty())
errors.add("姓名不能为空");
if (contact.getEmail() == null || !contact.getEmail().contains("@"))
errors.add("邮箱格式无效");
return errors;
}
// 在 Service 中执行熔断
List<String> errors = outputValidator.validateContact(contact);
if (!outputValidator.isValid(errors)) {
throw new RuntimeException("输出校验失败: " + String.join("; ", errors));
}
最佳实践:
- 输入验证:正则表达式过滤非法请求
- 输出过滤:检查敏感信息泄露或危险操作
- 熔断机制:异常行为自动触发回滚或降级
5.6 多 Agent 架构层
知识点:团队协作的"管理模式",让多个专业 Agent 协同工作。
代码体现:
java
// 主从模式
@Bean
public ReactAgent plannerAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("planner")
.systemPrompt("你是一个任务规划专家...")
.build();
}
@Bean
public ReactAgent executorAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("executor")
.systemPrompt("你是一个执行专家...")
.methodTools(weatherTool, calculatorTool)
.build();
}
最佳实践:
- 主从模式:主 Agent 分配任务,子 Agent 执行
- 标准化通信协议,定义清晰的任务描述格式
- 建立冲突解决机制,处理子 Agent 结果不一致的情况
5.7 评估与反馈层
知识点:持续优化的"学习闭环",通过测试和反馈不断改进系统。
代码体现:
java
// 质量评分
public int scoreReview(ProductReview review) {
int score = 0;
if (review.getRating() >= 1 && review.getRating() <= 5) score += 1;
if (review.getSentiment() != null) score += 1;
if (review.getKeyPoints() != null && review.getKeyPoints().length >= 2) score += 1;
if (review.getDetails() != null && review.getDetails().getSummary() != null) score += 1;
return score;
}
// 质量门禁
if (score < 3) {
feedbackLogger.logFailedCase(input, output, "质量评分过低");
feedbackLogger.triggerHumanReview(input, output);
throw new RuntimeException("质量门禁未通过");
}
最佳实践:
- 建立多维度的评估指标(准确性、完整性、安全性)
- 设置质量门禁阈值,未达标触发人工复核
- 收集用户修改行为,作为后续微调的数据
六、运行与测试
6.1 启动应用
在项目根目录执行:
cmd
mvn clean package
java -jar target/spring-ai-harness-demo-1.0.0.jar
或在 IDEA 中直接运行 SpringAiHarnessDemoApplication。
6.2 测试接口
① 联系人提取
cmd
curl -X POST "http://localhost:885/api/harness/contact?text=从以下信息提取联系方式:王五,wangwu@outlook.com,+86 139-9999-8888&sessionId=test01"
预期返回:
json
{
"success": true,
"data": {
"name": "王五",
"email": "wangwu@outlook.com",
"phone": "+86 139-9999-8888"
},
"sessionId": "test01"
}
② 商品评价分析
cmd
curl -X POST "http://localhost:885/api/harness/review?reviewText=这款耳机音质不错,降噪效果好,但佩戴舒适度一般,价格略高。&sessionId=test02"
预期返回:
json
{
"success": true,
"data": {
"rating": 4,
"sentiment": "positive",
"keyPoints": ["音质不错", "降噪效果好", "佩戴舒适度一般", "价格略高"],
"details": {
"pros": ["音质不错", "降噪效果好"],
"cons": ["佩戴舒适度一般", "价格略高"],
"summary": "整体满意,舒适度和价格有改进空间"
}
},
"sessionId": "test02"
}
③ 带质量评估的评价分析
cmd
curl -X POST "http://localhost:885/api/harness/review/eval?reviewText=还可以吧&sessionId=eval01"
预期返回(通过质量门禁时):
json
{
"success": true,
"data": {
"rating": 3,
"sentiment": "neutral",
"keyPoints": ["态度中立", "缺乏细节"],
"details": {
"pros": [],
"cons": [],
"summary": "评价者仅表示"还可以吧",态度中立,没有明确指出优劣。"
}
},
"sessionId": "eval01"
}
④ 多 Agent 协同
cmd
curl -X POST "http://localhost:885/api/harness/complex?request=查询北京天气并计算25+17&sessionId=multi01"
预期返回:
json
{
"success": true,
"result": "北京:晴,25°C,湿度40%\n42.0",
"sessionId": "multi01"
}
七、常见问题与解决方案
问题 1:Cannot resolve method 'tools(List<E>)'
原因 :ReactAgent.Builder 的 tools 方法不直接接受 Spring Bean 实例。
解决方案 :使用 methodTools 方法,它会自动扫描 @Tool 注解。
java
// ❌ 错误
.tools(weatherToolCallback, calculatorToolCallback)
// ✅ 正确
.methodTools(weatherTool, calculatorTool)
问题 2:Cannot resolve method 'builder(String, WeatherTool)'
原因 :FunctionToolCallback.builder("getWeather", weatherTool) 的 API 在当前版本不支持。
解决方案 :改用 methodTools,或使用 lambda 方式构建。
问题 3:解析结构化输出失败
原因:模型返回的 JSON 格式与 POJO 不完全匹配。
解决方案:
- 在
systemPrompt中明确要求 JSON 格式 - 使用
outputType或outputSchema强制格式 - 在
postProcess中增加兜底处理
问题 4:质量门禁总是失败
原因:评估阈值设置过高或评分逻辑过严。
解决方案:
- 根据实际测试结果调整阈值
- 增加评分维度,使评分更精确
- 区分"必须通过"和"建议通过"的指标
八、总结与进阶方向
8.1 总结
通过本示例,我们完成了:
| 目标 | 完成情况 |
|---|---|
| 理解 Harness Engineering 概念 | ✅ 核心组件 + 七层架构 |
| Windows 本地环境搭建 | ✅ JDK 17 + Maven + DashScope |
| 完整代码实现 | ✅ 6 个模块,20+ 个类 |
| 七层架构落地 | ✅ 每层都有对应的代码实现 |
| 运行与测试 | ✅ 4 个接口均可正常调用 |
8.2 Harness Engineering 在本示例中的映射
| Harness 组件 | 实现位置 | 作用 |
|---|---|---|
| Rules(规则) | ExtractionRules.md |
软约束,告诉 Agent 必须提取哪些字段 |
| Skills(技能) | ReviewAnalysisSkill.java |
半硬约束,封装标准化提示词和后处理 |
| Gate(门禁) | OutputValidator.java |
硬约束,强制校验输出结构 |
| State(状态) | MemorySaver + threadId |
管理会话上下文 |
| Instructions(指令) | systemPrompt + outputType |
明确输出格式和行为边界 |
| Verification(验证) | 异常处理 + Gate 校验 | 只有通过才算任务完成 |
| Tools(工具) | WeatherTool + CalculatorTool |
扩展 Agent 能力边界 |
| Evaluation(评估) | QualityScorer |
量化输出质量 |
| Feedback(反馈) | FeedbackLogger |
收集失败案例,触发人工复核 |
8.3 进阶方向
- 动态规则加载:将 Rules 从本地文件迁移到配置中心,实现热更新
- 更复杂的工具集成:接入数据库查询、Web 搜索、文件读写等
- 多 Agent 深度协作:实现流水线模式或对等协商模式
- 模型微调:将失败案例加入训练集,提升模型表现
- 可观测性:集成 Prometheus + Grafana 监控 Agent 运行状态
- 大规模部署:使用 Kubernetes 部署多个 Harness 实例