资讯动态

Loop Engineering与Harness:企业级LLM应用闭环实战

发布时间:2026/10/13 2:08:33 来源:尧图企业网站定制
经常会在B站看到各种“LLM Agent 项目解析”视频Demo 能跑、对话看起来智能可真放到企业里往往很快就撑不住了。为什么因为企业级 LLM 应用不是“调通一个模型”就行它需要一套完整的反馈闭环开发、评估、部署、监控、迭代每一环都要能度量、能自动化。这正是今天要聊的 Loop Engineering 与 Harness 组合要解决的问题。本文会从概念入手梳理这两个词到底是什么然后以一个真实的企业级 RAG 知识库问答服务为例带你完整走一遍从本地编码、自动化评测到 Harness Pipeline 持续交付的闭环流程。无论你是后端开发、MLOps 工程师还是刚入门的 LLM 应用开发者都能从里面找到可以直接落地的思路与代码。1. Loop Engineering 与 Harness 到底是什么1.1 Loop Engineering把 LLM 应用当作“循环系统”来经营Loop Engineering 直译是“循环工程”在生成式 AI 工程领域里它强调的是把大模型应用从“一次性开发”变成“持续迭代的循环系统”。传统的软件开发是“编码 → 测试 → 发布 → 维护”但 LLM 应用有一个更复杂的变量模型答案不可完全预测。同一个 Prompt模型可能今天答得好明天换了个上游模型版本就答偏了文档更新后检索系统可能带回更多噪音。因此我们需要把一个 LLM 应用拆成多个“环”开发环设计 Prompt、写检索逻辑、搭建 Agent 流程。评测环用评测集和指标判断当前版本是好是坏。部署环把通过评测的版本发布到线上环境。监控环持续采集线上数据观察回答质量与用户反馈。数据回流环将线上 Bad Case 转为新测试数据进入下一轮开发。这几个环不是孤立的而是持续转动。Loop Engineering 的目标就是让每个环都能被自动化、被量化最终让模型应用越迭代越稳定。理解这一点你就能明白为什么现在很多企业不再追捧“单点 Demo”而是关注可重复、可度量的交付闭环。1.2 Harness一个词两个层面Harness 这个词在技术领域有两个常见指向很多第一次接触的同学会混淆。第一个是 Harness CI/CD 平台。这是一套企业级持续交付解决方案支持 Pipeline as Code、灰度发布、云原生部署、审批流和安全治理。你可以把它理解成“代码从提交到上线的流水线总部”在 LLM 项目中负责构建、测试、评测、部署的自动化串联。第二个是 lm-evaluation-harness这是 EleutherAI 开源的大语言模型评测框架。它提供了标准化的方式把评测集、模型、生成参数组合起来输出统一指标比如准确率、F1、BLEU 等。在企业的 LLM 交付里lm-evaluation-harness 经常作为“评测环”的引擎集成到 CI/CD 流程中。所以当有人说“用 Harness 跑 LLM 项目”时他可能指两件事一是用 Harness 平台调度整个交付管线二是用 lm-evaluation-harness 做自动化评测。真正企业级的做法是把两者打通用 Harness Pipeline 调用 lm-evaluation-harness跑完评测再决定是否部署。1.3 为什么要组成企业级闭环如果你只是自己写脚本调用 API当然不需要这一套。但一旦涉及团队协作、多环境部署、模型版本切换、合规审计问题就会冒出来谁来保证新的 Prompt 不会把旧能力改坏如何让一次模型升级的收益和风险可视化出了问题之后怎么快速回滚到上一个稳定版本团队其他成员如何复现你的评测结果这些问题单靠人工记录是低效且容易出错的。Loop Engineering 给出的是方法Harness 提供的是工具链两者结合才能把“好想法”变成“稳定上线的系统”。2. 环境准备与版本说明开始实战之前先明确环境。以下版本是以当前较新的稳定版为例实际项目请以你依赖的真实版本为准不要看到文档就直接复制。环境项推荐值说明操作系统Ubuntu 22.04 或 macOS 14其他 Linux 发行版也可Python3.10 / 3.11LLM 相关依赖对 3.11 兼容性较好模型接口OpenAI 兼容接口可对接通义千问、DeepSeek、智谱等向量库FAISS本地生产环境可用 PostgreSQL pgvectorLLM 评测框架lm-evaluation-harness安装后提供 lm_eval 命令CI/CD 平台Harness CI/CD本文用 Pipeline as Code 示范建议使用虚拟环境隔离依赖python -m venv .venv source .venv/bin/activate pip install --upgrade pip本文示例代码基于 FastAPI LangChain 构建一个 RAG 问答服务。先用 FAISS 做本地向量检索这样教程不需要额外部署数据库拿到就能跑。生产环境再替换为 pgvector、Milvus 或 Elasticsearch 等专业向量检索方案。3. 核心概念拆解评测、反馈与持续交付3.1 评测不是简单跑个准确率很多人以为评测就是“拿测试集问一遍看答对多少”。但在企业级 LLM 应用中评测要分层第一层是离线评测。在发布前使用固定评测集比如 GSM8K 数学推理、MMLU 通用知识或者自建的领域 QA 集。这层决定了“这个版本能不能进入部署流程”。第二层是在线监控。在真实流量中采集用户 query、模型 answer、用户反馈对比阈值发现异常立即预警。第三层是回归测试。每次改动 Prompt、升级模型或更换向量库后都重新跑一遍历史 Bad Case 集防止“修了这个问题坏了那个问题”。lm-evaluation-harness 主要承担第一层和第三层。它的工作方式很直接声明一个模型接入方式声明一个或多个评测任务设定生成参数最后汇总指标。lm_eval --model local_completions \ --model_args modelqwen-plus,base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 \ --tasks gsm8k \ --num_fewshot 5 \ --output_path ./eval_results这里的关键点是local_completions。它让 lm-evaluation-harness 连接任意 OpenAI 兼容的本地或远程模型服务。model_args里传的是模型名和 API 地址--tasks指定评测集--num_fewshot控制 few-shot 示例个数--output_path决定结果输出目录。3.2 Harness 流水线如何编排 LLM 项目Harness Pipeline 的核心思想是“阶段串联”。在 LLM 项目中典型阶段如下阶段 A检出代码。阶段 B安装依赖。阶段 C运行单元测试。阶段 D运行 LLM 离线评测。阶段 E把通过评测的镜像部署到目标环境。由于模型评测存在随机性和时间成本通常不会在每个 commit 都跑完整评测集而是通过“触发条件”和“阈值校验”控制流程。比如只有主分支合并、或带有eval标签的 PR 才触发评测评测结果通过指标阈值后再继续部署。3.3 阈值策略谁来决定能不能上线LLM 应用最容易踩的坑是没有阈值策略。没有阈值评测数据就只是“参考一下”无法成为 Pipeline 的卡点。推荐的做法是对每个评测任务设置 baseline 指标例如准确率必须大于等于上一版本。对关键 Bad Case 集设置强校验任何一个用例失败都可能阻断发布。对小规模 A/B 实验使用置信区间而不是单点指标比较。这套策略可以通过 Harness 的条件判断、API 回调或外部脚本实现。后面实战中我们会用 shell 脚本方式做一个简化版评测结果通过后才执行部署阶段。4. 企业级实战构建一个 RAG 知识库问答服务下面我们完整实现一个“企业内部运维文档问答服务”并把它接入 Harness Pipeline。项目假设你有一批 Markdown 格式的运维手册需要让员工用自然语言提问系统自动从文档中检索答案同时保留来源引用。4.1 项目结构设计好的工程结构能让 Pipeline 阶段的职责更清晰我们按下面的目录组织代码llm-rag-service/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── core/ │ │ ├── __init__.py │ │ ├── config.py # 环境配置 │ │ └── llm.py # LLM 与 Embedding 客户端 │ └── rag/ │ ├── __init__.py │ ├── ingest.py # 文档灌库逻辑 │ └── retriever.py # 检索器与链路组装 ├── data/ │ └── docs/ # 知识文档Markdown ├── scripts/ │ ├── ingest.py # 灌库入口脚本 │ ├── start_api.sh # 启动服务脚本 │ └── run_llm_eval.sh # 评测脚本 ├── tests/ │ └── test_retriever.py # 最小单元测试 ├── requirements.txt ├── .env.example └── README.md这种结构把“配置”、“AI 链路”、“API 服务”、“脚本”分开。Pipeline 在 CI 阶段只需要安装依赖、跑指定脚本即可不需要关心业务代码内部细节。4.2 初始化依赖创建requirements.txt内容如下fastapi0.115.6 uvicorn0.34.0 langchain0.3.14 langchain-community0.3.14 langchain-openai0.3.4 langchain-text-splitters0.3.3 faiss-cpu1.9.0.post1 openai1.59.7 pydantic2.10.4 python-dotenv1.0.1 pytest8.3.4安装命令pip install -r requirements.txt如果你的模型服务不需要 OpenAI SDK 直连LangChain 底层其实也是封装了 OpenAI 兼容协议所以openai包是必要依赖。版本号建议以实际安装为准不同小版本接口可能有细微变化。4.3 编写配置与模型客户端环境变量统一放在.env文件里便于本地开发与 CI 环境注入# .env.example LLM_MODELqwen-plus LLM_BASE_URLhttps://dashscope.aliyuncs.com/compatible-mode/v1 LLM_API_KEYyour_api_key EMBEDDING_MODELtext-embedding-3-small EMBEDDING_BASE_URLhttp://localhost:8000/v1 EMBEDDING_API_KEYEMPTY这里把“生成模型”和“Embedding 模型”拆成两组配置因为生产环境中两者很可能是不同的服务或不同的供应商。配置读取逻辑独立成app/core/config.py# 文件路径app/core/config.py import os from dotenv import load_dotenv load_dotenv() LLM_MODEL os.getenv(LLM_MODEL, qwen-plus) LLM_BASE_URL os.getenv(LLM_BASE_URL, http://localhost:8000/v1) LLM_API_KEY os.getenv(LLM_API_KEY, EMPTY) EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, text-embedding-3-small) EMBEDDING_BASE_URL os.getenv(EMBEDDING_BASE_URL, http://localhost:8000/v1) EMBEDDING_API_KEY os.getenv(EMBEDDING_API_KEY, EMPTY)模型客户端独立封装成app/core/llm.py# 文件路径app/core/llm.py from langchain_openai import ChatOpenAI, OpenAIEmbeddings from app.core import config llm ChatOpenAI( modelconfig.LLM_MODEL, base_urlconfig.LLM_BASE_URL, api_keyconfig.LLM_API_KEY, temperature0.1, ) embeddings OpenAIEmbeddings( modelconfig.EMBEDDING_MODEL, base_urlconfig.EMBEDDING_BASE_URL, api_keyconfig.EMBEDDING_API_KEY, )注意temperature设置为 0.1用于降低回答随机性。评测和生产环境都应尽量固定生成参数否则每次结果可能都不一样。4.4 编写灌库逻辑灌库的作用是读入文档 → 切片 → 生成向量 → 写入向量库。对于企业内部知识库切片策略直接影响检索质量。切片太大会引入噪音切片太小会丢失上下文这里先用chunk_size800、chunk_overlap120示例。# 文件路径app/rag/ingest.py from pathlib import Path from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.vectorstores import FAISS from app.core.llm import embeddings def load_docs(data_dir: str data/docs): docs [] data_path Path(data_dir) for ext in (*.md, *.txt): for file_path in data_path.glob(ext): docs.extend(TextLoader(str(file_path), encodingutf-8).load()) return docs def build_index(data_dir: str data/docs, index_dir: str data/faiss_index): docs load_docs(data_dir) if not docs: raise RuntimeError(f未在 {data_dir} 下找到任何文档) splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap120, separators[\n\n, \n, 。, , , ], ) splits splitter.split_documents(docs) vectorstore FAISS.from_documents(splits, embeddings) vectorstore.save_local(index_dir) print(f索引完成共生成 {len(splits)} 个切片) return len(splits)build_index函数被独立出来方便 PyTest 调用也方便后续脚本调用。建议不要把灌库逻辑直接写在脚本入口里否则复用时只能靠复制代码。灌库脚本# 文件路径scripts/ingest.py import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from app.rag.ingest import build_index if __name__ __main__: build_index()运行python scripts/ingest.py预期输出类似索引完成共生成 42 个切片4.5 编写检索器与问答服务检索器负责加载索引、设定检索参数并组装简单的问答链路# 文件路径app/rag/retriever.py from langchain_community.vectorstores import FAISS from app.core.llm import embeddings, llm INDEX_DIR data/faiss_index vectorstore FAISS.load_local( INDEX_DIR, embeddings, allow_dangerous_deserializationTrue, ) retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4}, ) def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) def ask(question: str) - dict: docs retriever.invoke(question) context format_docs(docs) prompt_text ( 请基于以下资料回答用户问题不要编造资料中不存在的信息。\n\n f资料:\n{context}\n\n f问题: {question}\n回答: ) answer llm.invoke(prompt_text) return { answer: answer.content, sources: [d.metadata.get(source, ) for d in docs], }为什么用allow_dangerous_deserializationTrue新版 LangChain 对 FAISS 的load_local默认开启反序列化保护因为 FAISS 索引文件可能是攻击向量。在教程环境你可以这样写但生产环境必须确认索引文件来源可信否则建议重新灌库而不是加载未知文件。FastAPI 服务# 文件路径app/main.py from fastapi import FastAPI from pydantic import BaseModel, Field from app.rag.retriever import ask app FastAPI(titleLLM RAG Service, version0.1.0) class ChatRequest(BaseModel): question: str Field(min_length1, max_length2000) class ChatResponse(BaseModel): answer: str sources: list[str] app.post(/chat, response_modelChatResponse) def chat(req: ChatRequest): return ask(req.question) app.get(/healthz) def healthz(): return {status: ok}启动服务uvicorn app.main:app --host 0.0.0.0 --port 8000本地验证curl -X POST http://localhost:8000/chat \ -H Content-Type: application/json \ -d {question: 如何重启订单服务}预期返回 JSON包括回答文本和来源文档列表。此时一个最小可运行的 RAG 服务就完成了。4.6 接入 lm-evaluation-harness 自动化评测我们写一个评测脚本让 Pipeline 阶段能够一键触发评测# 文件路径scripts/run_llm_eval.sh #!/bin/bash set -euo pipefail TASKS${1:-gsm8k} OUTPUT_DIReval_results mkdir -p $OUTPUT_DIR lm_eval --model local_completions \ --model_args model${LLM_MODEL},base_url${LLM_BASE_URL} \ --tasks $TASKS \ --num_fewshot 5 \ --output_path $OUTPUT_DIR注意脚本中读取了LLM_MODEL和LLM_BASE_URL两个环境变量。在 Harness Pipeline 中我们可以直接从 Stage 变量传入。set -euo pipefail很关键一旦评测命令返回值非 0整个脚本就会失败Harness 会自动判定该阶段失败从而阻断后续部署流程。这比“看日志再人工判断”要可靠得多。如果你有自建的领域 QA 评测集可以在lm_eval的白名单配置中添加自定义任务然后通过--tasks指定。本文不展开因为不同版本的 lm-evaluation-harness 自定义任务写法略有差异建议先去官方仓库查看对应版本文档。4.7 编写 Harness Pipeline 串联全流程下面是一份简化版的 Harness Pipeline YAML。它的目标是代码提交后自动构建、跑单测、跑模型评测通过后再部署到测试环境。# 文件路径.harness/llm_rag_pipeline.yaml pipeline: name: llm-rag-pipeline identifier: llm_rag_pipeline projectIdentifier: LLMPlatform orgIdentifier: default properties: ci: codeRepo: connectorRef: account.github_connector repoName: llm-rag-service build: type: branch spec: branch: trigger.branch stages: - stage: name: build_and_test identifier: build_and_test type: CI spec: cloneCodebase: true execution: steps: - step: name: Install Dependencies type: Run spec: shell: Sh command: pip install -r requirements.txt - step: name: Run Unit Tests type: Run spec: shell: Sh command: pytest tests/ --maxfail1 --junitxmltest-report.xml - step: name: Run LLM Evaluation type: Run spec: shell: Sh command: | chmod x scripts/run_llm_eval.sh scripts/run_llm_eval.sh gsm8k - step: name: Build Docker Image type: BuildAndPushDockerRegistry spec: connectorRef: account.dockerhub_connector repo: yournamespace/llm-rag-service tags: pipeline.variables.IMAGE_TAG - stage: name: deploy_to_test identifier: deploy_to_test type: Deploy spec: serviceRef: llm_rag_service environmentRef: test_env execution: steps: - step: name: Trigger Test Deployment type: Run spec: shell: Sh command: | kubectl set image deployment/llm-rag-service \ llm-rag-serviceyournamespace/llm-rag-service:pipeline.variables.IMAGE_TAG variables: - name: IMAGE_TAG type: String value: latest需要再次说明这是演示用的简化 YAML。真实 Harness 项目里connectorRef、serviceRef、environmentRef都需要在平台中提前创建YAML 细节也会随版本变化。建议在 Harness 控制台先通过 UI 生成一个基础模板再对照模板把评测步骤嵌入进去不要直接复制陌生 YAML。Pipeline 的核心价值在于评测不在本地悄悄跑而是作为发布前置卡点。评测失败部署阶段不会开始评测通过镜像会自动推送到仓库并更新测试环境。你就拥有了一个“可重复、可审核、可追溯”的发布闭环。4.8 运行与验证流程总结本地完成python scripts/ingest.py灌库。本地启动uvicorn app.main:app --port 8000并用 curl 验证接口。在 Harness 中创建llm-rag-pipeline把代码推送到 Git 仓库触发流水线。观察流水线日志依赖安装、单元测试、lm_eval评测、镜像构建、部署。评测阶段失败时直接修复代码或 Prompt再次提交重跑。这时候你的项目已经不只是“一个 RAG 服务”而是一个有评测门禁、有自动部署路径的 LLM 工程系统。5. 常见问题与排查思路在实际跑通的过程中初学者最常遇到下面几类问题。问题现象常见原因解决思路lm_eval 报错找不到模型模型服务base_url 或 model 参数配置错误先用 curl 验证模型接口是否可用再检查 model_argsFAISS load_local 报反序列化错误LangChain 版本默认禁止反序列化显式传allow_dangerous_deserializationTrue并确认文件来源可信调用模型接口报 OpenAIConnectionError网络不通、API Key 错误、SSL 证书异常分端排查curl 接口、检查 env、确认白名单Pipeline 评测阶段超时评测集过大或模型响应过慢缩小评测集、增加 Pipeline 超时时间、使用更快的基础模型评测结果不稳定temperature 未固定、few-shot 顺序随机将 temperature 设为 0固定 seed对同一版本重复评测取均值同一问题多次回答不一致没有使用稳定 Prompt 模板把 Prompt 固定为模板禁止上下文随意拼接Pipeline 无法连接 Git 仓库连接器权限不足在 Harness 中单独测试 connector确认 token 与 IP 白名单排查时建议遵循“先本地、后线上”的原则。先在本地用同样的命令跑一遍如果本地能跑通问题大概率出在环境变量、网络或平台配置上如果本地也跑不通那就要检查代码逻辑和依赖版本。还有一点容易被忽略日志。在脚本中多打一行echo 当前模型: ${LLM_MODEL}在 Pipeline 日志里就能立刻看出环境变量是否注入成功。LLM 工程的错误往往不是“代码报错”而是“配置不匹配”。6. 最佳实践与工程建议6.1 版本管理要覆盖 Prompt 与评测集普通代码可以用 Git 管理但 LLM 项目还要额外管理 Prompt 模板、评测数据集、向量库索引版本。建议做法是Prompt 模板放入独立目录并使用版本号。评测集放 Git LFS 或独立对象存储随手记录每次变更。向量索引重建时在平台上标记数据版本、Embedding 模型版本和文档源版本。否则当线上效果发生变化时你很难定位是模型升级导致的、文档更新导致的还是 Prompt 被谁偷偷改了。6.2 评测要分场景不要一套指标打天下企业内部知识库问答单纯用 GSM8K 这种通用数学评测并不能完全反映业务效果。你需要三类补充领域检索评测给定 query检查检索 Top K 是否包含正确文档。合规安全评测检查模型是否回答隐私、越权或敏感内容。Bad Case 回归评测每次新版本先跑历史失败案例。建议把 Bad Case 回归评测做成强门禁。也就是说历史出过错的问题新版本必须全部通过才允许继续发布。6.3 用最小权限原则保护模型凭证与生产环境在 Harness Pipeline 中API Key 不应该硬编码在 YAML 里。所有敏感信息都应该使用 Harness 的 Secret Management 功能并通过${secret}引用。生产环境的部署阶段更建议加入人工审批哪怕你的评测和测试环境已经全自动生产发布仍然要保留“可干预”的环节。同时allow_dangerous_deserializationTrue这种参数只应在确认数据来源可信时开启。如果你从外部下载了 FAISS 索引文件来加载等于在你看不见的地方执行了反序列化代码这是非常危险的操作。企业环境中索引最好每次由自己的灌库流程生成。6.4 监控不仅是看延迟更要看质量上线之后Loop Engineering 的“监控环”才开始发挥价值。除了传统的接口延迟、错误率还要关注用户对回答的点踩率。无答案率模型明确表示不知道。检索命中率下降趋势。模型返回格式异常率。这些信号最好统一上报到日志或可观测平台。当质量指标低于阈值时自动创建 Bad Case 回流任务下一轮开发就有明确目标。6.5 控制成本引入分层模型策略LLM 应用生产环境的一大现实问题是成本。不是每一个 query 都需要最强模型。你可以按问题难度分流简单问题走小模型复杂问题才调用大模型。在 Pipeline 评测阶段也可以先跑小规模评测集快速筛选再跑完整评测集减少等待时间和 API 费用。7. 总结与下一步学习路线这篇文章的核心不只是教你启动一个 RAG 服务而是帮你建立一条“开发 → 评测 → 部署 → 监控 → 回流”的工程闭环。Loop Engineering 负责提供方法论Harness 负责把方法论变成可执行的自动化流程lm-evaluation-harness 则充当模型的质检员。如果你照着文章跑通了一遍下一步可以按顺序深入学习把本地 FAISS 替换成 PostgreSQL pgvector研究索引与权限控制。扩大评测集自建领域 QA 评测集并接入 Harness Pipeline。为线上服务接入使用日志搭建 Bad Case 自动回流流程。研究灰度发布策略先让 5% 流量走新模型对比指标后再全量。实际项目中最需要优先关注的不是模型能力本身而是发布风险。在一次模型升级前提前确认评测基线、回滚预案、监控告警都准备好了再点击发布按钮。希望这篇实战解析能帮你在 2026 年的 LLM 工程化道路上少踩几个坑。如果觉得有收获可以先收藏备用后面我会继续拆解更多企业级 AI 工程实战项目。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