如果你正在本地部署 Agent,你一定遇到过这种局面:既要考虑模型选型,又要搭推理框架,还要处理工具调用和评测逻辑,最后打开 GPU 服务器报价单或者 API 账单,发现还没开始写代码就已经想放弃。其实,这件事没有想象中那么贵。开源模型、本地推理框架和轻量编排层已经可以把整条链路压缩到“一台普通电脑 + 一套开源组件”的规模。
本文想给一个明确判断:本地部署一个“超智能 Agent”的核心并不在于买更贵的模型,而在于把本地模型、工具调用、编排循环和结果评测正确组合起来。“不花一分钱”在工程上不是营销口号,而是可以通过 Ollama、Qwen 系列开源模型以及 DeepSeek Harness 这类编排层真实跑通的方案。读完这篇文章,你可以理解 Agent 与 Harness 的关系、搭建一套可复现的本地 Agent 流程,并且知道如何验证、排错和工程化。
文章会先澄清一个容易混淆的概念:千问 3.8 这个版本号并不严谨,实际部署时应以 Qwen 官方仓库当前可用的模型标签为准。随后会拆解环境准备、核心流程、完整代码示例、运行验证、常见问题与最佳实践。最后会给出后续深入方向。如果你是刚接触 Agent 的开发者,建议先把最小循环跑通,再加复杂度。
1. 这篇文章真正要解决的问题
很多人误以为“本地部署 Agent = 本地跑一个大模型”。实际上,大模型只是 Agent 的决策内核,离完整的 Agent 还差得很远。一个可用的 Agent 至少需要四个部分:
- 模型服务:负责接收提示词并生成结果。
- 工具集合:让模型能查询数据、执行操作、读取外部信息。
- 编排循环:决定模型输出什么时调用哪个工具、工具结果如何处理、是否需要继续推理。
- 评测机制:判断 Agent 是否真正完成了任务,而不是“看起来回答了一堆”。
这篇文章解决的是后面三件事。因为大多数教程只讲“如何下载模型”,不告诉你“如何把模型变成能自动干活的 Agent”,更不会告诉你“如何用 Harness 去验证它是否可靠”。
“DeepSeek Harness”这个名字容易让人误以为它是一个官方固定产品。更准确地说,Harness 在 AI 工程里泛指“编排与评测框架”,类似 OpenAI Evals、模型评测工具链。在本文场景中,它承担的是 Agent 调度、工具调用记录和任务结果评估的职责。你可以用 LangChain 现成的组件,也可以用几十行 Python 自己实现。理解原理后,你会发现后者反而更可控,也更能暴露问题。
这篇文章适合谁?适合正在做本地化 Agent 原型验证的技术人员、想把私有数据接入 LLM 的应用开发者,以及想弄清楚“大模型到底怎么干活”的进阶学习者。如果你只有 CPU 环境,也可以跑通流程,只需要把模型换小一些。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念:Qwen 开源模型、Agent 与 Harness 到底是什么
2.1 关于“千问3.8”版本的澄清
先说明一件重要的事:Qwen 官方并没有一个叫“千问3.8”的正式版本。你在搜索时看到这个说法,大概率是营销标题里对“Qwen 最新可用版本”的不严谨表达。本文不会围绕这个不存在版本号展开,而是统一使用“Qwen 系列开源模型”的表述。
实操中你只需要记住:模型版本以 Ollama 仓库和 Qwen 官方 GitHub 仓库的标签为准。比如当前可用的 qwen2.5 系列有 0.5B、1.5B、3B、7B、14B 等多个尺寸,你可以根据自己的硬件选模型。部署思路是通用的,版本变化不影响理解。
2.2 Agent 不是“一个模型”,而是一个运行循环
Agent 的本质是一个循环:模型根据任务输出决策,系统执行决策,结果回传给模型,模型继续修正,直到产出最终答案。这个过程通常叫 ReAct(Reason and Act),意思是“先思考,再行动,观察结果,再继续思考”。
没有 Agent 时,你调用一次大模型接口,拿到一段文字就结束。有 Agent 时,模型可以输出“调用查询商品价格函数”,程序执行该函数得到价格,再返回给模型,让模型基于真实数据给出结论。这个能力在知识库问答、流程自动化、数据分析里非常有用。
新手最容易误解的地方在于:以为模型的“智能”决定一切。实际上,工具描述写得好不好、上下文管理是否正确、循环终止条件是否清晰,都直接影响最终效果。有时候模型本身没问题,是编排层写得太粗糙。
2.3 Harness 与 DeepSeek Harness 在本文中的含义
Harness 原意是“束具、挽具”,在 AI 工程里引申为“把模型、数据和评测逻辑套在一起的框架”。一个典型的 Harness 会做三件事:
- 加载任务列表。
- 调度模型与工具执行。
- 汇总执行轨迹、成功率和错误原因。
DeepSeek Harness 的理解要分两层。一方面,DeepSeek 提供开源模型和开放平台 API;另一方面,“Harness”是行业里通用的编排评测概念,并不是 DeepSeek 官方唯一的组件名。因此,本文不把它当作某个固定软件来安装,而是把它当作一种架构思想:用编排层把 Qwen 本地模型“包装”成可评测的 Agent 系统。
如果你希望用 DeepSeek 模型参与这个架构,有两条路:在本地跑 DeepSeek 开源模型,或调用 DeepSeek 平台 API。考虑到“不花一分钱”的目标,本文重点演示本地开源方案,云端 API 可以作为可选的补充,但不是零成本方案的核心。
2.4 为什么双模型架构能降低本地部署成本
在很多本地 Agent 系统里,设计者会把“规划模型”和“执行模型”分开。规划模型负责拆解任务、判断是否需要工具;执行模型负责具体生成回答。这种双模型架构的好处是:复杂任务交给能力强的模型,简单重复的操作交给小模型,从而降低整体开销。
用 Qwen 系列模型作为本地执行模型,用 DeepSeek 开源模型或 API 作为规划与复盘模型,是一种常见的组合。本地部署时两者都以开源形式存在,你不需要为每次调用支付费用,只需要承担硬件电力和维护成本。这也是“不花一分钱”在工程上的真实含义:免费的不是算力,而是这套软件链路不需要按次付费。
3. 环境准备与前置条件
在开始写代码之前,先把环境准备清楚。下面的配置不绑定具体版本,因为工具链迭代很快,建议以官方文档为准。
3.1 硬件与系统基线
- 操作系统:Windows 10/11、macOS、Linux 均可。Linux 服务器在生产环境更常见。
- 内存:最低 8GB。如果要跑 7B 量化模型,建议 16GB 以上。
- 显卡:有 NVIDIA GPU 体验更好,支持 CUDA 的 6GB 以上显存可流畅运行 7B 量化模型。没有显卡也能跑,但速度会明显变慢,适合先用 3B、1.5B 小模型验证流程。
- 磁盘空间:模型文件每个数 GB,建议预留 20GB 以上。
性能与成本的平衡原则是:先用小模型把流程跑通,再逐步换大模型。不要在第一步就追求 70B 参数,那会大幅拉高硬件门槛。
3.2 软件与依赖
你需要安装以下软件:
- Python 3.10 或更高版本。
- Ollama:用于本地拉取和运行 Qwen 系列模型,也兼容 OpenAI API 风格接口。
- openai Python 库:用来访问 Ollama 暴露的本地 OpenAI 兼容接口。
- 可选:LangChain 组件或 Jupyter Notebook。本文主示例采用纯 Python 实现,避免框架版本差异导致代码不可运行。
安装 Python 依赖:
bash复制python -m venv venv
source venv/bin/activate # Windows 下为 venv\Scripts\activate
pip install openai python-dotenv
安装 Ollama 后,启动服务并确认版本:
bash复制ollama --version
ollama serve
建议单独开一个终端窗口运行 ollama serve,之后所有本地请求都通过它转发到模型。
4. 本地部署核心流程拆解
本地部署一个 Agent 的核心流程可以拆成五步。每一步都有明确的完成标志,做错一步,后面就会出现对应的“怪现象”。
4.1 第一步:选择并启动本地推理服务
选择模型时要看两点:硬件承受能力和工具调用能力。Qwen2.5 系列对工具调用的支持相对稳定,是本地 Agent 的常见选择。
bash复制ollama pull qwen2.5:7b
ollama list
ollama list 会显示你本机已下载的模型列表。模型名称、标签必须和后续代码里的 MODEL_NAME 完全一致,否则调用接口时会返回 404。
4.2 第二步:确认推理服务可以被代码调用
Ollama 启动后,会监听 http://localhost:11434。它还提供一个 OpenAI 兼容接口,路径是 /v1。确认方式很简单:
bash复制curl http://localhost:11434/v1/models
如果返回 JSON 数组,说明接口可用。这一步经常被跳过,结果写代码时总报连接失败,还以为是 Python 代码问题。
4.3 第三步:定义工具与工具描述
Agent 能调用什么工具、工具描述写得是否清楚,决定了模型能否正确选择工具。工具描述要包含:工具名称、用途、输入参数格式。
建议先准备两个不依赖外部网络、结果稳定的工具:
- 获取当前时间。
- 查询商品价格(本地字典模拟)。
这样既不会产生额外费用,也不会因为网络波动导致 Agent 调试混乱。
4.4 第四步:实现 Agent 主循环
主循环逻辑是整篇文章的重点。你可以选择让模型输出结构化的 JSON,然后代码解析 JSON 执行工具。这种模式比依赖框架的 function calling 更可控,因为它把“模型输出”到“工具执行”之间的格式约定牢牢攥在代码手里。
主循环伪代码如下:
- 把系统提示词和用户任务构造为消息列表。
- 请求本地模型,获得文本回复。
- 解析文本为 JSON。
- 如果包含
final_answer,输出最终答案并结束。 - 如果包含
action和action_input,执行对应工具,把结果追加到消息列表,回到第 2 步。 - 超过最大轮数仍未结束,判定失败。
4.5 第五步:接入 Harness 做结果评估
一个没有评测的 Agent 是不可信的。Harness 在这里的做法是:定义一组测试任务,逐条执行 Agent,记录每一步的工具调用、模型回复、最终结果,最后统计成功率。
“成功率”不需要非常复杂的规则,只要两个维度即可:
- 是否在限定的轮数内结束。
- 最终答案是否包含期望的关键信息。
有了这个 Harness,你后续改提示词、换模型、新增工具时,都能用同一组任务快速对比,而不是靠感觉判断。
5. 完整示例与代码实现
下面是可运行的完整示例。建议先在一个新目录中创建项目,把配置和代码分开放。
5.1 代码一:Ollama 启动 Qwen 模型
bash复制# 终端 1:启动 Ollama 服务
ollama serve
# 终端 2:拉取并确认模型
ollama pull qwen2.5:7b
ollama list
如果机器内存不足,可以把 qwen2.5:7b 换成 qwen2.5:3b,流程完全一致。
5.2 代码二:最小 Agent 主循环
文件路径:agent.py
python复制import json
import os
from datetime import datetime
from openai import OpenAI
# 读取配置:这里没有写死任何密钥,Ollama 本地接口通常不校验 key
BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5:7b")
API_KEY = os.getenv("OLLAMA_API_KEY", "ollama")
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
SYSTEM_PROMPT = """你是一个本地部署的智能Agent,需要根据用户任务逐步执行。
你可以使用以下工具:
1. get_current_time:获取当前系统时间,不需要输入参数。
2. search_product_price:查询商品价格,输入参数为商品名称。
你的输出必须是严格合法的JSON,且只输出一个JSON对象,格式如下:
如果还需要调用工具:
{"thought": "你的思考", "action": "工具名称", "action_input": {"参数名": "参数值"}}
如果已经完成任务:
{"thought": "你的思考", "final_answer": "对用户的最终回答"}
不要输出任何额外的解释文字。"""
def get_current_time() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def search_product_price(product_name: str) -> str:
mock_price_table = {
"手机": "1999元",
"笔记本电脑": "5999元",
"耳机": "399元",
}
return mock_price_table.get(product_name, "未找到该商品")
TOOL_MAP = {
"get_current_time": get_current_time,
"search_product_price": search_product_price,
}
def call_llm(messages):
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=messages,
temperature=0.2,
)
return resp.choices[0].message.content
def parse_action(text):
try:
return json.loads(text)
except json.JSONDecodeError:
return {"thought": text, "final_answer": text}
def run_agent(task: str, max_steps: int = 5):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": task},
]
for step in range(max_steps):
print(f"\n[步骤 {step + 1}] 请求模型...")
response_text = call_llm(messages)
print(f"[模型输出]\n{response_text}")
parsed = parse_action(response_text)
messages.append({"role": "assistant", "content": response_text})
if "final_answer" in parsed:
return {
"status": "success",
"answer": parsed["final_answer"],
"steps": step + 1,
}
action = parsed.get("action")
action_input = parsed.get("action_input", {})
if action not in TOOL_MAP:
tool_result = f"工具 {action} 不存在,请检查工具名称"
else:
try:
tool_result = TOOL_MAP[action](**action_input)
except Exception as e:
tool_result = f"工具执行失败:{e}"
print(f"[工具执行] {action} 返回结果: {tool_result}")
messages.append({"role": "user", "content": f"工具执行结果:{tool_result}"})
return {"status": "max_steps_exceeded", "steps": max_steps}
if __name__ == "__main__":
result = run_agent("现在几点了?请查询后告诉我。")
print("\n最终结果:", result)
这段代码的逻辑可以理解为:模型每一次输出都必须遵循 JSON 格式。如果要调用工具,就输出 action 和 action_input;如果任务完成,就输出 final_answer。代码负责解析和执行工具,再把工具结果放回会话上下文。
这样设计有两个原因:第一,把格式判断从模型侧移到代码侧,减少模型乱输出导致的流程中断;第二,所有调用记录都能打印出来,方便排查问题。
5.3 代码三:带工具调用完整流程的任务
修改 agent.py 底部的运行任务即可:
python复制if __name__ == "__main__":
result = run_agent("帮我查询手机的价格,然后告诉我是否可以直接购买。", max_steps=6)
print("\n最终结果:", result)
预期流程是:模型先输出调用 search_product_price 的 JSON,工具返回 1999 元,模型再基于这个结果输出最终回答。如果模型一次就输出 final_answer,说明任务太简单,不需要工具调用。如果想要测试工具调用链路,可以加大任务复杂度,比如要求“先查时间,再查商品价格,再对比给出建议”。
5.4 代码四:轻量 Harness 评测脚本
文件路径:harness.py
python复制import json
from agent import run_agent
TASKS = [
{
"name": "查询当前时间",
"task": "现在几点了?",
"expect_keyword": "20",
"must_use_tool": "get_current_time",
},
{
"name": "查询商品价格",
"task": "帮我查询笔记本电脑的价格",
"expect_keyword": "5999",
"must_use_tool": "search_product_price",
},
{
"name": "跨工具任务",
"task": "先查询当前时间,再查询手机价格,最后用一句话总结。",
"expect_keyword": "1999",
"must_use_tool": "search_product_price",
},
]
def run_harness(max_steps=6):
results = []
for task in TASKS:
print("\n=== 开始评测:", task["name"], "===")
agent_result = run_agent(task["task"], max_steps=max_steps)
answer = agent_result.get("answer", "")
status = "PASS"
fail_reason = []
if agent_result.get("status") != "success":
status = "FAIL"
fail_reason.append(f"Agent状态异常: {agent_result.get('status')}")
if task["expect_keyword"] not in answer:
status = "FAIL"
fail_reason.append(f"最终答案缺少关键词: {task['expect_keyword']}")
results.append({
"name": task["name"],
"status": status,
"reason": "; ".join(fail_reason) if fail_reason else "OK",
"answer": answer,
"steps": agent_result.get("steps", 0),
})
return results
if __name__ == "__main__":
all_results = run_harness()
print("\n========== Harness 评测汇总 ==========")
for item in all_results:
print(json.dumps(item, ensure_ascii=False, indent=2))
pass_count = sum(1 for r in all_results if r["status"] == "PASS")
total = len(all_results)
print(f"\n通过率:{pass_count}/{total}")
Harness 的意义不在于复杂的评分算法,而在于固定一组任务,让 Agent 在改动后有一个可以对比的基准。这里的 expect_keyword 判断很粗糙,但已经能拦住大多数“模型回答了但没有完成任务”的情况。
6. 运行结果与效果验证
运行方式:
bash复制python agent.py
python harness.py
成功时的预期输出应该包含两层信息:
- 在
agent.py中,每一轮都会打印模型输出和工具执行结果。例如模型先输出包含search_product_price的 JSON,随后打印[工具执行] search_product_price 返回结果: 5999元。 - 在
harness.py末尾,会输出每一条任务的 PASS/FAIL 状态,以及最终通过率。
判断成功的关键不是“模型是否回复了文字”,而是“Agent 是否在限定的轮数内正确调用了工具,并基于工具结果生成了最终答案”。
如果失败,建议按下面顺序排查:
- 看模型输出是否为合法 JSON。
- 看
action是否与TOOL_MAP里的键完全一致。 - 看工具执行是否抛异常。
这三个位置能覆盖绝大多数本地 Agent 的运行问题。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 调用接口返回 404 | 模型名称写错,或模型未拉取成功 | 执行 ollama list 查看实际名称 |
修改代码中的 MODEL_NAME 为实际名称 |
| 请求超时或连接失败 | Ollama 服务未启动,或端口被占用 | 执行 ollama serve 查看日志,用 curl http://localhost:11434/v1/models 测试 |
确保服务在独立终端运行,检查端口占用 |
| 模型输出不是 JSON | 模型指令遵循能力不足,或提示词不含示例 | 打印原始输出,观察格式 | 在系统提示词中加入“只输出 JSON”和示例;换用工具调用更稳定的模型 |
| Agent 陷入死循环 | 模型反复输出相同的工具调用,或解析逻辑错误 | 监控步骤数,打印每轮消息上下文 | 设置 max_steps 上限;限制工具输入长度;要求模型引用上一次工具结果 |
| 工具执行时报参数错误 | action_input 数据结构与函数签名不匹配 |
打印 action_input,检查键名 |
统一参数格式,让工具函数接受字典并自行解析 |
| 显存不足或内存不足 | 模型过大,或量化格式不合适 | 查看占用情况,更换小模型 | 换成 3B 模型,或使用 q4_k_m 等量化格式 |
| 中文输出乱码 | 终端编码问题 | 检查系统编码,设置 UTF-8 | Windows 下设置 set PYTHONUTF8=1 |
其中,最隐蔽的问题是“模型输出合法 JSON 但引用了错误工具名称”。这种问题不会在模型侧暴露,只会在工具执行侧提示“工具不存在”。实际上是因为 TOOL_MAP 的键名和提示词里给出的工具名不一致。建议所有工具名统一使用小写加下划线,不要出现空格。
8. 最佳实践与工程建议
8.1 配置与密钥管理
即使 Ollama 本地接口不校验 Key,也不要把凭证写死在代码里。建议使用环境变量或 .env 文件管理以下配置:
OLLAMA_BASE_URLMODEL_NAMEOLLAMA_API_KEY
如果是调用云端 API,密钥必须放在服务端环境变量中,禁止提交到 Git 仓库。
8.2 工具白名单与最小权限
Agent 能调用的工具必须有明确的白名单。尤其是涉及执行命令、读写文件、访问数据库的工具,一定要限制为最小权限。生产环境不要直接把任意 Shell 命令开放给模型。本地原型可以随意,但一旦接入了真实业务数据,就必须重新审视工具边界。
8.3 日志与轨迹记录
每次 Agent 运行都会产生一条“思维轨迹”:模型输出、工具调用、工具结果、最终答案。建议把轨迹保存为 JSON 文件,命名格式为 时间戳_任务名.json。这样做的好处是:
- 出问题时可以回放完整上下文。
- 评测 Harness 时可以统计模型调用了多少次工具。
- 团队协作时可以讨论提示词和工具设计的改进点。
8.4 版本锁定与回滚
本地部署最容易踩的坑是“昨天还能跑,今天不行了”。原因往往是:
- Ollama 自动更新导致模型名称变化。
- Python 依赖版本升级导致接口变更。
- 系统提示词被无意修改。
建议将依赖版本记录到 requirements.txt,将 Agent 代码纳入 Git 管理。每次改动后,先跑 Harness 评测,确认通过率没有下降,再应用到真实任务。
8.5 数据安全边界
本地部署的最大优势是数据不出内网。但要注意,本地模型一样会记录上下文。如果上下文里包含敏感业务数据,日志和轨迹文件也要按敏感数据管理,不能随意分享和上传。
8.6 评测集建设
不要只用一个任务验证 Agent。更合理的做法是准备 5 到 10 个代表真实场景的任务,分别覆盖“单工具调用”“多工具配合”“异常输入处理”“无需调用工具直接回答”四类情况。Harness 的价值会随着评测集扩大而显现。
9. 总结与后续学习方向
这篇文章讲清楚了一件事:本地部署一个 Agent 体系的关键不是“买模型”,而是把“本地推理服务、工具设计、编排循环、评测 Harness”四件事串起来。Qwen 系列开源模型提供本地推理能力,DeepSeek Harness 所代表的编排与评测思想保证了 Agent 的可观测、可验证、可迭代。
你可以先按照 agent.py 跑通一个最小循环,再用 harness.py 建立任务评测集,然后逐步加入更多工具、调整提示词、更换更大模型。如果核心循环稳定后仍然觉得效果不够好,下一步可以深入三个方向:
- 记忆管理:让 Agent 能处理多轮对话和长期任务。
- 上下文压缩:当工具调用次数很多时,如何保留关键信息而不触发上下文超限。
- 模型微调:对特定工具的调用格式进行微调,降低 JSON 解析失败率。
建议收藏这篇文章,把它当作本地 Agent 项目的起步模板。遇到问题时,回到第 7 节的排查表,绝大多数异常都能在“模型输出、工具映射、服务状态”三件事里找到原因。
