工具离合器
工具连接器已打开 OpenAI聊天完成 代理居住在终端中的人。
它位于两者之间 OpenAI兼容客户端 (打开WebUI、vim-ai、你自己的curl别名)和 上游LLM服务器 (llama.cpp等)。您只需配置一次“堆栈”(系统消息、可选RAG、工具/操作),然后在每个客户端上重用相同的设置。
这是一个为想要杠杆的人提供的工具。如果启用命令执行,则选择电源而不是护栏。
心理模型
client --> Werkzeugkoppler --> upstream LLM
|
+-- init_messages (system prompts)
+-- last_user_message_readers (RAG hooks via commands)
+-- MCP servers (tool definitions)
+-- actions (local command-backed tools)
+-- @@direct commands (raw shell control, if enabled)两者 actions 和 @@ 直接命令最终通过Werkzeugkoppler执行本地shell命令。 区别在于谁请求执行: actions 由上游模型(工具调用)请求,而 @@ 直接命令由用户明确请求(在客户端键入)。
你得到的
- 一 服务URL 适用于所有客户(
service_base_url) - 中央 系统消息 (
init_messages) - 可通过外部命令选择“自带RAG”(
last_user_message_readers) - 工具来自 MCP服务器 (
mcp_servers) - 本地 行动 作为工具公开(模型触发;您预定义命令)(
actions) - 可选的 直接shell执行 通过聊天(用户通过触发
@@...)
范围
- 支持的端点: 仅
/v1/chat/completions和/v1/models - 无嵌入/音频/图像端点
- 支持流媒体,但Werkzeugkoppler将其标准化为 选择 (
n=1,选择指数0)
安全说明(必填,但也是正确的)
如果启用 actions 和 @@ 直接命令,此服务可以在主机上执行命令。 这就是重点。不要把它暴露给你不完全信任的人。
操作上:
actions因为模型决定调用一个动作。@@直接命令仅在用户键入时执行。
实际基线:
- 不要以root身份运行
- 绑定到
127.0.0.1除非你知道你为什么不 - 集
service_api_key通过局域网可访问时 - 如果你想要远程控制,把它放在反向代理和防火墙后面
需求
- python 3.10+
- 在Linux上测试。其他平台尚未经过测试。
安装
来源:
git clone https://github.com/GhostWithAHat/werkzeugkoppler
cd werkzeugkoppler
python3 -m pip install -r requirements.txt快速启动
1) 创建配置
cp config.yaml.min_example config.yaml2) 编辑最小字段
upstream_base_url:上游LLM基URL(示例:http://127.0.0.1:10000)service_base_url:Werkzeugkoppler监听的地方(示例:http://127.0.0.1:8000)- 可选:
service_api_key(建议在绑定到局域网后立即使用)
3) 快跑
python3 -m werkzeugkoppler --config config.yaml4) 测试
curl -s -H 'Content-Type: application/json' http://127.0.0.1:8000/v1/modelscurl -s -H 'Content-Type: application/json' http://127.0.0.1:8000/v1/chat/completions -d '{
"model": "whatever",
"messages": [
{ "role": "user", "content": "Say hi in one sentence." }
]
}'如果你设置 service_api_key,添加:
-H 'Authorization: Bearer '5) 向您的客户指出 service_base_url
查阅客户的自述文件。
推荐:通过systemd运行
针对目标受众(您实际控制的盒子)的实用设置。
建议布局
- 代码:
/opt/werkzeugkoppler - 配置:
/etc/werkzeugkoppler/config.yaml - 用户:
werkzeugkoppler(专用,非root)
示例设置
sudo useradd --system --home /nonexistent --shell /usr/sbin/nologin werkzeugkoppler || true
sudo mkdir -p /opt /etc/werkzeugkoppler
sudo git clone https://github.com/GhostWithAHat/werkzeugkoppler /opt/werkzeugkoppler
sudo python3 -m venv /opt/werkzeugkoppler/venv
sudo /opt/werkzeugkoppler/venv/bin/pip install -r /opt/werkzeugkoppler/requirements.txt
sudo cp /opt/werkzeugkoppler/config.yaml.min_example /etc/werkzeugkoppler/config.yaml
sudo chown -R werkzeugkoppler:werkzeugkoppler /opt/werkzeugkoppler /etc/werkzeugkoppler编辑 /etc/werkzeugkoppler/config.yaml,然后添加一个单位文件:
/etc/systemd/system/werkzeugkoppler.service
[Unit]
Description=Werkzeugkoppler (OpenAI-compatible proxy)
After=network.target
[Service]
Type=simple
User=werkzeugkoppler
Group=werkzeugkoppler
WorkingDirectory=/opt/werkzeugkoppler
ExecStart=/opt/werkzeugkoppler/venv/bin/python -m werkzeugkoppler --config /etc/werkzeugkoppler/config.yaml
Restart=on-failure
RestartSec=1
Environment=PYTHONUNBUFFERED=1
# Hardening (optional)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.target启用并启动:
sudo systemctl daemon-reload
sudo systemctl enable --now werkzeugkoppler跟踪日志:
journalctl -u werkzeugkoppler -f配置重载是自动的(不需要重新启动),但写YAML 原子性地 (写入临时文件并重命名)。
配置参考
重新加载行为
Werkzeugkoppler在不重新启动的情况下重新加载YAML。 如果另一个进程自动写入YAML,请将其写入 原子性地,否则它可能会读取半写文件。
定地址
Werkzeugkoppler绑定到解析自的主机+端口 service_base_url. 没有单独的绑定参数。
顶级字段(概述)
upstream_base_url(必填):转发请求的地方upstream_api_key(可选):向上游发送的承载令牌Authorization: Bearerservice_base_url(必填):客户端连接的地方service_api_key(可选):保护/v1/models和/v1/chat/completionsupstream_connect_retries(可选):如果上游不可用,则重试行为upstream_retry_interval_ms(可选):重试之间的固定延迟(毫秒)init_messages(可选):注入到每个请求中last_user_message_readers(可选):RAG通过外部命令挂钩mcp_servers(可选):来自MCP服务器的工具actions(可选):通过命令执行的本地工具allowed_direct_commands(可选):同种异体模式@@直接命令执行logging.level(可选):记录详细信息
最小配置
upstream_base_url: "http://127.0.0.1:10000"
service_base_url: "http://127.0.0.1:8000"
logging:
level: "INFO"认证
上游认证(upstream_api_key)
如果 upstream_api_key Werkzeugkoppler发送:
Authorization: Bearer
按请求应用(GET /v1/models、非流式聊天、流式聊天)。
服务认证(service_api_key)
如果 service_api_key 如果已设置,Werkzeugkoppler需要:
Authorization: Bearer
适用于:
/v1/models/v1/chat/completions
是否 不 适用于:
/healthz
系统消息(init_messages)
在每个请求开始时注入:
init_messages:
- role: "system"
content: |
You are a precise assistant.
Keep answers short and factual.RAG通过 last_user_message_readers (你提供给读者)
Werkzeugkoppler不提供搜索引擎。它执行您配置的任何内容。
合同
- 该命令必须在运行Werkzeugkoppler的主机上可执行。
- 它必须接受最后一条用户消息(通常通过
$LAST_USER_MESSAGE在争论中)。 - 它必须将文本(或JSON)打印到stdout。
读者被处决 每次请求一次 并按阅读器名称缓存在请求本地词典中。 没有对话级缓存,也没有TTL。
没有内置截断。保持阅读器输出较小。
示例
last_user_message_readers:
- name: "rag"
command: "/usr/local/bin/query_embeddings"
arguments:
- "search"
- "--index"
- "/path/to/index"
- "--results-only"
- "--query"
- "$LAST_USER_MESSAGE"在系统消息中注入:
init_messages:
- role: "system"
content: |
Additional context:
{ last_user_message_reader_output:rag }MCP服务器
注册MCP服务器并公开其工具:
mcp_servers:
- server_id: "demo_http_mcp"
transport: "http"
url: "http://127.0.0.1:18080/mcp/"操作(本地工具执行)
操作是由本地命令支持的工具。它们暴露在上游模型中,并在调用时执行。
重点: *请求* 执行来自模型(通过工具调用)。这 *命令行* 仍然是您的配置:模型选择操作名称并填充声明的参数,Werkzeugkoppler运行配置的命令。
actions:
- name: "memory_add"
description: "Persist one memory entry"
command: "/usr/local/bin/memories"
arguments:
- "add"
- "--text"
- "$TEXT"
parameters:
- name: "TEXT"
type: "insecure_string"
description: "Text content"
# optional:
# timeout: 60超时和输出
timeout默认值为 60秒stderr故意忽略- 进程退出代码当前未被评估;只有stdout重要
标准处理:
- 如果stdout是有效的JSON:作为JSON工具输出转发
- 否则:作为行数组转发
直接命令(allowed_direct_commands)
如果消息以开头 @@,Werkzeugkoppler解析余数 shlex.split() 和检查 只有第一个令牌 反对排外主义模式。
关键点:这里的命令行来自用户。Werkzeugkoppler应用allowlist检查,然后执行该命令。
如果允许,该命令将通过 外壳 (管道、重定向等工作)。
紧密对抗:
allowed_direct_commands:
- "echo"
- "ls"
- "find"
- "systemctl"推荐的“远程控制”模式
如果你明确希望聊天客户端完全控制,请允许一切:
allowed_direct_commands:
- "*"这会将您的聊天客户端变成一个远程shell(按设计)。
上游故障处理(upstream_connect_retries)
检索在以下情况下触发:
httpx.TransportErrorhttpx.HTTPStatusError与状态 404, 429,或 >= 500- 不完整的响应有效载荷(例如“响应有效载荷未完成”)
价值观:
0:不重试-1:无限次重试(重试时流状态)N > 0:最多重试N次
重试延迟:
upstream_retry_interval_ms(默认值: 1000毫秒),固定延迟,无指数回退
在重试(和流式传输)时,Werkzeugkoppler发送一个推理增量状态块,然后在恢复后立即切换到上游的直播流。
流媒体行为
SSE格式:
data: { "object": "chat.completion.chunk", ... }data: [DONE]
警告:响应标准化为 选择 (n=1,索引 0).
故障排除
- 401/403:
- service_api_key 已设置,但客户端未发送(或发送错误) Authorization 头球
- 上游无法到达:
- 验证 upstream_base_url - 确认上游正在运行且可到达 - 如果 upstream_connect_retries 是 -1,您的客户端将看到重试状态块,直到上游返回
- 读取器/操作不返回任何内容:
- 检查路径和权限 - 请先手动运行该命令 - 记住:没有截断;保持小输出
- 配置更新后的奇怪行为:
- 你的配置编写器不是原子的;写入临时文件并重命名
