ForkSilly应用的API模板仓库。
示例模板请在仓库templates目录下载:https://github.com/fatsnk/APItemplateV2/tree/main/templates
模板引擎通过一个 JSON 配置文件来描述任意 LLM API 的请求/响应结构。引擎读取配置后自动完成:URL 拼接、请求头构建、请求体填充、采样参数映射、图片注入、流式/非流式响应解析等全部工作。
模板文件是一个 .json 文件,顶层结构如下:
{
"version": 2,
"name": "模板显示名称",
"connection": { ... },
"request": { ... },
"response": { ... },
"model": { ... },
"features": { ... },
"uiOverridableSamplers": [ ... ],
"media": { ... }
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
version |
number |
✅ | 必须为 2 |
name |
string |
✅ | 模板名称,显示在 UI 中 |
定义 API 的端点地址和认证方式。
"connection": {
"endpoint": "/v1/chat/completions",
"streamEndpoint": "/v1/chat/completions",
"modelEndpoint": "/v1/models",
"headers": {
"Content-Type": "application/json"
},
"auth": {
"header": "Authorization",
"prefix": "Bearer ",
"extraHeaders": {}
}
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
endpoint |
string |
✅ | 非流式请求的端点路径,支持 {{model}} 宏 |
streamEndpoint |
string |
❌ | 流式请求的端点路径。不填则复用 endpoint |
modelEndpoint |
string |
❌ | 获取模型列表的端点路径,默认 /models |
headers |
object |
❌ | 额外请求头,会合并到最终请求中 |
auth |
object |
❌ | 认证配置。不填则默认使用 Authorization: Bearer <key> |
auth.header |
string |
— | 认证头名称,如 "Authorization" 或 "x-goog-api-key" |
auth.prefix |
string |
— | 认证值前缀。默认 "Bearer "。设为 "" 表示直接使用 key 无前缀 |
auth.extraHeaders |
object |
— | 额外的认证相关头 |
端点路径中的宏替换: {{model}} 会被替换为用户选择的模型名称。这对 Gemini 这类将模型名嵌入 URL 的 API 很有用:
"endpoint": "/v1beta/models/{{model}}:generateContent"最终 URL = 用户填写的 apiUrl(去掉末尾斜杠)+ 端点路径。例如用户填 https://generativelanguage.googleapis.com,最终为:
https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent
定义请求体的结构、采样参数映射、prompt 格式。
这是请求体的"骨架"。引擎会深拷贝它,然后往里面填充 prompt、采样参数等。你可以在里面放任何 API 需要的固定字段。
"bodyTemplate": {
"contents": [],
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 2048
},
"safetySettings": [
{ "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" }
]
}规则:
bodyTemplate中的固定值会原样保留在最终请求中- 如果某个采样参数在
samplerMappings中声明了,且用户在 UI 中启用了该参数,则 UI 值会覆盖bodyTemplate中的对应值 - 如果用户未启用,则保留
bodyTemplate中的默认值 - 不在
samplerMappings中的字段永远保持原样(如上面的safetySettings)
将应用内的标准采样参数映射到请求体中的具体路径。
"samplerMappings": [
{ "samplerID": "temperature", "path": "$.generationConfig.temperature" },
{ "samplerID": "maxTokens", "path": "$.generationConfig.maxOutputTokens", "transform": "integer" },
{ "samplerID": "topP", "path": "$.generationConfig.topP" },
{ "samplerID": "topK", "path": "$.generationConfig.topK", "transform": "integer" }
]| 字段 | 说明 |
|---|---|
samplerID |
标准参数 ID,可选值见下表 |
path |
写入请求体的 JSONPath 路径 |
transform |
可选的值转换:"integer"(取整)、"string"(转字符串)、"boolean"(转布尔) |
可用的 samplerID:
| samplerID | 说明 |
|---|---|
temperature |
温度 |
maxTokens |
最大输出 token 数 |
topP |
Top-P 采样 |
topK |
Top-K 采样 |
frequencyPenalty |
频率惩罚 |
presencePenalty |
存在惩罚 |
reasoningEffort |
推理强度 |
"stop": {
"path": "$.generationConfig.stopSequences",
"limit": 5
}应用中没有开放停止序列的配置,如果有需求,请将固定值配置到body中,不要使用这里的配置。
| 字段 | 说明 |
|---|---|
path |
停止序列写入请求体的 JSONPath |
limit |
最大停止序列数量 |
指定格式化后的消息数组写入请求体的哪个位置。
"promptPath": "$.contents"- OpenAI 格式通常是
"$.messages" - Gemini 格式通常是
"$.contents"
定义消息的格式化方式。支持两种类型:
"promptFormat": {
"type": "chat",
"roles": {
"user": "user",
"assistant": "model",
"system": "user"
},
"contentKey": "parts"
}| 字段 | 说明 |
|---|---|
type |
固定为 "chat" |
roles.user |
user 角色在 API 中的名称。OpenAI 为 "user",Gemini 为 "user" |
roles.assistant |
assistant 角色在 API 中的名称。OpenAI 为 "assistant",Gemini 为 "model" |
roles.system |
system 角色在 API 中的名称。Gemini 不支持 system role,映射为 "user" |
contentKey |
消息内容字段名。OpenAI 为 "content",Gemini 为 "parts" |
引擎会将内部消息格式转换为 API 要求的格式。例如 Gemini 模板会生成:
[
{ "role": "user", "parts": [{ "text": "你好" }] },
{ "role": "model", "parts": [{ "text": "你好!" }] }
]"promptFormat": {
"type": "text"
}所有消息会被拼接为 "role: content\nrole: content\n..." 的纯文本字符串。
定义如何从 API 响应中提取内容。
"response": {
"transport": { "type": "sse" },
"contentPath": "$.candidates[0].content.parts[0].text",
"streamContentPath": "$.candidates[0].content.parts[0].text",
"reasoningPath": "$.candidates[0].content.parts[0].thought",
"streamReasoningPath": "$.candidates[0].content.parts[0].thought",
"error": {
"detectPath": "$.error",
"messagePath": "$.error.message"
}
}| type | 说明 |
|---|---|
"sse" |
Server-Sent Events 流式传输 |
"fetch" |
普通 HTTP 请求,等待完整响应 |
"polling" |
轮询模式,适用于异步任务型 API(未测试) |
"transport": {
"type": "sse",
"format": "standard",
"doneSignal": "[DONE]"
}| 字段 | 说明 | 默认值 |
|---|---|---|
format |
SSE 数据格式:"standard" / "ndjson" / "auto" |
"standard" |
doneSignal |
流结束信号 | "[DONE]" |
"transport": {
"type": "polling",
"submitPath": "/v1/tasks",
"statusPath": "/v1/tasks/{{taskId}}",
"taskIdPath": "$.task_id",
"donePath": "$.status.completed",
"interval": 2000
}| 字段 | 说明 |
|---|---|
submitPath |
提交任务的端点路径 |
statusPath |
查询任务状态的端点路径,支持 {{taskId}} 宏 |
taskIdPath |
从提交响应中提取任务 ID 的 JSONPath |
donePath |
判断任务完成的 JSONPath(值为 truthy 时视为完成) |
interval |
轮询间隔(毫秒),默认 2000 |
| 字段 | 必填 | 说明 |
|---|---|---|
contentPath |
❌ | 从非流式响应中提取内容的 JSONPath ,如果存在contentParts,则此项非必填 |
contentParts |
❌ | 从非流式响应中提取内容的 JSONPath ,数组格式,可配置多个path。与contentPath同时存在时,优先此项 |
streamContentPath |
❌ | 从流式响应中提取内容的 JSONPath。不填则复用 contentPath |
reasoningPath |
❌ | 从非流式响应中提取推理/思考内容的 JSONPath |
streamReasoningPath |
❌ | 从流式响应中提取推理/思考内容的 JSONPath |
contentParts只支持非流,通常用于图片生成api。详情请参考模板oai Image b64
"contentParts": [
{
"path": "$.data[0].revised_prompt"
},
{
"path": "$.data[0].b64_json",
"isMedia": {
"type": "image",
"mimeType": "image/png"
}
}
],
/* 支持的类型: */
type: 'image' | 'video' | 'audio';
/** MIME 类型,默认 'image/png' */
mimeType?: string;
聊天中只能渲染图片类型。不指定则为普通文本。
常见 API 的路径对照:
| API | contentPath | streamContentPath |
|---|---|---|
| OpenAI | $.choices[0].message.content |
$.choices[0].delta.content |
| Gemini | $.candidates[0].content.parts[0].text |
$.candidates[0].content.parts[0].text |
| Claude | $.content[0].text |
$.delta.text |
"error": {
"detectPath": "$.error",
"messagePath": "$.error.message"
}| 字段 | 说明 |
|---|---|
detectPath |
错误检测路径。该路径的值存在且为 truthy 时视为错误 |
messagePath |
错误消息的 JSONPath |
不配置时,引擎会默认检查响应中的 error 字段。
定义如何解析模型列表 API 的响应。
"model": {
"useModelContextLength": true,
"listPath": "$.models",
"namePath": "$.name",
"contextLengthPath": "$.inputTokenLimit"
}| 字段 | 说明 |
|---|---|
useModelContextLength |
是否使用模型返回的上下文长度 |
listPath |
模型列表数组的 JSONPath,如 OpenAI 的 "$.data",Gemini 的 "$.models" |
namePath |
模型名称的 JSONPath(相对于列表中的每个元素),如 "$.id" 或 "$.name" |
contextLengthPath |
上下文长度的 JSONPath(相对于列表中的每个元素) |
不配置时,引擎会尝试自动识别 OpenAI 格式($.data[].id)和其他常见格式。
控制部分功能逻辑的开关。
"features": {
"useKey": true,
"useModel": true,
"useStreaming": true
}| 字段 | 默认值 | 说明 |
|---|---|---|
useKey |
true |
是否需要 API Key |
useModel |
true |
是否需要模型选择 |
useStreaming |
true |
是否允许切换流式/非流式 |
列出哪些采样参数用户可手动配置。未列出的参数使用 bodyTemplate 中的固定值。(建议配置)
"uiOverridableSamplers": ["temperature", "maxTokens", "topP", "topK"]可选值与 samplerID 相同:temperature、maxTokens、topP、topK、frequencyPenalty、presencePenalty、reasoningEffort。
定义图片发送的格式。
"media": {
"supportsImages": true,
"imageContentTemplate": {
"inlineData": {
"mimeType": "{{mime_type}}",
"data": "{{image_base64}}"
}
},
"textContentTemplate": {
"text": "{{text}}"
}
}| 字段 | 说明 |
|---|---|
supportsImages |
是否支持发送图片,默认 false |
imageContentTemplate |
图片在消息中的格式模板,支持 {{image_base64}} 和 {{mime_type}} 宏 |
textContentTemplate |
文本在消息中的格式模板(当消息同时包含文本和图片时使用),支持 {{text}} 宏 |
不同 API 的图片格式对照:
OpenAI:
"imageContentTemplate": {
"type": "image_url",
"image_url": { "url": "data:{{mime_type}};base64,{{image_base64}}" }
},
"textContentTemplate": {
"type": "text",
"text": "{{text}}"
}Gemini:
"imageContentTemplate": {
"inlineData": { "mimeType": "{{mime_type}}", "data": "{{image_base64}}" }
},
"textContentTemplate": {
"text": "{{text}}"
}Claude:
"imageContentTemplate": {
"type": "image",
"source": { "type": "base64", "media_type": "{{mime_type}}", "data": "{{image_base64}}" }
},
"textContentTemplate": {
"type": "text",
"text": "{{text}}"
}当 textContentTemplate 存在时,纯文本消息的 content 也会被包装为数组格式。例如 Gemini 模板下,"你好" 会变成 [{ "text": "你好" }]。
不配置 media 或 supportsImages 为 false 时,图片功能关闭。
模板中所有 path 字段使用简化的 JSONPath 语法:
| 语法 | 含义 | 示例 |
|---|---|---|
$ |
根对象 | $ |
。key |
对象属性 | $.temperature |
[0] |
数组索引 | $.choices[0] |
["key"] |
带引号的属性 | $.["special-key"] |
组合示例:$.candidates[0].content.parts[0].text 表示 obj.candidates[0].content.parts[0].text。
写入时,如果中间路径不存在,引擎会自动创建对象或数组。
模板中的字符串值支持 {{macro}} 宏替换:
| 宏 | 可用位置 | 说明 |
|---|---|---|
{{model}} |
endpoint、streamEndpoint、bodyTemplate 中的字符串 |
替换为用户选择的模型名称 |
{{text}} |
textContentTemplate |
替换为消息文本内容 |
{{image_base64}} |
imageContentTemplate |
替换为图片的 base64 数据 |
{{mime_type}} |
imageContentTemplate |
替换为图片的 MIME 类型 |
{{taskId}} |
polling.statusPath |
替换为轮询任务 ID |
{
"version": 2,
"name": "OpenAI Compatible",
"connection": {
"endpoint": "/v1/chat/completions",
"modelEndpoint": "/v1/models",
"auth": {
"header": "Authorization",
"prefix": "Bearer "
}
},
"request": {
"bodyTemplate": {
"model": "{{model}}",
"messages": [],
"stream": false
},
"samplerMappings": [
{ "samplerID": "temperature", "path": "$.temperature" },
{ "samplerID": "maxTokens", "path": "$.max_tokens", "transform": "integer" },
{ "samplerID": "topP", "path": "$.top_p" },
{ "samplerID": "frequencyPenalty", "path": "$.frequency_penalty" },
{ "samplerID": "presencePenalty", "path": "$.presence_penalty" }
],
"promptPath": "$.messages",
"promptFormat": {
"type": "chat",
"roles": { "user": "user", "assistant": "assistant", "system": "system" },
"contentKey": "content"
}
},
"response": {
"transport": { "type": "sse" },
"contentPath": "$.choices[0].message.content",
"streamContentPath": "$.choices[0].delta.content",
"error": {
"detectPath": "$.error",
"messagePath": "$.error.message"
}
},
"model": {
"listPath": "$.data",
"namePath": "$.id"
},
"features": {
"useKey": true,
"useModel": true,
"useStreaming": true
},
"uiOverridableSamplers": ["temperature", "maxTokens", "topP", "frequencyPenalty", "presencePenalty"],
"media": {
"supportsImages": true,
"imageContentTemplate": {
"type": "image_url",
"image_url": { "url": "data:{{mime_type}};base64,{{image_base64}}" }
},
"textContentTemplate": {
"type": "text",
"text": "{{text}}"
}
}
}{
"version": 2,
"name": "Google Gemini (Native API)",
"connection": {
"endpoint": "/v1beta/models/{{model}}:generateContent",
"streamEndpoint": "/v1beta/models/{{model}}:streamGenerateContent?alt=sse",
"modelEndpoint": "/v1beta/models",
"headers": { "Content-Type": "application/json" },
"auth": { "header": "x-goog-api-key", "prefix": "" }
},
"request": {
"bodyTemplate": {
"contents": [],
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 2048,
"topP": 0.95,
"topK": 40
},
"safetySettings": [
{ "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" },
{ "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE" },
{ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE" },
{ "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE" },
{ "category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "BLOCK_NONE" }
]
},
"samplerMappings": [
{ "samplerID": "temperature", "path": "$.generationConfig.temperature" },
{ "samplerID": "maxTokens", "path": "$.generationConfig.maxOutputTokens", "transform": "integer" },
{ "samplerID": "topP", "path": "$.generationConfig.topP" },
{ "samplerID": "topK", "path": "$.generationConfig.topK", "transform": "integer" }
],
"stop": { "path": "$.generationConfig.stopSequences", "limit": 5 },
"promptPath": "$.contents",
"promptFormat": {
"type": "chat",
"roles": { "user": "user", "assistant": "model", "system": "user" },
"contentKey": "parts"
}
},
"response": {
"transport": { "type": "sse" },
"contentPath": "$.candidates[0].content.parts[0].text",
"streamContentPath": "$.candidates[0].content.parts[0].text",
"reasoningPath": "$.candidates[0].content.parts[0].thought",
"streamReasoningPath": "$.candidates[0].content.parts[0].thought",
"error": { "detectPath": "$.error", "messagePath": "$.error.message" }
},
"model": {
"useModelContextLength": true,
"listPath": "$.models",
"namePath": "$.name",
"contextLengthPath": "$.inputTokenLimit"
},
"features": { "useKey": true, "useModel": true, "useStreaming": true },
"uiOverridableSamplers": ["temperature", "maxTokens", "topP", "topK"],
"media": {
"supportsImages": true,
"imageContentTemplate": {
"inlineData": { "mimeType": "{{mime_type}}", "data": "{{image_base64}}" }
},
"textContentTemplate": { "text": "{{text}}" }
}
}- 阅读目标 API 的官方文档,了解请求/响应格式
- 复制一个最接近的示例模板作为起点
- 修改
connection中的端点路径和认证方式 - 修改
bodyTemplate,放入 API 要求的固定字段 - 配置
samplerMappings,将标准参数映射到bodyTemplate中的正确路径 - 配置
promptPath和promptFormat,确保消息格式正确 - 配置
response中的内容提取路径(可以先用非流式测试,再配流式路径) - 如需图片支持,配置
media部分 - 保存为
.json文件,在应用中导入测试
调试技巧: 如果响应解析不到内容,先用非流式模式测试,观察 API 返回的完整 JSON 结构,然后调整 contentPath。流式模式下每个 SSE 事件的 JSON 结构可能与非流式不同(如 OpenAI 的 delta vs message),需要分别配置 streamContentPath。