Harness Engineering:从入门到精通
Harness Engineering 驾驭工程,是一种方法论、工作/思维模式,让人类通过约束、linter等方式,在规定的自由空间内,让AI发挥自己的能力,做到更高质量的输出成果。
再说简单点,Harness是一种思维方式,让人知道在哪里进行一些规则的设定,让AI在偏离业务需求时,可以拽回来,重新回到预定的轨道前进。
就像互联网软件开发一样,程序员有自己的代码规范、git提交规范、业务逻辑规范、测试用例等,这样可以更高效的产出结果。
本章是讲Harness的入门到精通,咱们就从Harness的诞生开始讲起,这样渐进式的讲解,能让大家更明白什么是Harness思维。
AI时代的软件开发 —— 从SDD 到 TDD
SDD:规格驱动开发
SDD(Specification-Driven Development) :规格驱动开发,写规格文档(Spec),让规格 = 一等公民,代码只是规格的可执行表达。
在 AI 编程时代,这份 Spec 既是给人看的需求文档,也是给 AI Agent 看的任务说明书。
| 传统开发 | SDD开发 |
|---|---|
| 口头说需求/PRD文档 --> 直接写代码 --> 事后补文档 | 写SPEC文档 --> 从SPEC提取测试代码 --> 按测试代码写开发代码 |
| 问题:需求在脑子里,AI看不到/无法理解/理解会有偏差 | 优势:规格是可版本化,人与AI都看得懂,可被AI读取 |
SPEC文档格式:
# Stock Deep Research with AkShare -- 规格文档 (Specification)
> 本文档是项目的"一等公民"。所有实现代码都是本规格的可执行表达。
> 修改本文档时必须同步更新对应的测试和实现。
## 功能目标
输入一个股票代码(如 "600519"),自动从多个维度联网搜索信息,
通过 Qwen 大模型进行分析,生成结构化深度研究报告。
使用 AkShare 库获取金融数据,增强数据采集能力。
## 系统架构
```<br><br>用户输入(股票代码)<br><br> |<br><br> v<br><br> QwenClient -- API 客户端层:封装 Qwen 联网搜索能力<br><br> |<br><br> v<br><br> Collector -- 数据采集层:按维度并行采集<br><br> / \<br><br> / \<br><br>AkShare 网络搜索<br><br>(金融数据) (新闻、分析)<br><br> |<br><br> v<br><br> Analyzer -- 分析层:汇总评分 + 风险识别<br><br> |<br><br> v<br><br> Reporter -- 报告层:生成结构化报告 + 校验<br><br> |<br><br> v<br><br> 结构化 JSON 报告<br><br>```
## 数据采集维度
| 维度 | 英文标识 | 采集内容 | AkShare 数据源 |
|------|---------|---------|---------------|
| 基本面 | fundamental | 公司简介、主营业务、近期财报摘要 | stock_zh_a_spot, stock_financial_analysis_indicator |
| 市场面 | market | 近期股价走势、成交量变化、技术指标信号 | stock_zh_a_daily, stock_zh_a_index_daily, stock_zh_a_tech_indicator |
| 消息面 | news | 近期重大新闻、公告、行业动态 | stock_news_em, stock_info_em |
| 分析师观点 | analyst | 机构评级、目标价、投资建议汇总 | stock_analyst_grade_em |
## 输出格式
报告必须严格遵循以下 JSON 结构:
```json<br><br>{<br><br> "stock_code": "600519",<br><br> "stock_name": "贵州茅台",<br><br> "report_date": "2026-04-20",<br><br> "dimensions": {<br><br> "fundamental": {<br><br> "summary": "不少于100字的基本面分析...",<br><br> "confidence": 0.85,<br><br> "akshare_data": true<br><br> },<br><br> "market": {<br><br> "summary": "不少于100字的市场面分析...",<br><br> "confidence": 0.78,<br><br> "akshare_data": true<br><br> },<br><br> "news": {<br><br> "summary": "不少于100字的消息面分析...",<br><br> "confidence": 0.72,<br><br> "akshare_data": true<br><br> },<br><br> "analyst": {<br><br> "summary": "不少于100字的分析师观点...",<br><br> "confidence": 0.80,<br><br> "akshare_data": true<br><br> }<br><br> },<br><br> "overall_rating": "buy",<br><br> "risk_factors": ["风险因素1", "风险因素2"],<br><br> "sources": ["https://...", "https://...", "https://..."],<br><br> "akshare_version": "1.10.60"<br><br>}<br><br>```
## 约束条件(Constraints)
以下约束条件将直接转化为测试用例和 linter 规则:
### C1: 维度完整性
- 报告必须包含全部 4 个维度:fundamental, market, news, analyst
- 缺少任何一个维度视为报告不合格
### C2: 摘要最小长度
- 每个维度的 summary 字段不少于 100 个字符
- 空摘要或过短摘要说明数据采集不充分
### C3: 置信度范围
- 每个维度的 confidence 必须在 [0.0, 1.0] 闭区间内
- 超出范围说明评分逻辑有误
### C4: 评级有效值
- overall_rating 只能取 "buy"、"hold"、"sell" 三个值之一
- 其他值(如 "strong_buy"、"outperform")不被接受
### C5: 来源数量
- sources 列表必须包含至少 3 个来源 URL
- 来源过少说明研究深度不够
### C6: 风险因素
- risk_factors 列表不能为空
- 任何投资都有风险,空列表说明分析不完整
### C7: 必填字段
- stock_code, stock_name, report_date 为必填字段
- 缺少任何一个视为报告结构不完整
### C8: AkShare 数据使用
- 每个维度必须标记是否使用了 AkShare 数据 (akshare_data 字段)
- akshare_version 字段必须包含当前使用的 AkShare 版本
## API 依赖
- 模型:通过 DashScope 调用 Qwen 系列模型
- 联网搜索:enable_search=True, search_strategy="agent"
- 认证:通过 DASHSCOPE_API_KEY 环境变量
- 金融数据:AkShare 库 (版本 >= 1.10.60)
Spec 中的每一条约束(C1~C7),都可以直接翻译为一个测试用例。每个约束也是SDD与TDD的天然连接点,即规格文档写完,测试用例也就有了。
TDD:测试驱动开发
TDD(Test-Driven Development): 测试驱动开发,V模型,有测试单元 对应 开发单元。没有先失败的测试,就没有生产代码。先有测试用例,第一次是无代码状态,肯定都是失败,然后再根据测试用例编写程序,再进行验证。
核心:红-绿-重构循环
| 阶段 | 做什么 | 验证 |
|---|---|---|
| RED 红灯 | 写一个最小的失败测试 | 运行测试,看到 FAILED |
| GREEN 绿灯 | 写最少的代码让测试通过 | 运行测试,看到 PASSED |
| REFACTOR 重构 | 在测试保持绿灯的前提下优化代码 | 运行测试,仍然 PASSED |
没有先失败的测试,就没有生产代码:
TDD 的红-绿-重构(Red-Green-Refactor)循环中的硬性顺序:
- Red(红): 先写一个测试,运行它,确保它失败。这一步证明:
测试本身是有效的(不是 永远通过的假测试)
当前确实没有实现这个功能
你清楚知道"完成"的标准是什么(测试代码就是需求规格) - Green(绿): 编写刚好够让测试通过的最少代码,不做多余设计;
- Refactor(重构): 在不破坏测试的前提下优化代码结构。
简而言之,在看到一个失败的测试之前,你不能写任何生产代码。这个失败是扳机,确认你正在解决一个真实存在的问题,而不是臆想。
如何理解先写了代码再补测试?删掉代码,重新开始?
为了防止自欺欺人的心理陷阱:
- 如果先写代码: 你潜意识里会写 刚好能让这段代码通过的测试 => 测试变成了验证代码正确性的工具,而不是定义需求的规格。
- 测试失去防护价值: 当需求变更或重构时,这些后补的测试往往过于宽松,或者测试的是实现细节而非行为,无法保护代码。
删掉重来是为了强制你回到正确的思维轨道:让需求(测试)驱动设计,而不是让设计(实现)扭曲需求。
TDD的错误理解:
- 太简单不需要测试 => 简单代码也会坏。写测试只要 30 秒。
- 我先写代码,之后补测试 => 后补的测试立刻通过,证明不了任何东西。
- TDD 太教条了,我更务实 => TDD 就是务实:先找 bug 比事后 debug 快 10 倍。
- 已经手动测过了 => 手动测试无法重复、无法回归、无法证明覆盖面。
- 删掉 X 小时的代码太浪费 => 沉没成本谬误。留着你无法信任的代码才是浪费。
案例:AI投研工具
我这里以AI投研工具案例,来讲解下SDD --> TDD 怎么使用。
在写案例之前,这里正好聊到古法编程(目前流行将近20年的代码编程) ,现在AI进化越来越快,很多人在使用各类AI coding 工具(Trae、Cursor、Claude code、lingma等) 进行编程,也叫Vibe coding(氛围编程),古法编程变得越来越少。
从以前TDD指导开发,到现在的SDD指导开发,或者是2者的结合:
- 方法一:SDD => vibe coding => TDD
- 方法二:SDD => TDD => vibe coding
其实SDD、TDD 都是一种思维方式,更好的让AI去高质量、高效率输出我们想要的结果。
| 传统编程模式 | AI编程模式 |
|---|---|
| 先写代码,关注怎么写逻辑判断,有出现2个问题: 1、容易过度设计(考虑性能、通用性); 2、测试变成 验证我对了 |
先写测试,关注用户输入什么,我应该返回什么。 只关注业务需求,测试变成 定义什么叫对 等高效的AI约束,让AI知道怎么做是错的,要回到正轨上来。 |
我们聊回案例:需求是我们要做一个股票深度研究工具,输入一个股票代码(如 600519贵州茅台),系统自动通过 akshare 获取真实的财务数据、新闻、机构评级等信息,再结合 Qwen 大模型联网搜索分析,最终生成一份结构化的多维度研究报告。
step1: 撰写一个SPEC,通过Trae Idea来编写:
我想撰写一个软件SPEC,需求是:
输入一个股票代码(如“600519”),自动从多个维度联网搜索信息,
通过Qwen大模型进行分析,生成结构化深度研究报告。
使用到akshare
方便后续进行测试和代码生成,给我SPEC即可,写入到spec文件夹中。
# Stock Deep Research with AkShare -- 规格文档 (Specification)
> 本文档是项目的"一等公民"。所有实现代码都是本规格的可执行表达。
> 修改本文档时必须同步更新对应的测试和实现。
## 功能目标
输入一个股票代码(如 "600519"),自动从多个维度联网搜索信息,
通过 Qwen 大模型进行分析,生成结构化深度研究报告。
使用 AkShare 库获取金融数据,增强数据采集能力。
## 系统架构
```<br><br>用户输入(股票代码)<br><br> |<br><br> v<br><br> QwenClient -- API 客户端层:封装 Qwen 联网搜索能力<br><br> |<br><br> v<br><br> Collector -- 数据采集层:按维度并行采集<br><br> / \<br><br> / \<br><br>AkShare 网络搜索<br><br>(金融数据) (新闻、分析)<br><br> |<br><br> v<br><br> Analyzer -- 分析层:汇总评分 + 风险识别<br><br> |<br><br> v<br><br> Reporter -- 报告层:生成结构化报告 + 校验<br><br> |<br><br> v<br><br> 结构化 JSON 报告<br><br>```
## 数据采集维度
| 维度 | 英文标识 | 采集内容 | AkShare 数据源 |
|------|---------|---------|---------------|
| 基本面 | fundamental | 公司简介、主营业务、近期财报摘要 | stock_zh_a_spot, stock_financial_analysis_indicator |
| 市场面 | market | 近期股价走势、成交量变化、技术指标信号 | stock_zh_a_daily, stock_zh_a_index_daily, stock_zh_a_tech_indicator |
| 消息面 | news | 近期重大新闻、公告、行业动态 | stock_news_em, stock_info_em |
| 分析师观点 | analyst | 机构评级、目标价、投资建议汇总 | stock_analyst_grade_em |
## 输出格式
报告必须严格遵循以下 JSON 结构:
```json<br><br>{<br><br> "stock_code": "600519",<br><br> "stock_name": "贵州茅台",<br><br> "report_date": "2026-04-20",<br><br> "dimensions": {<br><br> "fundamental": {<br><br> "summary": "不少于100字的基本面分析...",<br><br> "confidence": 0.85,<br><br> "akshare_data": true<br><br> },<br><br> "market": {<br><br> "summary": "不少于100字的市场面分析...",<br><br> "confidence": 0.78,<br><br> "akshare_data": true<br><br> },<br><br> "news": {<br><br> "summary": "不少于100字的消息面分析...",<br><br> "confidence": 0.72,<br><br> "akshare_data": true<br><br> },<br><br> "analyst": {<br><br> "summary": "不少于100字的分析师观点...",<br><br> "confidence": 0.80,<br><br> "akshare_data": true<br><br> }<br><br> },<br><br> "overall_rating": "buy",<br><br> "risk_factors": ["风险因素1", "风险因素2"],<br><br> "sources": ["https://...", "https://...", "https://..."],<br><br> "akshare_version": "1.10.60"<br><br>}<br><br>```
## 约束条件(Constraints)
以下约束条件将直接转化为测试用例和 linter 规则:
### C1: 维度完整性
- 报告必须包含全部 4 个维度:fundamental, market, news, analyst
- 缺少任何一个维度视为报告不合格
### C2: 摘要最小长度
- 每个维度的 summary 字段不少于 100 个字符
- 空摘要或过短摘要说明数据采集不充分
### C3: 置信度范围
- 每个维度的 confidence 必须在 [0.0, 1.0] 闭区间内
- 超出范围说明评分逻辑有误
### C4: 评级有效值
- overall_rating 只能取 "buy"、"hold"、"sell" 三个值之一
- 其他值(如 "strong_buy"、"outperform")不被接受
### C5: 来源数量
- sources 列表必须包含至少 3 个来源 URL
- 来源过少说明研究深度不够
### C6: 风险因素
- risk_factors 列表不能为空
- 任何投资都有风险,空列表说明分析不完整
### C7: 必填字段
- stock_code, stock_name, report_date 为必填字段
- 缺少任何一个视为报告结构不完整
### C8: AkShare 数据使用
- 每个维度必须标记是否使用了 AkShare 数据 (akshare_data 字段)
- akshare_version 字段必须包含当前使用的 AkShare 版本
## API 依赖
- 模型:通过 DashScope 调用 Qwen 系列模型
- 联网搜索:enable_search=True, search_strategy="agent"
- 认证:通过 DASHSCOPE_API_KEY 环境变量
- 金融数据:AkShare 库 (版本 >= 1.10.60)
step2: 撰写测试用例,@stock_research_with_akshare_spec.md 这个是SPEC,这里有 C1 - C8的约束,帮我先撰写测试代码(我打算用TDD的模式驱动开发,先不用开发);
测试代码放到 tests 文件夹中,到时候实际代码会放到 src 文件夹中。

