
之前做 AI 应用联调时最头疼的不是模型效果不理想而是缺少一套系统化的测试评估工具。临时脚本写了一个又一个用例管理靠文件夹堆结果对比靠肉眼盯跑完一轮连“哪个版本更稳”都回答不出来。后来我把这套流程整理成了 Harness 方式——一个轻量、可扩展、围绕 DeepSeek 模型 API 的自动化测试与评估框架。本文把这套完整方案拆解成可落地的教程包含安装配置、核心模块设计、完整代码和排错清单无论你是在做应用开发、Prompt 调优还是模型选型对比都可以直接用。在正式进入代码之前先说明一点本文所讲的 Harness 不是某个特定的官方软件包名称而是一类“大模型测试评估工程化框架”的统称。下面我将带你从头搭建一套属于自己的 DeepSeek Harness。1. 什么是 DeepSeek Harness1.1 Harness 在 LLM 测试中的含义Harness 这个词在软件工程里一般理解为“测试脚手架”或“测试夹具”。在传统后端开发中它指的是驱动被测代码运行、收集运行结果、判定测试是否通过的一套基础设施。到了大模型时代这套概念被自然地迁移到了 LLM 评估场景中。你的被测对象不再是一个函数、一个接口而是一个通过 API 暴露出来的大语言模型。此时 Harness 需要做几件事管理测试用例包括输入、期望输出、判定规则。调用模型接口将用例中的输入发送给模型。捕获模型输出记录耗时、Token 消耗等运行指标。执行断言评估判断模型输出是否符合预期。生成测试报告以结构化方式展示测试结果。所以DeepSeek Harness 本质上就是“面向 DeepSeek 模型 API 的自动化测试与评估框架”。它解决的核心痛点是当模型版本升级、Prompt 调整、参数变化时如何能够快速、可重复、低主观性地判断“变化是好是坏”。1.2 为什么需要专门一套 Harness用临时脚本当然也能调 API、打日志。但一旦用例数量超过几十条或者测试对象不只一个 Prompt 时临时脚本就会暴露出几个明显问题用例没有统一格式散落在代码里难以维护。断言逻辑和业务代码混在一起改一个标准就要改多处。没有累计对比上一轮和这一轮的结果差异无法量化。没有输出结构化报告回归测试形同虚设。Harness 的核心价值就是把“测什么、怎么测、怎么判、怎么报”四件事解耦让测试用例可以单独维护评估逻辑可以复用报告可以自动化产出。1.3 本文方案的技术边界需要说明的是目前公开渠道中并不存在一个名为“Deepseek Harness”的官方统一框架不同团队会根据自身需求实现不同的评估工具。为了不误导读者本文采用纯 Python 实现基于 DeepSeek 官方提供的 OpenAI 兼容接口来构建一套最小可用的 Harness 框架。这种实现方式的优势在于不依赖特定封闭框架代码逻辑完全可控。只要替换 Base URL 和模型名称就能兼容其他 OpenAI 兼容模型服务。结构清晰方便你按项目需要扩展新的评估维度。2. 环境准备与版本说明2.1 运行环境本文示例以常见的 Python 3.10 环境为例操作系统不限Windows / macOS / Linux 都可以运行。你需要先确认本机已经安装 Python 3.10 或以上版本python --version如果提示找不到 python可能是 Python 未安装或未加入环境变量需要先自行安装。2.2 安装依赖本文的 Harness 核心依赖有两个requests用于直接调用 HTTP API避免引入过重的 SDK。openaiDeepSeek 官方文档推荐使用 OpenAI SDK 兼容方式接入用它更省事。安装命令如下pip install requests openai如果你的网络环境使用国内镜像源可以加上镜像参数加快下载pip install requests openai -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 API Key 与访问配置使用 DeepSeek API 需要一个有效的 API Key。你需要到 DeepSeek 开放平台注册账号然后在控制台创建 API Key。创建完成后建议把 Key 配置为环境变量不要直接写在代码里。在 Linux / macOS 下export DEEPSEEK_API_KEYsk-xxxx在 Windows PowerShell 下$env:DEEPSEEK_API_KEYsk-xxxx在 Python 代码中通过 os.environ 读取import os api_key os.environ.get(DEEPSEEK_API_KEY) if not api_key: raise RuntimeError(请先设置 DEEPSEEK_API_KEY 环境变量)2.4 模型版本选择DeepSeek 官方提供的模型名称会随着版本迭代变化常见的有 deepseek-chat 和 deepseek-reasoner 两类。其中 deepseek-chat 适合普通对话和大多数文本生成任务deepseek-reasoner 适合复杂推理场景。本文示例默认使用 deepseek-chat。具体可用的模型名以你在 DeepSeek 开放平台控制台看到的实际列表为准不必死记硬背。3. 核心原理拆解一个 Harness 由哪几层组成在照抄代码之前值得先花几分钟理解 Harness 的分层设计。这是整套框架能保持灵活的关键。3.1 模型调用层模型调用层负责与 DeepSeek API 通信将测试输入转换成模型请求并返回模型输出。需要关注三个点请求地址DeepSeek 兼容 OpenAI 接口Base URL 为 https://api.deepseek.com。请求参数主要是 model、messages、temperature、max_tokens 等。响应解析从响应结构中提取 content 字段、usage 字段和耗时信息。这一层应该保持纯粹只做通信和解析不参与业务判断。这样当模型服务从 DeepSeek 切换到其他兼容服务时只需要修改这一层。3.2 测试用例层测试用例层负责定义“测什么”。一条典型的测试用例至少应该包含用例 ID唯一标识。输入内容发送给模型的 prompt 或用户消息。期望结果可选用于比对或参考。判定规则指定使用哪种评估方式。元信息所属分类、创建时间等便于统计。用例推荐的存储格式为 JSON 文件而不是硬编码在 Python 脚本里。原因很简单JSON 可以被非开发人员理解和维护也便于做版本管理。3.3 评估与断言层评估与断言层是 Harness 的核心决定模型输出“好不好”或者“对不对”。根据任务类型可以选择不同的评估策略规则匹配适合答案格式固定、关键词明确的场景比如分类结果、关键词抽取。数值比较适合数学计算、数值抽取场景。参考答案比对适合有标准答案的问答。模型打分适合开放式生成任务由另一个模型或同一个模型对输出打分。人工评估适合主观性强的任务Harness 输出待评估列表人工在报告上标记。本文示例以规则判定和参考答案比对为主模型打分作为扩展思路在最佳实践部分讨论。3.4 报告输出层报告输出层把测试结果汇总成结构化数据。最简单的方式是输出 Markdown 表格方便直接在 CSDN 或 GitHub 上阅读。进阶方式包括输出 JSON 报告方便程序继续处理或者输出 HTML 报告方便共享给团队成员。报告至少应包含总体通过率。每条用例的通过状态。模型输出的摘要或完整内容。消耗的 Token 数、耗时。失败用例的失败原因。4. 完整实战搭建一个可运行的 DeepSeek Harness下面进入正题从零开始搭建一套可运行的 Harness。4.1 创建项目结构建议按下面结构创建目录deepseek-harness/ ├── requirements.txt ├── config.py ├── model_client.py ├── evaluate.py ├── run_harness.py └── testcases/ ├── example_tasks.json └── history/其中requirements.txt 声明 Python 依赖。config.py 存放公共配置。model_client.py 封装模型调用。evaluate.py 实现评估逻辑。run_harness.py 主入口。testcases 目录存放 JSON 测试用例文件。先创建目录mkdir deepseek-harness cd deepseek-harness mkdir testcases testcases/history4.2 添加依赖文件新建 requirements.txtopenai1.0.0 requests2.28.0安装依赖pip install -r requirements.txt4.3 编写配置文件 config.pyconfig.py 负责统一管理模型名称、API 地址、网络超时等参数。# 文件路径deepseek-harness/config.py import os # DeepSeek 兼容 OpenAI 接口 BASE_URL os.environ.get(DEEPSEEK_BASE_URL, https://api.deepseek.com) API_KEY os.environ.get(DEEPSEEK_API_KEY, ) # 默认使用的模型按平台实际可用模型调整 DEFAULT_MODEL os.environ.get(DEEPSEEK_MODEL, deepseek-chat) # 请求参数 TEMPERATURE float(os.environ.get(HARNESS_TEMPERATURE, 0.3)) MAX_TOKENS int(os.environ.get(HARNESS_MAX_TOKENS, 1024)) TIMEOUT int(os.environ.get(HARNESS_TIMEOUT, 60)) # 报告输出路径 REPORT_PATH os.environ.get(HARNESS_REPORT_PATH, report.md)这里把所有可变参数都提升到环境变量是为了后续接入 CI 时不用改代码只改环境变量即可。4.4 编写模型调用模块 model_client.pymodel_client.py 使用 openai 库封装 DeepSeek 调用。它的职责只有一个输入一段用户消息输出模型返回的文本。# 文件路径deepseek-harness/model_client.py from openai import OpenAI from config import BASE_URL, API_KEY, DEFAULT_MODEL, TEMPERATURE, MAX_TOKENS, TIMEOUT class DeepSeekClient: 封装 DeepSeek 模型调用的客户端 def __init__(self): if not API_KEY: raise RuntimeError(请先设置 DEEPSEEK_API_KEY 环境变量) self.client OpenAI(api_keyAPI_KEY, base_urlBASE_URL, timeoutTIMEOUT) self.model DEFAULT_MODEL def chat(self, user_content: str, system_content: str None) - dict: messages [] if system_content: messages.append({role: system, content: system_content}) messages.append({role: user, content: user_content}) response self.client.chat.completions.create( modelself.model, messagesmessages, temperatureTEMPERATURE, max_tokensMAX_TOKENS, ) # 提取结果文本 content response.choices[0].message.content.strip() # 提取 token 用量 usage response.usage return { content: content, prompt_tokens: usage.prompt_tokens, completion_tokens: usage.completion_tokens, total_tokens: usage.total_tokens, }这里把返回结果设计成字典是因为评估层需要同时用到文本结果和 Token 消耗数据。如果只返回字符串后面统计成本时还需要重新解析比较麻烦。4.5 编写测试用例加载模块测试用例文件 testcases/example_tasks.json 内容如下{ tasks: [ { id: case-001, category: cloze, prompt: 请补全下面的句子只输出答案秋天的校园___。, expected: [], keywords: [落叶, 桂花], rule: keyword_match }, { id: case-002, category: extract, prompt: 从下面这段话中提取城市名称只输出城市名多个城市用逗号分隔。文本小明从北京出发先飞到上海然后坐高铁去了杭州。, expected: [北京, 上海, 杭州], keywords: [], rule: exact_match }, { id: case-003, category: cloze, prompt: 请补全下面的句子只输出答案冬天到了___。, expected: [], keywords: [雪花, 寒风], rule: keyword_match } ] }写一个 load_cases.py 用于加载用例# 文件路径deepseek-harness/load_cases.py import json from typing import List, Dict def load_test_cases(path: str) - List[Dict]: 从 JSON 文件加载测试用例 with open(path, r, encodingutf-8) as f: data json.load(f) return data[tasks] if __name__ __main__: cases load_test_cases(testcases/example_tasks.json) print(f共加载 {len(cases)} 条测试用例)4.6 编写评估与断言模块 evaluate.pyevaluate.py 实现判定规则。这里实现两种简单规则keyword_match检查模型输出是否包含至少一个关键词。exact_match将模型输出与期望列表进行比对。# 文件路径deepseek-harness/evaluate.py from typing import Dict, List def normalize_text(text: str) - str: 去空格、去标点用于宽松比对 import re text text.strip().lower() text re.sub(r[\s。、,.!?;:], , text) return text def evaluate_case(case: Dict, model_output: str) - Dict: 对单条测试用例进行判定。 返回包含 pass、reason、details 的字典。 rule case.get(rule, keyword_match) keywords case.get(keywords, []) expected case.get(expected, []) if rule keyword_match: if not keywords: return {pass: False, reason: 未配置关键词无法判定} hit_keywords [kw for kw in keywords if kw in model_output] passed len(hit_keywords) 0 return { pass: passed, reason: 命中关键词: (、.join(hit_keywords) if hit_keywords else 无), details: {hit_keywords: hit_keywords} } if rule exact_match: # 期望列表转小写比对 expected_set {normalize_text(e) for e in expected} output_items model_output.replace(, ,).split(,) output_set {normalize_text(o) for o in output_items} missed expected_set - output_set passed len(missed) 0 return { pass: passed, reason: 缺失项: (、.join(missed) if missed else 无), details: {expected: list(expected_set), output: list(output_set)} } return {pass: False, reason: f未知评估规则: {rule}}normalize_text 的作用是消除中英文标点和空格带来的干扰。比如模型输出“北京,上海, 杭州”和期望列表可以正常比对不会因为格式差异误判失败。4.7 编写主流程入口 run_harness.py主流程负责加载所有测试用例。调用模型获取输出。执行评估。汇总结果并输出 Markdown 报告。# 文件路径deepseek-harness/run_harness.py import time import json import datetime from model_client import DeepSeekClient from load_cases import load_test_cases from evaluate import evaluate_case from config import REPORT_PATH SYSTEM_PROMPT 你是一个严谨的测试助手请严格按照用户要求输出结果不要有多余解释。 def run_single_case(client, case_info: dict, index: int, total: int) - dict: print(f[{index}/{total}] 执行用例 {case_info[id]} ...) start_time time.time() try: result client.chat(case_info[prompt], SYSTEM_PROMPT) elapsed round(time.time() - start_time, 2) model_output result[content] evaluation evaluate_case(case_info, model_output) return { id: case_info[id], category: case_info.get(category, ), prompt: case_info[prompt], model_output: model_output, pass: evaluation[pass], reason: evaluation[reason], elapsed: elapsed, prompt_tokens: result[prompt_tokens], completion_tokens: result[completion_tokens], total_tokens: result[total_tokens], } except Exception as e: elapsed round(time.time() - start_time, 2) return { id: case_info[id], category: case_info.get(category, ), prompt: case_info[prompt], model_output: fERROR: {str(e)}, pass: False, reason: f调用失败: {str(e)}, elapsed: elapsed, prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, } def generate_markdown_report(results: list) - str: passed sum(1 for r in results if r[pass]) total len(results) pass_rate passed / total * 100 if total else 0 lines [ # DeepSeek Harness 测试报告, , f- 生成时间{datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S)}, f- 总用例数{total}, f- 通过数{passed}, f- 失败数{total - passed}, f- 通过率{pass_rate:.1f}%, , ## 用例明细, , | 用例ID | 分类 | 是否通过 | 耗时(s) | 判定原因 |, | --- | --- | --- | --- | --- |, ] for r in results: lines.append( f| {r[id]} | {r[category]} | {✅ if r[pass] else ❌} | {r[elapsed]} | {r[reason]} | ) lines.append() lines.append(## 详细输出) for r in results: lines.append() lines.append(f### {r[id]}) lines.append() lines.append(f**问题** {r[prompt]}) lines.append() lines.append(f**模型输出**) lines.append() lines.append(ftext) lines.append(r[model_output]) lines.append(f) lines.append() lines.append(f**判定结果** {r[reason]}) return \n.join(lines) def main(): client DeepSeekClient() cases load_test_cases(testcases/example_tasks.json) total len(cases) if total 0: print(没有加载到测试用例) return print(f共加载 {total} 条测试用例开始执行...) results [] for index, case in enumerate(cases, start1): result run_single_case(client, case, index, total) results.append(result) # 打印简要结果 for r in results: status PASS if r[pass] else FAIL print(f [{status}] {r[id]}: {r[reason]}) # 生成报告 report generate_markdown_report(results) with open(REPORT_PATH, w, encodingutf-8) as f: f.write(report) # 同时保存 JSON 格式结果方便后续处理 json_path REPORT_PATH.replace(.md, .json) with open(json_path, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) print(f\n测试完成报告已保存{REPORT_PATH}) print(fJSON 结果已保存{json_path}) if __name__ __main__: main()4.8 运行与验证确认环境变量已设置后执行python run_harness.py预期输出类似共加载 3 条测试用例开始执行... [1/3] 执行用例 case-001 ... [2/3] 执行用例 case-002 ... [3/3] 执行用例 case-003 ... [PASS] case-001: 命中关键词: 落叶 [FAIL] case-002: 缺失项: 杭州 [PASS] case-003: 命中关键词: 雪花 测试完成报告已保存report.md JSON 结果已保存report.json注意由于大模型输出的不确定性case-002 可能因为模型输出格式略有不同而判定失败这正是 Harness 需要存在的原因——它帮你在第一时间发现问题而不是上线后才发现。5. 常见问题与排查思路在搭建和使用 Harness 过程中以下几类问题最常遇到。问题现象常见原因解决思路运行时报“请先设置 DEEPSEEK_API_KEY”环境变量未设置或设置后未重启终端重新 export 环境变量并在脚本中打印 os.environ.get 验证报错 timeout 或连接超时网络环境无法访问 DeepSeek API或超时时间设置过短检查网络连通性调大 HARNESS_TIMEOUT 环境变量返回内容包含多余解释没有设置 system prompt 或 system prompt 不够严格在 SYSTEM_PROMPT 中明确“只输出答案不要解释”同一条用例反复失败但人工看输出没问题断言规则过于严格如 exact_match 对格式要求过高改用 keyword_match或增强 normalize_text 兼容更多符号报告乱码控制台编码问题在 Python 脚本入口设置 PYTHONIOENCODINGutf-8或输出到文件后查看Token 消耗过大测试成本偏高用例过多或 max_tokens 设置偏大分批执行压缩 prompt按需求调小 HARNESS_MAX_TOKENS5.1 环境变量不生效这是一个非常常见的坑。如果你在终端里执行了 export但 Python 读到的还是空字符串先确认是不是设置命令之后新开了终端窗口。更稳妥的做法是在运行命令前先打印验证echo $DEEPSEEK_API_KEY如果为空说明环境变量没有在当前 shell 中生效。另一种更推荐的方式是使用 .env 文件配合 python-dotenv 管理密钥避免每次手动 export。5.2 模型输出格式不稳定大模型很难保证每次都输出完全相同的格式。用 exact_match 做严格比对时经常因为一个逗号全角半角差异导致误判。建议先跑一轮测试把模型实际输出打印出来观察输出格式的规律再决定断言规则。不要一开始就使用非常严格的匹配规则。5.3 连续调用 API 触发限流如果你一次性执行几百条用例可能触发 API 速率限制。此时 Harness 会自动记录“调用失败”表现为大量 FAIL。解决方案是在主循环中加入一个简单的延迟比如在 run_single_case 调用之间加上 time.sleep(0.5)。也可以在 config.py 中增加一个 REQUEST_INTERVAL 参数由环境变量控制这样测试节奏不需要改代码。5.4 失败用例重跑并不是所有失败都代表模型效果不好。可能是临时网络抖动导致单次调用失败。建议增加一个 retry 机制当一次调用抛异常时重试 1 到 2 次仍然失败才标记为 FAIL。重试次数同样建议通过环境变量配置而不是硬编码。6. 最佳实践与工程建议6.1 测试用例与代码分离前面已经提到测试用例应该使用 JSON 文件单独维护。这一点值得反复强调。当用例数量增长到成百上千条时如果你还在 Python 代码里写“测试数据”维护成本会急剧上升。分离之后你可以用 Git 对用例做版本管理每次 Prompt 或模型调整都清楚知道改动了哪些用例。6.2 把 Harness 接入定时任务或 CI当 Harness 能稳定输出 Markdown 报告后下一步就是让它自动跑。最简单的用法是配合 cron 定时执行# 每天早上 9 点跑一次测试 0 9 * * * cd /path/to/deepseek-harness python run_harness.py runner.log 21在开发阶段也可以在 Git 的 pre-commit 或 CI 中配置当 Prompt 仓库有变更时自动运行一份小规模用例集快速判断改动是否有明显退化。6.3 断言规则要分层设计不要只依赖一种判定方式。建议按任务类型分层格式校验先判断输出是否包含必要结构。关键词/字符校验再判断核心内容是否出现。语义相似度用于开放式生成。模型打分用 LLM 做裁判适合没有标准答案的任务。人工抽检机器判定通过后人工再审查一部分结果。每一层解决不同的问题组合使用才能覆盖足够多的失败场景。6.4 记录每次运行的原始输出测试报告之外建议把每次运行的完整输出保存为带时间戳的文件而不是覆盖旧的 report.md。比如reports/ ├── 20250510_093000_report.md ├── 20250511_093000_report.md ├── 20250512_093000_report.md有了历史数据才能回答“上周的模型效果是不是比这周好”这类问题。6.5 控制成本与 Token 消耗每次测试都会真实消耗 Token所以在用例设计上要节制。在实际项目中建议把测试集拆成两层快速回归集50 条以内高频执行。完整评估集数百条以上低频执行如每周一次。这样既能及时发现问题又不会让成本失控。Token 消耗数据已经保存在 JSON 结果中可以定期累计分析。6.6 安全与合规提醒使用 DeepSeek API 时需要注意以下安全边界API Key 不要提交到公开代码仓库建议加入 .gitignore。测试用例中不要包含真实用户隐私信息。对模型的输出不要盲目采信特别是涉及事实性、时效性内容的场景。生产环境接入时应遵循合法合规的数据使用规范先评估数据脱敏和合规要求。7. 总结与后续学习建议至此你已经掌握了一套面向 DeepSeek 模型 API 的 Harness 搭建方法包括环境配置、模型调用封装、测试用例管理、评估断言和报告输出。这套框架虽然是针对 DeepSeek 实现但底层逻辑适用于任何 OpenAI 兼容的模型服务。如果你希望继续深入可以按以下路线扩展增加模型打分评估用一个强大的模型对另一个模型的输出打分适用于没有标准答案的开放任务。接入向量检索评估对“知识库问答”类应用需要同时评估检索结果和生成结果这里需要引入检索评估指标。做 Prompt 版本对比同一组用例跑两个 Prompt对比通过率和输出质量用数据驱动 Prompt 迭代。增加多模型对比把同一个用例集同时跑 DeepSeek 和其他模型输出横向对比报告。本质上Harness 的价值不在于代码本身而在于它替你建立了“持续观察模型行为”的基础设施。建议你从今天的三五条用例开始先跑通流程再逐步扩充。当某天你发现一个 Prompt 改动让通过率从 90% 掉到 60% 时你会感谢当初留下这套框架的决定。