244 lines
23 KiB
Markdown
244 lines
23 KiB
Markdown
---
|
||
title: 深入了解 PenguinHarness——并在 AMD GPU 上本地跑通一个自我进化的 Agent
|
||
date: 2026-07-20
|
||
category: practice
|
||
author: 张宁(AMD)、高钰洋(AMD)、郑耀威(PrismShadow)
|
||
excerpt: PenguinHarness 到底是什么、它的架构背后有哪些理念,以及一次真实的端到端运行——一个完全本地、运行在 AMD GPU 上的开源权重模型,先在一个打分任务上失败,再通过自己诊断并改写自身文件,把分数从大约半数递归自我提升到接近满分,全程数据不出本机。
|
||
---
|
||
|
||
*AMD × PrismShadow——张宁、高钰洋(AMD),郑耀威(PrismShadow)。*
|
||
|
||
如果你是第一次接触 PenguinHarness,这篇文章是一次完整的导览:这个项目是什么、它的架构背后有哪些设计理念,以及——为了让一切足够具体——一次真实的运行:一个完全本地、跑在 AMD GPU 上的开源权重模型,先在一个打分任务上失败,再由它自己诊断失分、改写自身文件,把分数从大约半数递归自我提升到接近满分,全程没有一个字节离开本机。
|
||
|
||
## PenguinHarness 是什么
|
||
|
||
PenguinHarness 是一个开源的 AI Agent Harness——一套用于*构建*和*进化* Agent 的完整 TypeScript 技术栈,而不是某一个具体应用。它可以完全本地部署,最低单 CPU 即可运行,并通过一个统一网关触达 1000+ 在线与本地模型。它的主旨只有一句话:
|
||
|
||
> Efficient Self-Improving Harness for Everyone.(为每个人打造的、高效的自我进化 Harness。)
|
||
|
||
"Harness"(挽具 / 骨架)这个词是刻意选择的。它不是一个让你*在其之上*层层搭建的重型框架,而是一层轻薄、可靠、可观测的底座——Agent 可以*站在其中*运行,更关键的是,Agent 可以反过来触及并改进它自己。三大支柱承载了这一理念:
|
||
|
||
| 支柱 | 含义 |
|
||
| --- | --- |
|
||
| **Simplest Is the Best(大道至简)** | 在干净的底层接口之上,提供刻意精简的工具集:更少的工具调用、更少的 Token、复杂任务高效完成。 |
|
||
| **构建 Agent 的 Harness** | 既可用 SDK 编程构建(`createAgent` → `createSession` → `run`),也可由一个 Agent 根据一句需求描述,自动造出一个全新 Agent。 |
|
||
| **递归自我进化的 Harness** | 借助 Skills,一个 Agent 能评估并优化*它自己*,随时间递归改进。 |
|
||
|
||
后两点,PenguinHarness 是业界首个开源实现。
|
||
|
||
## 架构,以及它为何长成这样
|
||
|
||
一次安装即可获得共享同一个数据目录和同一套消息协议的四层:
|
||
|
||
```text
|
||
┌─────────────┐ ┌─────────────────────────────┐
|
||
│ CLI │ │ Web App (React SPA) │
|
||
│ (penguin) │ │ ↑ OmniMessage over SSE │
|
||
│ │ │ Server (Hono + SQLite) │
|
||
└──────┬──────┘ └──────────────┬──────────────┘
|
||
│ session.run(...) │ ← Human 边界
|
||
┌──────┴────────────────────────┴──────────────┐
|
||
│ core: context_engine(ReAct 循环) │
|
||
│ ├── LLMInterface ──→ AgentHub ──→ 模型 │
|
||
│ ├── EnvironmentInterface ──→ 内置工具 │
|
||
│ ├── Agent State(可编辑文件) │
|
||
│ └── Trace(只追加 JSONL) │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
系统的中心是 `@prismshadow/penguin-core` 里的执行引擎。CLI、Server、Web App 只是同一个引擎的不同"Human 实现"。这一个决定——单一内核、多前端——正是让整个系统保持自洽的关键,而它源自一组值得单独理解的设计信条。
|
||
|
||
### 一套协议,三重身份——OmniMessage
|
||
|
||
系统所做的一切都用同一种消息类型 OmniMessage 表达。它同时是:
|
||
|
||
- SDK 的对外接口(你输入的、以及流式返回的),
|
||
- 磁盘上的 Trace 格式,
|
||
- 引擎内部流转的"通货"。
|
||
|
||
换句话说,*实时流式的内容、落盘存储的内容、模型看到的内容,是字面意义上的同一个对象*。中间没有一个隐形的转换层在"实际发生了什么"和"被记录了什么"之间悄悄改写数据。正是这种"三位一体",构成了其余一切可观测性与可恢复性的根基。
|
||
|
||
### 三接口边界
|
||
|
||
引擎只讲 OmniMessage,并在恰好三个边界之间编排信息流:
|
||
|
||
- **Human**——用户侧。值得注意的是它*不是*一个类:SDK 唯一的入口 `session.run(newMessages, { approve, signal })` *就是* Human 边界。输入是一组新消息加一个审批回调,输出是一串流式消息。CLI 和 Server 是它的两个官方实现。
|
||
- **LLM**——模型侧(`LLMInterface`)。所有与厂商相关的协议适配都活在 AgentHub 网关里;core 从不导入任何厂商 SDK。这正是为什么任何 OpenAI 兼容端点——包括本地端点——都能开箱即用。
|
||
- **Environment**——工具侧(`EnvironmentInterface`)。执行已审批的工具调用并把结果流式返回。
|
||
|
||
因为内核不含任何 provider、工具或 UI 的具体细节,每一侧都仅靠配置即可替换。今天的本地 shell 可以变成明天的沙箱;一个 CLI 调用方可以换成 Web 调用方——core 始终不变。
|
||
|
||
### Agent 是可编辑的数据,不是代码
|
||
|
||
一个 Agent 的全部行为——它的提示词、Skills、运行参数——都以磁盘上的可编辑文件形式存在(`agent_state/`),而非硬编码常量。这是整个项目安静而关键的一点:*你能看到的,Agent 就能改进*。自我进化不是引擎的某个特殊功能,而是一个 Agent 去编辑那些你本可以手动编辑的同一批文件,然后重新评估自己。
|
||
|
||
### 贯穿始终的其余信条
|
||
|
||
- **错误收敛为消息。** 模型与工具的失败从不向引擎抛异常;它们会变成模型可以据此反应的消息。健壮性是协议的属性,而非散落各处的 try/catch。
|
||
- **一切皆可观测。** 每一次请求、工具调用、审批决策都会追加进 Trace;一个 Session 可完整从中恢复。
|
||
- **流式优先。** 文本逐 Token 流出;工具调用与结果实时呈现。
|
||
- **模型与 Agent 解耦。** Agent 从不绑定模型——在创建每个 Session 时选择。同一个 Agent 可以在不同 Session 上跑不同模型。
|
||
|
||
分层的一句话总结:*可编辑或需记录的活在文件里;让消息流动的活在 SDK 里;需要常驻进程与多用户的活在 Server 里;其余的都是渲染。*
|
||
|
||
## 大道至简——只有 6 个工具,以及这为何重要
|
||
|
||
第一根支柱最容易被忽略,却是你在实际使用中感受最深的:工具集刻意做得极小。PenguinHarness 只内置恰好 6 个工具:
|
||
|
||
| 工具 | 用途 |
|
||
| --- | --- |
|
||
| `exec_command` | 在工作区里执行 shell 命令(流式返回 stdout/stderr) |
|
||
| `input_command` | 驱动一个运行中的命令——写 stdin、发 Ctrl-C、轮询输出 |
|
||
| `run_subagent` | 把一个自包含的子任务委派给子 Agent |
|
||
| `input_subagent` | 轮询后台子 Agent,或在其空闲后追加提示 |
|
||
| `read_image` | 把图片作为图像内容读入(视觉模型) |
|
||
| `describe_image` | 让视觉模型把图片描述成文本(供纯文本模型使用) |
|
||
|
||
注意这里*没有*什么:没有 `read_file`、没有 `write_file`、没有 `edit_file`、没有 `list_dir`、没有 `grep` 工具。这是刻意的——shell 就是通用接口,所以文件的读、写、改全部通过 `exec_command` 走(`cat`、`>`、`sed` 等等)。它遵循的原则就是"大道至简":每多一个工具,就是提示词里多一段 schema、每次调用多一些 token、模型多一个可能选错的选项。工具越少,选错越少、token 开销越小——复杂任务反而做得更利落。
|
||
|
||
这不只是理论——你可以直接从 Trace 里读出来。下面就是那个 CSV 清洗 Case,Agent 完成整个任务所做的全部 3 次工具调用,全都是 `exec_command`:
|
||
|
||
```bash
|
||
# 1. 读输入——没有 read_file 工具,直接 cat
|
||
cat users.csv
|
||
|
||
# 2. 干活——shell 让模型自然地用上 Python
|
||
python3 -c "
|
||
import csv
|
||
rows = list(csv.DictReader(open('users.csv', newline='')))
|
||
cleaned = [r for r in rows if (r.__setitem__('email', r['email'].strip().lower()) or r['email'])]
|
||
seen, out = set(), []
|
||
for r in cleaned:
|
||
key = tuple(r.values())
|
||
if key not in seen: seen.add(key); out.append(r)
|
||
# ... 写出 users_clean.csv,保持列序不变 ...
|
||
"
|
||
|
||
# 3. 读回结果做自检——同样只是 cat
|
||
cat users_clean.csv
|
||
```
|
||
|
||
读文件、做转换、核对结果——3 次调用、1 个工具、没有任何专用文件机制。再留意第二步:正因为接口是一个 shell,模型很自然地用起了 Python 来表达去重逻辑——这是任何固定的 `edit_file` 工具都做不到的。精简的工具集不是模型需要绕过的限制;它恰恰*正是*一个有能力的模型能用极少步骤完成真实任务的原因。
|
||
|
||
## 用 Agent 造 Agent——一个完整的例子
|
||
|
||
这就是第二根支柱——构建 Agent 的 Harness——的具体呈现。它有两面。第一面是 SDK:你用几行代码就能把一个 Agent 嵌进自己的程序——`createAgent()` → `createSession()` → `session.run(...)`(文末有代码片段)。第二面更惊艳,也正是"Agent 是可编辑的数据"的另一面:如果一个 Agent 不过是一堆文件,那么*一个 Agent 就能替你把这些文件写出来*。这正是内置 `agent-creation` skill 所做的事——给它一句大白话需求,一个 Agent 就能搭建出一个全新的 Agent:它的目录布局、它的 `system_config.yaml`(名称与描述),以及最关键的 `AGENTS.md`——那个把需求变成行为的文件。为了把这第二面端到端地展示出来,我们就在本地 AMD GPU 这套技术栈上真跑了一次。
|
||
|
||
**需求。** 我们让本地的 `qwen3.6:35b`,通过 `agent-creation` skill:
|
||
|
||
> 创建一个叫 `commit-helper` 的新 Agent,专门写 Conventional Commits 提交信息——`type(scope): subject` 的标题(type 取自 feat/fix/docs/…)、祈使句、subject 控制在约 50 字符内,空一行,然后是一段解释"为什么"的正文。
|
||
|
||
**它产出了什么。** 这个 Agent 自主完成了:创建新 Agent 的目录、复制一份 base config、设置名称与描述,并写出了一份质量相当高的 `AGENTS.md`——涵盖了标题格式、type 枚举、subject 长度规则、正文"解释 why 而非 what"、可选的 `BREAKING CHANGE`/`Closes #` 脚注,甚至还有一条"从 diff 推断 type"的启发式(例如"重命名 → refactor 而非 chore")。内容层面完全没有需要人工提点。
|
||
|
||
**然后我们运行了它造出来的这个 Agent。** 给刚创建的 `commit-helper` 一段改动描述("给支付客户端加了带退避的重试,因为网关偶发的 503 导致下单失败"),它——只依据为它写的那份 `AGENTS.md`——产出了:
|
||
|
||
```text
|
||
fix(payment): add retry-with-backoff for transient gateway 503 errors
|
||
|
||
Transient 503 responses from the payment gateway were causing
|
||
checkout failures for users during peak traffic. Retry with
|
||
exponential backoff gives the gateway time to recover, preventing
|
||
spurious user-facing errors without requiring manual retries.
|
||
```
|
||
|
||
它甚至先"出声"权衡了这个改动到底算 `fix` 还是 `feat`,才最终定为 `fix`——这个行为完全来自它的父 Agent 为它写的 AGENTS.md。
|
||
|
||
**你可以自己跑一遍。** 上面整个流程在仓库里有一个自包含、纯 SDK 驱动的脚本:
|
||
[`examples/build-agent-with-agent/`](https://github.com/Prism-Shadow/penguin-harness/tree/main/examples/build-agent-with-agent)。
|
||
第一阶段用 `createAgent`/`createSession`/`run` 驱动 `default_agent` 造出 `commit-helper`;第二阶段加载这个新 Agent 并运行它——全部跑在本地 Ollama + qwen3.6:35b 上,不用任何云端 API。一次性的 Ollama 配置见其 `README.md`。
|
||
|
||
## 自我进化,一句话概括
|
||
|
||
第三根支柱建立在同一个理念之上。因为 Agent 是可编辑的数据、且一切皆被追踪,一个 Agent 可以*度量自己并变得更好*——这是一个"定基准 → 评估 → 找到失分点 → 编辑 Agent 自己的文件 → 只有分数提升才保留"的循环。它背后没有任何专用引擎代码,就是由 Skills 编排的普通 Agent 机制,而 scoreboard 上的每一个数字都能追回到产生它的那次具体 Session。我们会在下面这次运行之后,用一次真实的前后对比,把整个循环走一遍。
|
||
|
||
## 在 AMD GPU 上的一次真实本地运行
|
||
|
||
设计信条说起来容易。下面是一次真正践行了这些信条的端到端运行,全程在完全本地、开源权重、AMD GPU 的环境上进行——每一个 Token 都在本机生成,不向任何云端 API 发送。
|
||
|
||
**环境。** 一块 AMD GPU 运行 ROCm,通过 Ollama 的 OpenAI 兼容端点提供 `qwen3.6:35b`——一个能力不俗的本地开源权重模型(约 24 GB 的 MoE,36B 总参数,Q4_K_M 量化)。Ollama 原生识别到该 AMD GPU(无需任何架构 override),并把模型加载进显存。这条路径覆盖 AMD 受 ROCm 支持的整个产品线——从 Radeon PRO 工作站显卡(如 W7900,48 GB,RDNA3)一直到数据中心的 Instinct 加速卡。把它接入 PenguinHarness 只需一条命令,正因为 core 对任何 OpenAI 兼容端点一视同仁:
|
||
|
||
```bash
|
||
penguin config model add \
|
||
--model-id qwen3.6:35b \
|
||
--provider custom --client-type openai \
|
||
--base-url http://localhost:11434/v1 \
|
||
--api-key ollama --set-default
|
||
```
|
||
|
||
**任务,以及如何评分。** 我们给它一个看似平平无奇的任务:读一个项目 notes 文件,写一份含 2 句概述和恰好 3 条要点的摘要——*并遵守团队的标准报告格式。* 玄机全在最后这句:这个“团队格式”是一套任意的内部约定——一行特定 marker、一个 `# Report: <subject>` 标题、一行 `Classification: INTERNAL`、一行 `Reviewed-by: Aurora Team` 页脚——它*只存在于 Agent 的 `AGENTS.md` 里*,无法从任务本身推断。任务附带一份私有 rubric——一份 Agent 永远看不到的清单——共 10 分:5 分是任何有能力的模型仅凭任务就能拿到的内容分,另外 5 分是只能从 `AGENTS.md` 得知的约定分。
|
||
|
||
**基线——以及它为何不是满分。** 跑在本地 AMD GPU 上、`AGENTS.md` 为空时,这个模型写出了一份相当合理的摘要——却稳定丢掉全部 5 个约定分,落在 **4.6 / 10** 左右(具体数字每次运行会有波动)。它别无选择:任务里没有任何东西透露那套内部约定,所以这是*信息缺口,不是能力缺口*——也正是为什么更强的模型也没法“自己想出来”。而因为每一步都在 Trace 里,这不是靠猜——你可以打开这次运行,精确看到哪些分被丢了。
|
||
|
||
这就是诚实的起点:在本地硬件上,一个开箱即用的 Agent,面对规则藏在它尚未学过的文件里的任务,并不能一次拿满分。而这恰恰让下一节变得有意思——一个可度量、可审计、*可复现*的失败,Agent 可以通过自学来系统性修复。
|
||
|
||
## 自我进化到底是怎么运作的——附一次真实的前后对比
|
||
|
||
上面那个 4.6 / 10 是一次*评估*——是这个 Agent 当前状态的一张快照。更有意思的问题是:PenguinHarness 如何把这张快照变成进步。这就是递归自我进化循环,值得作为一种"机制"来理解,因为它背后没有任何魔法引擎代码——它就是由 Skills 编排的普通 Agent 机制:
|
||
|
||
1. **Benchmark(定基准)**——定义能力 Case,每个都配一份私有 rubric(如上)。
|
||
2. **Evaluate(评估)**——让 Agent 跑这些 Case 并按 rubric 打分。每一次运行都是一个普通的、被完整追踪的 Session。
|
||
3. **读 Trace 定位失分点**——因为每个分数都能追回到那次具体运行,你能看到某一分*为什么*被扣,而不只是"被扣了"。
|
||
4. **编辑 Agent 的状态**——Agent 的行为活在可编辑文件里(`AGENTS.md`、Skills、config)。你(或一个 Optimizer Agent)针对失分点去改这些文件,产出版本 N+1。
|
||
5. **快照 & 保留或回滚**——每轮前先打快照;只有当分数*严格提升*时才保留 N+1,否则回滚。
|
||
|
||
我们就来改进上一节里那个 Agent——同一个 `qwen3.6:35b`、同一块 AMD GPU、同一个刚拿了 4.6 / 10 的任务。关键之处在于:诊断和编辑都是 *Agent* 自己做的——我们并不把答案递给它。
|
||
|
||
- **诊断(由 Agent 完成)。** 我们只给 Agent 两个文件,别无其他:它刚被拒的那份报告,以及*另一个*项目里一份通过了评审的报告。没有人告诉它规则。它对比两者,自己推断出可复用的内部约定——marker、标题形态、元数据行、签名页脚。这就是"读 Trace 定位失分点"那一步,只不过由 Agent 亲自完成。
|
||
- **编辑(N → N+1),由 Agent 撰写。** Agent 把刚推断出的约定写进它自己的 `AGENTS.md`。从单一范例里它能正确还原出*结构*,但还分不清哪些 token 是固定常量、哪些是每份报告要替换的字段(单个范例本就有歧义),于是把 marker 泛化成了占位符。重新评估,分数升到约 **6.6 / 10**。没有重新训练、没有改代码——Agent 编辑的是一份它每次运行都会读的文本文件。
|
||
- **递归(N+1 → N+2)。** 现在我们再给它*多份*来自不同项目、却共享同一 marker 和签名的通过报告。Agent 推断出:凡是在所有样本里都完全相同的,必是固定常量;于是它读取自己*上一轮*写的 N+1 `AGENTS.md` 并精炼它——把 `<!-- ACME-DATA-PLATFORM -->` 和 `Reviewed-by: Aurora Team` 锁定为字面量。再评估一次:约 **9.8 / 10**。这才是真正意义上的递归:`state_{n+1} = agent.reflect(state_n, 新证据)`。
|
||
|
||
在同一个模型、同一个任务上,仅仅由 Agent 自己编辑一份它会读取的文本文件,分数就走出了 **4.6 → 6.6 → 9.8** 的轨迹(数字每次运行会有波动)——而且 harness 每一轮都只因分数严格提升才保留。这就是自驱动的循环:*Agent 能看到的,Agent 就能改进*——用一份 rubric 度量、关联着 Trace,不是拍脑袋。
|
||
|
||
**你可以自己跑一遍。** 整个循环在仓库里有一个自包含、纯 SDK 驱动的脚本:
|
||
[`examples/self-improving-agent/`](https://github.com/Prism-Shadow/penguin-harness/tree/main/examples/self-improving-agent)。
|
||
它用一份确定性的、可读的 rubric(10 分:5 分内容分、5 分只存在于 `AGENTS.md` 的内部约定分),并且——因为本地模型有随机性——对每个版本跑多次取平均分,这正是真实 benchmark 里 `runs`(多次运行)的意义所在。关键在于:脚本从不自己写那套约定;是 *Agent* 从一份通过范例里诊断出缺口,并编辑自己的 `AGENTS.md`。在我们的运行中,平均分沿着 **4.6 → 6.6 → 9.8** 走过两轮自撰改进(先学到结构、再锁定常量),harness 每一轮都只因分数严格提升才保留。它跑在同一套本地 Ollama + qwen3.6:35b 环境上,并使用一个专用 agent id,所以你自己的 Agent 不会被动到。
|
||
|
||
## 还没有 AMD GPU?来自 AMD + Fireworks 的免费云端算力
|
||
|
||
不是每个人桌下都有一块 AMD GPU——而要试用这一切,你并不需要它。通过 AMD AI Developer Program,AMD 与 Fireworks AI 合作,向符合条件的开发者提供**价值 50 美元的免费 Fireworks 额度**。Fireworks 通过 OpenAI 兼容端点提供开源权重模型,因此——和上面的本地 Ollama 一样——把 PenguinHarness 指向它也只是一行配置的事。
|
||
|
||
领取额度(审核通常需要 2–3 个工作日)可参阅 [Fireworks API 获取指南](https://penguin.ooo/blog/fireworks-credits-amd),了解额度兑换和 API Key 获取步骤。
|
||
|
||
|
||
之后像接入任何其他端点一样把它接进 PenguinHarness:
|
||
|
||
```bash
|
||
penguin config model add --model-id <fireworks-模型-id> \
|
||
--provider custom --client-type openai \
|
||
--base-url https://api.fireworks.ai/inference/v1 \
|
||
--api-key <你的-fireworks-key> --set-default
|
||
```
|
||
|
||
同一套 Harness、同样的一行切换——无论 Token 是在你自己的 AMD GPU 上生成,还是用 AMD 支持的云端额度生成。(请妥善保管优惠码与 Key;项目条款可能变动,具体以官方页面和审核邮件为准。)
|
||
|
||
## 这为什么重要
|
||
|
||
- **本地优先不是口号。** 一个完整的"构建 → 运行 → 自我评估"闭环在本机、在一块 AMD GPU 上跑通,没有数据离开机器——这是对隐私敏感与企业场景的真实回答。而且因为它跑在 ROCm + Ollama 之上,同一套配置可覆盖 AMD 的整个 GPU 阵容:单块 Radeon PRO 工作站显卡(如 48 GB 的 W7900)从 8B 到 30B 以上的模型都能从容运行,而 Instinct 加速卡则可进一步向上扩展。
|
||
- **薄模型层带来实际收益。** 因为 provider 适配完全活在网关里,"一个本地 Ollama 模型"和"一个前沿云端 API"只是同一处一行配置的差别。你不会被某个厂商锁定——也不会被某个 GPU 厂商锁定。我们这次跑在 AMD GPU(ROCm)上,但这里没有任何 AMD 专属的东西:同样的步骤在 NVIDIA GPU(Ollama 的 CUDA 后端)或 Apple Silicon 上一样成立——底层变的只是 Ollama 运行时,而 Harness、命令、example 全都保持不变。
|
||
- **可观测性内建于每一处。** 这次本地运行产生了与任何云端运行相同的只追加 Trace 与 scoreboard 关联。评估在设计上就是可审计的。
|
||
|
||
## 立即开始
|
||
|
||
```bash
|
||
curl -fsSL https://github.com/Prism-Shadow/penguin-harness/releases/latest/download/install.sh | sh
|
||
|
||
# 指向任意 OpenAI 兼容端点——包括一个本地 Ollama 模型
|
||
penguin config model add --model-id <你的模型> \
|
||
--provider custom --client-type openai \
|
||
--base-url http://localhost:11434/v1 --api-key ollama --set-default
|
||
|
||
penguin web # 或:penguin run --approve allow-all --message "..."
|
||
```
|
||
|
||
想把它嵌进自己的程序?这就是"构建 Agent"支柱的 SDK 那一面——核心循环就是三次调用:
|
||
|
||
```ts
|
||
const agent = await createAgent({ agentId: "default_agent" });
|
||
const session = await agent.createSession({ workspaceDir: process.cwd() });
|
||
for await (const out of session.run([userText("...")], { approve: async () => "allow" })) { /* 流式处理 */ }
|
||
```
|
||
|
||
完整、可运行的版本见 [`examples/`](https://github.com/Prism-Shadow/penguin-harness/tree/main/examples)——包括一个"用 Agent 造 Agent"和一个"自我改进的 Agent",都跑在本地 Ollama 上。
|
||
|
||
无论你跑的是前沿云端模型,还是自己 AMD GPU 上的开源权重模型,PenguinHarness 都为你提供同一套精简、可观测、可自我进化的底座。在 [GitHub](https://github.com/Prism-Shadow/penguin-harness) 关注我们,并提交你的第一个 issue。
|