antoinezambelli/forge

GitHub: antoinezambelli/forge

一个 Python 框架,为自托管 LLM 的 tool-calling 和多步 agent 工作流提供可靠性护栏层。

Stars: 2190 | Forks: 168

# forge [![PyPI](https://img.shields.io/pypi/v/forge-guardrails.svg)](https://pypi.org/project/forge-guardrails/) [![测试](https://github.com/antoinezambelli/forge/actions/workflows/tests.yml/badge.svg)](https://github.com/antoinezambelli/forge/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/antoinezambelli/forge/branch/main/graph/badge.svg)](https://codecov.io/gh/antoinezambelli/forge) [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/) [![许可证: MIT](https://img.shields.io/badge/license-MIT-green.svg)](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绕过, 计算机取证, 逆向工具