step3: 运行测试用例:
运行测试用例,使用TDD 红 - 绿 - 重构循环

step4: 用 akshare 获取真实股票数据,输出报告 这个项目 @stock_research_with_akshare_spec.md 基于这个SPEC文档完成,
然后完成相应的测试(基于TDD 红-绿-重构循环),你刚才已经写了测试用例

step5: @stock_research_with_akshare_spec.md 基于这个文档完成 完整的项目,这里会用到 akshare 和 qwen大模型(可以使用DevAGI平台,apikey可以使用环境变量中的 DEV_AGI_API_KEY,你可以参考 @example03.py 文件,看下怎么调用大模型)

Harness Engineering(驾驭工程)
2026年最火的技术是什么?我想大家第一时间想到的就是Harness Engineering(驾驭工程),把AI比喻成马,Harness就是马的缰绳,由我们人类通过Harness来驾驭AI(马),让AI输出人类想要的结果。
前段时间,我在抖音看到的一则新闻:
一个 3 人团队,用 5 个月时间,从空仓库开始,完全不手写代码,所有代码都由 AI(Codex)生成。最终产出了一个真正的产品,超过 100 万行代码,提交了约 1500 个 PR。后来团队扩展到 7 人,吞吐量仍然在增长。
他们人均每天合并 3.5 个 PR。单次 Codex 运行可以持续 6 小时以上,通常在人类下班睡觉的时间自动工作。
他们是怎么做到的?答案就是 Harness Engineering。
其实 上面的案例:AI投研工具 用的也是Harness思维进行编写项目的。
Harness 是模型之外的一切,即:约束系统、反馈回路、工具环境、验证机制。
裸模型不是 Agent,只有给它装上缰绳,它才能可靠工作。

