- process.go: attach resp.ReasoningContent when building assistant messages - deepseek.lua v2.1.0: remove last_reasoning closure hack; rely on core-provided reasoning_content in messages - openai.lua: strip reasoning_content from messages in transform_request (not supported by OpenAI API)
8.6 KiB
English | 中文
Lua Adapter — LLM 源适配指南
内核内部格式说明:下文描述的 CompletionRequest / CompletionResponse JSON 格式是内核 LLM 适配器的私有内部线缆协议。 该格式以 Go 结构体定义在
internal/agent/api/provider.go中,不导出为外部 API。 本文档公开此格式的唯一目的是作为 Lua 适配器脚本的契约标准——用户按照此文档编写 Lua 脚本,即可接入任意 LLM API 源。
每个 LLM API 源对应一个 Lua 脚本,负责请求转换(内核私有格式 → API 格式)和响应转换(API 格式 → 内核私有格式)。
适配器契约
Lua 脚本必须返回一个包含以下字段和函数的 table:
local adapter = {}
-- 元信息
adapter.name = "my_provider" -- 唯一标识,与 config 中 adapter 字段一致
adapter.version = "2.0.0"
adapter.endpoint = "/v1/chat/completions" -- API 路径,拼接到 base_url 后
adapter.headers = {} -- 额外 HTTP 请求头
-- 请求转换:内核私有格式 → API 格式
function adapter.transform_request(raw_json)
-- raw_json: 内核 CompletionRequest 的 JSON 字符串(完整字段见下文)
-- 返回: 应发送给 API 的 JSON 字符串
return transformed_json
end
-- 响应转换:API 格式 → 内核私有格式
function adapter.transform_response(raw_json)
-- raw_json: API 返回的原始 JSON 字符串
-- 返回: 统一 CompletionResponse JSON 字符串(完整格式见下文)
return unified_json
end
-- 流式块转换(可选)
function adapter.transform_stream_chunk(raw_line)
-- raw_line: SSE 中 data: 后的原始 JSON 字符串
-- 返回: 统一 StreamChunk JSON 字符串(格式见下文),返回 "" 表示跳过该 chunk
return chunk_json
end
return adapter
内核私有 CompletionRequest 格式(Go → Lua)
{
"model": "deepseek-v4-flash",
"messages": [
{ "role": "system", "content": "你是 AI 助手" },
{ "role": "user", "content": "你好" },
{ "role": "assistant", "content": "你好!", "reasoning_content": "思考过程...", "tool_calls": [ { "id": "call_xxx", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } } ] },
{ "role": "tool", "tool_call_id": "call_xxx", "content": "天气:晴" }
],
"temperature": 0.7,
"max_tokens": 4096,
"stream": false,
"tools": [ { "type": "function", "function": { "name": "get_weather", "description": "...", "parameters": { ... } } } ],
"tool_choice": "auto",
"disable_thinking": true
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
model |
string | 否 | 模型名,内核用 adapter 所在源的 BaseConfig.Model 自动填充 |
messages |
array | 是 | 对话消息列表(详见下方 Message) |
temperature |
float | 否 | 采样温度,默认 0.7 |
max_tokens |
float | 否 | 最大生成 token 数,默认 4096 |
stream |
bool | 否 | 是否流式输出 |
tools |
array | 否 | 工具定义列表(OpenAI tools 格式) |
tool_choice |
string/object | 否 | 工具选择策略,"auto" / "none" / { type: "function", function: { name: "..." } } |
disable_thinking |
bool | 否 | 是否禁用 CoT 思考(适用于 DeepSeek-R1 等推理模型) |
扩展字段:内核可能会将不在此表中的额外键值对合并到顶层 JSON(通过内部 ExtraBody 机制),Lua 脚本应当透传或按需处理这些字段。
Message 对象
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
role |
string | 是 | 角色:system / user / assistant / tool |
content |
string/array | 否 | 文本内容;多模态时可为 ContentBlock 数组(见下方) |
reasoning_content |
string | 否 | 思维链/推理内容(仅 assistant 角色,如有) |
tool_call_id |
string | 否 | 工具调用 ID(仅 tool 角色,与 assistant 的 tool_calls 对应) |
tool_calls |
array | 否 | 工具调用列表(仅 assistant 角色) |
ContentBlock 对象(多模态消息)
当 content 为数组时,每个元素格式:
{ "type": "text", "text": "描述图片" }
{ "type": "image_url", "image_url": { "url": "https://...", "detail": "auto" } }
{ "type": "audio_url", "audio_url": { "url": "https://..." } }
ToolCall 对象(CompletionRequest messages 中)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 是 | 工具调用唯一 ID |
type |
string | 是 | 固定为 "function" |
function |
object | 是 | 内含 name (string) 和 arguments (JSON 字符串,非对象!) |
注意:在 CompletionRequest 的 messages 中,tool_calls 使用的是 OpenAI 线缆格式:
{id, type, function: {name: string, arguments: string}},其中 arguments 是 JSON 字符串(非对象),因为内核序列化时对 ToolCall 做了此转换。
Lua 适配器在 transform_response 中返回给内核的 CompletionResponse 则使用扁平格式(见下节)。
内核私有 CompletionResponse 格式(Lua → Go)
{
"content": "回复内容",
"reasoning_content": "思维链内容",
"finish_reason": "stop",
"token_usage": { "prompt": 10, "completion": 20, "total": 30 },
"tool_calls": [
{ "id": "call_xxx", "type": "function", "name": "tool_name", "arguments": { "key": "val" } }
]
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
content |
string | 是 | 回复文本内容 |
reasoning_content |
string | 否 | 思维链内容(如模型返回) |
finish_reason |
string | 否 | 结束原因:"stop" / "tool_calls" / "length" 等 |
token_usage |
object | 否 | Token 用量,含 prompt / completion / total 三个 int 字段 |
tool_calls |
array | 否 | 工具调用列表(扁平格式:{id, type, name, arguments: {object}},与 CompletionRequest 中 messages 的 function: {name, arguments: string} 格式不同,请勿混淆) |
流式 StreamChunk 格式
Lua 的 transform_stream_chunk 应返回以下 JSON:
{ "content": "增量文本", "done": false }
{ "content": "", "done": true, "tool_call": { "id": "call_xxx", "type": "function", "name": "get_weather", "arguments": { "city": "北" } } }
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
content |
string | 是 | 本轮增量的文本内容 |
done |
bool | 是 | 是否结束 |
tool_call |
object | 否 | 工具调用增量(部分调用时 arguments 可能为不完整 JSON) |
返回空字符串 "" 表示跳过该 chunk。
Lua VM 内置函数
json.encode(table) — 将 Lua table 编码为 JSON 字符串
json.decode(string) — 将 JSON 字符串解码为 Lua table
log(level, message) — 输出日志(level: info/warn/error)
http_get(url) — 发起 HTTP GET 请求,返回响应体字符串
http_post(url, body) — 发起 HTTP POST 请求,返回响应体字符串
适配典型 API
| API | endpoint | auth 方式 | 格式差异 |
|---|---|---|---|
| OpenAI | /chat/completions |
Authorization: Bearer <key> |
标准 OpenAI 格式 |
| DeepSeek | /chat/completions |
Authorization: Bearer <key> |
OpenAI 兼容,强制 temperature=1 |
| Anthropic | /v1/messages |
x-api-key: <key> |
Messages API,system 消息分离,content 为 block 数组 |
| Gemini | /v1/models/{model}:generateContent |
?key=<key> 或 Bearer |
contents/parts 格式,role 用 model 而非 assistant |
| Mistral | /v1/chat/completions |
Authorization: Bearer <key> |
OpenAI 兼容 |
| Groq | /openai/v1/chat/completions |
Authorization: Bearer <key> |
OpenAI 兼容 |
| GitHub Models | /chat/completions |
Authorization: Bearer <pat> |
OpenAI 兼容 |
| Ollama | /api/chat |
无 | 不同的 options 格式 |
添加新 LLM 源步骤
- 编写 Lua 适配器脚本,定义
transform_request和transform_response函数 - (可选)定义
transform_stream_chunk支持流式 - 将脚本文件
<name>.lua放入数据目录下的adapters/文件夹中(即daemon.data_dir/adapters/) - 在配置中引用该适配器:
"adapter": "<name>"(与脚本中adapter.name一致) - 无需重新编译——VM 启动时自动扫描该目录并加载所有
.lua文件
注意:内置适配器存放在
internal/lua/adapters/目录下,编译时嵌入二进制。 用户自定义适配器不需要放入源码目录,只需放入daemon.data_dir/adapters/即可。 同名适配器:自定义文件优先级高于内置文件。