antoinezambelli/forge
GitHub: antoinezambelli/forge
一个 Python 框架,为自托管 LLM 的 tool-calling 和多步 agent 工作流提供可靠性护栏层。
Stars: 2190 | Forks: 168
# forge
[](https://pypi.org/project/forge-guardrails/)
[](https://github.com/antoinezambelli/forge/actions/workflows/tests.yml)
[](https://codecov.io/gh/antoinezambelli/forge)
[](https://www.python.org/downloads/)
[](LICENSE)
一个为自托管 LLM tool-calling 提供的可靠性层。你只需为 forge 提供一组工具;模型可以随意以任何顺序调用它们。工作流结构是可选的——`required_steps`、`prerequisites` 和 `terminal_tool` 允许你在需要时约束循环,但即使在零必选步骤的情况下,forge 的护栏(rescue parsing、重试提示、响应验证)同样适用。
在 forge 包含 26 个场景的 v0.7.0 评估套件中,forge 使得 8B 本地模型的准确率从个位数提升至 84%——甚至在相同的工作负载下,将 Sonnet 4.6 从 85% 提升至 98%(Anthropic 的数据在 v0.6.0 中测得;由于成本较高,在 v0.7.0 中未重新运行)。
**forge 不是什么:**
- **不是 agent orchestrator。** Forge 位于单个 agentic loop 内部,确保其 tool calls 的可靠性。多 agent 图、DAG planners 以及跨 agent 的协调不在范围内。
- **不是编程框架。** Forge 是领域无关的。如果你正在构建编程 agent(或已经在使用 opencode、aider、Cline 等工具),[proxy mode](#proxy-server) 可以利用 forge 的护栏来增强你现有的框架——无需重写代码。
**三种使用方式:**
- **Proxy server** —— 原生代理(`python -m forge.proxy`),同时支持 OpenAI chat-completions 和 Anthropic Messages(`/v1/messages`)API,可部署在任何客户端与本地模型服务器之间。将兼容 OpenAI 的工具(opencode、Continue、aider)**或 Claude Code** 指向它,forge 就会透明地应用护栏——客户端会以为自己正在与一个更聪明的模型对话。这是最受欢迎的切入点。
- **WorkflowRunner** —— 定义工具、选择 backend、运行结构化的 agent 循环。Forge 管理整个生命周期:系统提示词、工具执行、上下文压缩以及护栏。**SlotWorker** 增加了对具有自动抢占功能的共享推理 slot 的优先队列访问机制——适用于多个专家工作流共享同一 GPU slot 的多 agent 架构。在你直接基于 forge 构建应用时,这是最佳选择。
- **Guardrails middleware** —— 在你自己的 orchestration loop 中使用 forge 的可靠性堆栈([composable middleware](examples/foreign_loop.py))。由你控制循环;forge 负责验证响应、挽救格式错误的 tool calls 并强制执行必选步骤。
支持 Ollama、llama-server (llama.cpp)、Llamafile、vLLM 和 Anthropic 作为 backend。
## 环境要求
- Python 3.12+
- 正在运行的 LLM backend(见下文)
## 安装
```
pip install forge-guardrails # core only
pip install "forge-guardrails[anthropic]" # + Anthropic client
```
用于开发环境:
```
git clone https://github.com/antoinezambelli/forge.git
cd forge
pip install -e ".[dev]"
```
### Backend 设置(任选其一)
**llama-server**(推荐 —— 排名前 10 的评估配置均运行在 llama-server 上):
```
# 从 https://github.com/ggml-org/llama.cpp/releases 安装
llama-server -m path/to/Ministral-3-8B-Instruct-2512-Q8_0.gguf --jinja -ngl 999 --port 8080
```
**Ollama**(替代方案 —— 设置更简单,但在处理复杂任务时表现稍弱):
```
# 从 https://ollama.com/download 安装
ollama pull ministral-3:8b-instruct-2512-q4_K_M
```
**Anthropic**(API,无需本地 GPU):
```
pip install -e ".[anthropic]"
export ANTHROPIC_API_KEY=sk-...
```
查看 [Backend 设置](docs/BACKEND_SETUP.md) 获取完整说明,以及 [模型指南](docs/MODEL_GUIDE.md) 了解适合你硬件的模型。
## 快速开始
使用你常用的任何方式启动 llama-server(例如在另一个终端窗口中):
```
llama-server -m path/to/Ministral-3-8B-Instruct-2512-Q8_0.gguf --jinja -ngl 999 --port 8080
```
然后运行 Python 代码(例如在另一个终端中):
```
import asyncio
from pydantic import BaseModel, Field
from forge import (
Workflow, ToolDef, ToolSpec,
WorkflowRunner, LlamafileClient,
ContextManager, TieredCompact,
)
def get_weather(city: str) -> str:
return f"72°F and sunny in {city}"
class GetWeatherParams(BaseModel):
city: str = Field(description="City name")
workflow = Workflow(
name="weather",
description="Look up weather for a city.",
tools={
"get_weather": ToolDef(
spec=ToolSpec(
name="get_weather",
description="Get current weather",
parameters=GetWeatherParams,
),
callable=get_weather,
),
},
required_steps=[],
terminal_tool="get_weather",
system_prompt_template="You are a helpful assistant. Use the available tools to answer the user.",
)
async def main():
client = LlamafileClient(
gguf_path="path/to/Ministral-3-8B-Instruct-2512-Q8_0.gguf",
mode="native",
recommended_sampling=True,
)
ctx = ContextManager(strategy=TieredCompact(keep_recent=2), budget_tokens=8192)
runner = WorkflowRunner(client=client, context_manager=ctx)
await runner.run(workflow, "What's the weather in Paris?")
asyncio.run(main())
```
有关多步骤工作流、多轮对话和 backend 自动管理的详细信息,请参阅 [用户指南](docs/USER_GUIDE.md)。如果你正在构建长时间运行的会话(CLI、聊天服务器、语音助手),请参阅 [长时间运行会话指南](docs/USER_GUIDE.md#long-running-sessions-filtering-transient-messages),了解有关过滤瞬态消息的重要指导。
## Proxy Server
作为原生代理,它可以部署在任何客户端与本地模型服务器之间,同时支持 OpenAI chat-completions API 和 Anthropic Messages API(`/v1/messages`)。只需将你的客户端指向该代理(例如 `http://localhost:8081/v1`),forge 就会透明地应用其护栏——客户端会以为自己正在与一个更聪明的模型对话。
这是**将 forge 与现有框架结合使用**的途径(opencode、Continue、aider、Cline,以及任何使用 OpenAI chat-completions schema 的工具——或者是使用 Anthropic Messages API 的 Claude Code)。无需用 Python 重写。Reasoning replay 默认设为 `none`:Forge 仍然会捕获推理过程以实现可观测性,但在后续轮次中会将其排除在面向 backend 的历史记录之外——这是最节省 token 的策略,并且在评估套件中与 replay-all 在统计上毫无差异(参见 [reasoning-replay 结果](docs/results/raw/reasoning-replay.md))。使用 `--reasoning-replay keep-last` 可仅重放最新的推理块,或使用 `--reasoning-replay full` 恢复为历史 replay-all 的行为。
```
# External mode — 你负责管理 backend,forge 对其进行代理
python -m forge.proxy --backend-url http://localhost:8080 --port 8081
# Managed mode — forge 同时启动 backend 和 proxy
python -m forge.proxy --backend llamaserver --gguf path/to/model.gguf --port 8081
# Managed vLLM — 通过 --model-path 传递 model 目录或 HF repo id
python -m forge.proxy --backend vllm --model-path /path/to/awq-dir --port 8081
```
然后将你的客户端配置为使用 `http://localhost:8081/v1` 作为 API base URL。
**Claude Code:** 该代理同样在 `POST /v1/messages` 上提供 Anthropic Messages API,因此你可以将 Claude Code 指向一个受 forge 保护的本地模型——为 `claude` 进程设置 `ANTHROPIC_BASE_URL=http://localhost:8081` 和 `ANTHROPIC_AUTH_TOKEN=anything`。有关完整的设置细节(原生 FC 与提示词 FC 的对比、Anthropic-shape downstreams、`cache_control`),请参阅[将 forge 与 Claude Code 结合使用](docs/USER_GUIDE.md#using-forge-with-claude-code)。
**Backend 兼容性:**
- **Managed mode** 会为你自动启动 backend。支持的 backend 包括:`llamaserver`、`llamafile`、`ollama`、`vllm`(对于基于 GGUF 的 backend 使用 `--backend ` 搭配 `--gguf`,对于 vllm 使用 `--model-path`,对于 ollama 使用 `--model`)。
- **External mode** 与 backend 无关——只要你的 `--backend-url` 指向的目标支持 OpenAI schema,forge 就会向其发送 `POST /v1/chat/completions` 请求。Tool calls 必须以 OpenAI `tool_calls` 格式或 forge 支持的 rescue-parsed 格式(Mistral 的 `[TOOL_CALLS]`、Qwen 的 `` XML、带代码块的 JSON)返回。对于 vLLM 服务器,请添加 `--backend vllm`,以便代理采用 vLLM 的 `--served-model-name`(与 llama.cpp 不同,vLLM 在 `model` 字段不匹配时会返回 404 错误)。
### Proxy mode 增强的功能
在每次 `POST /v1/chat/completions` 请求中,forge 会按顺序应用以下操作:
1. **响应验证** —— 根据请求中的 `tools` 数组,检查模型响应中的每个 tool call。在响应返回给你的客户端之前,会捕获对未知工具名称的调用或形状格式错误的情况。
2. **Rescue parsing** —— 当模型以错误的格式发出 tool calls 时(例如代码块中的 JSON、Mistral 的 `[TOOL_CALLS]name{args}`、Qwen 的 `... ` XML),forge 会提取结构化的调用,并以标准的 OpenAI `tool_calls` schema 重新发出。这对于 Mistral 系列模型来说是最大的实用性提升。
3. **带错误追踪的重试循环** —— 如果验证失败,forge 会在标准通道上发送纠正性的 tool-result 消息,并最多重试 `--max-retries`(默认为 3)次推理,而不是直接返回格式错误的响应。从客户端的角度来看,该代理就像是一个仅仅多花费了几毫秒的单次请求。
4. **Synthetic `respond` 工具注入** —— 当请求中包含工具时,forge 会注入一个合成的 `respond` 工具,让模型调用该工具而不是直接生成纯文本。这个 `respond` 调用会从对外响应中被剥离——客户端看到的是正常的文本响应(`finish_reason: "stop"`),并且永远不知道该工具的存在。这对于无法保证在文本和 tool calls 之间做出正确选择的小型本地模型(约 8B)来说至关重要。完整分析请参见 [ADR-013](docs/decisions/013-text-response-intent.md)。
### Proxy mode *不会* 做的事情
Proxy mode 针对每个请求进行单次处理;由于 OpenAI chat-completions schema 不携带某些 forge 功能所需的多轮工作流状态,因此这些功能不可用:
- **Prerequisite 强制执行和步骤排序** —— 这些需要跨越多个轮次的工作流定义。可在 `WorkflowRunner` 中使用。
- **上下文压缩和会话记忆** —— proxy mode 会按原样转发入站消息列表;管理滚动窗口是客户端的职责。
- **感知 VRAM 的预算检测** —— 通过 `--budget-mode forge-full` 或 `--budget-mode forge-fast` 启用;否则,代理将使用 backend 报告的预算。
要获取完整的护栏功能,请直接使用 `WorkflowRunner`。代理模式是以牺牲深度来换取“在现有设置中使用 forge,且无需代码重写”。
### 实用标志
| 标志 | 默认值 | 用途 |
|---|---|---|
| `--max-retries N` | 3 | 每次验证失败的重试预算 |
| `--no-rescue` | (开启 rescue) | 禁用 rescue parsing(仅用于调试) |
| `--budget-mode {backend,manual,forge-full,forge-fast}` | `backend` | 上下文预算来源 |
| `--budget-tokens N` | — | 手动 token 预算(需要 `--budget-mode manual`) |
| `--serialize` / `--no-serialize` | auto | 强制请求序列化(针对单 slot backends) |
### Docker
你可以将 forge 代理作为 Docker 容器运行。
**构建镜像:**
```
docker build -t forge-proxy .
```
**运行容器:**
```
# 连接到外部 backend(例如托管在同一台机器上的 vLLM)
docker run -p 8081:8081 forge-proxy --backend-url http://host.docker.internal:8000 --backend vllm --budget-mode manual --budget-tokens 8192
```
注意:如果你的 backend 运行在宿主机的 `localhost` 上,请使用 `http://host.docker.internal:PORT`(在 macOS/Windows 上)或宿主机的 IP 地址,以便容器能够访问它。
## Backends
| Backend | 最适用场景 | 原生 FC? |
|---------|----------|------------|
| **Ollama** | 设置最简单,内置模型管理 | 是 |
| **llama-server** | 性能最佳,完全可控 | 是(需配合 `--jinja`) |
| **Llamafile** | 单一二进制文件,零依赖 | 否(prompt-injected) |
| **vLLM** | 高吞吐量服务,支持 AWQ/GPTQ 权重 | 是(服务端 parser) |
| **Anthropic** | 前沿基准,混合工作流 | 是 |
查看 [Backend 设置](docs/BACKEND_SETUP.md) 了解安装细节,以及 [模型指南](docs/MODEL_GUIDE.md) 选择适合的模型。
## 运行测试
```
python -m pytest tests/ -v --tb=short
```
```
python -m pytest tests/ --cov=forge --cov-report=term-missing
```
## 评估工具
包含 26 个场景,用于衡量模型与 backend 组合在多步骤 tool-calling 工作流中的可靠性——分为作为基准的 OG-18 层级,以及用于区分顶尖水平的 8 场景 advanced_reasoning 层级。完整 CLI 参考请参见 [评估指南](docs/EVAL_GUIDE.md)。
```
# llama-server(首先在另一个终端中启动;参见 Eval Guide)
python -m tests.eval.eval_runner --backend llamafile --llamafile-mode prompt --gguf "path/to/Ministral-3-8B-Instruct-2512-Q8_0.gguf" --runs 10 --stream --verbose
# Batch eval(JSONL 输出,自动恢复)
python -m tests.eval.batch_eval --config all --runs 50
# Reports — 默认为 ASCII 表;--html / --markdown 导出视图
python -m tests.eval.report eval_results.jsonl
python -m tests.eval.report eval_results.jsonl --html docs/results/dashboard.html
python -m tests.eval.report eval_results.jsonl --markdown docs/results/raw/
```
## 项目结构
```
src/forge/
__init__.py # Public API exports
errors.py # ForgeError hierarchy
server.py # setup_backend(), ServerManager, BudgetMode
core/
messages.py # Message, MessageRole, MessageType, MessageMeta
workflow.py # ToolSpec, ToolDef, ToolCall, TextResponse, Workflow
inference.py # run_inference() — shared front half (compact, fold, validate, retry)
runner.py # WorkflowRunner — the agentic loop
slot_worker.py # SlotWorker — priority-queued slot access
steps.py # StepTracker
guardrails/
guardrails.py # Guardrails facade — applies the full stack in foreign loops
nudge.py # Nudge dataclass
response_validator.py # ResponseValidator, ValidationResult
step_enforcer.py # StepEnforcer, StepCheck
error_tracker.py # ErrorTracker
clients/
base.py # ChunkType, StreamChunk, LLMClient protocol
ollama.py # OllamaClient (native FC)
llamafile.py # LlamafileClient (native FC or prompt-injected)
anthropic.py # AnthropicClient (frontier baseline)
context/
manager.py # ContextManager, CompactEvent
strategies.py # CompactStrategy, NoCompact, TieredCompact, SlidingWindowCompact
hardware.py # HardwareProfile, detect_hardware()
prompts/
templates.py # Tool prompt builders (prompt-injected path)
nudges.py # Retry and step-enforcement nudge templates
tools/
respond.py # Synthetic respond tool (respond_tool(), respond_spec())
proxy/
__main__.py # CLI entry point: python -m forge.proxy
proxy.py # ProxyServer — programmatic start/stop API
server.py # Raw asyncio HTTP server, SSE streaming
handler.py # Request handler — bridge between HTTP and run_inference
convert.py # OpenAI messages ↔ forge Messages conversion
tests/
unit/ # 865 deterministic tests — no LLM backend required
eval/ # Eval harness — model qualification against real backends
```
## 文档
- [用户指南](docs/USER_GUIDE.md) —— 使用模式、多轮对话、上下文管理、护栏、slot worker、长时间运行会话指南
- [模型指南](docs/MODEL_GUIDE.md) —— 适合你硬件的模型与 backend
- [Backend 设置](docs/BACKEND_SETUP.md) —— Backend 安装与服务器设置
- [评估指南](docs/EVAL_GUIDE.md) —— 评估工具 CLI 参考、批量评估
- [架构](docs/ARCHITECTURE.md) —— 完整设计文档
- [Workflow 内部机制](docs/WORKFLOW.md) —— Workflow 设计与 runner 内部机制
- [贡献指南](CONTRIBUTING.md) —— 如何设置环境、测试以及添加新的 backend 或场景
## 论文
forge 护栏框架及消融研究已发表于:
预印本也可在 [docs/forge_ieee_preprint.pdf](docs/forge_ieee_preprint.pdf) 获取——作为历史保留产物。请引用上述已发表的版本;DOI 链接因出版商的发布时间安排而无法立即生效。
## 许可证
[MIT](LICENSE) — 版权所有 (c) 2025-2026 Antoine Zambelli
标签:DLL 劫持, Python框架, 人工智能, 大语言模型, 工作流编排, 智能代理, 本地部署, 用户模式Hook绕过, 计算机取证, 逆向工具