LLM 工程范式的演进
2023年的Prompt Engineering 提示词工程 :
- 关注: 说什么(单次指令);
- 解决: 让 AI 听懂单次指令;
也是驱动大模型唯一的语言(prompt)
2025年的Context Engineering 上下文工程:
- 关注: 知道什么(上下文管理);
- 解决: 让 AI 获得所需信息;
从而兴起了RAG知识库,一个Agent配备了RAG知识库的话,RAG会先执行,它类似数据库一样,基于用户query,先去知识库内检索相关信息,再拼接好新的prompt,给到LLM模型。想详细了解的可以去看下我之前写的文章 # 浅聊Prompt、向量知识库、RAG。
2026年的Harness Engineering 驾驭工程:
- 关注: 在什么环境做事(系统构建)
- 解决: 让 Agent 可靠自主完成复杂任务
它是方法论、工作/思维模式,不是代码也不是工具,让约束好AI,并且让AI高效、高质量输出结果。
随着AI进化越来越快,它能解决越来越多的复杂事务,单一个提示词,或者上下文是没办法满足需求,所以就衍生了Harness,甚至我个人猜测后面还会其他的AI工程出现,这也是一种必然的趋势。
Harness的三大支柱
告知!!!约束!!!验证!!!
Inform(告知):
AGENTS.md 导航文件、上下文工程 => AGENTS.md 项目导航入口
让AI知道,你的角色是什么,如果你想要找某些技能,找某些内容,从哪里去看,这就是我们的告知支柱。
# Stock Deep Research - AGENTS.md
> 本文件是项目导航入口(给 AI Agent 和开发者看的目录页)。
> 遵循 Harness Engineering "地图而非手册" 原则:~50 行入口,指向更深层文档。
## 项目定位
AI 驱动的股票深度研究工具,通过 Qwen 大模型联网搜索生成多维度结构化研报。
同时作为 TDD + SDD + Harness Engineering 的教学案例。
## 关键文件导航
| 文件 | 用途 |
|------|------|
| `spec/stock_research_with_akshare_spec.md` | 规格文档(一等公民) -- 所有约束条件的权威来源 |
| `src/qwen_client.py` | Qwen API 客户端封装 |
| `src/collector.py` | 多维度数据采集 |
| `src/analyzer.py` | 数据汇总分析 + 评分 |
| `src/reporter.py` | 报告生成 + 结构校验 |
| `src/validator.py` | 报告验证器(C1-C8 约束条件) |
| `tests/test_validator.py` | 测试用例(31 个测试) |
## 开发约定
1. **TDD 强制**:所有新功能必须先写失败的测试,再写实现
2. **Spec 同步**:修改报告结构时必须同步更新 `spec/stock_research_with_akshare_spec.md`
3. **测试隔离**:单元测试禁止调用真实 API,使用 Mock
4. **结构对称**:`src/` 下每个模块对应 `tests/` 下的 `test_` 同名文件
## 测试命令
```bash<br><br>pytest tests/ -q # 全部单元测试<br><br>pytest tests/test_validator.py -v # 单个模块<br><br>python src/main.py 600519 # 生成股票研究报告<br><br>```
## 架构约束
- 依赖方向:`client -> collector -> analyzer -> reporter`
- 禁止反向依赖(reporter 不能 import collector)
- API 调用只发生在 `qwen_client.py` 中,其他模块不直接调用外部 API
- 所有核心代码放置在 `src/` 文件夹内
Constrain(约束):
架构边界、权限控制、自定义 Linter(代码检查器) => lint_structure.py
我到底要做哪些事情,有哪些权限,我到底怎么去做检测,这就是约束支柱。
#!/usr/bin/env python3
"""
项目结构校验工具
遵循 Harness Engineering 核心理念:文档会腐烂,lint 规则不会
"""
import os
import sys
import json
import re
from typing import List, Dict, Optional
class ProjectStructureLinter:
"""项目结构校验器"""
def __init__(self):
self.required_files = {
"src": [
"__init__.py",
"validator.py",
"collector.py",
"analyzer.py",
"reporter.py",
"qwen_client.py",
"main.py"
],
"tests": [
"__init__.py",
"test_validator.py"
],
"spec": [
"stock_research_with_akshare_spec.md"
]
}
self.errors: List[str] = []
def check_directory(self, directory: str, required_files: List[str]) -> None:
"""检查目录是否包含所有必需的文件"""
if not os.path.exists(directory):
self.errors.append(f"ERROR: 目录 {directory} 不存在。请创建该目录。")
return
existing_files = os.listdir(directory)
for file in required_files:
if file not in existing_files:
self.errors.append(f"ERROR: 目录 {directory} 中缺少必需文件 {file}。请创建该文件。")
...
def run(self) -> bool:
"""运行所有校验"""
print("正在进行项目结构校验...")
# 检查目录结构
for directory, files in self.required_files.items():
self.check_directory(directory, files)
# 检查导入规则
src_files = ["src/main.py", "src/reporter.py", "src/analyzer.py"]
...
else:
print("\n项目结构校验通过!")
return True
if __name__ == "__main__":
linter = ProjectStructureLinter()
success = linter.run()
sys.exit(0 if success else 1)
Verify(验证):
其实约束就会给你写个验证,它会通过测试的方式,给你完成一个验证。拿自动化测试去完成。
"""
股票深度研究报告验证器测试用例
基于 TDD 模式,针对 SPEC 文档中的约束条件 C1-C8
"""
import pytest
import sys
import os
# 添加项目根目录到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.validator import StockReportValidator
class TestC1_DimensionCompleteness:
"""C1: 维度完整性测试"""
def test_all_dimensions_present(self):
"""测试:报告包含全部 4 个维度"""
report = {
"stock_code": "600519",
"stock_name": "贵州茅台",
"report_date": "2026-04-20",
"dimensions": {
"fundamental": {"summary": "测试基本面分析" * 15, "confidence": 0.85, "akshare_data": True},
"market": {"summary": "测试市场面分析" * 15, "confidence": 0.78, "akshare_data": True},
"news": {"summary": "测试消息面分析" * 15, "confidence": 0.72, "akshare_data": True},
"analyst": {"summary": "测试分析师观点" * 15, "confidence": 0.80, "akshare_data": True}
},
"overall_rating": "buy",
"risk_factors": ["风险因素1", "风险因素2"],
"sources": ["https://example1.com", "https://example2.com", "https://example3.com"],
"akshare_version": "1.10.60"
}
validator = StockReportValidator(report)
assert validator.validate() is True
assert len(validator.get_errors()) == 0
class TestC2_SummaryMinLength:
"""C2: 摘要最小长度测试"""
def test_all_summaries_meet_min_length(self):
"""测试:所有维度的摘要都满足最小长度要求"""
report = {
"stock_code": "600519",
"stock_name": "贵州茅台",
"report_date": "2026-04-20",
"dimensions": {
"fundamental": {"summary": "测试基本面分析" * 15, "confidence": 0.85, "akshare_data": True},
"market": {"summary": "测试市场面分析" * 15, "confidence": 0.78, "akshare_data": True},
"news": {"summary": "测试消息面分析" * 15, "confidence": 0.72, "akshare_data": True},
"analyst": {"summary": "测试分析师观点" * 15, "confidence": 0.80, "akshare_data": True}
},
"overall_rating": "buy",
"risk_factors": ["风险因素1", "风险因素2"],
"sources": ["https://example1.com", "https://example2.com", "https://example3.com"],
"akshare_version": "1.10.60"
}
validator = StockReportValidator(report)
assert validator.validate() is True
class TestC3_ConfidenceRange:
"""C3: 置信度范围测试"""
def test_all_confidences_in_valid_range(self):
"""测试:所有置信度都在有效范围内"""
report = {
"stock_code": "600519",
"stock_name": "贵州茅台",
"report_date": "2026-04-20",
"dimensions": {
"fundamental": {"summary": "测试基本面分析" * 15, "confidence": 0.85, "akshare_data": True},
"market": {"summary": "测试市场面分析" * 15, "confidence": 0.78, "akshare_data": True},
"news": {"summary": "测试消息面分析" * 15, "confidence": 0.72, "akshare_data": True},
"analyst": {"summary": "测试分析师观点" * 15, "confidence": 0.80, "akshare_data": True}
},
"overall_rating": "buy",
"risk_factors": ["风险因素1", "风险因素2"],
"sources": ["https://example1.com", "https://example2.com", "https://example3.com"],
"akshare_version": "1.10.60"
}
validator = StockReportValidator(report)
assert validator.validate() is True
Inform 告知:Agent 该做什么 => Constrain 约束:Agent 不能做什么 => Verify 验证:Agent 做得对不对!!!
Harness组件
仓库(记忆系统)
为什么仓库是记录系统?
在传统团队中,很多重要信息散落在 微信、飞书、钉钉等聊天群内(聊天记录)、飞书文档、会议纪要、甚至团队成员的脑子里。
对于人类是可以的,但对 AI Agent 来说——这些信息等于不存在。
| 信息存放位置 | 人类能否访问 | AI Agent 能否访问 |
|---|---|---|
| 飞书/禅道/gitee 中的需求文档 | 能 | 不能 |
| Slack/微信群里的技术讨论 | 能 | 不能 |
| 老刘脑子里的架构决策 | 能(问老刘) | 不能 |
| 仓库里的 Markdown 文档 | 能 | 能 |
| 代码 + 注释 | 能 | 能 |
| Lint 规则 | 间接能 | 能(强制执行) |
一切决策、规范、计划都必须以版本化的文件提交到仓库。
在我们的 stock-research 中,Spec 文档(spec/stock_research_with_akshare_spec.md)就是这个记录系统的体现。报告的输出格式、
约束条件 C1-C8、API 依赖——全部写在仓库里,而不是在脑子里
AGENTS.md:地图而非手册
既然所有信息都要进仓库,那把所有规则写成一个巨大的说明文件行不行?
不行!HumanLayer 团队的实践表明:AGENTS.md 超过 60 行,效果反而会下降。
巨型指令文件有三个死因:挤占上下文窗口、无法维护、无法被机械验证。
AGENTS.md是地图,是导航。就像我们去图书馆看一本书(python入门秘籍),图书管有上十万本书,我们不可能一本一本的去翻,这样效率极其低下,并且耗时太长,所以图书馆内有会把同类型的书籍放在一个类目区域内,并且在图书馆大门口有目录指引。
我们的AGENTS.md也是这个道理,它是一个大目录,告诉AI你想要的东西在哪里可以找到,类似渐进式披露。
我们上面聊到的案例:AI投研工具进行撰写AGENTS.md。
prompt:
你查看整个项目,写一个AGENTS.md,AGENTS.md 不能超过 60 行,AGENTS.md只有关键路标,不写具体规则(具体规则在 Spec 和 Linter 里)。
生成后的文件:
# Stock Deep Research - AGENTS.md
> 本文件是项目导航入口(给 AI Agent 和开发者看的目录页)。
> 遵循 Harness Engineering "地图而非手册" 原则:~50 行入口,指向更深层文档。
## 项目定位
AI 驱动的股票深度研究工具,通过 Qwen 大模型联网搜索生成多维度结构化研报。
同时作为 TDD + SDD + Harness Engineering 的教学案例。
## 关键文件导航
| 文件 | 用途 |
|------|------|
| `spec/stock_research_with_akshare_spec.md` | 规格文档(一等公民) -- 所有约束条件的权威来源 |
| `src/qwen_client.py` | Qwen API 客户端封装 |
| `src/collector.py` | 多维度数据采集 |
| `src/analyzer.py` | 数据汇总分析 + 评分 |
| `src/reporter.py` | 报告生成 + 结构校验 |
| `src/validator.py` | 报告验证器(C1-C8 约束条件) |
| `tests/test_validator.py` | 测试用例(31 个测试) |
## 开发约定
1. **TDD 强制**:所有新功能必须先写失败的测试,再写实现
2. **Spec 同步**:修改报告结构时必须同步更新 `spec/stock_research_with_akshare_spec.md`
3. **测试隔离**:单元测试禁止调用真实 API,使用 Mock
4. **结构对称**:`src/` 下每个模块对应 `tests/` 下的 `test_` 同名文件
## 测试命令
```bash<br><br>pytest tests/ -q # 全部单元测试<br><br>pytest tests/test_validator.py -v # 单个模块<br><br>python src/main.py 600519 # 生成股票研究报告<br><br>```
## 架构约束
- 依赖方向:`client -> collector -> analyzer -> reporter`
- 禁止反向依赖(reporter 不能 import collector)
- API 调用只发生在 `qwen_client.py` 中,其他模块不直接调用外部 API
- 所有核心代码放置在 `src/` 文件夹内
Lint代码级规则
Harness内的Lint规则,其实类似于 程序员通过Idea编写代码,都会设置一个静态代码检查lint,确保代码的规范性,违反我们的规范/约束的话,就会失败,报错,警告等。
Lint规则的重要性如何?
OpenAI 团队在实践中发现一个规律:写在文档里的规范,Agent 经常忘记或忽略。
但写成 Lint 规则的约束,Agent 每次都会遵守——因为违反规则会导致 CI 失败,Agent 无法跳过。
更关键的洞察是:Lint 错误信息里可以嵌入修复指令。
普通的错误信息只告诉你错了,但 Agent 不知道怎么修。如果错误信息里直接给出修复步骤,Agent 就能自我纠正,形成闭环。
Lint(或Linter) 是一种静态代码分析工具,它在不运行代码的情况下扫描源代码,自动检测潜在错误。
机械化执行:文档会腐烂,Lint 规则不会
| 普通错误 | Harness 错误 |
|---|---|
| Error: File exceeds 500 lines. | Error: File exceeds 500 lines. # Agent 看到后:知道怎么修,可以自己执行 Fix: Split into domain-specific modules following docs/ARCHITECTURE.md. Consider extracting types to types/ and service logic to service/. |
在案例:AI投研工具内,validator.py的每个错误都嵌入了修复指令。当 Agent 生成的报告不合格时,它可以读取错误信息,按指令自动修复。
def _validate_c1_dimension_completeness(self) -> None:
"""
C1: 维度完整性
报告必须包含全部 4 个维度:fundamental, market, news, analyst
"""
if "dimensions" not in self.report:
self.errors.append("C1: 报告缺少 'dimensions' 字段")
return
dimensions = self.report["dimensions"]
missing_dimensions = [dim for dim in self.REQUIRED_DIMENSIONS if dim not in dimensions]
if missing_dimensions:
self.errors.append(f"C1: 报告缺少以下维度: {', '.join(missing_dimensions)}")
...
自定义结构 Linter项目级规则
除了代码层面的校验,还需要编写项目级的结构检查器。
检查项目有没有按照约定组织,比如 reporter.py 里是不是有validate_report 函数、REQUIRED_DIMENSIONS
常量是不是包含了全部 4 个维度。
def check_validate_report_exists(reporter_path):
"""检查 reporter.py 必须包含 validate_report 函数"""
source = reporter_path.read_text(encoding="utf-8")
tree = ast.parse(source)
func_names = [node.name for node in ast.walk(tree)
if isinstance(node, ast.FunctionDef)]
if "validate_report" not in func_names:
return [
"ERROR: src/reporter.py 缺少 validate_report() 函数。\n"
"FIX: 添加 def validate_report(report: dict) -> list[dict],\n"
" 逐条检查 spec/research_spec.md 中的约束条件 C1-C7。\n"
]
return []
Guides x Sensors 矩阵
Harness 有很多组件,但这些零件是如何协同工作的?
Martin Fowler 团队用一个 2 x 2 矩阵做了分类:

