ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于DeepSeek Harness构建AI工作台:从需求到成果的工程化实践

基于DeepSeek Harness构建AI工作台:从需求到成果的工程化实践 我接触 DeepSeek Harness 其实挺早当时第一反应是它更像一个“模型运行框架”离普通用户能用的东西还隔着好几层。你得自己写任务调度、自己接工具、自己处理模型返回的乱格式更别提把结果整理成能交付的产物。后来我在这套官方 Harness 外面包了一层壳做了一个开源 AI 工作台核心就一句话从一句需求到看得见的成果。你扔一句“帮我分析这批销售数据并出一份带图表的报告”它自己拆任务、调用技能、跑脚本、生成 HTML 报告你只需要看结果。这篇文章想把整个项目的设计思路、核心机制、本地部署过程和一些我踩过的坑都摊开来讲适合想上手 DeepSeek Harness、想自己搭 AI 工具体系、或者纯粹被“AI 工作台”这个词吸引而来的开发者。1. 为什么基于 DeepSeek Harness 而不是从零开始写1.1 Harness 是什么替我们扛下了哪些脏活很多同学第一次看到 Harness 这个词会懵它和平时说的 Agent 框架有什么区别我的理解是Harness 比 Agent 更底层它是“把大模型包在一个可编程环境里运行”的那套基础设施。官方 DeepSeek Harness 帮你处理了几件特别烦人的事一个是模型会话管理多轮对话的上下文怎么存、怎么截断、怎么在工具调用返回后正确拼回去另一个是工具调用协议模型说出“我要调用某个函数”之后框架帮你完成参数校验、函数执行、结果回填还有一个是并发控制与重试策略避免一次跑二十个任务时把 API 打爆或者被限流。如果没有这层 Harness你从零开始写一个月都未必能把上面这些边界情况处理干净。我最早自己写过一版“prompt 拼接器”调一次模型、解析一次返回、再拼一次上下文看起来能用一旦遇到工具链超过三步就乱套。后来换成基于官方 Harness相当于把最不稳的地基换成了别人已经踩实的地面我只需要在上面盖业务楼层。用生活化的类比Harness 就像出租车的发动机和底盘你拿到手就能开但要变成网约车、出租车、货运车还得自己加计价器、顶灯和货箱。我这个工作台就是基于官方底盘改的一辆“AI 全能货车”既能拉代码也能拉文档还能自动卸货成 HTML 报告。1.2 工作台的整体架构一句需求怎么流成成果整套工作台我从设计上就坚持四层结构每一层只做一件事这样出了问题能快速定位也方便不同的人贡献代码。第一层是需求接入层。它负责接收自然语言输入包括一句话、一段描述、甚至一个包含附件的文件夹路径。这一层要做两件事把用户输入整理成结构化的任务意图判断意图是否完整、是否有明显歧义。比如用户说“帮我看看这个 CSV 有没有异常”系统会追问一句“你希望生成异常报告还是直接清洗数据”避免后续白跑。第二层是规划编排层也就是整个工作台的大脑。它基于 DeepSeek Harness 的模型调度能力把大任务拆解成若干子任务每个子任务被描述为“需要调用哪个 Skill、输入什么、输出到哪里”。拆解结果是一份 JSON 格式的任务清单之后所有执行动作都围绕这份清单展开。规划层不直接碰具体工具只做拆解和排序所以即使后面换了模型这层逻辑也不用大改。第三层是技能执行层也就是 Skill Runner。每个 Skill 是一个可独立运行的插件有清晰的输入输出接口。执行层负责按顺序或并行拉起 Skill收集每步的中间产物并在 Skill 失败时决定是重试还是换一个替代方案。这是整个系统里最复杂的一层因为它要处理真实世界的各种不确定性文件不存在、网络超时、代码报错、格式不符合预期。第四层是成果输出层。它把中间产物统一收拢按任务 ID 建立文件夹自动生成 Markdown 总结报告对支持的类型做可视化预览最后提供一个静态页面让用户直接查看。这个设计其实是我刻意加重的因为“看得见的成果”才是这个工作台区别于其他 Agent Demo 的关键。如果 AI 跑完只给你一段终端日志那它只是玩具如果它给你一个能打开、能分享、能交付的 HTML 报告它才是工具。1.3 技术选型背后的取舍技术选型我踩过几次坑这里直接说结论。整个项目用 Python 3.10 写因为 DeepSeek Harness 官方就是 Python 生态而且 AI 工具链里 Python 的兼容性最好。服务端用 FastAPI 暴露 HTTP 接口方便后续接 Web 前端、桌面客户端或者当 API 供别的系统调用。任务队列没有上 Celery 这种重家伙而是用自带的多进程池加一张 SQLite 任务表原因很简单本地工作台绝大多数场景是单用户、几十个任务量级Celery 的部署成本远大于收益。模型层我做了一层抽象统一接口。你可以接 DeepSeek 官方 API 的 deepseek-chat、deepseek-reasoner也可以接本地通过 Ollama 跑起来的开源模型。这个抽象带来的好处是平时测试用本地小模型不花钱也不限流正式跑复杂任务时切回官方大模型质量稳定。切换只需改配置里的 model 字段代码完全不用动。我还特意把“技能市场”独立成一个目录而不是把技能写死在代码里。这样第三方开发者可以只写一个文件夹就完成插件的发布不需要改动主程序。目录结构大概是ai-workbench/ ├── app/ │ ├── planner.py # 任务规划 │ ├── runner.py # 技能执行 │ └── assembler.py # 成果组装 ├── skills/ │ ├── builtin/ # 内置技能 │ └── market/ # 第三方技能 ├── outputs/ # 所有任务产物 └── config/ ├── config.yaml └── .env.example这个结构看起来很朴素但胜在清晰。新手拿到手能看懂老手想改也能快速找到对应文件。做开源项目最怕结构过度设计我自己维护过那种“抽象了三层结果没人看得懂”的项目太痛苦。2. 核心功能拆解需求理解、技能编排与成果生成2.1 “一句需求”如何被拆解成可执行任务这是整个工作台里最体现“AI 味”的一环。用户输入“帮我根据 data.csv 生成月度销售趋势分析并配一份 PPT”规划层要做的是把这个需求拆成三件事读文件、分析数据、生成 PPT。但真实输入往往没那么规整比如用户会说“你看看这堆数据到底怎么回事”或者“帮我写个脚本统计一下最近三个月的情况”。这种模糊表达如果直接丢给模型大概率得到一堆废话。我实际的做法是给规划层一个非常具体的 system prompt要求它必须输出严格 JSON字段包括 task_id、skill、input、output_path、depends_on。同时我会在 prompt 里告诉模型如果输入信息不足以拆解可以主动追问不要硬拆。这个“允许追问”的设定非常关键它避免了很多错误的自动规划。举个例子用户输入“分析一下 data/sales.csv 里的数据把结果整理成报告。”规划层可能拆成这样{ tasks: [ { task_id: check_file, skill: file_inspector, input: {path: data/sales.csv}, output_path: outputs/demo_001/check_result.json }, { task_id: analyze, skill: data_analyzer, input: {source: outputs/demo_001/check_result.json, metrics: [monthly_sales, growth_rate]}, output_path: outputs/demo_001/analysis.json }, { task_id: report, skill: report_generator, input: {data: outputs/demo_001/analysis.json, format: html}, output_path: outputs/demo_001/report.html } ] }依赖关系由 depends_on 字段控制runner 执行前会做一个拓扑排序确保 check_file 跑完才跑 analyze。如果某个步骤失败工作台会自动检查后续任务是否依赖它依赖了就停止不依赖的继续跑。这个设计避免了一颗老鼠屎坏了一锅汤。2.2 Skill 机制给工作台装上可插拔的“手和脚”Skill 是这套工作台最重要的扩展单位。我经常跟人解释大模型是大脑Skill 就是手和脚没有 Skill大脑再聪明也只能停在“想一想”的阶段。每个 Skill 由两个部分组成manifest.json 负责描述元信息main.py 负责具体实现。一个最简单的 Skill 长这样。先看 manifest{ id: file_inspector, name: 文件检查器, version: 1.0.0, description: 检查文件是否存在、格式、大小、行数, params: [ {name: path, type: string, required: true, description: 文件路径} ], output: file_inspector_result }再看执行逻辑# skills/builtin/file_inspector/main.py import os from pathlib import Path def run(ctx, params): path params[path] if not os.path.exists(path): ctx.log(f文件不存在: {path}) raise FileNotFoundError(path) p Path(path) stat p.stat() result { exists: True, path: str(p.resolve()), size_bytes: stat.st_size, suffix: p.suffix, lines: sum(1 for _ in open(p, r, encodingutf-8, errorsignore)) } ctx.push_result(result) return result这里有个细节ctx 是执行上下文它承担了日志注入和结果推送。每个 Skill 跑完中间产物会被写到一个 JSON 文件里路径由输出层的调度器决定。这就是“从一句需求到看得见的成果”里的“中间可见性”每一步都有痕迹出了问题可以直接打开中间文件排查而不是只看到一句“执行失败”。我在内置技能里放了几个高频工具文件检查器、CSV/Excel 分析器、Python 代码执行器、HTML 报告生成器、图片生成器调用绘图接口、Markdown 转 PPT 工具。第三方技能只要放进 skills/market 目录并在配置里声明启用就能被规划层调度。你完全可以把公司内部的数据查询脚本封装成一个 Skill让 AI 工作台直接调用这相当于把内部工具系统接进了大模型。2.3 工作流编排串行、并行与失败重试任务之间有时候不只是简单的先后顺序还可能存在并行关系。比如要生成一份竞品分析报告需要同时拉取三家竞品的公开资料每个拉取任务互不依赖完全可以并行。工作台的 runner 会读取任务清单里的 depends_on构建依赖图然后按波次执行第一波跑没有依赖的任务全部完成后第二波跑依赖于它们的任务以此类推。并行执行也不是无脑开线程。我给 runner 配置了全局并发上限默认是 3也就是同时最多跑三个 Skill。这个数字不是拍脑袋定的而是我实测后权衡的结果。并发开到 6 时本地 CPU 和内存占用明显上升如果某个技能里有比较重的计算甚至会拖垮整个 Python 进程开到 3 时速度和稳定性比较平衡。当然你可以在 config.yaml 里调大但我的建议是宁小勿大。失败重试是我特别想强调的一块。早期版本只要某个 Skill 报错整个任务链就断掉非常恼人。后来我加了两层保护第一层针对瞬时错误比如网络超时、目标文件被临时占用会在 30 秒内自动重试最多 3 次重试之间做一个指数退避避免频繁打同一下第二层针对逻辑错误比如脚本语法错误、数据格式不符这种情况重试也没用工作台会把这个任务标记为“需要人工介入”并尝试继续执行不依赖它的其他任务。任务执行的日志我也做了分级。默认只输出关键节点比如“开始执行”“执行成功”“重试第 2 次”打开 verbose 模式后会输出每个 Skill 的完整调用日志包括模型返回的原始 JSON、传给 Skill 的完整参数、Skill 执行时的每一行 stdout。定位问题的时候开 verbose 基本一抓一个准。2.4 从执行结果到“看得见的成果”成果输出层是我个人最喜欢的一层因为它直接决定了用户体感。每次任务开始前assembler 会创建一个 output 目录结构按任务 ID 命名outputs/demo_001/assets/放图片等附件outputs/demo_001/result.json是结构化结果outputs/demo_001/report.md是自动生成的总结文档。如果任务里包含 HTML 报告assembler 还会额外生成一个静态预览页把多个中间产物嵌到一个页面里直接可以用浏览器打开。自动生成报告的 prompt 大概是这样的把任务原始需求、各步骤结果摘要、所有输出文件路径给模型让它写一份“面向人”的总结要求包含背景、过程、结论、文件索引。这一步的好处是哪怕中间走了很多弯路最终交付给用户的是一份干净清爽的文档而不是一堆散落的脚本。我实际用下来觉得这个“成果化”设计对非技术用户特别友好。你不需要知道任务拆解成了几步也不需要理解中间每个文件的含义只要打开最终的 report.html 就能看到结论、图表、文件列表。这也是我为什么在标题里强调“从一句需求到看得见的成果”——很多 AI 工具跑完只有聊天记录但这套工作台交付的是一个完整的作品集。3. 从零部署环境准备、配置与首个真实任务3.1 部署环境与安装避坑部署这个工作台比我预想的要简单但也有几个值得注意的坑。先说基础环境建议 Linux 或者 macOSWindows 也能跑但部分技能脚本涉及路径分隔符偶尔会有小问题。Python 版本必须 3.10 以上我一开始在 3.8 上装直接报语法错误因为项目里用了新版类型注解写法。拿到源码后安装分三步git clone 项目仓库地址 cd ai-workbench python -m venv venv source venv/bin/activate pip install -r requirements.txt cp config/.env.example config/.env python app/main.py如果你是用 conda 管理环境也可以先conda create -n workbench python3.11再走后面的流程。启动成功后默认监听127.0.0.1:8000浏览器打开就是工作台的 Web 主界面。界面很朴素左边是任务输入框右边是任务列表和成果预览区没有多余的花哨东西。这里我必须专门说一个社区里高频出现的问题DeepSeek Harness 0.1.5 安装失败。网上很多同学报错其实 90% 的原因是依赖冲突。老版本安装脚本里没有锁 pydantic 和 fastapi 的版本如果你环境中已经有比较新的 fastapi会和 harness 的某些旧依赖打架。我的处理办法是新建一个干净的虚拟环境然后手动固定关键依赖版本再装项目依赖。正确的安装顺序是先安装官方 Harness 及其固定依赖再安装工作台自身的 Web 依赖最后配置 .env。如果你已经装了 0.1.5 但启动时报“cannot import name”之类的问题卸载后按上面顺序重装一次基本能解决。3.2 配置文件一个中心化入口搞定模型与技能所有关键设置都收拢在 .env 和 config.yaml 这两个文件里不需要改代码。先看 .env# 模型接入配置 DEEPSEEK_API_KEYsk-xxxxxxxx DEEPSEEK_BASE_URLhttps://api.deepseek.com/v1 AI_MODELdeepseek-chat # 本地模型备选通过 Ollama 接入 # AI_MODELollama/qwen2.5:14b # OLLAMA_BASE_URLhttp://127.0.0.1:11434 # 运行参数 MAX_WORKERS3 TASK_TIMEOUT600 RETRY_TIMES3config.yaml 里则是技能启用列表和输出路径output_dir: outputs verbose: false skills: enabled: - file_inspector - data_analyzer - code_runner - report_generator market_dir: skills/market server: host: 127.0.0.1 port: 8000这里我想解释一下为什么把模型接入放到环境变量而不是配置文件里。因为不同人的模型密钥敏感性不同硬编码到 YAML 里容易误提交到 Git。而 config.yaml 放的是非敏感但偏自定义的设置比如启用哪些技能、并发数、路径。这样分工既安全又便于协作。还有一个容易被忽略但很重要的配置项是 TASK_TIMEOUT。有些任务特别长比如让模型写一个 2000 行的数据处理脚本或者批量处理一百个文件耗时可能超过十分钟。如果超时设得太短任务会被误杀太长又会让卡死的任务一直占着资源。我默认设 600 秒并且代码里对每个子任务单独计时因为子任务的粒度通常较小超时判断更准确。3.3 实战演示一句需求完成数据脚本这部分我用一个真实跑过的任务来演示。我在本地放了一个data/sales.csv大概两千行每行是一条销售记录。我在输入框里写“写一个 Python 脚本统计这个表的月度销售额趋势画一张折线图并生成一份 HTML 报告。”工作台的执行链路是这样的。规划层先把需求拆成四个子任务检查文件格式、读取并清洗数据、执行统计分析、生成图表与报告。文件检查器先跑确认 CSV 有表头、列名符合预期。接着 data_analyzer 被唤起它会先读懂表结构确认日期字段和金额字段然后用 pandas 做月度聚合。这一步输出了一个analysis.json里面包含了每月销售额、环比增长率、异常值标记。然后是 code_runner它根据分析结果自动生成一段 matplotlib 脚本执行后产出折线图保存为outputs/demo_001/assets/monthly_trend.png。最终 report_generator 把分析结论、图表路径、关键数据拼进一个 HTML 模板生成一份带图表的报告。整个流程跑完大约一分半钟其中大部分时间花在模型生成 Python 代码和渲染图表上。我特意打开outputs/demo_001/report.html看过排版不算惊艳但信息完整有摘要、有图表、有数据表格完全可以拿去做周报素材。这已经达到“看得见的成果”的标准了。而且整个过程我没有写一行代码只输入了一句话。这种体验是单纯聊天式 AI 给不了的。3.4 多任务并发与资源控制单个任务跑通只是第一步实际使用中你会经常同时提交多个任务。我有一次一口气提交了五个任务两个数据分析、一个竞品信息整理、一个代码生成、一个文档翻译。工作台会把它们全部塞进队列由 runner 按 MAX_WORKERS 限制并行度。我亲眼见过并发失控的场面把 MAX_WORKERS 调到 6同时跑三个数据分析任务和两个模型生成任务内存直接飙到 12GB风扇疯狂转最后有一个任务因为进程超时被杀掉。后来我把并发调回 3并且给每个任务加了内存使用上限的提示如果某个技能的内存使用超过 2GB工作台会在日志里给出警告。这个警告不是强制限制但能提醒我哪些技能需要优化而不是盲目堆并发。对于资源规划我的经验是如果你的机器是 16GB 内存建议 MAX_WORKERS 设为 232GB 以上可以到 3如果你用的是带 GPU 的机器并且让本地模型参与推理还要额外给模型推理预留显存。并发配置直接影响稳定性宁可用慢一点但稳定也不要频繁看到 OOM。我后来整理了一个表格给自己参考机器配置推荐并发数适合场景8GB 内存 / 无 GPU1轻量文本任务16GB 内存 / 无 GPU2中等数据分析、文档处理32GB 内存 / 有 GPU3多任务并行、本地模型推理64GB 以上4-6批量自动化需要稳定网络和散热这个表不绝对但能作为一个起步值。实际跑的时候建议打开任务管理器或者htop观察你本机的负载再微调。4. 常见问题与排查实录4.1 安装与启动失败的五种典型情况这半年我在社区里见过大量部署问题总结下来无非五种。第一是 Python 版本太低症状是安装时提示语法错误或某些包找不到解决办法是换 3.10 以上版本。第二是依赖冲突症状是安装到一半报 pip 的 “conflict” 错误建议用干净的虚拟环境安装并按要求固定版本。第三是 .env 文件没复制或者密钥没填症状是启动后模型调用一直报 401这个最容易自查打开 config/.env 看看 DEEPSEEK_API_KEY 是否被正确填入。第四是端口被占用症状是启动失败提示 address already in use最简单的办法是改 config.yaml 里的端口比如换成 8001。第五是技能目录没配置症状是任务规划完成后 runner 提示找不到 skill检查 config.yaml 里的 skills.enabled 列表是否包含该技能 ID。还有一种更隐蔽的情况你在技能 market 目录里放了一个文件夹但 manifest.json 的 id 字段和文件夹名不一致。系统加载时会报 “skill not found”但日志里不会特别显眼。我在项目里加了启动时的技能扫描日志启动时如果有加载失败会直接打印一行 “Failed to load skill: xxx”。所以排查这类问题第一步永远是看启动日志而不是直接看任务日志。4.2 模型调用异常与超时处理模型调用是整个工作台里最容易出问题的一环但好消息是问题种类非常有限。最常见的三个API Key 无效或余额不足症状是 HTTP 401 或 402模型名写错症状是 400 错误提示 model not found请求超时症状是任务一直卡在 planning 或者某个 skill 的调用上。针对超时我在 config 里加过 TASK_TIMEOUT 和 REQUEST_TIMEOUT 两个字段前者控制整个子任务后者控制单次模型请求。一般单次请求超时设置 120 秒足够如果模型频繁超时建议先检查网络连通性和 API 服务状态而不是盲目调大超时时间。你在本地跑任务时如果发现调用大模型很慢可以考虑先切到本地小模型做功能验证流程跑通了再切回正式模型这样既省时间又不浪费 token。还有一个小细节模型返回的 JSON 偶尔会被截断尤其是生成长文本时。Harness 本身有一定容错机制但我在工作台里加了一道“JSON 修复”工序如果解析失败会尝试截取最外层括号再解析如果还失败就把原始返回内容保存到一个 debug 文件里。这个 debug 文件对定位模型输出问题帮助巨大很多“工作台怎么突然傻了”的问题打开 debug 文件一看原来是模型输出了 Markdown 代码块把 JSON 给包住了。4.3 Skill 不生效、工作流卡住的定位方法Skill 不生效大概能占到所有问题的三成。我遇到过的第一种情况是 manifest.json 写了但没保存为 UTF-8 编码导致中文字段乱码解析失败。第二种是参数名称对不上规划层生成的 JSON 里 key 是file_path而 Skill 的 manifest 里写的是path虽然描述的是同一个东西但程序只认名字直接报缺少必要参数。这也是我强烈建议 manifest 里的参数名要写“短、小、明确”的原因别搞出input_file_path_with_full_absolute_location这种又长又容易拼错的名字。工作流卡住的原因往往更容易定位因为每一层的日志都分开了。如果卡在 planning说明模型调用这块有问题优先查模型服务如果卡在某个 Skill日志里会有明显的 task_id 和 skill 名称如果卡在 output assembler一般是报告生成时模板渲染出了问题。我会建议所有使用者熟悉一个命令打开 verbose 模式。之前有个用户问我为什么任务总是自动停止我让他开 verbose 跑一遍日志里明确写着 “Worker 2 killed due to memory limit”问题瞬间就清楚了。4.4 资源吃紧与任务中断恢复任务跑到一半被杀或者整个工作台崩了是另一个高频问题。早期版本没有断点恢复任务归档了就是归档了只能从头再跑非常浪费。后来我加了一个中间产物持久化机制每个子任务完成就会立刻把结果写入 outputs 目录目录结构稳定不变。这样即使主程序崩了你重启后把同一个任务 ID 重新提交规划层会跳过已经成功产出过结果的子任务只补跑缺失的部分。这个“断点续跑”功能在长任务上非常实用。比如一个任务要跑二十个子任务跑到第十五个时机器重启如果没有恢复机制前十五个全白跑。现在只要把任务 ID 填回去系统会迅速跳过已有产物的步骤只花五分之一的时间就完成剩余工作。不过我也得说清楚这个恢复机制只对无状态 Skill 有效如果你的 Skill 执行过程中依赖外部服务状态比如要持续跟踪某个网页的滚动位置那恢复时可能产生不一致需要手动确认。磁盘空间也是容易被忽略的点。每个任务都会在 outputs 下生成一堆中间文件和附件任务多了之后可能占据几个 GB。我习惯每月清理一次outputs/目录里超过 30 天的任务产物或者在 config.yaml 里设置产物保留天数让 assembler 自动清理。这个功能默认关闭因为有些用户希望永久保留所有中间数据但如果你只是日常试用建议打开。个人体验与最后一点私货这套基于官方 DeepSeek Harness 的工作台我陆陆续续迭代了小半年最大的体会是AI 工具的护城河不在模型本身而在工程化程度。谁都能接一个聪明的模型但只有把模型输出变成稳定、可复用、可交付的产物才真正有复用价值。我一开始也沉迷于“让模型多做点事”后来发现真正好用的系统反而是“每次只让模型做一小步但每一步都有检查点”。从一句需求到看得见的成果听起来是产品口号实际上背后是一整套可靠的分层架构和容错机制。最后再分享一个小技巧当你想让工作台完成一个没跑过的流程时先别急着让它直接做完整版。我会先让它把整个流程跑通哪怕结果粗糙一点确认每个子任务都能产出文件和中间结果然后再提交第二个任务让它在已有基础上优化细节。这样做的好处是即使第二个任务中途失败你手里还握有第一版粗糙但完整的产物不至于两手空空。这个“先通后优”的思路不仅适用于这个工作台做任何 AI 自动化项目都值得试试。
返回列表