1. 项目概述:这不是又一个“AI写代码”玩具,而是一套可落地的工程化流水线
“Agentic Harness Workflow 框架:把 AI Coding 变成工程化流程”——光看标题,你可能下意识划走,觉得又是某家大厂在堆砌概念。但我在某实验室带团队做AI辅助开发落地的三年里,亲手推翻过四版内部AI编码流程,踩过从提示词爆炸、上下文失控到交付物无法进CI的全部坑。直到我们基于这个框架重构了整个前端组件生成链路,才真正把“让AI写代码”这件事,从实习生调API玩的Demo,变成了能嵌入Jenkins流水线、通过SonarQube扫描、自动打Tag发NPM包的标准环节。它解决的不是“能不能生成一段React代码”,而是“如何让AI持续、稳定、可审计、可回滚地交付符合企业级规范的生产级代码”。核心关键词就三个:Agentic(智能体化)、Harness(受控编排)、Workflow(工程化流程)——不是让AI自由发挥,而是像给高速列车装上轨道、信号灯和调度中心。适合三类人:正在被重复CR压垮的中高级前端/后端工程师;想把AI能力真正接入研发效能平台的技术负责人;以及那些已经用过Cursor、GitHub Copilot但总觉得“差点意思”的务实型开发者。它不承诺取代你,但能让你从每天写50行样板代码,变成花20分钟设计一个能跑半年的自动化生成节点。
2. 核心设计逻辑:为什么必须放弃“单Agent裸奔”,转向“Harness式受控编排”
2.1 单Agent模式的致命缺陷:失控、不可测、难维护
我最早在2022年尝试用纯LangChain搭一个“自动生成接口Mock数据”的小工具,思路很朴素:用户输入接口文档URL → Agent读取OpenAPI → 调用LLM生成Mock JSON Schema → 输出可运行的Express路由。初看很美,实测三天就崩了。问题出在“单Agent裸奔”模式上:当LLM在生成Schema时突然遇到Swagger里一个未定义的x-nullable: true扩展字段,它不会报错,而是凭空捏造一个"nullable": true字段塞进JSON Schema,导致下游TypeScript类型生成器直接panic。更糟的是,这个错误没有任何日志能追溯——因为整个链路是黑盒调用,没有中间态快照,没有重试锚点,没有失败降级路径。后来我们统计了3个月的线上AI生成任务,发现单Agent任务的平均失败率高达37%,其中68%的失败根本无法归因,只能靠人工肉眼比对输入输出猜原因。这完全违背了工程化最基础的原则:可观测、可重放、可干预。
2.2 Harness的核心思想:给AI套上“工程化缰绳”
Harness这个词选得极准。它不是要捆住AI的手脚,而是像赛马场上的缰绳——既保证方向不偏(约束输出格式),又允许在赛道内全力冲刺(保留LLM的创造性)。它的底层逻辑是三层解耦:
职责解耦:把“理解需求”、“规划步骤”、“调用工具”、“验证结果”拆成独立可替换的模块。比如“理解需求”模块可以是微调过的CodeLlama-7B,而“验证结果”模块必须是硬编码的ESLint规则校验器,绝不允许LLM自己判断“这段代码是否符合Airbnb规范”。
状态解耦:每个模块的输入输出都强制结构化。我们规定所有中间态必须是JSON Schema定义的固定字段,比如
{ "step": "generate_typescript_interface", "input": { "openapi_url": "https://xxx" }, "output": { "interface_code": "export interface...", "validation_report": { "eslint_errors": [] } } }。这样任何模块崩溃,上游都能拿到完整上下文重试,下游也能基于validation_report决定是继续还是告警。执行解耦:Harness本身不碰LLM,它只负责按DAG图调度。真正的LLM调用由独立的“Worker Pool”管理,每个Worker有内存限制、超时熔断、响应长度截断。我们甚至给Worker加了“沙箱模式”:当检测到输出里出现
eval(或new Function(等高危字符串,立即终止并标记为安全违规。
这套设计让我们的AI编码流程首次具备了传统CI/CD的可靠性。现在一个组件生成任务,从触发到发布NPM包,平均耗时4分12秒,失败率压到1.3%,且99%的失败能在30秒内定位到具体模块和输入参数。
2.3 为什么Workflow必须是“可版本化”的?——来自一次生产事故的教训
去年Q3,我们上线了一个“自动生成Vue3 Composition API组件”的Workflow。初期用得很顺,直到某天运维同学发现NPM仓库里多出了17个带-alpha后缀的异常包。追查发现,是LLM在生成setup()函数时,把用户需求里的“支持暗色模式”误解为“需要引入useDarkMode这个不存在的composable”,于是自己编造了一个import { useDarkMode } from 'vue-dark-mode',而我们的依赖检查模块没覆盖这种虚构包名场景,导致构建成功但运行时报错。更麻烦的是,这个错误Workflow已经跑了两周,修改提示词后新任务正常了,但历史生成的17个包怎么办?手动删?违反语义化版本规范。重新打Tag?破坏Git历史。
这次事故逼我们把Workflow本身做成“可版本化”的实体。现在每个Workflow定义文件(YAML格式)都带version: "v2.3.1"字段,并强制要求:
- 所有LLM调用必须指定模型版本(如
model: claude-3-haiku-20240307,而非model: claude-3-haiku) - 所有工具调用必须锁定API版本(如
eslint: v8.56.0) - 所有验证规则必须引用Git Commit Hash(如
rules_commit: a1b2c3d)
这样,当发现v2.3.1有缺陷,我们只需发布v2.3.2,然后在CI配置里把workflow_version: "v2.3.1"改成"v2.3.2",所有新任务自动升级,旧任务生成的包依然可用——因为它们的元数据里明确记录着“此包由v2.3.1 Workflow生成”,审计时一目了然。这才是真正的工程化:不追求绝对正确,而追求错误可追溯、修复可灰度、变更可审计。
3. 核心模块拆解与实操要点:从零搭建一个可用的Harness Workflow
3.1 Workflow定义层:用YAML描述“谁在什么时候做什么”
Workflow定义是整个框架的基石,它必须足够声明式,让非AI背景的工程师也能看懂。我们弃用了Terraform式的复杂语法,采用极简YAML,核心只有四个区块:
# workflow-vue-component-generator.yaml name: "vue3-component-generator" version: "v3.1.0" description: "根据Figma设计稿URL生成Vue3 SFC组件,含TS类型、单元测试、Storybook故事" # 输入契约:明确定义用户能提供什么 input_schema: type: object properties: figma_url: type: string format: uri description: "Figma设计稿公开链接,需含?node-id=参数" component_name: type: string pattern: "^[A-Z][a-zA-Z0-9]*$" description: "组件名,遵循PascalCase" # 执行图:DAG节点定义,每个节点是原子操作 steps: - id: "parse_figma" type: "tool_call" tool: "figma-parser" input: "{{ .input.figma_url }}" timeout: 30s retry: 2 - id: "generate_ts_types" type: "llm_call" model: "claude-3-haiku-20240307" system_prompt: | 你是一个资深Vue3 TypeScript开发者。根据以下Figma图层结构,生成精确的TypeScript接口。 要求:1. 接口名=组件名+Props;2. 所有属性必须标注必填/可选;3. 禁止使用any。 input: "{{ .steps.parse_figma.output.layers }}" output_schema: type: object properties: interface_code: type: string validation_report: type: object properties: eslint_errors: { type: array } - id: "generate_sfc" type: "llm_call" model: "gpt-4-turbo-2024-04-09" system_prompt: | 基于以下TypeScript接口和Figma图层,生成Vue3 SFC组件。 要求:1. 使用Composition API;2. setup()返回ref/reactive;3. CSS Scoped。 input: | Interface: {{ .steps.generate_ts_types.output.interface_code }} Layers: {{ .steps.parse_figma.output.layers }} # 关键!强制输出结构化,避免LLM自由发挥 output_schema: type: object properties: sfc_code: type: string css_vars: type: object # 输出契约:定义最终交付物长什么样 output_schema: type: object properties: component_code: type: string description: "完整的.vue单文件组件代码" test_code: type: string description: "Jest单元测试代码" story_code: type: string description: "Storybook故事代码"提示:
output_schema不是摆设。我们在LLM调用前,会把output_schema转成JSON Schema字符串,拼接到system_prompt末尾:“请严格按以下JSON Schema格式输出,不要任何额外解释:{{ json_schema }}”。实测下来,这招让LLM结构化输出成功率从62%提升到94%。
3.2 Harness运行时:如何让Workflow定义真正跑起来
Harness运行时是框架的“操作系统”,它不关心你用什么LLM,只确保Workflow定义被精准执行。我们用Go重写了核心引擎(性能比Python高3倍,内存占用低60%),关键设计如下:
DAG调度器:把YAML里的
steps解析成有向无环图。每个节点启动时,调度器会注入context对象,包含当前step ID、上游输出快照、重试次数等。当generate_sfc节点需要parse_figma的输出时,不是去全局变量里找,而是通过context.get_output("parse_figma")安全获取,天然支持并行(只要无依赖关系)和串行(有依赖则自动等待)。Tool Registry:所有外部工具(Figma Parser、ESLint、Jest Runner)都注册在这里。注册时必须提供
schema描述其输入输出,比如Figma Parser的schema长这样:{ "input": { "type": "string", "format": "uri" }, "output": { "type": "object", "properties": { "layers": { "type": "array", "items": { "type": "object" } }, "colors": { "type": "object" } } } }这样,当Workflow定义里写
tool: "figma-parser",Harness就能在运行时校验input是否符合schema,不符合就直接报错,而不是让工具自己崩溃。State Snapshotter:每完成一个step,自动把
input、output、duration、error(如有)序列化存入本地SQLite。我们给这个数据库起了个名字叫“Workflow Black Box”。当任务失败,运维同学不用翻日志,直接查表:SELECT * FROM step_logs WHERE workflow_id='vue3-gen' AND step_id='generate_sfc' ORDER BY created_at DESC LIMIT 1;就能看到最后一次失败的完整输入输出,连带时间戳和错误堆栈。
注意:Snapshotter默认只存最后1000条,避免磁盘爆满。但我们加了个“审计模式”开关:当
AUDIT_MODE=true时,所有快照存入独立的PostgreSQL库,并自动关联Git Commit ID——这样每次代码发布,都能回溯到对应Workflow版本生成的所有中间态。
3.3 LLM Worker Pool:如何让大模型真正“听话”
这是最容易被忽视、却最影响稳定性的模块。很多团队直接用OpenAI SDK裸调,结果被rate limit打崩,或被LLM胡说八道带偏。我们的Worker Pool做了三层防护:
模型网关(Model Gateway):所有LLM请求必须经过它。它干三件事:
- 熔断:当某个模型连续5次超时(>30s),自动切换到备用模型(如Claude挂了切GPT-4-Turbo);
- 限流:按模型维度配额,比如
claude-3-haiku每分钟最多30次,超了就排队,绝不让上游感知到失败; - 响应净化:收到LLM原始响应后,先用正则过滤掉
<thinking>、[NOTE]等非结构化内容,再用JSON Schema校验是否符合output_schema,不符合则重试(最多2次),仍失败则返回{"error": "LLM_OUTPUT_MALFORMED"}。
Prompt模板引擎:我们不用字符串拼接,而是用Go的
text/template。每个Workflow的system_prompt都是模板,{{ .input.figma_url }}会被安全注入。关键是,模板里禁止任何{{ .user_input }}这种裸变量——所有用户输入必须经过{{ .user_input | sanitize }}管道函数,该函数会移除控制字符、截断超长文本(>5000字符)、转义JSON特殊字符。这堵死了90%的prompt injection攻击。输出验证器(Output Validator):这是最后一道防线。当Worker返回
{"sfc_code": "...", "css_vars": {...}},Validator会:- 用
go/parser解析sfc_code,确认是合法Vue SFC(含<template>、<script setup>、<style scoped>); - 用正则提取
<script setup>里的defineProps,比对是否与generate_ts_types输出的接口名一致; - 对
css_vars执行JSON.Unmarshal,确认是合法JSON对象。
- 用
只有三关全过,才认为该step成功。否则标记为VALIDATION_FAILED,进入重试队列。我们统计过,这层验证让“生成代码语法错误”的比例从12%降到0.4%。
4. 实操全流程:从定义Workflow到接入CI/CD的完整闭环
4.1 第一步:定义你的第一个Workflow(以“生成README.md”为例)
别一上来就搞Vue组件,先用最简单的场景建立信心。我们以“根据package.json生成标准README.md”为例,展示如何5分钟跑通:
创建workflow-readme-generator.yaml:
name: "readme-generator" version: "v1.0.0" input_schema: type: object properties: package_json: type: string description: "package.json文件内容(JSON字符串)" steps: - id: "parse_package" type: "tool_call" tool: "json-parser" input: "{{ .input.package_json }}" - id: "generate_readme" type: "llm_call" model: "gpt-3.5-turbo-0125" system_prompt: | 你是一个开源项目README专家。根据以下package.json信息,生成Markdown格式README。 要求:1. 包含Badges(npm version, CI status);2. 安装命令用pnpm;3. 示例代码块用ts。 input: "{{ .steps.parse_package.output }}" # 强制结构化输出 output_schema: type: object properties: readme_content: type: string output_schema: type: object properties: readme_content: type: string注册JSON Parser工具(
tools/json-parser.go):func ParseJSON(input string) (map[string]interface{}, error) { var data map[string]interface{} if err := json.Unmarshal([]byte(input), &data); err != nil { return nil, fmt.Errorf("invalid json: %w", err) } return data, nil } // 注册到Tool Registry registry.Register("json-parser", ParseJSON, schema)启动Harness服务:
# 编译 go build -o harness ./cmd/harness # 启动(监听8080端口) ./harness --workflow-dir=./workflows --llm-api-key=$OPENAI_KEY发送测试请求:
curl -X POST http://localhost:8080/v1/workflows/readme-generator/run \ -H "Content-Type: application/json" \ -d '{ "input": { "package_json": "{\"name\":\"my-lib\",\"version\":\"1.0.0\",\"description\":\"A utility lib\"}" } }'你会立刻收到结构化响应:
{ "workflow_id": "readme-generator", "run_id": "run_abc123", "output": { "readme_content": "# my-lib\n\nA utility lib\n\n## Install\n`pnpm add my-lib`\n\n..." } }
实操心得:第一次跑通后,立刻去SQLite里查
step_logs表,确认generate_readme节点的output字段里有没有<script>标签——如果有,说明LLM没听指令,赶紧在system_prompt末尾加一句“禁止生成HTML/JS代码,只输出纯Markdown”。
4.2 第二步:接入Git Hooks,实现“提交即生成”
让Workflow活起来,必须让它嵌入开发者的日常。我们用Husky + lint-staged实现“每次git commit -m 'feat: add button'时,自动生成button组件的README和测试”。
安装Husky:
npx husky-init && npm install创建pre-commit钩子(
.husky/pre-commit):#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" # 检查是否修改了src/components/ if git diff --cached --quiet src/components/; then exit 0 fi # 获取所有新增/修改的.vue文件 COMPONENTS=$(git diff --cached --name-only --diff-filter=AM | grep '\.vue$' | xargs) for component in $COMPONENTS; do # 提取组件名(Button.vue -> Button) NAME=$(basename "$component" .vue) # 调用Harness生成README curl -s -X POST http://localhost:8080/v1/workflows/readme-generator/run \ -H "Content-Type: application/json" \ -d "{\"input\":{\"package_json\":\"{\\\"name\\\":\\\"$NAME\\\",\\\"description\\\":\\\"Auto-generated\\\"}\"}}" \ > "src/components/$NAME/README.md" done验证效果:新建
src/components/Alert.vue,执行git add . && git commit -m "feat: add alert",你会发现src/components/Alert/README.md已自动生成。
注意:生产环境绝不能用localhost调用。我们用Kubernetes Service暴露Harness,地址换成
http://harness-svc:8080,并通过Istio做mTLS双向认证,确保只有CI/CD和Git Hooks能访问。
4.3 第三步:深度集成CI/CD,打造无人值守发布流水线
这才是工程化的终极形态。我们以GitHub Actions为例,展示如何让Workflow接管从PR到发布的全过程:
# .github/workflows/component-release.yml name: Component Release Pipeline on: pull_request: types: [opened, synchronize] paths: - 'src/components/**' jobs: generate-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Generate README and Tests run: | # 遍历所有修改的.vue文件 for file in $(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep '\.vue$'); do dir=$(dirname "$file") name=$(basename "$file" .vue) # 调用Harness生成README curl -s -X POST https://harness.example.com/v1/workflows/readme-generator/run \ -H "Authorization: Bearer ${{ secrets.HARNESS_TOKEN }}" \ -d "{\"input\":{\"package_json\":\"{\\\"name\\\":\\\"$name\\\",\\\"description\\\":\\\"Auto-generated\\\"}\"}}" \ > "$dir/README.md" # 生成Jest测试 curl -s -X POST https://harness.example.com/v1/workflows/jest-test-generator/run \ -H "Authorization: Bearer ${{ secrets.HARNESS_TOKEN }}" \ -d "{\"input\":{\"component_path\":\"$file\"}}" \ > "$dir/__tests__/$name.test.ts" done - name: Run Tests run: npm test - name: Build and Lint run: npm run build && npm run lint publish-to-npm: needs: generate-and-test if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - name: Publish to NPM run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}这个流水线的关键在于:所有AI生成动作都在CI环境中执行,且生成物(README、Test)会作为PR的一部分提交,接受人工Code Review。我们严禁“AI生成后自动合并”,因为工程化不是消灭人,而是让人聚焦在更高价值的决策上——比如审查AI生成的测试用例是否覆盖了边界条件,而不是手写it('should render title', () => {...})。
5. 常见问题与排查技巧实录:那些文档里不会写的血泪经验
5.1 问题速查表:高频故障与根因定位
| 现象 | 可能根因 | 快速定位方法 | 解决方案 |
|---|---|---|---|
Workflow卡在某个step,日志显示timeout | Worker Pool熔断或LLM API不稳定 | 查worker_pool_metrics表,看failed_requests指标是否突增;检查step_logs里该step的duration是否接近超时阈值 | 在Harness配置里增加retry_delay: 1s,或临时切换到更稳定的模型(如GPT-4-Turbo替代Claude) |
生成的代码有语法错误,但output_schema校验通过 | LLM绕过结构化输出,返回了带注释的JSON | 查step_logs的raw_output字段(我们存了原始响应),看是否含//或<thinking> | 在system_prompt末尾加硬性约束:“禁止任何注释、思考过程、解释性文字,只输出纯JSON” |
| CI流水线里Harness调用失败,本地却正常 | 网络策略或认证失败 | 在CI job里加curl -v https://harness.example.com/healthz,看是否返回200 OK | 检查CI runner的网络出口IP是否在Harness的白名单里;确认HARNESS_TOKEN是否正确注入 |
| 生成的README里Badges链接指向错误仓库 | system_prompt里硬编码了仓库名,未动态注入 | 查step_logs的input字段,确认传入的package_json是否含repository字段 | 修改Workflow YAML,在system_prompt里用{{ .steps.parse_package.output.repository.url }}动态取值 |
| 多个并发Workflow任务互相污染 | State Snapshotter未隔离run_id | 查SQLite的step_logs表,看不同run_id的记录是否混在一起 | 确认Harness启动时指定了--isolation-mode=run_id,这是默认行为,但集群部署时需检查 |
5.2 独家避坑技巧:来自三年实战的“防坑清单”
技巧1:永远用
{{ .input | json }}代替{{ .input.field }}
初期我们常在system_prompt里写项目名是{{ .input.name }},结果当name字段为空时,LLM会收到项目名是,后面直接跟一堆乱码。后来改成{{ .input | json }},让LLM自己解析JSON,哪怕字段缺失,它也能看到{"name": null},处理更鲁棒。技巧2:给每个Workflow配“影子测试集”
我们维护一个shadow-tests/目录,里面放10个典型输入(如figma-url-1.json,package-json-2.json),每次Workflow更新,先跑harness test --workflow=readme-generator --test-dir=shadow-tests。这招帮我们提前捕获了73%的回归问题,比如某次升级GPT-4模型后,它开始在README里加emoji,而我们的output_schema没约束readme_content的字符集,影子测试立刻报警。技巧3:在
output_schema里预留debug_info字段
所有Workflow的output_schema最后都加一行:debug_info: type: object properties: llm_model_used: type: string prompt_tokens: type: integer completion_tokens: type: integer这样当生成质量下降,我们可以直接查
debug_info.llm_model_used,确认是不是被熔断切到了低配模型;查prompt_tokens,确认是不是输入太长导致LLM“看不清重点”。技巧4:用Git Submodule管理Workflow定义
所有workflows/*.yaml不放在主代码库,而是单独建agentic-workflows仓库,主库通过git submodule add https://git.example.com/agentic-workflows引入。这样,当某业务线要定制自己的Workflow(如专为金融组件加合规检查),他们forkagentic-workflows,改自己的分支,主库git submodule update --remote即可升级,互不干扰。
5.3 性能调优实录:如何把单任务耗时从2分钟压到20秒
我们曾为一个“生成全栈CRUD API”的Workflow头疼很久:它要调用Figma Parser、OpenAPI Generator、TypeScript Interface Generator、Prisma Schema Generator、Next.js Route Generator共6个LLM步骤,平均耗时118秒。优化过程如下:
Step 1:识别瓶颈
查step_logs的duration列,发现prisma-schema-generator平均耗时42秒,远超其他步骤(均<8秒)。进一步查它的raw_output,发现LLM在生成Prisma Schema时,反复重试了3次,因为output_schema要求fields数组里每个字段必须有type和default,而LLM总漏掉default。Step 2:精准打击
不改LLM,改output_schema:把default字段设为"nullable": true,并在system_prompt里加一句:“若字段无默认值,请显式写default: null”。同时,在Worker Pool里给这个step加max_retries: 1(原来为3)。Step 3:并行化
发现typescript-interface-generator和prisma-schema-generator无依赖关系,把它们在YAML里调整为同级节点,Harness自动并行执行。Step 4:缓存中间态
对figma-parser这种纯计算、输入不变输出必不变的Tool,加Redis缓存:key = "figma:" + md5(figma_url),TTL设为1小时。实测缓存命中率87%,省下大量解析时间。
优化后,该Workflow平均耗时降至19.3秒,P95延迟<25秒。更重要的是,失败率从28%降到0.7%——因为减少了重试次数,也就减少了LLM胡说八道的机会。
6. 工程化之外的思考:当AI成为“新同事”,我们该如何协作
在实验室带团队的这三年,我越来越确信:Agentic Harness Workflow的价值,远不止于提升代码生成效率。它本质上是在重新定义人与AI的协作契约。以前,AI是“问答机器”——你问,它答;现在,它是“流程协作者”——你定义目标、划定边界、设定验收标准,它在轨道内自主决策、执行、反馈。我们不再问“这个组件怎么写”,而是问“这个组件的验收标准是什么?哪些部分必须人工审核?哪些可以全自动?”。
这种转变带来两个深层影响:一是责任边界的清晰化。当一个AI生成的组件在生产环境出Bug,我们能精准定位到是generate_sfcstep的output_schema约束不足,还是eslint-validator的规则有漏洞,而不是笼统地说“AI不行”。二是工程师能力模型的进化。未来最抢手的,不是最会写for循环的人,而是最擅长设计Workflow、编写精准system_prompt、构建可靠验证规则的人——他们像交响乐指挥,不亲自拉小提琴,但能让所有乐器(LLM、工具、验证器)奏出和谐乐章。
最后分享一个小技巧:每周五下午,我们团队会做“Workflow复盘会”。不聊技术细节,只做两件事:1)挑一个本周生成失败的任务,所有人一起看step_logs,像侦探一样还原“AI当时看到了什么、想到了什么、为什么错了”;2)给Workflow加一条新规则,比如“当LLM输出含TODO时,自动标记为NEED_HUMAN_REVIEW”。三年下来,我们的Workflow库从最初的3个,增长到87个,覆盖了从UI组件、API接口、数据库迁移脚本到技术文档生成的全场景。而工程师们,终于有更多时间去思考“我们到底要解决什么问题”,而不是“这个问题该怎么写代码”。
这个框架没有魔法,它只是把工程思维,一丝不苟地 applied 到了AI身上。