只有引导器(只告诉 Agent 怎么做,不检查结果)= 不知道规则是否生效,可能一直犯同样的错;
只有传感器(只检查结果,不提前引导)= Agent 反复试错,效率低下;
两者结合 = 先引导提高首次成功率,再检测兜底,形成闭环。
在 AI投研工具 案例中:
- 引导器: AGENTS.md 告诉 Agent 项目结构、Spec.md 定义约束、Prompt提示词模板引导 Qwen 按格式分析。
- 传感器: 代码级Lint 检查报告结构、Linter 检查项目结构、pytest 运行所有测试。
CI/CD 质量门禁
代码提交后,CI/CD 流水线 会自动运行三道质量检查。
门禁从快到慢分层排列,越早发现问题,修复成本越低。

# .github/workflows/quality_gate.yml
name: Quality Gate
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: quality-${{ github.ref }}
cancel-in-progress: true
jobs:
# 第一道门:结构检查(Harness Engineering 的机械化执行)
structure-lint:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Structure Lint
run: python linters/check_report_structure.py
# 第二道门:单元测试(TDD 的验证)
unit-tests:
runs-on: ubuntu-latest
timeout-minutes: 5
needs: structure-lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run unit tests
run: pytest tests/ -v --tb=short -m "not integration"
env:
DASHSCOPE_API_KEY: ""
# 第三道门:集成测试(仅 main 分支,需要真实 API Key)
integration-tests:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: unit-tests
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run integration tests
run: pytest tests/test_integration.py -v -m integration
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
Harness 思维方式
如何给 Agent 自由
你觉得给Agent自由度越大越好,还是约束越严越好?
想回答这个问题之前,你可以先去看下我这篇文章 # 浅谈扩散模型如何编织创意图像的魔法,自由与约束是相倚的。
给AI过大的自由度,它就越能产出高质量的结果?
给 Agent 的自由度越大,它犯错的概率越高。通过架构约束收窄解空间,Agent 在约束内的表现会显著提升。
其实限制一定的自由空间,反而能让AI更可靠。就像 DALL·E 2模型一样。
就像我们用vibe coding 给它一个指令,可能3-5句话,它执行指令后,发现它偏离你想要的东西后,你就会重新下达指令,把它拉回来,然后它这样来回拉,就是一个自由度的问题。
如何让它完整性的输出,约束会让它质量更好些?
harness其实就是通过不同的文档,进行约束,让Agent高质量输出
以2个小案例来解说下:
情况 A:你告诉 Agent “用任何你觉得合适的方式写一个股票分析工具”。
结果:Agent 可能用各种风格写代码,数据结构不统一,某些文件几千行。每次运行结果都不一样。
情况 B:你告诉 Agent “按照 AGENTS.md 和 spec/research_spec.md 写代码,必须通过 linter 检查和全部测试”。
结果:Agent 在明确的约束内工作,输出结构统一,行为可预测。——不会跑偏,同时在轨道上跑得又快又稳。
在AI投研案例中:
- 架构约束: 依赖方向 client → collector → analyzer → reporter,禁止反向依赖
- 数据约束: 报告必须严格遵循 Spec 的 JSON 结构
- 命名约束: 4 个维度必须是 fundamental/market/news/analyst,不能自创
- 质量约束: 每个维度的摘要不少于 100 字、置信度在 0~1 之间
这些约束看似限制了 Agent 的创造力,实际上让它产出了更可靠的结果。
Agent 会复制坏模式
Agent会模仿之前的坏模式吗?
OpenAI 团队发现了一个令人头疼的问题:Agent 会模仿仓库中已有的模式——包括坏模式。
比如仓库里有一个文件用了 console.log 打调试日志,Agent 会在新代码里也用 console.log,而不是用项目规定的结构化日志。坏代码就像传染病,通过 Agent 的模仿能力迅速扩散。
他们最初的做法是每周五花 20% 时间人工清理 AI 残渣。结果不出意料——完全不可持续。
如何做更合理?
好模式的传播路径:人类审查评论 → 写成文档 → 编码为 Lint 规则 → 自动应用于每一行代码。
一旦好模式被编码为规则,它就会被持续、无遗漏地执行。
技术债就像高息贷款——小额持续偿还(自动垃圾回收),远好过累积到痛苦时一次性清偿(大重构)。
坏模式有可能被复制,但是我们用好模式去约束它,把它的质量进行提升。
写成 Lint 规则的约束,Agent 每次都会遵守——因为违反规则会导致 CI 失败,Agent 无法跳过。
更关键的洞察是:Lint 错误信息里可以嵌入修复指令。

