内容
# Ingot
**基于证据的代理指令变更控制。**
[](https://github.com/SlanchaAI/ingot/actions/workflows/ci.yml) [](LICENSE) [](Dockerfile) [](docker-compose.yml)
代理的[技能](https://github.com/anthropics/skills)是指导代理行为的指令。**Ingot**是一个本地优先的库,它将技能视为需要审查流程的版本化状态。每个技能文件夹都经过内容寻址。每个提出的更改都将被隔离,直到人类读取证据并批准。升级是原子的,记录了它替换的内容。
**MCP 服务器**提供每个任务的正确技能的已批准修订,这使得廉价或本地模型可以重用原本需要前沿模型的方法。
系统实际保证:
- **修订版本命名确切的技能。** `skill_revision`哈希文件夹中的每个文件,因此跟踪、证据和磁盘上的修订版本都是可比较的。
- **更改被隔离。** 代理作者的技能(`create_skill`)和生成的重写被放入`runs/pending/`,不能路由流量。每个技能有一个审查槽;被取代的候选者被存档,而不是被丢弃。
- **批准需要证据。** 重写携带一个包,存放在`runs/evidence/<skill>/<ts>/`,作为`evidence.json`和`EVIDENCE.md`,可以从审查卡中读取。一个正文通过包保存保留的冠军和挑战者的分数、每个案例的增量、第一个行为分歧、代币成本和门控判决;一个描述通过包保存路由器的指标,这些指标对其进行了门控。 提升重新检查证据是否与磁盘上的冠军匹配;如果挑战者在均值上获胜但在门控上失败,则记录并拒绝。
- **提升是原子且可逆的。** 被取代的修订版本快照到`runs/revisions/`,目录通过重命名进行交换;如果交换失败,则恢复原始状态。可以通过UI的历史部分或CLI恢复任何快照。
- **决策被审计。** 批准和回滚在`runs/approval-audit.jsonl`中追加元数据记录(操作、技能、修订版本、执行者、时间戳)。绝不涉及技能文本,绝不涉及凭证。追加是尽力而为的:记录在更改已经提交到磁盘后写入,因此如果写入失败,则记录为警告,并将提升或回滚留在原处,而不是撤销它。将跟踪视为本地操作日志,而不是防篡改账本。
首先为个人用户构建:
- **精简版。** `docker compose up`仅启动路由器和变更控制UI。跟踪堆栈是一个可选的升级( `--profile langfuse`),不是必需的。
- **本地。** 将其指向Ollama或vLLM,它在没有API密钥的情况下运行;技能、跟踪和评估永远不会离开您的机器。
- **安全。** 主机调用默认使用OpenRouter,强制执行零数据保留提供商路由,并且在每个请求上强制执行。并且没有任何服务可以通过您的机器访问。
- **简单。** 技能是一个带有`SKILL.md`的文件夹。将一个放入其中,它将在下一个请求中生效。
变更来自哪里?大多来自您和您的代理。Ingot还附带一个**可选**的候选生成器:它挖掘真实的跟踪数据以查找失败的技能,起草重写,并在保留的任务上进行衡量。它是一个后台实验,产生提案,而不是激活。在[下面的记录演练](#5-optional-generate-a-candidate-in-the-background)中,一个过时的技能的保留平均裁判分数从0.133上升到0.767,而输出令牌从1278下降到1032,成本约为0.04美元,并且仍然需要人类批准。
[快速入门](#quickstart-lite-mode) · [完整教程](#tutorial) ·
[架构](ARCHITECTURE.md) · [贡献](CONTRIBUTING.md) · [安全](SECURITY.md) ·
[MIT 许可证](LICENSE)
## 快速入门(精简模式)
```bash
git clone https://github.com/SlanchaAI/ingot.git && cd ingot
cp .env.example .env # 添加 OpenRouter 密钥,或将 BASE_URL 指向 Ollama(无密钥)
scripts/fetch_skills.sh all # 获取约 70 个真实技能到 ./skills(参见技能来源)
docker compose up # 默认精简版:技能路由器(localhost:8000)+ 变更控制
# UI(localhost:8080)+ 一个演示代理运行
docker compose run --rm agent "如何将多个 PDF 合并为一个并添加页码?"
```
精简堆栈使用端口`8000`和`8080`,以及固定的Compose项目名称`ingot`。在同一主机上启动第二个检出之前,请停止现有的Ingot堆栈。
没有托管密钥?最终命令仍然验证路由器:`suggest_skills`返回`pdf`技能,评分为0.74,然后代理解释如何配置模型以获取完整答案。
每个代理运行都追加到本地跟踪存储(`runs/traces.jsonl`);`optimize-mine`和保留的A/B读取它,当Langfuse不可用时,因此整个循环在精简模式下工作。您只会丢失跟踪浏览器和实验UI。想要这些?
本地跟踪记录使用模式版本1。原始无版本记录仍然可读。存储默认使用机密模式的路径进行编辑,模式为`0600`,父目录模式为`0700`,最大旋转10 MiB,备份三个。在`.env`中配置它:
```bash
LOCAL_TRACE_ENABLED=false # 完全退出本地跟踪
LOCAL_TRACE_REDACT=true # 编辑公共令牌、密钥、密码和机密赋值
LOCAL_TRACE_MAX_AGE_DAYS=30 # 追加时修剪较旧的记录;0 禁用年龄保留
LOCAL_TRACE_MAX_BYTES=10485760 # 在追加下一个之前旋转到此大小
LOCAL_TRACE_BACKUPS=3 # 保留的旋转文件;0 截断旋转
TRACES_FILE=/app/runs/traces.jsonl # 可选的替代路径
```
编辑是一种深度防御,而不是数据丢失预防系统。避免在任务中放置机密,审查跟踪访问,根据保留要求删除旧备份。Langfuse具有单独的保留控制。
```bash
docker compose --profile langfuse up # 添加自托管的 Langfuse 堆栈(localhost:3100)
```
所有内容都在原地升级。下面教程在精简模式下运行;其中没有任何内容需要Langfuse堆栈。
## 优先考虑隐私
三个属性,全部是默认值,无需可选:
- **零数据保留LLM调用。** 默认提供商是 **OpenRouter**,在每个请求上强制执行 [零数据保留(ZDR)](https://openrouter.ai/docs/features/zdr)。每个调用(代理运行、候选展开和反射、裁判、任务起草)都带有硬编码的提供商偏好:
```json
{"provider": {"zdr": true, "data_collection": "deny"}}
```
然后,OpenRouter仅路由到由不收集用户数据的提供商运营的ZDR端点。没有合格端点的模型会大声失败,而不是回落到保留提示的模型。提供商直接端点也起作用:例如,Fireworks AI 默认情况下 [零数据保留](https://docs.fireworks.ai/guides/security_compliance/data_handling)适用于服务器上的开放模型。
- **自托管跟踪。** Langfuse(带有Postgres、ClickHouse和MinIO)在撰写堆栈内运行。跟踪、技能内容和评估输出永远不会离开您的机器。
- **仅限本地主机。** 没有服务可以从机器外部访问(参见 [网络暴露](#network-exposure))。
离开您机器的唯一数据是LLM流量本身。`BASE_URL` + `API_KEY` 将所有内容指向任何OpenAI兼容的提供商(`MODEL_BASE_URL` / `MODEL_API_KEY` 仅覆盖服务角色):
```bash
# 默认(.env.example):OpenRouter,ZDR提供商路由强制在代码中执行
BASE_URL=https://openrouter.ai/api/v1
API_KEY=sk-or-...
# 提供商直接替代:Fireworks AI(开放模型在服务器端默认零数据保留)
BASE_URL=https://api.fireworks.ai/inference/v1
API_KEY=fw_...
AGENT_MODEL=accounts/fireworks/models/qwen3p7-plus
GEPA_MODEL=accounts/fireworks/models/glm-5p2
JUDGE_MODEL=accounts/fireworks/models/deepseek-v4-pro
# 完全本地(无需密钥):Ollama / vLLM上的所有内容
BASE_URL=http://172.17.0.1:11434/v1 AGENT_MODEL=qwen3:32b GEPA_MODEL=qwen3:32b JUDGE_MODEL=llama3.3:70b
```
当没有任何内容指向托管端点时,不需要API密钥。从撰写容器内部,“localhost”就是容器本身;使用主机的LAN IP(或Linux上的`172.17.0.1`)。
## 教程
本教程带您完成一个技能的整个生命周期:编写快速的第一稿技能,观察其交付不佳,诊断问题,生成候选更改,然后审查、提升和(如果您想恢复)回滚。下面的每个命令和数字都来自实际运行。
### 1. 设置和启动堆栈
```bash
git clone https://github.com/SlanchaAI/ingot.git && cd ingot
cp .env.example .env # 将您的 OpenRouter 密钥放入其中(https://openrouter.ai/settings/keys)
scripts/fetch_skills.sh all # 获取约 70 个真实技能到 ./skills(参见技能来源)
docker compose up --build
```
这将启动MCP服务器(`localhost:8000`)和变更控制UI(`localhost:8080`),然后在演示任务上运行一次代理。
没有提交到此存储库的技能。`fetch_skills.sh`克隆每个源,复制其技能,并删除克隆,因此所有内容都保留在其自己的上游许可证下。没有API密钥,所有内容仍然启动,路由器仍然打印建议;代理和候选生成器告诉您设置并干净地退出。
### 2. 代理路由到技能并使用它
```bash
docker compose run --rm agent "如何将多个 PDF 合并为一个并添加页码?"
```
```
COMPATIBLE ROUTE (MCP route_and_load):
0.74 pdf: 在用户希望对 PDF 文件执行任何操作时使用此技能...
SERVING MODEL: accounts/fireworks/models/qwen3p7-plus
LOADED SKILLS (MCP route_and_load): ['pdf@83a75cf1f9b5…']
TOKENS: 23233 in / 698 out
RESULT:
... (有效的 pypdf + reportlab 代码,按照加载的技能)
```
代理询问了规范路由器(`route_and_load`),收到一个兼容的技能主体,并遵循它。`@…`后缀是技能的内容哈希修订版,也作为标签记录在跟踪中。无约束建议永远不会控制服务模型或加载指令。路由任务在廉价的`AGENT_MODEL`上运行,因为技能带有方法;只有真正新颖的任务才会升级到`STRONG_MODEL`(步骤10)。步骤2到4记录在Fireworks模型ID上,步骤5到8记录在OpenRouter默认值(`qwen/qwen3-32b`服务);`SERVING MODEL`行始终显示配置的`AGENT_MODEL`。运行的完整跟踪已记录在`runs/traces.jsonl`中;本地存储就是矿工在步骤4中读取的内容。
### 3. 编写第一稿技能并观察其交付不佳
技能是一个带有`SKILL.md`的目录:YAML前置事项的描述是路由键,主体是代理加载的。教程技能是 **Tailwind CSS**,一个具有硬版本破坏(v4将配置从`tailwind.config.js`移到CSS)的库,因此快速记录的内容不仅薄弱,而且过时:
```bash
mkdir -p skills/tailwind
cat > skills/tailwind/SKILL.md <<'EOF'
---
name: tailwind
description: 当用户需要帮助使用 Tailwind CSS 时,请使用此技能。
---
# Tailwind CSS
1. 使用 npm 安装并运行 init 命令以创建 tailwind.config.js。
2. 将您的源文件添加到 `content` 数组中,以便捕获类。
3. 在 `theme.extend` 下放置自定义颜色、字体和断点。
4. 以 `@tailwind base;` `@tailwind components;` `@tailwind utilities;` 开头编写 CSS。
EOF
```
该主体的每一行都是v3方式,自v4以来不正确。`skills/`是绑定挂载的,服务器在更改时热加载,因此技能立即生效。发送一些现实流量:
```bash
for t in "在我的 Tailwind 设置中添加品牌颜色,以便使用 bg-brand。" \
"在新的 Vite 应用中设置 Tailwind。" \
"为什么 node_modules 中的组件库类没有被样式化?"; do
docker compose run --rm agent "$t"
done
```
```
== Add a brand color to my Tailwind setup so bg-brand works.
0.723 tailwind - 当用户需要帮助使用 Tailwind CSS 时,请使用此技能。
LOADED SKILLS (MCP get_skill): (none)
== Set up Tailwind in a fresh Vite app.
0.709 tailwind - 当用户需要帮助使用 Tailwind CSS 时,请使用此技能。
LOADED SKILLS (MCP get_skill): ['tailwind@77156f5efe1a…']
== Why aren't the classes from my component library in node_modules getting styled?
0.621 web-artifacts-builder (相关 - compose/extend)- 一套用于创建复杂...的工具
LOADED SKILLS (MCP get_skill): (none)
```
三个请求,三个不同的失败。说“Tailwind”的请求路由正确,第二个请求实际加载了技能,为实时流量提供过时的v3笔记。第三个请求从未说“Tailwind”,而是错误路由到`web-artifacts-builder`,后者也在其描述中提到了Tailwind。所有三个失败现在都位于您的跟踪中。
### 4. 从真实跟踪中挖掘失败的内容
`optimize-mine`使用多维LLM裁判([SkillForge论文](https://arxiv.org/abs/2604.08618)的“故障分析器”应用于您的跟踪)重新评估已记录的流量。如果跟踪标记了技能或将其列为嵌入前5名,则该跟踪计入技能:
```bash
docker compose run --rm optimize-mine tailwind
```
```
[mine] 39/50 最近的跟踪与 'tailwind' 相关
[mine] 分析了 39 条真实跟踪 · 平均裁判分数 0.50 · 20 个坏案例(分数 < 0.5)
[mine] 失败维度,按常见程度排序:
correctness 20/39 █████
completeness 20/39 █████
instruction_following 20/39 █████
efficiency 16/39 ████
[mine] 6 个最弱任务挖掘为评估候选 → 优化这些任务
```
诊断是版本漂移:答案基于`tailwind.config.js`和`@tailwind`指令,即v3世界所教导的内容。最弱的挖掘任务作为评估候选被提出,用于`optimize/tasks/<skill>.yaml`;参见[编写评估任务集](#writing-eval-task-sets)。
基于参考的实时流量评估是有噪声的。将挖掘的维度视为诊断结果进行调查,而不是判决;证据门控运行在规则上。
### 5. (可选) 在后台生成候选方案
此时,您已经知道问题所在,可以手动修复正文:编辑 `SKILL.md`,路由器将在下一个请求中提供新的修订版本。本步骤以另一种方式实现,使用可选的候选生成器,以便变更附带测量证据。
编写一个用于技能的评估任务集 (`optimize/tasks/tailwind.yaml`),包含训练和验证任务,其评分标准携带 v4 地面实况(教师也可以在技能的首次 CLI 运行中自动生成一个)。然后无界面运行它,这是它的预期运行方式:
```bash
docker compose run --rm optimize tailwind
```
生成器并行创建多个候选正文,在训练任务上竞争,优胜者通过完整代理在保留任务上与冠军进行 A/B 测试(大约两分钟,几美分)。UI 的 **生成候选** 按钮启动相同的运行。我们的运行:
```
[bestofn] 研究简报从缓存中重复使用 (eca23a52979eadc6.json)
[bestofn] 种子得分 0.033 on 6 个训练任务;并行生成 5 个候选方案…
[bestofn] 竞赛轮次 1/6(在 Vite 项目中设置 Tailwind CSS v4。问题是…):3 个候选方案晋级,2 个被淘汰
[bestofn] 竞赛轮次 2/6(在 Tailwind v… 中添加自定义颜色 `brand` (#7c3aed)):2 个候选方案晋级,1 个被淘汰
[bestofn] 竞赛在第 2 轮后确定 2 个决赛选手;对剩余的 4 个任务进行评分
[bestofn] 优胜者:候选方案 1,得分 1.000(种子得分 0.033)
[ab] 冠军:平均评判得分 0.133 [0.0, 0.2, 0.2, 0.0, 0.2, 0.2]
[ab] 挑战者:平均评判得分 0.767 [0.7, 0.9, 1.0, 0.9, 0.9, 0.2]
[ab] 冠军 0.133 vs 挑战者 0.767 -> 挑战者获胜
[ab] 输出令牌/任务:1278 -> 1032 (-246)
预估成本:$0.04(OpenRouter 列表价格)
[ab] ⚠ 挑战者丢弃 100% 的冠军正文,取决于仅 6 个保留任务 - 仔细审查删除内容
[ab] 待批准的输出写入 /app/runs/pending/tailwind.json - 在 http://localhost:8080 上审查 + 推广
```
阅读发生的事情。存根在裸露的 rollout 中得分 0.033,由于其正确性失败看起来像知识空白,因此作者被简报了一个共享的网络研究通行证(缓存,因此重复运行不会额外增加搜索成本)。此研究步骤是可选的:它仅在设置 `TAVILY_API_KEY` 时触发,如果没有该密钥,运行将完全按照预期进行,只是没有简报。多么值得购买的是,该简报提供了作者模型训练后出现的新事实 - 正是像这样的版本漂移技能所需要的,否则重写只能与作者自己的知识一样最新(参见[候选生成](#候选生成))。优胜者提炼出一个完整的 v4 正文。在保留的 A/B 测试中,通过真正的代理,存根冠军得分 0.133:一个小型服务模型跟随加载的 v3 建议直接进入错误答案,因此过时的正文实际上会造成伤害。挑战者得分 0.767,优势为 +0.63,远远超过默认晋升门槛(+0.15),并且回答所需的令牌较少。 ⚠ 保留警告是审查系统履行其职责:这个挑战者替换了整个正文,因此人类在任何内容发布之前都会查看差异。
注意运行 **未** 执行的操作:它未触及 `skills/tailwind/`。它写入了一个隔离的记录和一个证据包,然后停止。
胜利的大小取决于服务模型:**正文传递的胜利集中在正文承载的知识是服务模型所不具备的区域**(弱小或较旧的模型、内部工具、您的约定、截止依赖项)。在前沿服务模型上,相同的实验以另一种方式结束:在早期记录的运行中,冠军得分完美的 1.000(强大的模型识别出 v3 存根已过时,并从其自己的知识中回答 v4),而关卡拒绝了重写。关卡的拒绝使胜利值得信赖:在伴侣运行中针对 NVIDIA 授权的 `accelerated-computing-cudf` 技能,一个挑战者丢弃了冠军正文的一半并使一个保留任务退化,被阻止,0.980 vs 0.800。
生成是贪婪的:一次一个组件,每个组件由其自己的角色指标评分:
| 传递 | 命令 | 候选搜索目标 | 成本 |
|------|---------|---------------------------|------|
| 正文(默认) | `optimize tailwind`,或 UI 的 **生成候选** 按钮 | LLM 对训练任务的评判;用于证据的全代理 A/B 测试 | ~$0.05 |
| 描述 | `optimize tailwind --description` | 路由套件,由实际嵌入式路由器评分;无 LLM rollout | ~$0.01,几分钟 |
该分割存在,因为质量评判者无法衡量路由,而路由器无法衡量质量。
没有脚本传递:捆绑文件是可选的文本组件(`OPTIMIZE_COMPONENTS=body,file:<path>`),它们经过差异审查,从不执行。正文传递的 rollout 服务于每个候选方案,在确切的合同下,A/B 测试服务,因此搜索无法针对与 A/B 测试衡量的不同指令进行优化。`GEPA_ROLLOUTS=agent` 将每个 rollout 通过完整的代理脚手架运行(一个遗留变量名称:它早于 GEPA 正文循环的删除,现在选择最佳的 N 搜索的 rollout 模式)。
### 6. 审查、推广、回滚
回到 **http://localhost:8080**,头部 pill 变成 **1 待审查**,审查部分以证据为开头,`tailwind` 行显示一个 `change awaiting review` 芯片。卡片上显示评判分数、令牌偏移、保留警告、记录的证据包和组件差异(路由更改以相同的方式显示其度量差异),然后是 **Approve & promote** 和 **Reject**。快照和批准轨迹位于其下方,位于 **History** 中。
> 这里不包括截图。最近的截图是针对一个被取代的标签集(“Pending challenger”,“promotion gate”)拍摄的,本 README 仅包含真实运行的工件,因此它被删除而不是重新标注或重新绘制。重新捕获它需要一个带有真实待处理更改的实时堆栈,这是一个后续工作。
**Approve & promote** 验证证据仍然与磁盘上的冠军匹配,将上一个修订版本快照到 `runs/revisions/tailwind/<revision>/`,将挑战者重命名为 `skills/tailwind/`,并将 `approve` 记录追加到 `runs/approval-audit.jsonl`。MCP 服务器捕获修订版本更改,无需重启。**Reject** 丢弃候选方案。
该快照是撤销操作。撤销操作出现在 UI 的 **History** 部分,恢复它只需一步或一条命令:
```bash
# --entrypoint python 替换服务自己的 `python -m optimize.ab` 入口点
docker compose run --rm --entrypoint python optimize -m optimize.promote rollback tailwind <revision>
```
回滚快照它所取代的修订版本,因此往返是对称的,并且它写入自己的审计记录。轨迹将操作员记录为 `local-operator`,用于每个操作:本地 UI 没有身份或身份验证,因此它可以记录本地操作员批准的人员,而不是谁。两个记录都在交换发生后追加,因此审计写入失败(满或只读磁盘)将被记录,变更仍然存在:缺少一行意味着轨迹不完整,而不是推广被回滚。
每个技能存在一个审查槽:在运行不同的传递或存档的候选方案被审查之前,请先推广或拒绝它们。
### 7. 使用描述传递修复路由
正文已修复,但步骤 3 的第三个请求仍然路由错误:路由密钥是 `description`,因此路由有自己的传递和自己的度量标准,与 `optimize/tasks/tailwind.yaml` 中的 `routing:` 测试用例对抗。重要的测试用例是实际的遗漏,因此请将挖掘的流量放入测试套件中(我们添加了 node_modules 请求原文,加上“classes disappear in the production build” 变体):
```bash
docker compose run --rm optimize tailwind --description
```
```
[routing] 内循环得分:种子 0.286 -> 最佳 0.714
[routing] 冠军:top1 0.500 · recall@3 0.500 · no-route 精确度 0.000
[routing] 挑战者:top1 1.000 · recall@3 1.000 · no-route 精确度 0.333
[routing] 待处理描述写入 /app/runs/pending/tailwind.json - 在 http://localhost:8080 上审查 + 推广
[usage] 此路由传递的令牌花费(仅反射):
反射 7 次调用 3,796 输入 7,154 输出
预估成本:$0.01(OpenRouter 列表价格)
```
顶部-1 路由从 0.500 提升到 1.000,消耗一美分的反射调用。每个候选描述都由实际嵌入式路由器针对实际技能语料库进行评分,因此 LLM 评判者不会被愚弄。获胜的描述命名了用户实际输入的症状(“missing styles from component libraries in node_modules”)并从 `expected: null` 测试用例中学习了明确的否定(“Do not use this skill for plain CSS, vanilla CSS grid/flexbox styling”),这是 no-route 精确度增益的来源。关卡要求在任何路由度量标准上没有回归,至少有一个严格的改进,并且与另一个技能的描述没有冲突。传递是随机的:运行可能会产生较弱(仍然通过关卡)的挑战者,并且重新运行相同的传递会原地覆盖槽位。
### 8. 同一请求现在查找技能
在 UI 中推广路由挑战者与步骤 6 中的操作完全相同,然后重放遗漏:
```bash
docker compose run --rm agent "Why aren't the classes from my component library in node_modules getting styled?"
```
```
建议的技能(MCP suggest_skills):
0.667 tailwind - 使用此技能解决 Tailwind CSS 问题,包括…
服务模型:qwen/qwen3-32b
加载的技能(MCP get_skill):(无)
```
步骤 3 中路由错误的相同请求现在路由到 `tailwind` 排名第一。 一个诚实的警告,在那里可见:该请求服务模型在加载路由技能的情况下直接回答。实时加载行为属于服务模型;受控的正文对比测试是步骤 5 的 A/B 测试,它注入正文并保证服务。路由将正确的技能放在模型前面;A/B 测试证明了实际使用时会发生什么。
这是生命周期:第一稿技能 → 真实流量 → 挖掘诊断 → 带有保留证据的提议变更 → 人工审批 → 热重载 → 快照以便回滚。
### 9. (可选) 无人值守运行候选生成
这是候选生成应该发生的地方:在后台,而不是在审查路径上。单个命令挖掘每个技能的真实流量以获取健康状况,并仅为实际失败的技能提议变更,将所有幸存者隔离在审查队列中(没有任何自动推广):
```bash
docker compose run --rm optimize-loop # 所有带有评估集的技能;添加名称以定位某些技能
```
```
[loop] ===== pdf =====
[mine] 分析了 23 个真实轨迹 · 平均评判得分 0.52 · 11 个坏案例
[loop] pdf:低于健康栏(平均 0.52),正在优化…
[loop] 完成。 1 个挑战者排队等待审查:['pdf']
```
### 10. 扩展库
库有三种扩展方式:
- **自己编写**,与步骤 3 完全相同。只有两个 frontmatter 字段重要:`name`(一个 slug)和 `description`(路由触发器;以“Use this skill when…” 开头,写成“pushy”风格,因为常见的故障是触发不足)。
- **让代理提议一个。** 当 `suggest_skills` 返回空列表时,请求升级到 `STRONG_MODEL`(默认为 `GEPA_MODEL`):它解决问题并通过 `create_skill` 排队它学习到的内容。候选方案在人类批准之前保持非活动状态。发生这种情况的频率由 `RELATED_SCORE` 控制。
- **组合而不是扩展。** 如果技能仅相关(相似度低于路由阈值),`suggest_skills` 返回它标记为 `related: true`,并告知代理扩展或组合它,而不是编写近乎重复的内容。
```bash
docker compose run --rm agent "Plan a strict low-FODMAP weekly dinner menu for two people"
# PROPOSED SKILLS (MCP suggest_skills): <- 空:无技能涵盖此内容
# SERVING MODEL: accounts/fireworks/models/glm-5p2 (strong)
# mcp log: [ingot] 为人类审批排队技能候选 'low-fodmap-meal-planning'
```
---
## 证据关卡(反奖励黑客)
生成的变更仅凭附加的证据才值得信赖,而针对 LLM 评判者的优化会引发经典的失败:挑战者学会取悦评判者,而不是变得更好。关卡是审查者在阅读这些数字时所依赖的,因此它会关闭明显的路径:
1. **评判者 ≠ 作者。** 教师 LM (`GEPA_MODEL`) 编写技能;评判者 (`JUDGE_MODEL`) 是不同的模型。设置 `JUDGE_MODELS=a,b` 以使用集成评判者。
2. **保留关卡,而不是幸运的平均值。** 晋升需要一个边际 (`PROMOTE_MIN_MARGIN`,默认为 +0.15),足够的样本 (`PROMOTE_MIN_SAMPLES`),并且没有灾难性的逐任务回归。
3. **没有路由黑客。** 重写的 `description` 会针对其他技能的描述进行重新检查;如果重写与另一个技能的描述重叠(余弦 ≥ `COLLISION_SCORE`),则会阻止重写。
4. **执行基础的评判,默认情况下在沙盒中隔离。** 对于代码任务,`execcheck.py` 解析代码并向评判者提供一个必须视为真实的判决 默认情况下(`EXEC_SANDBOX=docker`),代码也在一个丢弃的锁定容器中运行:无网络,无挂载,根文件系统只读,`nobody` 用户,所有功能被丢弃,内存/进程/ CPU 限制。如果 docker 不可用,检查失败并被视为不确定;永远不会静默回退到非沙盒执行。`SANDBOX_RUNTIME=runsc` 交换 [gVisor](https://gvisor.dev);`EXEC_SANDBOX=1` 是旧的裸子进程模式;`EXEC_SANDBOX=off` 禁用执行。任务还可以提供 `check:` 规范(夹具 + 断言)在其任务 YAML 中,用于工件验证的执行;损坏的夹具被视为不确定,从不反对答案。
5. **长度惩罚。** 目标对膨胀的正文扣分。
6. **删除需要证据。** 挑战者丢弃大部分冠军正文(保留低于 `RETENTION_WARN`)会在审查 UI 中显示 ⚠ 警告以及保留数和样本数。
7. **阻止意味着阻止。** 挑战者在平均值上获胜但在关卡上失败时仍会被记录用于诊断,但 UI 拒绝批准并显示确切的原因,并且 `approve_pending` 在服务器端再次拒绝。
8. **证据仍需描述磁盘上的技能。** 晋升会重新计算冠军和挑战者修订版;如果冠军自运行后更改,批准被拒绝而不是应用于技能,证据从未衡量过。
```
[ab] champion 0.55 vs challenger 0.60 -> CHALLENGER WINS
[ab] ⚠ challenger 赢得了平均值,但晋升关卡 **blocked**:
边缘 +0.10 < 需要 +0.15;在一个任务上发生灾难性回归冠军通过
```
## 配置
在 `.env` 中设置(从不提交):
| 变量 | 默认值 | 备注 |
| --- | --- | --- |
| `BASE_URL` | `https://openrouter.ai/api/v1` | 所有操作的端点;任何 OpenAI 兼容的提供商。在 OpenRouter 上,[ZDR 提供商路由](https://openrouter.ai/docs/features/zdr) 在代码中强制执行。`OPENROUTER_BASE_URL` 是旧别名 |
| `API_KEY` | (无) | `BASE_URL` 的 bearer token;`OPENROUTER_API_KEY` 是旧别名。本地 `http://` 端点不需要密钥 |
| `AGENT_MODEL` | `qwen/qwen3.6-27b` | 代理:执行技能的所有内容,包括 rollout;`MODEL` 是旧别名 |
| `MODEL_BASE_URL` / `MODEL_API_KEY` | `BASE_URL` / `API_KEY` | 混合设置的服务角色覆盖 |
| `OPENROUTER_PROVIDERS` | (无) | OpenRouter 独有:提供商优先级(例如 `fireworks,groq`),按顺序尝试;与 ZDR 组合,对于没有列出的提供商的角色,退回到开放的 ZDR 池 |
| `GEPA_MODEL` | `z-ai/glm-5.2` | 教师模型:编写候选技能,并为描述阶段进行反思。旧名称,保留现有 `.env` 文件 |
| `STRONG_MODEL` | `GEPA_MODEL` | 服务新型请求(无匹配技能)和创建新技能 |
| `JUDGE_MODEL` | `google/gemini-2.5-flash` | LLM 评判器;必须与 `GEPA_MODEL` 不同 |
| `MIN_SCORE` | `0.65` | 到达/以上:可路由匹配;以下:`related` 频段或新型 |
| `RELATED_SCORE` | `0.45` | `related` 频段的下限;以下任务为新型(弱/强升级) |
| `EMBED_MODEL` | `BAAI/bge-small-en-v1.5` | 路由嵌入模型;与 Dockerfile 的构建参数保持同步 |
| `BODY_TARGET_CHARS` | `6000` | 正文大小超过此值时开始惩罚 |
| `LENGTH_PENALTY` | `0.10` | 非常长的正文的最大分数减去 |
| `LOOP_HEALTH_THRESHOLD` | `0.7` | 后台循环为技能提出更改,其挖掘的平均分数低于此值 |
| `LOOP_PASSES` | `body` | 循环运行的次数,每个不健康的技能(例如 `body,description`) |
| `OPTIMIZE_CANDIDATES` | `5` | 作者撰写并竞争的候选重写 |
| `TAVILY_API_KEY` | (无) | 可选的作者侧网络研究,在候选生成期间 |
| `GEPA_ROLLOUTS` | `direct` | 候选搜索的推出方式:`direct`(在服务合同下一次调用)或 `agent`(每个推出的完整支架,约 10 倍成本)。旧名称,保留现有 `.env` 文件 |
| `RETENTION_WARN` | `0.5` | 当挑战者保留的冠军正文比例少于此值时,审查警告 |
| `OPTIMIZE_COMPONENTS` | `body` | 可以重写的组件;添加 `description` 或 `file:<path>` 条目 |
| `EXEC_SANDBOX` | `docker` | `docker` = 锁定容器,`1` = 裸子进程(旧版),`off` = 静态检查仅 |
| `SANDBOX_IMAGE` | `ingot-optimize` | 沙箱容器运行的镜像 |
| `SANDBOX_RUNTIME` | (无) | 可选的容器运行时,例如 `runsc` for gVisor |
| `SKILL_MAX_DESCRIPTION` | `1024` | `create_skill` 描述上限(Agent Skills 规范) |
| `SKILL_MAX_BODY` | `40000` | `create_skill` 正文上限(约 500 行) |
| `TRACES_FILE` | `runs/traces.jsonl` | 本地 JSONL 跟踪存储:由每个代理运行写入,`optimize-mine` 读取,当 Langfuse 无法访问时,以便在没有跟踪堆栈的情况下进行挖掘 |
| `MAX_RUN_USD` | (无) | 每个优化运行的硬性支出上限:ledger 根据每次调用后的 OpenRouter 列表价格估算成本,并在达到上限后中止运行 |
| `LANGFUSE_BASE_URL` | `http://langfuse-web:3000` | Langfuse 端点,每个服务跟踪并挖掘 |
| `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` | `pk-lf-local-demo` / `sk-lf-local-demo` | 项目密钥;默认值是捆绑堆栈的本地演示字面量 |
| `LANGFUSE_PUBLIC_URL` | `http://localhost:3100` | 浏览器访问 Langfuse 的位置(UI 跟踪链接) |
证据门控旋钮(`PROMOTE_MIN_MARGIN`、`PROMOTE_MIN_SAMPLES`、`COLLISION_SCORE`、`JUDGE_MODELS`)在 [证据门控](#证据门控-反奖励黑客) 中涵盖。
### 候选生成
一个候选搜索已发货:最佳 N 带竞争(`optimize/bestofn.py`)。种子在每次训练任务中一次性推出,其裁判反馈成为失败简报,通过一次调用的诊断报告进行锐化,该报告将每次失败归因于技能部分负责,类型为缺失/不足/不正确(SkillForge 论文的技能诊断器);N 个候选重写(`OPTIMIZE_CANDIDATES`)并行起草,每个由不同角度引导 - 其中一个始终是论文的最小累加编辑“不造成伤害”学科,以便在每次竞赛中都有一个干净的差异候选;然后在训练任务上进行连续减半,每个回合都丢弃底部一半。获胜者的累积平均值(减去长度惩罚)必须击败种子。几分钟,而不是几十分钟,大约 15 次推出。
第二个顺序 GEPA 正文循环曾经位于 `--gepa` / `OPTIMIZE_STRATEGY=gepa` 后面。它已被**删除**:它为大致 20 倍的成本优化了相同的目标,只有通过选择加入才能访问,并且没有自己的测试覆盖。`OPTIMIZE_STRATEGY` 不再被读取,设置它的运行会说明这一点,而不是静默忽略。GEPA 本身仍然用于描述阶段的反射步骤(`optimize/routing.py`),并且 `GEPA_MODEL` / `GEPA_ROLLOUTS` 保留其名称,以便现有 `.env` 文件能够正常工作。
冠军的保留 A/B 结果缓存在 `runs/eval-cache/` 中,按(技能修订版、保留任务、服务模型、裁判)键控,以便对未更改的冠军重复运行只支付挑战者的费用。
**可选的作者侧网络研究**(`TAVILY_API_KEY`):当种子的失败看起来像是知识空白时,运行会进行一次网络研究步骤,并为每个作者提供相同的简报。研究仅限作者(裁判永远不会看到它),每个运行共享一个简报,在 `runs/research-cache/` 中缓存内容地址。注意信任边界:搜索内容流入技能正文,成为代理的系统提示;每个提升的人工审查门控是网络和您的库之间的唯一保障。
### 编写评估任务集
任务集是运行时工件,而不是已发货的意见;存储库不提交任何任务集。它们位于 `optimize/tasks/<skill>.yaml`(gitignored)。通过手动编写一个,让老师在第一次 CLI 优化运行时自动起草一个,或推广矿工的“最弱现实任务”候选者,可以为每个技能获取一个。解剖学:
```yaml
skill: accelerated-computing-cudf
train: # 候选搜索看到这些;评分标准是它提炼的真实性依据
- task: You trained a large XGBoost model, but GPU inference is bottlenecked by Python
overhead and row-by-row execution. Which RAPIDS feature can run the trained forest
efficiently without retraining it?
rubric: "Must name cuML's Forest Inference Library (FIL), NOT Treelite. Must say FIL
imports trained XGBoost, LightGBM, scikit-learn, and Treelite-format ensembles for
batched GPU inference."
deliverable: text # 可选:text | command | css | 任何非代码都禁用静态“答案必须包含可运行的 Python 块”检查
holdout: # 证据门控仅信任这些;候选搜索从未看到它们
- task: Our fraud team has a LightGBM ensemble trained offline; scoring 200M rows nightly
is too slow. Without retraining, how do we speed this up with RAPIDS?
rubric: "Must recommend FIL loading the LightGBM model and discuss two trade-offs."
deliverable: text
# 每任务可选执行基础(代码任务):
# check:
# fixture: open("input.txt", "w").write("hello")
# assert: assert open("output.txt").read() == "HELLO"
routing: # 描述阶段优化这些;门控检查它们
- task: "Which RAPIDS feature runs my trained forest on GPU without retraining?"
expected: accelerated-computing-cudf
harness: codex
- task: "Merge two PDF files and add page numbers."
expected: null # 负面:必须不路由到这里的任务(无路由精度)
harness: codex
```
使任务集值得门控的规则:保留必须是一个真正的分割(一个扁平的 `tasks:` 列表被标记为泄漏,永远无法推广);保留任务应该重新组合训练评分标准所教的内容,而不是引入新的事实(您的评分标准是真实性进入系统的途径);而整个池子都通过的每个任务都是浪费。
### 使用您自己的 Langfuse 项目
`docker compose --profile langfuse up` 运行一个自我托管的 Langfuse(UI 在 **http://localhost:3100**,登录 `demo@local.dev` / `localdemo123`)。要将 ingot 指向现有的 Langfuse 项目,请跳过配置文件,在 `.env` 中设置所有三个,然后重启(`docker compose up -d`):
```bash
LANGFUSE_PUBLIC_KEY=pk-lf-... # 您的项目的密钥:项目设置 -> API 密钥
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_BASE_URL=https://cloud.langfuse.com # 或您的自我托管 URL
LANGFUSE_PUBLIC_URL=https://cloud.langfuse.com # 可选:浏览器访问它的位置
```
一个需要注意的问题:`LANGFUSE_BASE_URL` 必须能从容器内部访问(不是 `http://localhost:<port>`,在容器内部这是容器本身;使用 `http://host.docker.internal:<port>` 或主机的 LAN IP)。捆绑堆栈仅在 `--profile langfuse` 下启动,因此指向您自己的项目不会添加额外的容器。
## 工作原理
<p align="center">
<img src="docs/ingot.jpg" alt="Ingot, the mascot, handing skills out to AI agents" width="720">
</p>
- **`mcp_server/`**:[FastMCP](https://github.com/jlowin/fastmcp) v3 服务器(HTTP 传输),六种工具:
- `suggest_skills(task, k)`:通过嵌入相似性(CPU [fastembed](https://github.com/qdrant/fastembed),无 GPU)进行路由匹配;近似匹配返回标记为`related`;空 = 真正新颖
- `get_skill(name)`:完整的 SKILL.md;标题行带有内容哈希修订版
- `create_skill(name, description, body)`:排队一个新代理作者的候选(永远不会激活或覆盖)
- `reload_skills()`:批准或直接操作员编辑后的热重载
- `route_and_load(task, harness, cwd, available_tools, available_mcps)`:单次路由和加载,用于直接或相关兼容路由(参见 [使用您自己的代理](#使用您自己的代理-mcp-仅限))
- **`agent/run.py`**:[deepagents](https://github.com/langchain-ai/deepagents) LangGraph 代理,连接到这些工具,跟踪到 Langfuse。在弱 `AGENT_MODEL` 上提供路由任务,并将真正新颖的任务升级到 `STRONG_MODEL`,后者可以排队可重用的候选进行审查。
- **`skills/<name>/SKILL.md`**:YAML `description` 是路由键;正文是代理加载的内容。其文件夹的内容哈希是其修订版。
- **`optimize/promote.py`**:变更控制核心,和唯一写入 `skills/` 的模块:待处理队列、证据检查、修订快照、原子提升和回滚交换,以及批准审计追加。
- **`optimize/`** (其余部分,均可选):跟踪挖掘(`mine.py`)、多维 LLM 裁判(`judge.py`)、候选搜索(`bestofn.py`)及其推出/教师管道(`rollout.py`)、保留 A/B(`ab.py`)、便携式证据包(`evidence.py`)、路由阶段(`routing.py`)、后台循环(`loop.py`)、令牌分类帐(`usage.py`)。这些都无法激活任何内容:它们写入待处理记录。A/B 代理被剥夺了突变工具。挖掘、分类失败和失败诊断的想法(加上最小编辑作者角度)源自 [SkillForge (Liu et al., arXiv:2604.08618)](https://arxiv.org/abs/2604.08618).
- **`ui/`**:FastAPI 变更控制 UI(单个 HTML 页面,无构建步骤):证据和批准/拒绝决定,然后是修订历史和回滚,然后是库和可选的候选运行。它是唯一一个可以激活待处理创建或重写的正常应用程序路径。
### 使用您自己的代理(仅限 MCP)
大多数部署仅使用 MCP 服务器和它们自己的支架(Claude Code、Codex、自定义代理);捆绑的 `agent/run.py` 是一个参考客户端,而不是必需品。将您的支架指向 `http://localhost:8000/mcp` 并调用 `route_and_load` 每次请求:
- **`match`**:直接匹配。遵循 `skill_body`;弱/廉价模型足够。
- **`related_match`, `novel: false`**:最接近的兼容技能低于直接匹配阈值。加载其身份、修订版、根和唯一正文,以便弱模型可以组合或扩展它。`alternatives` 保持无正文。
- **`novel: true`**:没有相关内容。由您的强模型提供服务,然后调用 `create_skill` 以排队可重用的候选。它在 UI 批准之前保持非活动状态。
要从您自己的支架中为跟踪挖掘循环提供数据,请参阅 [从您自己的支架中跟踪](#从您自己的支架中跟踪-mcp-仅限).
### 可选的共享技能根
Docker 演示版读取和写入 `skills/`。要跨附加库路由,设置 `SKILL_ROUTER_PATHS` 为以平台分隔的目录列表:
```bash
export SKILL_ROUTER_PATHS="$HOME/Source/team-skills:$HOME/.agents/skills"
docker compose up --build
```
本地 `skills/` 根目录优先搜索;第一个重复名称在警告后生效。可选的 `metadata.skill-router` 前言可以限制自动匹配的支架、项目路径、平台、所需工具/MCP、信任、激活模式、优先级和冲突。
## 技能来源
不提交技能。每个源都由 `scripts/fetch_skills.sh` 从上游拉取(克隆、复制技能目录、删除克隆),因此每个源都位于自己的许可下。所有技能均从 [VoltAgent 索引](https://github.com/VoltAgent/awesome-agent-skills) 整理而来:
| 源参数 | 仓库 | 技能 | 许可 |
| --- | --- | --- | --- |
| `anthropics` | [anthropics/skills](https://github.com/anthropics/skills) | 文档技能(pdf、docx、pptx、xlsx、…) | 每个技能(查看前言) |
| `nvidia` | [nvidia/skills](https://github.com/nvidia/skills) | GPU / 基础设施 / 数据 / 医疗成像 | Apache-2.0 |
| `lambdatest` | [LambdaTest/agent-skills](https://github.com/LambdaTest/agent-skills) | 测试框架(pytest、playwright、cypress、appium、…) | MIT |
| `trailofbits` | [trailofbits/skills](https://github.com/trailofbits/skills) | 安全分析(semgrep、静态分析、漏洞扫描器、…) | CC-BY-SA-4.0 |
```bash
scripts/fetch_skills.sh all # 以上所有
scripts/fetch_skills.sh anthropics trailofbits # 或选择来源
docker compose restart mcp # 加载新技能
```
在重新分发之前,请查看每个源的许可。
## 安全性和威胁模型
**已加载的技能是代理遵循的指令。** 将技能内容视为代码,将技能库视为受信任状态,必须加以管理。您无法在检索和遵循指令的系统中完全“解决”提示注入;设计目标是适度的防护措施加上小型的防御写入表面。
写入路径,以及每个路径的防护措施:
- **`create_skill`** 是代理作者的,但仅为待处理状态。 除非在 UI 中获得人类批准,否则它无法路由。 作者使用斜杠 + 前言净化,从不停用,代理规范的名称/描述限制,指令覆盖/提示注入短语检查(`mcp_server/safety.py`),嵌入冲突检查以阻止路由阴影攻击,以及可选的 ML 分类器(如下)。 已接受的技能被标记为`source: agent`。
- **生成的重写** 进入 `runs/pending/` 并且无法自行激活。 它们还需要证据,其冠军和挑战者修订版仍然与磁盘上的技能匹配,然后才能在 UI 批准之前。
- **批准和回滚** 是唯一写入 `skills/` 的应用程序路径。 两个操作都经过 `optimize/promote.py`,两个操作都快照了它们所取代的内容,并且都追加了一个审计记录(如果追加失败,则记录并不会撤销已提交的更改)。 触发它们的 UI 端点带有同源检查,因为跨站页面可以 POST 到 localhost,即使它无法读取响应,而在给定的 UI 进程中只有一个可以运行:第二个批准或回滚被 HTTP 409 拒绝而不是交错。
- **直接文件系统编辑** 允许操作员控制 `skills/` 下的受信任状态。 此显式逃生舱位于应用程序批准保证之外。
- **第三方技能** 是未经审计但在运行时不受攻击者控制的;请像对待任何依赖项一样审查它们。
### 可选:ML 提示注入分类器
除了正则表达式启发式之外,`create_skill` 可以在 ONNX 运行时上运行 [vLLM 语义路由器](https://github.com/vllm-project/semantic-router) 越狱检测器([`llm-semantic-router/mmbert32k-jailbreak-detector-merged`](https://huggingface.co/llm-semantic-router/mmbert32k-jailbreak-detector-merged),一个 mmBERT CPU 分类器,Apache-2.0 许可)。选择加入;模型下载(约 1.2GB,一次)来自 Hugging Face 在首次使用时:
```bash
export SKILL_GUARD_MODEL=llm-semantic-router/mmbert32k-jailbreak-detector-merged
# optional: export SKILL_GUARD_THRESHOLD=0.7
# optional: export SKILL_GUARD_ONNX_FILE=onnx/model.onnx
```
高于阈值的分类被 CPU 拒绝(~20ms/调用)。如果模型丢失,它将静默地降级为正则表达式启发式。在 Docker 中,在 `mcp` 服务上设置 `SKILL_GUARD_MODEL`(安装一个持久的 `HF_HOME`)。
### 网络暴露
**默认情况下,所有内容仅限本地访问,因为没有任何身份验证。** MCP 工具和变更控制 UI 的端点(可以触发付费候选运行,激活技能,或回滚技能)没有自己的身份验证;演示的保护措施是没有任何服务可以在机器外部访问:
- `docker-compose.yml` 将所有端口仅发布到环回接口(`127.0.0.1:8000` MCP,`127.0.0.1:8080` UI,`127.0.0.1:3100` Langfuse)。
- 在 Docker 之外运行,MCP 服务器也默认绑定到 `127.0.0.1`。
要公开服务,请在 `docker-compose.yml` 中将端口映射从 `"127.0.0.1:8000:8000"` 更改为 `"8000:8000"`(或绑定到特定接口)。请谨慎操作:任何可以访问这些端口的人都可以排队候选,批准激活,回滚技能,并花费您的 API 预算。对于任何超出受信任局域网的网络,请在前面添加一个身份验证反向代理。
**未采取的措施:** 我们不拒绝 shell 命令,`.env` 文件引用或 `curl … | sh` 在技能主体中,因为合法的技能通常包含代码和安装步骤。请从操作上控制剩余风险:在容器中运行代理,没有实际的机密或敏感的主机路径。更多信息:[OpenAI 关于提示注入的信息](https://openai.com/safety/prompt-injections/)。
## 从您自己的运行环境中跟踪(仅限 MCP)
跟踪挖掘通过 Langfuse 的公共 API 读取数据;它不关心谁编写了跟踪。仅限 MCP 的部署通过记录每个请求的跟踪,遵循两个约定,获得完整的挖掘对等:
1. **形状挖掘可以解析的内容**: 要么跟踪 `input = {"task": "<用户请求>"}`,输出为纯字符串,要么通过 Langfuse `CallbackHandler` 使用 LangChain/LangGraph `{"messages": [...]}` 状态。
2. **归属标签**(推荐):使用提供的技能名称和 `revision=<名称>@<修订>` 标记跟踪;当您升级到强大的模型时,标记为 `novel`。未标记的跟踪将退回到嵌入相关性。
一个最小的示例(Langfuse Python SDK v4):
```python
from langfuse import get_client
lf = get_client() # LANGFUSE_BASE_URL / _PUBLIC_KEY / _SECRET_KEY,与 compose 堆栈中的值相同
r = route_and_load(task, harness="claude", cwd=cwd) # 通过 MCP
selected = r["match"] or r["related_match"]
tags = ([selected, f"revision={selected}@{r['revision']}"] +
(["related"] if r["related_match"] else [])) if selected else ["novel"]
with lf.propagate_attributes(tags=tags):
with lf.start_as_current_observation(name="serve", input={"task": task}) as span:
answer = my_agent(task, r["skill_body"]) # 您的运行环境,您的模型
span.update(output=answer)
```
跟踪流动后,`docker compose run --rm optimize-mine <技能>` 和后台循环工作保持不变。两个注意事项:挖掘重新评估流量使用 `JUDGE_MODEL`(在您的 API 账单上),候选发布仍然在捆绑的脚手架上执行,因此请将 `AGENT_MODEL` 设置为生产服务模型。
连接信息
猜你喜欢
暂无推荐服务器