modlink-agent —— 模联平台的 Agent 工具包
把模联(ModLink)的模型能力封装成 CLI 命令与 MCP 工具,供 AI Agent 调用。
平台侧接口见 API 文档,本包是它的 Agent 友好封装。
⚠️ 安全约束:不提供对话模型
本包刻意不封装 LLM 对话模型(qwen3-coder-next),CLI 也没有 chat 子命令。
平台十余个模型里,只有对话模型同时具备两个特性:
- 接受任意提示词 —— 其余模型都是「输入固定格式数据、输出固定格式结果」
- 能调用工具 —— 可通过
tools/tool_choice组合出调用链
把它做成 Agent 可调用的工具,等于把 Agent 的控制面交给不可信输入。提示词注入的后果是任意的:
- 诱导 Agent 读取不该读的文件、执行不该执行的命令
- 通过
tools组合出「看起来像合法业务调用」的越权链 - 借 Agent 的身份绕过人类已经设好的权限边界
这不是能力缺失,是刻意的取舍。
- 需要使用对话模型的用户:仍可直接调平台的
/v1/chat/completions+ 自建 API Key - Agent 的规划与生成能力:应交给宿主自带的主模型,不要二次调用本平台的对话模型
该约束由单元测试与端到端验证双重钉死(hasattr 断言 + 子命令集检查),
防止日后有人「顺手加回来」。
安装
已发布到 PyPI,MCP 客户端推荐用 uvx——它会自动准备隔离环境,无需预先安装任何东西:
{
"mcpServers": {
"modlink": {
"command": "uvx",
"args": ["--from", "modlink-agent", "--with", "modlink-agent[mcp]", "modlink-mcp"],
"env": { "MODLINK_API_KEY": "ml_live_xxxxxxxx" }
}
}
}
四个参数缺一不可,各有原因(以下均为实测结论,不是推演):
| 参数 | 作用 | 不写会怎样 |
|---|---|---|
--from modlink-agent |
uvx 第一个参数是包名,不是命令名 |
uvx modlink-mcp 会去 PyPI 找一个叫 modlink-mcp 的包,报 was not found in the package registry |
--with modlink-agent[mcp] |
uvx 默认不装 optional-dependencies | 包装成功但启动即报「缺少 MCP SDK」,且没有任何工具可用 |
modlink-mcp |
命令名 | 执行的是这个入口,而不是 CLI 的 modlink |
为什么配置里没有 URL
因为本 Server 跑在用户自己的机器上,不部署在服务器上:
Agent ──stdin/stdout──> 本机 modlink-mcp 进程 ──HTTPS──> 模联网关
↑ 网关地址在这一层,客户端不需要知道
你可能见过配置里带 "url": "http://…" 的 MCP,那是 HTTP 传输
(server 在远端,客户端主动去连)。本包用的是 stdio 传输
(客户端在本机起一个子进程,用标准输入输出通信),
所以配置里本来就不该有 URL 字段 —— 加了反而会让人误以为 server 在远端。
网关地址有内置默认值 https://asr.syncmeet.tech:9443,
需要改时在 env 里加 MODLINK_BASE_URL 覆盖(见「换域名时该做什么」)。
uvx 是 uv 自带的工具,需要先装一次:
| 平台 | 命令 |
|---|---|
| Windows | `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 |
| macOS / Linux | curl -LsSf https://astral.sh/uv/install.sh | sh |
包名
modlink-agent与命令名modlink-mcp不同名,这是有意的: 命令名若复用modlink,uvx modlink-agent会去找名为modlink-agent的命令—— 而我们注册的是modlink,找不到。代价是用户必须写--from。
不用 uvx 也能走 pip,写法更短:
pip install "modlink-agent[mcp]" # 含 MCP Server
pip install modlink-agent # 仅 CLI
依赖只有 requests 一个;MCP SDK 是可选项,只用 CLI 不需要装。
配置 API Key
API Key 在模联用户控制台 → API Key 页创建。 支持四种来源,优先级从上到下:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | 环境变量 MODLINK_API_KEY |
推荐。不进 shell history 与进程列表 |
| 2 | 配置文件 ~/.config/modlink-agent/config.json 的 apiKey |
免重复输入 |
| 3 | MCP 客户端配置的 env 段 |
见下方「MCP 接入」 |
| 4 | 交互式输入(仅 CLI 且在 TTY 下) | 首次试用 |
优先级不可绕过:环境变量存在时会忽略明文。 否则「先设了环境变量、后又改了面板配置」会静默用错一把 Key, 表现为「明明配了 A 却按 B 的额度扣费」,极难排查。
为什么不用命令行传 Key
--api-key ml_live_xxx 会进 shell history,也会被 ps aux 看到。
环境变量在同一台机器上通过 /proc/PID/environ 也能被同用户读到,
但不会落进 history 与审计日志——这是 CLI 工具的通行取舍。
--key 参数仍然保留(供程序化调用),但优先级低于环境变量。
全部环境变量
| 变量 | 必填 | 默认 | 说明 |
|---|---|---|---|
MODLINK_API_KEY |
✅ | — | API Key |
MODLINK_BASE_URL |
https://asr.syncmeet.tech:9443 |
网关地址 | |
MODLINK_WORK_DIR |
./modlink-out |
音频/图片落盘目录 | |
MODLINK_TIMEOUT |
600 |
单请求超时(秒) | |
MODLINK_ASYNC_THRESHOLD |
15 |
超过该秒数视为长任务,转异步轮询 |
模型白名单自动生效
在用户端创建 Key 时若设了「模型白名单」,本包无需任何配置即可正确受限:
# 用只授权向量的 Key
export MODLINK_API_KEY=ml_live_xxx
modlink models
# 只列出 qwen3-embedding —— 平台侧 /v1/models 已按白名单过滤
modlink translate "你好" --to 英语
# 当前 API Key 无权调用模型 hymt2-translate。该 Key 的模型白名单为:qwen3-embedding。
# 如需调用,请在「API Key」页修改白名单或新建 Key。
CLI 用法
modlink models # 列出当前 Key 可调用的模型
modlink health # 检查平台可达性
modlink transcribe a.wav --model funasr --diarization
modlink synthesize "欢迎使用模联" --model cosyvoice
modlink translate "你好" --to 英语
modlink embed "模联平台" --dimensions 1024
modlink image photo.jpg --task upscale
modlink generate "一只橘猫" --width 1024 --height 1024
没有
chat子命令 —— 见上方「安全约束」。
所有子命令支持 --json 输出结构化结果,便于脚本与 Agent 解析:
modlink models --json | jq '.data[].id'
图像任务对应关系
--task |
默认模型 | 额外必填 |
|---|---|---|
remove_background |
birefnet-matting |
— |
erase |
lama-inpaint |
--mask |
upscale |
realesrgan-upscale |
— |
face_restore |
codeformer-restore |
— |
face_swap |
inswapper-face-swap |
--source-image |
作为 Python 库
from modlink_agent import ModLinkClient
client = ModLinkClient() # 自动读环境变量
# 文本类直接返回结果
print(client.translate("你好", target_lang="英语"))
print(client.embed(["文档A", "文档B"], dimensions=1024))
# 二进制类返回**文件路径**,不是内容
audio = client.synthesize("欢迎使用模联")
print(audio.path) # ./modlink-out/tts-20261006-200547-xxx.wav
print(audio.describe()) # 一句话摘要(不含任何二进制内容)
客户端没有
chat/chat_text/stream_chat方法 —— 见上方「安全约束」。
三个为 Agent 做的关键设计
① 二进制不进上下文
一张 1024×1024 图转 base64 约 1.4MB token,会直接撑爆 Agent 上下文。 本包一律落盘后只返回绝对路径:
result = client.generate_image("一只猫")
print(result.path, result.size_bytes) # 路径 + 字节数,不含内容
② 429 自动退避
图像模型同卡互斥(平台文档明写「必须串行调用」),Agent 天生并发,一并发就吃 429。 若把 429 原样抛出,Agent 只会反复重试并再次 429,形成死循环。
本包读 Retry-After 头做指数退避,重试耗尽后返回可执行的提示:
模型互斥或触发限流(429),重试 4 次仍失败。
平台说明:图像生成类模型**同卡互斥,必须串行调用**。
请改为逐个调用(等上一个返回后再发下一个),或稍后再试。
③ 长任务不超时
- ASR:长音频自动走
/v1/asr/tasks异步接口,内部轮询到完成 (按音频大小启发式判断,调用方无需决策) - 图像:平台无异步接口,用长超时 + 返回值带耗时秒数
实测真机耗时(供参考,用于设置 MCP 客户端超时):
| 操作 | 耗时 |
|---|---|
| 翻译(2 条批量) | 0.16s |
| 向量化(2 条 / 256 维) | 0.09s |
| 图像超分(64×64) | 0.15s |
| 语音合成(1 句 → 86KB WAV) | 1.38s |
| 语音转写(1 句) | 0.29s |
图像生成(1024×1024,20B 模型)实测需数十秒到数分钟, 且同卡互斥(并发会 429)——MCP 客户端应给足超时并串行调用。 本包已内置 429 退避,但客户端超时也要相应放宽。
关于函数调用(工具调用)
平台侧的 /v1/chat/completions 完整支持 tools / tool_choice
与多轮 tool_calls 回传(平台只做透传,不执行工具——模型只负责
「提出调用请求」,由调用方执行后把结果作为 role=tool 消息回传,
漏掉 tool_call_id 会让模型重复发起同一次调用)。
但本包不封装对话模型(见上方「安全约束」),因此 Agent 侧 不会拿到这个入口——函数调用能力请直接在平台侧使用。
MCP 接入
配置
方式一:uvx(推荐)
{
"mcpServers": {
"modlink": {
"command": "uvx",
"args": ["--from", "modlink-agent", "--with", "modlink-agent[mcp]", "modlink-mcp"],
"env": {
"MODLINK_API_KEY": "ml_live_xxxxxxxx"
}
}
}
}
args 四个值缺一不可,原因见上方「安装」节的表格。
env 段写死的值就是「配置文件/显式传入」层,优先级低于机器上真实的环境变量
(见上方 Key 表格第 1 行)。也就是说:面板里填了明文,但进程环境变量里
存在 MODLINK_API_KEY 时,后者胜出。这样 Key 可以从 CI secret 或
~/.bashrc 统一注入,不必落到面板配置里。
Claude Desktop 需填 uvx 的完整路径(它的 PATH 常常不含用户级可执行目录)。 先在终端跑
where uvx(Windows)或which uvx(macOS/Linux), 把结果填进command。其它 IDE 插件(Cursor / VS Code 等)用uvx即可。
首次启动会下载约 34 个包(含 MCP SDK 及其依赖),耗时十几秒到一分钟, 取决于网速。某些客户端在首次启动时会因超时而报「连接失败」, 属正常现象——等它下载完重连一次即可,后续启动是秒开。
方式二:已用 pip 装到本地
{
"mcpServers": {
"modlink": {
"command": "modlink-mcp",
"env": {}
}
}
}
Windows 上若提示找不到命令,改用完整路径
(C:\Users\<你>\AppData\Roaming\Python\Python312\Scripts\modlink-mcp.exe),
或改用 python -m modlink_agent.mcp_server。
方式三:从源码目录跑
{
"mcpServers": {
"modlink": {
"command": "python",
"args": ["-m", "modlink_agent.mcp_server"],
"env": { "MODLINK_API_KEY": "ml_live_xxxxxxxx" }
}
}
}
前两种是用户场景,第三种供平台自身开发与排障。
工具清单(7 个)
按「族」暴露,不按模型拆成一堆工具 —— 一模型一工具会让 Agent 在 20+ 个 名字里纠结选择,且模型下线后工具名就变成死引用。
| 工具 | 作用 | 覆盖模型 |
|---|---|---|
modlink_list_models |
列出当前 Key 可调模型 | 全部(按白名单过滤) |
modlink_transcribe |
语音转写 | funasr / sensevoice / paraformer-bilingual / zipformer-bilingual |
modlink_synthesize |
语音合成 | cosyvoice / indextts |
modlink_translate |
文本翻译 | hymt2-translate |
modlink_embed |
文本向量化 | qwen3-embedding |
modlink_image_process |
图像处理 | 抠图 / 消除 / 超分 / 人脸修复 / 换脸 |
modlink_image_generate |
图像生成 | flux2-generate / qwen-image-2.1 |
不包含对话工具(modlink_chat 不存在)——与 CLI 保持一致的安全边界。
客户端可用 modlink_list_models 自查可调范围,但其中不会出现对话模型。
Server Instructions
Server 带一段面向 Agent 的说明,覆盖三类「模型自身不会说」的知识:
- 二进制结果返回的是文件路径而非内容,需要 Agent 再用文件工具读取
- 图像生成/处理同卡互斥,并发会 429
- 图像生成耗时长(1024×1024 数十秒到数分钟),需要给足超时
modlink_list_models 的存在是必要的:不调它,Agent 无从知道
「这个 Key 到底能用什么」,只能靠猜或反复试错吃 403。
错误处理
| 异常 | 触发条件 | 提示要点 |
|---|---|---|
AuthenticationError |
401 | Key 无效/吊销/过期,指向控制台检查 |
ForbiddenError |
403 | 模型白名单未授权,提示去改白名单 |
InsufficientCredits |
402 | 积分不足,指向充值页 |
RateLimitError |
429 | 模型互斥,提示改串行 |
ModelNotFound |
404 / 503 | 模型标识错或未部署 |
NetworkError |
连不上网关 | 点名 MODLINK_BASE_URL,并明确不要重试 |
UpstreamError |
5xx | 平台或引擎异常 |
错误文案都是给 Agent 读的,每条都包含下一步该做什么—— Agent 拿到「HTTP 403」只会反复重试同一个错误调用。
NetworkError 尤其关键:域名是会变的(平台换接入点、或用户填错地址)。
裸 requests 异常里只有英文堆栈,Agent 读不出「这是配置问题」,
于是反复重试同一个必然失败的地址。工具包把它翻译成
「请检查 MODLINK_BASE_URL……这是配置问题,重试无用」。
换域名时该做什么
网关地址有四级取值,优先级从高到低:
- 显式传入(
--base-url/ModLinkClient(base_url=...)) - 环境变量
MODLINK_BASE_URL - 配置文件
~/.config/modlink-agent/config.json的baseUrl字段 - 内置默认值
config.DEFAULT_BASE_URL
平台更换域名时,改代码里的默认值只对升级到新版的人生效—— 已经装在用户机器上的旧版本仍写着旧地址。完整处置:
- 改
config.py的DEFAULT_BASE_URL与pyproject.toml的project.urls,发新版; - 旧域名保留一段转发,避免存量用户直接断线;
- 公告新的
MODLINK_BASE_URL取值。
已知风险
uvx 每次启动都重新解析依赖。 pip 装一次的用户不受上游升级影响,
但 uvx 用户每次启动都在解析 —— 上游一次破坏性发布就能让所有用户的
MCP 同时崩溃。
因此 mcp 依赖硬 pin >=1.0,<2(mcp 2.x 已移除 mcp.server.fastmcp)。
先例:mcp-search-console 0.3.3 就是为此发过补丁版本。
若将来 mcp 2.x 的适配完成,应同时验证 uvx 与 pip 两条路径,
再放宽上限。
首次启动会下载约 34 个包(MCP SDK 及其依赖)。 部分 MCP 客户端在首次启动时会因超时而报「连接失败」—— 这是下载没完成,不是配置错误,等它下完重连即可。
包名与命令名不同名。 uvx 的第一个位置参数是包名,
所以必须写 --from modlink-agent,不能简写成 uvx modlink-mcp。
这是为避免「包名与命令名相同」带来的歧义所付的代价。
测试
python tests/test_client_local.py # 32 项:起本地假 HTTP 服务,不连生产
python tests/test_mcp_local.py # 44 项:含真实 stdio 握手
test_mcp_local.py 会真的起一个 python -m modlink_agent.mcp_server
子进程,走完整 JSON-RPC 握手并 list_tools —— 因为 stdio 传输下
stdout 是协议通道,任何一处 print 都会破坏 JSON-RPC 帧,
而这类问题不真起进程就测不出来。
真机验证覆盖 7 个族各调一次。
Metadata
Release files for modlink-agent 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| modlink_agent-1.1.1.tar.gz | 57.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| modlink_agent-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 100.7 kB
Release files / modlink_agent-1.1.1.tar.gz
| Download URL | modlink_agent-1.1.1.tar.gz |
|---|---|
| Size | 57.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a9eb460ddbd8f34238a4ec130394d53e5d29b46a1b6c0f0b1d54d3cee7c7def
|
|
BLAKE2b-256 checksum How to use checksums |
0e797793459c700bc7a9603f7d923317ae38f348cf8613aaf7d25dd1c2db8455
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.8
|
Release files / modlink_agent-1.1.1-py3-none-any.whl
| Download URL | modlink_agent-1.1.1-py3-none-any.whl |
|---|---|
| Size | 43.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c76dfaf88d9d669f5f0be82e80d59ad3575766c95edc1687a6e4984fe0c271f8
|
|
BLAKE2b-256 checksum How to use checksums |
e5023cb317f1cf35547e450204dfbc139492852bdda474e10500975398bd6d57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.8
|