吞吐量改变合并理念
在传统开发中,每个 PR 都要人工审查、反复讨论、仔细打磨。这在人类写代码的时代是对的——因为人力贵、产出慢,每行代码都要珍惜。
但当 AI Agent 的吞吐量远超人类时,经济学发生了根本变化:

OpenAI 团队的实践:
- PR 生命周期很短 ——不再是精雕细琢的大作,而是快速流动的小变更
- 测试偶发失败? 后续重跑解决,不无限期阻塞
- 人类可以审核 PR,但不是必须的 ——逐渐过渡到 Agent 审核 Agent
快速合并、快速纠错是未来的趋势!!!
快速合并,快速纠错的前提是什么?
前提是有足够的背压机制(测试 + Lint + 结构检查)来保证基本质量。
没有背压的快速合并,不是"快速迭代",而是"快速腐烂"。
这也是为什么第一部分讲 TDD、这一部分讲 Harness 的原因——它们是 Agent 高吞吐的基础设施。
背压机制是一种反向流量控制机制:当系统下游处理能力不足时,向上游发送减速信号,防止数据堆积和系统崩溃。

Harness概念(总结)
- 仓库即记录系统:
不在仓库里的东西,对 Agent 不存在
你的聊天记录,你开会的会议记要,要把它转变成spec,放在AGENTS地图里面,它才能看到。
- 地图而非手册:
地图不是一个又大又全的东西,它是个目录,是个导航,是渐进式的方法
AGENTS.md 是目录页(50~100行),不是百科全书
需要 AGENTS.md 只做导航,详情在各模块
- 机械化执行:
AI写代码很快,我们很难跟上节奏,所以我们要有lint与背压、测试,要有约束,限制好AI的自由空间,让它更高效的输出。
文档会腐烂,Lint 规则不会;
错误信息要嵌修复指令=> linters/ + validate_report() 的 fix 字段
- 智能体可读性:
选稳定的技术(API 稳定、训练集覆盖好):选 akshare(稳定)而非自建爬虫;
约束越严,Agent 越可靠。
- 吞吐量改变合并理念:
纠错成本低于等待成本,快速合并 + 靠背压 + CI/CD 三道门禁 + TDD 保底。
进入ai行业后,其实更大的成本是等待成本,你发一个指令,AI会去生成代码、测试代码、检查等。
所以现在吞吐量是不一样的
- 熵管理(垃圾回收):
Agent 会复制坏模式,把好模式编码成规则并定期扫描=> Linter 持续检查项目结构一致性
Harness Engineering 不是一个工具,而是一种工程思维的转变!
项目内会有docs目录,里面都是好的模板,然后外面会有AGENTS.md文件地图
工程师的核心产出从写代码 => 变成了 设计让 AI 可靠工作的约束系统。
以前辅助性的工作,现在把他们变成基础环境/设施,比如:测试、Lint、CI/CD 这些在传统开发中的辅助工具 => 在 AI 时代变成了让 Agent 可靠运转的核心基础设施。
简而言之,我们已经接受AI的编程替代了人类的编程,那么我们的思维也要进行转变。
我们的思维模式转变成,我们不写代码了,我们要做的事就是打造这个环境,让AI在这个环境中干活,所以harness的几个模块也是从不同的角度去怎么准备这个信息
比如,prompt:把你刚才的工作和洞察整理到.md中, 放到 docs 文件夹中,并且根据docs文件夹内信息,生成AGENTS.md文件,简要描述
Ralph 编排器
什么是 Ralph?
Ralph 的名字来自《辛普森一家》中的角色 Ralph Wiggum(一个天真但不可预测的小孩)。
这个命名很有深意:AI Agent 就像 Ralph Wiggum——能力惊人但需要引导,如果不给它明确的边界和任务清单,它可能做出各种意想不到的事情。
Ralph是一个约束的系统!
AI里面还有一个ReAct,它是一个Agent自主的逻辑,AI的编排,可以看下 初始Agent文章,现在AI基本上都默认ReACT(Reasoning+Action)你要完成一件事,它要先去想我要怎么做,然后再去做,这是它一个思考的逻辑。想进一步了解的,可以看下我之前的文章 # 初识Agent。
2026 年初,开发者社区在实践 Harness Engineering 时遇到一个问题:理论很好,
但怎么让 AI Agent 真正 在循环中自主工作直到完成任务?
Ralph 就是要解决这个问题,它是 Harness Engineering 的开源实现。
Ralph = 一个 Bash 脚本编排器,反复启动 AI Agent,每次迭代清空上下文,让 Agent 在循环中自主完成任务。
人类唯一要做的就是写一份 PROMPT.md(任务描述文件),然后坐等结果。
Ralph的信条
Ralph 只是一个工具吗?
它不只是工具,背后还有一套工程哲学 —— Ralph 的六大信条
信条1:Fresh Context Is Reliability (新鲜的上下文就是可靠性)
每轮迭代都给 Agent 一个干净的上下文窗口。不要让它带着上一轮的记忆碎片工作——那些碎片可能已经过时或错误。
对应 Harness概念的 智能体可读性
信条2:Backpressure Over Prescription(用背压代替处方)
不规定 Agent 怎么做,但用测试和 Lint 拒绝坏结果。就像水管的阀门——不告诉水怎么流,但不合格的水流不过去。
对应 Harness概念的 机械化执行
信条3:The Plan Is Disposable(计划是用完即弃的)
Agent 的计划不值得珍惜。如果执行中发现计划有问题,直接重新生成一个新计划——成本只是一次 AI 调用。
对应 Harness概念的 熵管理
信条4:Disk Is State, Git Is Memory(磁盘是状态,Git 是记忆)
Agent 的工作成果写在文件里(磁盘),决策历史保存在 Git 里。跨迭代的上下文不靠 Agent 记住了什么,而靠文件里写了什么。
对应 Harness概念的 仓库即记录系统
信条5:Steer With Signals, Not Scripts(用信号引导,不用脚本控制)
人类的角色是加路标(AGENTS.md、Spec),而不是写脚本一步步指挥。
给 Agent 方向,让它自己找路。
对应 Harness概念的 地图而非手册
信条6:Let Ralph Ralph(让 Ralph 做 Ralph 的事)
人类坐在循环的上方(设计约束),不要坐到循环的里面(微操执行)。如果你发现自己在手动修改Agent 的代码,那说明 Harness 有缺失。
对应 Harness概念的 人类掌舵,Agent 执行
我们了解了Ralph的信条后,当Agent遇到困难,出现Bug的时候,要专注修改代码吗?
当 Agent 遇到困难时,不要自己下手修代码。
我们要去改 Harness——补一条 Lint 规则、加一段 AGENTS.md 说明、增加一个测试。
Ralph的设计理念:修 Harness 一次,Agent 以后每次都做对。
小案例:
你让AI去完成一个任务A,它可能把这个事作对了,然后你又让它去完成任务B\C\D,去干这些任务,最后你会发现,以前A明明是对的,现在A又出错了,问题在AI没有检测机制,AI在做事的过程中,重新写了一次A,第一次作对了,是因为单独把错误贴给他,告诉它这是个错误,你要怎么测试,你要通过,否则你是没办法验收的,只有A验收了才能做BCD,所以第一次A任务是对的。所以针对这样的情况,我们要改下Harness,添加一条Lint规则,已经完成的任务不要再次执行。
Ralph 帽子系统
人类工程师 VS AI Agent:
人类工程师在工作中会自然切换角色:一会儿在规划,一会儿在写代码,一会儿在审查。
但 AI Agent 缺少跨窗口,跨场景的能力,如果不加区分地同时做这些事,结果往往混乱——既想写新功能,又想重构旧代码,还想修 Bug。
如何解决这个问题?
Ralph 用帽子系统解决这个问题:每轮迭代只让 Agent 戴一顶帽子,专注做一件事。
Critic(审查者)和 Builder(构建者)是不同的 Agent 实例。
Critic 不知道 Builder 做了什么决策,它从零开始独立验证——就像让另一个人来审查你的代码,而不是自己审查自己。

