new-api 接入 io.net 集群部署:pkg/ionet 客户端库与 API 交互实战指南
【免费下载链接】new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.项目地址: https://gitcode.com/QuantumNous/new-api
本指南以仓库文档 docs/ionet-client.md 为核心骨架,围绕 io.net 集群重命名这一具体 API 调用展开,并结合 pkg/ionet 的完整客户端实现,系统讲解如何在 QuantumNous/new-api 中集成 io.net(api.io.solutions)的容器集群部署能力:包括客户端初始化、认证与统一请求封装、集群与部署生命周期管理、容器运维、硬件与位置查询、价格估算,以及前端模型部署设置中的真实接线方式。读者读完可掌握 io.net 客户端库的全部公开方法、参数语义、返回结构与调用注意事项,并能直接将其用于接入新的渠道或自动化运维脚本。
一、文档定位:一次真实的集群重命名调用
仓库中与 io.net 客户端相关的说明文档 docs/ionet-client.md 记录了一次真实的 HTTP 调用示例,原文如下:
Request URL https://api.io.solutions/v1/io-cloud/clusters/654fc0a9-0d4a-4db4-9b95-3f56189348a2/update-name Request Method PUT {"status":"succeeded","message":"Cluster name updated successfully"}这段内容展示了 io.net API 的典型交互特征:
- 请求方式:
PUT(幂等更新语义),用于修改集群名称; - URL 结构:
https://api.io.solutions/v1/io-cloud/clusters/{cluster_id}/update-name,其中654fc0a9-0d4a-4db4-9b95-3f56189348a2是集群的唯一 ID(UUID 格式); - 成功响应:返回 JSON 对象
{"status":"succeeded","message":"Cluster name updated successfully"},注意该响应没有data包装层,与大多数 io.net 端点返回{"data": ...}的格式不同。
这段文档中的 URL 与响应体,在仓库源码 pkg/ionet/deployment.go 中得到了精确的对应实现——UpdateClusterName方法正是向/clusters/{clusterID}/update-name发送PUT请求,并直接以UpdateClusterNameResponse(status+message两个字段)解析响应体。这为理解整个 io.net 客户端库提供了一个真实的、可对照的样本。
二、客户端库总览:pkg/ionet 包结构与能力地图
io.net 客户端被封装在 pkg/ionet 目录下,共 6 个源文件,各自职责清晰:
| 文件 | 职责 |
|---|---|
| client.go | 客户端构造、HTTP 客户端抽象、统一请求封装、查询参数构建 |
| types.go | 全部请求/响应/错误的数据结构定义 |
| deployment.go | 部署(Deployment)与集群(Cluster)生命周期管理 |
| container.go | 容器(Container)查询、日志、重启/停止/执行 |
| hardware.go | 硬件类型、位置、可用性与余量查询 |
| jsonutil.go | 响应解析辅助:data解包与宽松时间解析 |
从整体能力看,该客户端覆盖了 io.net 平台的核心操作域,可以划分为五大类:
- 集群/部署生命周期:创建、列表、详情、更新、延长、删除、改名;
- 容器运维:容器列表/详情、日志获取与流式跟踪、重启、停止、执行命令;
- 硬件与位置:硬件类型、单卡最大 GPU 数、位置列表、实时可用性;
- 价格估算:按位置、硬件、时长、副本数估算成本并返回明细拆分;
- 连接自检:测试 API Key 有效性并返回硬件与余量统计(供前端"测试连接"使用)。
三、客户端初始化与认证
3.1 三个构造函数
在 pkg/ionet/client.go 中定义了三个构造入口:
// 面向公有 API(默认 base URL) func NewClient(apiKey string) *Client { return NewClientWithConfig(apiKey, DefaultBaseURL, nil) } // 面向企业 API func NewEnterpriseClient(apiKey string) *Client { return NewClientWithConfig(apiKey, DefaultEnterpriseBaseURL, nil) } // 全参数构造:可自定义 base URL 与 HTTP 客户端 func NewClientWithConfig(apiKey, baseURL string, httpClient HTTPClient) *Client对应的常量定义(pkg/ionet/client.go):
const ( DefaultEnterpriseBaseURL = "https://api.io.solutions/enterprise/v1/io-cloud/caas" DefaultBaseURL = "https://api.io.solutions/v1/io-cloud/caas" DefaultTimeout = 30 * time.Second )注意:NewClientWithConfig中baseURL为空时回退到DefaultBaseURL,httpClient为 nil 时使用带 30 秒超时的默认 HTTP 客户端(NewDefaultHTTPClient(DefaultTimeout))。HTTPClient是一个可插拔接口,便于测试时注入 mock。
3.2 认证方式:X-API-KEY 头
所有请求统一在makeRequest(pkg/ionet/client.go)中注入认证头:
headers := map[string]string{ "X-API-KEY": c.APIKey, "Content-Type": "application/json", }即通过X-API-KEY请求头传递 API Key,而非 Bearer Token 或查询参数。makeRequest还统一负责:请求体 JSON 序列化、拼接BaseURL + endpoint、错误响应处理。
3.3 统一错误处理
当响应状态码>= 400时,makeRequest会优先尝试解析 io.net 常见的错误格式{"detail": "message"},构造APIError{Code, Message};解析失败则回退为APIError{Code, Message: "API request failed with status N", Details: 原始响应体}。APIError实现了error接口(pkg/ionet/types.go),调用方可通过类型断言判断具体错误。
四、集群/部署生命周期管理
4.1 创建部署:DeployContainer
func (c *Client) DeployContainer(req *DeploymentRequest) (*DeploymentResponse, error)请求体结构(pkg/ionet/types.go)及其必填校验(pkg/ionet/deployment.go):
| 字段 | 类型 | 说明 | 校验规则 |
|---|---|---|---|
resource_private_name | string | 资源私有名称 | 必填,非空 |
duration_hours | int | 租用时长(小时) | ≥ 1 |
gpus_per_container | int | 每容器 GPU 数 | ≥ 1 |
hardware_id | int | 硬件类型 ID | > 0 |
location_ids | []int | 目标位置 ID 列表 | 非空 |
container_config | ContainerConfig | 容器配置(副本数、环境变量、入口命令、流量端口、参数) | replica_count≥ 1 |
registry_config | RegistryConfig | 镜像仓库配置(image_url必填,可选用户名/密钥) | image_url必填 |
其中ContainerConfig支持env_variables(普通环境变量)与secret_env_variables(敏感环境变量)两组键值,Entrypoint与Args均为字符串数组,TrafficPort用于声明对外流量端口。成功时端点POST /deploy直接返回{"status": "...", "deployment_id": "..."}。
4.2 查询部署列表与详情
- 列表:
ListDeployments(opts *ListDeploymentsOptions)请求GET /deployments,支持status、location_id、page、page_size、sort_by、sort_order等过滤与分页参数(pkg/ionet/types.go),这些参数会经过buildQueryParams转为查询字符串。返回的DeploymentList中每个Deployment还额外派生GPUCount与Replicas两个字段(当前按HardwareQuantity1:1 映射,见 pkg/ionet/deployment.go)。 - 详情:
GetDeployment(deploymentID)请求GET /deployment/{id},返回的DeploymentDetail包含完整的计费与运行信息:AmountPaid(已支付金额)、CompletedPercent(完成度)、TotalGPUs、ComputeMinutesServed/Remaining(已服务/剩余算力分钟)、Locations等。
4.3 更新、延长与删除
- 更新配置:
UpdateDeployment(deploymentID, req)通过PATCH /deployment/{id}修改环境变量、入口命令、镜像等(UpdateDeploymentRequest,pkg/ionet/types.go)。 - 延长租期:
ExtendDeployment(deploymentID, req)通过POST /deployment/{id}/extend追加duration_hours(≥ 1),响应为最新的部署详情。 - 删除部署:
DeleteDeployment(deploymentID)通过DELETE /deployment/{id}删除活跃部署。
4.4 集群名称管理(对应文档示例)
这正是 docs/ionet-client.md 记录的核心操作,源码实现位于 pkg/ionet/deployment.go:
// 先校验名称可用性 func (c *Client) CheckClusterNameAvailability(clusterName string) (bool, error) // GET /clusters/check_cluster_name_availability?cluster_name=... // 再更新集群名称 func (c *Client) UpdateClusterName(clusterID string, req *UpdateClusterNameRequest) (*UpdateClusterNameResponse, error) // PUT /clusters/{clusterID}/update-name其中UpdateClusterNameRequest的 JSON 字段为cluster_name,响应结构UpdateClusterNameResponse{Status, Message}与文档中的{"status":"succeeded","message":"Cluster name updated successfully"}完全对应。两个方法均有空值校验:集群 ID 非空、名称非空。文档中的完整请求 URL 与源码端点拼装一致:PUT https://api.io.solutions/v1/io-cloud/clusters/{cluster_id}/update-name。
五、容器运维与日志
容器相关方法集中在 pkg/ionet/container.go:
| 方法 | HTTP 端点 | 说明 |
|---|---|---|
ListContainers(deploymentID) | GET /deployment/{id}/containers | 列出部署下全部容器(含public_url、uptime_percent、事件流) |
GetContainerDetails(deploymentID, containerID) | GET /deployment/{id}/container/{cid} | 单容器详情 |
GetContainerJobs(deploymentID, containerID) | GET /deployment/{id}/containers-jobs/{cid} | 容器任务列表 |
GetContainerLogs(deploymentID, containerID, opts) | GET /deployment/{id}/log/{cid} | 归一化日志(把\r\n归一为\n,逐行转为LogEntry) |
GetContainerLogsRaw(...) | 同上 | 原始文本日志 |
StreamContainerLogs(...) | 同上 +follow参数 | 轮询式流式日志(每 2 秒轮询一次,支持 cursor 续传) |
RestartContainer(...) | POST /deployment/{id}/container/{cid}/restart | 重启容器 |
StopContainer(...) | POST /deployment/{id}/container/{cid}/stop | 停止容器 |
ExecuteInContainer(...) | POST /deployment/{id}/container/{cid}/exec | 在容器内执行命令,返回output |
日志查询选项GetLogsOptions(pkg/ionet/types.go)支持start_time/end_time(*time.Time)、level、stream(stdout/stderr)、limit、cursor(分页游标)与follow(流式跟踪)。StreamContainerLogs采用轮询实现:将follow置为 true,不断拉取并以回调函数逐条投递日志条目,遇到has_more=false且无next_cursor时结束,每次轮询间隔 2 秒以降低 API 压力(源码注释也提示:真实场景可用 SSE 或 WebSocket 做更高效的流式日志,见 pkg/ionet/container.go)。
六、硬件、位置与价格估算
6.1 硬件与位置查询(pkg/ionet/hardware.go)
GetMaxGPUsPerContainer():请求GET /hardware/max-gpus-per-container,返回每种硬件的max_gpus_per_container、available余量、硬件/品牌名称;ListHardwareTypes():基于上述端点把MaxGPUInfo映射为HardwareType,名称缺失时回退为Hardware {id},并计算总的可用数量(Total为 0 时按各硬件available求和);GetAvailableReplicas(hardwareID, gpuCount):请求GET /available-replicas,返回各位置可用的副本数量;ListLocations()/GetLocation(id):位置列表与详情,ISO2国家码会被统一转为大写;GetLocationAvailability(locationID):请求GET /locations/{id}/availability,返回该位置各硬件的实时可用数量(AvailableCount)与单卡上限(MaxGPUs)。
6.2 价格估算(pkg/ionet/deployment.go)
GetPriceEstimation(req)请求GET /price,是参数最复杂的端点之一。请求参数PriceEstimationRequest(pkg/ionet/types.go)与默认值逻辑:
currency:默认"usdc";duration_type:支持hour/day/week/month(大小写不敏感,含复数形式),默认hour,内部会换算为hourly/daily/weekly/monthly传给 API,并据此计算总时长小时数(日 ×24、周 ×24×7、月 ×24×30);duration_qty:时长数量,缺省回退到duration_hours;hardware_qty:硬件数量,缺省回退到gpus_per_container;- 必填校验:
location_ids非空、hardware_id非 0、replica_count≥ 1。
响应解析遵循 io.net 文档给出的data包装格式:
{ "data": { "replica_count": 0, "gpus_per_container": 0, "available_replica_count": [0], "discount": 0, "ionet_fee": 0, "ionet_fee_percent": 0, "currency_conversion_fee": 0, "currency_conversion_fee_percent": 0, "total_cost_usdc": 0 } }内部将其转换为统一的PriceEstimationResponse:EstimatedCost取total_cost_usdc,PriceBreakdown拆分为ComputeCost(总成本扣除 io.net 手续费与货币转换费)、TotalCost与HourlyRate(总成本 ÷ 时长小时数),Currency转为大写(如USDC)。
七、响应解析的鲁棒性设计
io.net API 存在两类显著的不一致性,客户端通过 pkg/ionet/jsonutil.go 做了统一兼容:
data包装层不一致:多数端点返回{"data": ...}(如价格估算、部署列表),而部分端点直接返回裸对象(如集群改名响应、部署创建响应)。客户端据此分为两组:使用decodeData/decodeDataWithFlexibleTimes(自动解包data)与直接json.Unmarshal(裸解析)。这也是UpdateClusterName特意注释"根据 API 文档直接解析响应、不做 data 包装"(pkg/ionet/deployment.go)的原因。- 时间戳时区缺失:部分端点返回不带时区的本地时间字符串(如
2006-01-02T15:04:05),直接time.Time反序列化会失败。decodeWithFlexibleTimes先解析为interface{},递归遍历所有字符串值,尝试按RFC3339Nano、RFC3339及多种无时区布局解析,成功则统一规范为 UTC 的RFC3339Nano后再反序列化(pkg/ionet/jsonutil.go)。部署详情、容器列表、日志等端点均走该路径。
八、查询参数构建规则
buildQueryParams(pkg/ionet/client.go)是所有 GET 请求的参数引擎,规则如下:
nil值跳过;- string 空值跳过,int/int64 为 0 跳过,bool 为 false 跳过,
time.Time为零值跳过; time.Time格式化为 RFC3339,*time.Time判空后同样处理;[]int、[]string非空时以 JSON 数组形式编码(如location_ids=[1,2]);- 其余类型使用
fmt.Sprint兜底; - 最终以
?+url.Values.Encode()拼接。
这套规则保证了"零值不传参"的语义,例如ListDeployments不设置任何过滤项时不会携带无意义参数。
九、在 new-api 中的真实接线:模型部署设置
io.net 客户端并非孤立库,它已被 new-api 的模型部署(Model Deployment)功能集成,入口为 controller/deployment.go:
- 开关与密钥:通过全局配置项
model_deployment.ionet.enabled("true"才启用)与model_deployment.ionet.api_key控制(controller/deployment.go);配置缺失或未启用时返回"io.net model deployment is not enabled or api key missing"; - 客户端选择:
getIoClient使用公有 APIionet.NewClient(apiKey),getIoEnterpriseClient使用企业 APIionet.NewEnterpriseClient(apiKey)(controller/deployment.go),对应上一节的两种 base URL; - 连接自检:
TestIoNetConnection优先使用请求体中的api_key,否则回退到已存储的配置密钥,随后调用client.GetMaxGPUsPerContainer()验证密钥有效性,成功则返回hardware_count与total_available统计(controller/deployment.go);失败时对*ionet.APIError做类型断言,向用户展示detail中的真实错误信息; - 前端设置页:系统设置中的 io.net 部署设置区块位于 web/src/features/system-settings/integrations/ionet-deployment-settings-section.tsx,相关的启用/配置状态类型定义在 web/src/features/system-settings/types.ts,hooks 文件 web/src/features/models/hooks/use-model-deployment-settings.ts 负责与后端设置接口交互,共同构成"填写 Key → 测试连接 → 启用部署"的完整管理闭环。
十、实战小结与调用建议
结合 docs/ionet-client.md 的示例与 pkg/ionet 的实现,接入 io.net 时的关键经验总结如下:
- 认证统一:所有请求都依赖
X-API-KEY请求头,务必通过NewClient/NewEnterpriseClient构造,不要手动拼接头部; - 端点语义区分:创建、延长、重启等变更操作为
POST,更新配置为PATCH,改名与删除为PUT/DELETE,查询一律GET; - 响应格式分叉:
data包装与裸响应并存,使用decodeData系函数解析列表/详情类端点,对集群改名、部署创建这类裸响应端点直接json.Unmarshal; - 名称管理流程:先
CheckClusterNameAvailability校验,再UpdateClusterName提交{"cluster_name": "..."},成功响应即文档中的{"status":"succeeded","message":"Cluster name updated successfully"}; - 错误排查:捕获
*ionet.APIError读取Message/Details,优先展示detail字段中的服务端原始信息; - 扩展接入:若要将 io.net 能力暴露为 new-api 的模型部署渠道,可参照 controller/deployment.go 的
getIoClient/TestIoNetConnection模式:配置项开关 + API Key 存储 + 连接自检,再在 router 层注册对应路由。
以文档中的集群 ID654fc0a9-0d4a-4db4-9b95-3f56189348a2为例,一次完整的改名调用应为:
curl -X PUT \ "https://api.io.solutions/v1/io-cloud/clusters/654fc0a9-0d4a-4db4-9b95-3f56189348a2/update-name" \ -H "X-API-KEY: <your-api-key>" \ -H "Content-Type: application/json" \ -d '{"cluster_name": "my-new-cluster-name"}'期望返回{"status":"succeeded","message":"Cluster name updated successfully"}——与文档记录完全一致,也可作为验证UpdateClusterName实现的端到端基准。
【免费下载链接】new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.项目地址: https://gitcode.com/QuantumNous/new-api
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考