案例:文字计数器
step1: 在Trae Idea新建一个prompt_wc.md文件
任务:构建一个计算器模块
创建一个名为 calc.py 的 Python 模块,要求如下:
1、实现加法、减法、乘法、除法四个函数
2、除以零的情况需抛出携带清晰提示信息的 ValueError 异常
3、所有函数接收两个数值型参数,并返回一个数值
编写一个使用 pytest 框架的测试文件 test_calc.py:覆盖常规场景和边界场景(除以零、负数、浮点数运算)
当所有测试用例执行通过后,输出 LOOP_COMPLETE
step2: 编写一份ralph_wc.py文件,
它是Ralph 编排循环:
迭代 1 – Planner 规划者
Agent 戴上规划者帽子。读取 prompt_wc.md,拆解任务为具体步骤,
把计划写入 scratchpad.md(便签本),然后交给下一轮。
迭代 2 – Builder 构建者 [TDD]
Agent 戴上构建者帽子,严格遵循 TDD:
1)先写 test_wc.py(7 个测试:正常文件计数、输出格式、空文件、文件不存在、错误退出码、无参数退出码、使用说明)
2)运行测试 → 7 个全红
3)写 wc.py 实现
4)运行测试 → 发现 char count 有个 off-by-one 错误
5)自己修复了 bug(把 24 改成了正确的 23)
6)运行测试 → 7/7 全绿
迭代 3 – Critic 审查者
全新的 Agent 实例,戴上审查者帽子。它不知道 Builder 的任何决策过程,从零开始:
1)独立重跑 pytest → 7/7 通过
2)手动测试 5 种 CLI 场景:正常文件、无参数、文件不存在、空文件、无换行符结尾的文件
3)全部通过,给出 PASSED 判定
迭代 4 – Finalizer 终结者
确认所有 prompt_wc.md 中的需求都已满足,没有遗留任务,输出 LOOP_COMPLETE 信号,循环终止。
ralph_wc.py代码如下:

step3: 运行ralph_wc.py文件,
运行结果如下:


案例流程对应Ralph的概念:

案例:mini Ralph
任务:用 Python + Qwen 编写简化版的 ralph_demo.py
体验Agent使用Qwen模型, 在循环中自主工作的过程。
ralph_mini.py 的核心设计只有三步:
1)定义四顶帽子(提示词);
2)按顺序调用 Qwen;
3)用文件传递上下文。
step1: 定义四顶帽子
每顶帽子就是一段 system prompt,告诉 Qwen 当前它扮演什么角色、该做什么、不该做什么。
写一个ralph_mini.py文件
核心设计只有三步:
1)定义四顶帽子(提示词)
2)按顺序调用 Qwen
3)用文件传递上下文。
# 四顶帽子的提示词定义
HAT_PLANNER = """你是 Planner(规划者)。你的职责是:
1. 阅读用户的任务描述
2. 将任务拆解为具体的实施步骤
3. 必须包含"先写测试,再写实现"的 TDD 步骤
只输出计划,不要写代码。"""
HAT_BUILDER = """你是 Builder(构建者)。你的职责是:
4. 严格按照 TDD 流程:先写测试文件,再写实现文件
5. 使用 pytest 作为测试框架
输出格式:===FILE:文件名=== ... ===END==="""
HAT_CRITIC = """你是 Critic(审查者)。你是独立角色,不知道
Builder 的决策。
检查:测试是否通过?逻辑是否正确?边界情况是否覆盖?
最后一行必须是:VERDICT: PASSED 或 VERDICT: FAILED"""
HAT_FINALIZER = """你是 Finalizer(终结者)。
确认所有目标完成、测试通过、审查通过后,输出:
LOOP_COMPLETE"""
# 主循环 -- 4 轮迭代
每轮迭代做一件事,用 scratchpad.md 文件在迭代之间传递上下文(而不是靠 Agent 记住上一轮做了什么)
# 调用 qwen大模型(可以使用DevAGI平台,api——key可以使用环境变量中的 DEV_AGI_API_KEY,你可以参考 @example03.py 文件,看下怎么调用大模型)
# 最后运行.py文件, 并返回结果(背压门控的核心)
AI生成的py文件:

python ralph_mini.py "创建一个计算器程序,支持加减乘除运算"
运行生成的scratchpad.md,如下:

终端运行截图:

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)