尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用 Genkit compat_oai 包构建 OpenAI 兼容插件:从零实现多提供商模型接入
用 Genkit compat_oai 包构建 OpenAI 兼容插件从零实现多提供商模型接入【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkitGenkitGoogle 开源的跨语言 AI 应用框架覆盖 JavaScript、Go、Dart 与 Python在 Go 侧提供了一个专门用于对接 OpenAI 兼容 API 的插件基础包compat_oai它把注册模型与 Embedder、处理消息、支持工具调用、管理配置等重复劳动统一抽象让一个新提供商插件只需声明自己的配置类型与模型能力目录即可接入。本文以 go/plugins/compat_oai/README.md 为主线结合 compat_oai.go、config.go、generate.go 等源码实现完整讲解如何基于该包从零编写一个 OpenAI 兼容插件并剖析其认证隔离、配置 Schema、结构化输出等关键设计读完你即可独立为任意 OpenAI 兼容服务商落地 Genkit 插件。一、包定位compat_oai解决什么问题compat_oai是一个用于构建 OpenAI API 兼容插件的基底包所有子插件openai、anthropic、dashscope、deepseek、kimi、openrouter、xai、zai都构建在其上。其核心是OpenAICompatible这个基础实现统一处理四类职责模型与 Embedder 注册NewModel/NewChatModel/NewEmbedder构造出可被框架注册的 Action消息处理ModelGenerator将 Genkit 的ai.Message转换为 OpenAI Chat Completions 格式并处理文本、图片、工具调用、推理内容等各类 Part工具支持将 Genkit 的ai.ToolDefinition转为 OpenAItools参数并支持 strict 模式配置管理从插件自声明的配置类型反射出 JSON Schema框架据此对每个请求做校验。从源码结构看compat_oai.goOpenAICompatible结构体持有client*openai.Client、OptsSDK 请求选项、Provider提供商唯一标识用作模型名前缀如myprovider/model-name、APIKey、BaseURL与可选的ListModels回调Init负责装配客户端并返回 Action 列表。二、实现一个新插件完整示例与逐步拆解原文档给出了一个完整的新插件实现骨架README 的 Usage Example这是理解整个包的最佳入口。下面按步骤拆解。1. 定义配置类型ChatConfig插件需要为自己的模型定义一个配置类型声明提供商 API 接受的每一个字段并内嵌compat_oai.RequestConfig来继承 Genkit 所拥有的设置按请求的 API Key、模型版本号、以及extra透传。// ChatConfig is the plugins per-request model config. type ChatConfig struct { compat_oai.RequestConfig // Temperature controls the degree of randomness, from 0 to 1. Temperature *float64 json:temperature,omitempty jsonschema:minimum0,maximum1 jsonschema_description:Controls the degree of randomness in token selection, from 0 to 1. EnableSearch *bool json:enableSearch,omitempty jsonschema_description:Lets the model consult web search, sent as the APIs enable_search. } func (c ChatConfig) ApplyToChatCompletion(params *openai.ChatCompletionNewParams) { c.ApplyVersion(params) if c.Temperature ! nil { params.Temperature openai.Float(*c.Temperature) } if c.EnableSearch ! nil { compat_oai.AddExtraFields(params, map[string]any{enable_search: *c.EnableSearch}) } }这里有三条关键约定源码注释与 README 一致Schema 即契约框架会根据配置类型反射出的 Schema 校验每个请求而这个 Schema 也正是 Dev UI 配置面板所展示的。因此提供商 API 参考文档里有什么就声明什么不多不少——多声明一个提供商不接受的字段就等于在 Dev UI 里多暴露一个提供商必然拒绝的开关。字段注释双写每个字段除了 Go 文档注释面向 Go 读者还要写jsonschema_description面向 Dev UI 的帮助文本。测试 conformance_test.go 中的requireDescriptions会递归检查所有字段凡是缺少描述的字段直接判失败。硬性限制进jsonschematag提供商文档标注为硬性限制的范围、枚举、长度放进jsonschematag如minimum0,maximum1让校验在请求发出、产生计费之前就拒绝违规值而逐模型差异的限制如上下文长度只能写进描述因为 Schema 被插件服务的所有模型共享只能表达对全部模型都成立的约束。2. 实现ChatConfig接口配置类型要能作为NewChatModel[Config]的泛型参数必须满足 config.go 中定义的ChatConfig接口type ChatConfig interface { ApplyToChatCompletion(params *openai.ChatCompletionNewParams) RequestAPIKey() string RequestExtra() map[string]any }内嵌RequestConfig会自动继承RequestAPIKey()与RequestExtra()但ApplyToChatCompletion必须由插件自己编写。源码中特别注释ApplyVersion之所以不命名为ApplyToChatCompletion正是因为若内嵌一个完整的 apply 方法会让类型无意中满足接口从而静默丢掉插件声明的一切字段config.go。ApplyToChatCompletion的书写规则SDK 已建模的字段直接赋值其余字段通过AddExtraFields走 extra。上例中Temperature是 OpenAI SDKChatCompletionNewParams自带的字段直接赋值enable_search不在 SDK 模型内则通过compat_oai.AddExtraFields注入。AddExtraFields是合并语义而非整体替换——它保留 params 上已设置的 extra 再叠加新字段config.go避免覆盖配置类型内嵌层已有的透传字段。3. 声明能力集与模型目录插件用ai.ModelSupports描述模型能力。README 示例中拆成了textOnly与multimodal两个共享能力集var ( textOnly ai.ModelSupports{ Multiturn: true, Tools: true, SystemRole: true, Media: false, ToolChoice: true, Output: []string{text, json}, Constrained: ai.ConstrainedSupportAll, } multimodal ai.ModelSupports{ Multiturn: true, Tools: true, SystemRole: true, Media: true, ToolChoice: true, Output: []string{text, json}, Constrained: ai.ConstrainedSupportAll, } )supportedModels为已知模型整理能力但它不是可用模型的全集——任何未收录的模型 ID 也会按需解析并套用dynamicModelOptionsvar supportedModels map[string]ai.ModelOptions{ my-model: {Label: My Model, Supports: textOnly}, my-model-vision: {Label: My Model Vision, Supports: multimodal}, } var dynamicModelOptions ai.ModelOptions{ Supports: multimodal, Versions: []string{}, Stage: ai.ModelStageStable, } func (p *MyPlugin) modelOptions(id string) ai.ModelOptions { return compat_oai.ModelOptionsFor(myprovider, id, supportedModels, dynamicModelOptions, p.Models) }modelOptions是全插件唯一的模型能力来源Init、ListActions、ResolveAction三条路径全部经由它。这正是ModelOptionsForcompat_oai.go的设计要点调用方的Models覆盖条目对任何路径先描述到的模型都权威有效——它是叠加Overlay而非替换一条只修一个能力的条目不需要重述 label、版本等其他信息。4. 装配Initfunc (p *MyPlugin) Name() string { return myprovider } func (p *MyPlugin) Init(ctx context.Context) []api.Action { // initialize the plugin with the common compatible package p.openAICompatible.Provider p.Name() actions : p.openAICompatible.Init(ctx) // Define plugin-specific models for model : range supportedModels { actions append(actions, compat_oai.NewChatModelChatConfig)) } // Define embedders, if applicable return actions }README 特别强调Init注册的模型事后无法反注册所以目录描述错了的唯一修正途径就是让Init在注册时就读到调用方的Models覆盖——这正是modelOptions一条路径贯穿三处的原因。Init之外的ListActions列出提供商 API 实际暴露的模型与ResolveAction按需解析未预先注册的模型也应使用同一modelOptions保证列出来与解析到永远一致ListChatActions/ResolveChatActioncompat_oai.go即为此提供的现成实现。5. 两种模型构造方式的分工NewChatModel[Config]配置类型是提供商自己的ChatConfig。框架按 Config 反射的 Schema 校验并反序列化配置再通过Config.ApplyToChatCompletion把配置合并进传出请求随后forwardRequestExtracompat_oai.go将Extra中的字段以提供商 wire 名原样追加冲突键以后写入者为准。NewModel配置类型直接就是 OpenAI SDK 的openai.ChatCompletionNewParams即原始 OpenAI 请求本身适用于openai插件或面向真实 OpenAI API 的代理。其底层newSDKModel会先调用rejectManagedConfigcompat_oai.go拒绝由 Genkit 管理的字段。三、RequestConfigGenkit 拥有的三个按请求设置每个插件配置都内嵌RequestConfigconfig.go它承载三项Genkit 拥有而非提供商拥有的设置所有 OpenAI 兼容提供商实现完全一致字段JSON 名说明APIKeyjson:-仅针对这一个请求覆盖插件的 API Key。它是客户端凭证而非请求参数永不参与序列化因此不会出现在对外公布的 Schema、记录的 trace 或发出的请求体里且只能通过代码中的类型化配置设置JSON 或 map 配置无法提供Versionversion钉死该请求实际由哪个模型版本服务例如gpt-4o-2024-11-20之于gpt-4o族。会覆盖请求本应携带的模型 ID。ApplyVersionconfig.go将其写入params.ModelWithParams让 params 携带的 model 优先于生成器的 modelExtraextra配置未声明但需要透传的请求体字段按提供商的 wire 名通常是 snake_case写在请求顶层原样转发。与配置已声明字段冲突时以 Extra 为胜因此漏映射永远不会阻塞提供商本来能接受的请求Extra的意义在于提供商新增字段不需要等插件升级声明调用方可以直接从 Extra 传入。但 Genkit 从请求本身构建的字段messages、tools、tool_choice、response_format、已废弃的functions/function_call对以及 embedder 的input会被forwardRequestExtra/forwardEmbeddingExtra明确拒绝因为配置试图替换对话内容时静默清空只会掩盖错误。RequestConfig特意不含任何采样类设置temperature 等因为各提供商在可用性、命名、取值范围上互不相同——共享结构体只会强迫每个配置都暴露某些提供商拒绝的字段config.go。四、认证与身份隔离绝不把 OpenAI 的身份发给别的提供商OpenAICompatible的认证由插件自己供给填在OpenAICompatible.APIKey或以WithAPIKey形式放在Opts中。这里有一个非常关键的底层设计——环境变量身份清洗。OpenAI 官方 SDK 在为每个构建的客户端读取OPENAI_API_KEY、OPENAI_ORG_ID、OPENAI_PROJECT_ID三个环境变量。compat_oai.Init会先调用clearInheritedOpenAIIdentity()compat_oai.go将三者全部清空后再应用插件自己组合的认证确保服务其他提供商的插件绝不会把 OpenAI 的身份转发到该提供商的端点func clearInheritedOpenAIIdentity() []option.RequestOption { return []option.RequestOption{ option.WithAPIKey(), option.WithHeaderDel(Authorization), option.WithOrganization(), option.WithHeaderDel(OpenAI-Organization), option.WithProject(), option.WithHeaderDel(OpenAI-Project), } }源码注释解释了为什么每个都要清两遍设置该选项的 option 会同时写入请求配置字段和 Header而设置空值并不会移除 Header必须配合WithHeaderDel才能真正清掉。这是一次对渗入物的清洗而非禁用——它发生在插件组合任何选项之前所以想要其中某一个如openai插件的插件自己读取并显式设置即可获胜openai.go 正是这样做的读取OPENAI_API_KEY/OPENAI_ORG_ID/OPENAI_PROJECT_ID后显式option.WithAPIKey等。按请求的 API Key 覆盖则走clientForKeycompat_oai.go当RequestConfig.APIKey非空时克隆插件 Opts避免并发请求写入彼此的底层数组并追加WithAPIKey(key)最后一个选项胜出。Embedding 侧由EmbeddingConfig.APIKey提供同样的能力。五、模型能力目录的工程约定README 后半部分集中阐述了本包所有插件遵循的目录布局约定目录形状统一每个插件都是同一副面孔命名能力集 → 带文档的单行条目supportedModels→dynamicModelOptions兜底 → 叠加调用方Models的modelOptions方法。提供商发布带日期的快照时折叠进条目的Versions而非每个快照注册一个模型见 openai.go 中gpt-4o的Versions: []string{gpt-4o, gpt-4o-2024-11-20, ...}。模型 ID 用字符串字面量不用导出常量导出常量ModelMyModel会比它所命名的模型活得更久模型 ID 每几个月就换而常量一经导出就因破坏性变更无法删除。map 键本就是modelOptions查找的唯一事实来源将退场的模型用Stage: ai.ModelStageDeprecated标注——那是数据而非 API 表面。Models是唯一的覆盖机制不提供RegisterModel应用层永远不需要注册模型插件未收录的 ID 会按需解析Models里的条目即可描述它。注册调用只能添加本就可用的 ID而最可能需要修正的恰恰是Init已注册、无法二次注册的那些。这与googlegenai和原生anthropic插件保持一致。生成字段逐个声明而非继承各提供商对生成字段的存在性与命名分歧很大DeepSeek 去掉了 frequency/presence 惩罚Z.ai 把temperature上限设为 1Kimi 的 K 系列两者都不要maxOutputTokens在部分提供商是max_tokens、在另一部分是max_completion_tokens。约定是同一设置在别的插件里用什么 camelCase 名这里就用什么——conformance_test.go 的canonicalTypes表跨整个包强制这一契约规范字段规范类型versionstringtemperaturenumbertopPnumbermaxOutputTokensintegerstopSequencesarrayfrequencyPenaltynumberpresencePenaltynumberlogProbsbooleantopLogProbsintegerseedintegerreasoningEffortstringTestConfigSchemaConformance还强制每个配置必须有version属性、不得出现apiKey凭证不出现在序列化配置里、属性名不得含-/_camelCase 契约、每个属性都要有描述。openai插件刻意缺席该测试——它的配置就是 OpenAI SDK 请求类型按设计讲 snake_case。网关型插件是另一种形状并非每个提供商都是模型厂商。openrouter是一个网关前端服务数百个模型。它完全不维护supportedModelsInit不注册任何模型ListActions不返回任何描述符一个描述符携带完整请求/响应 Schema列出那套目录会让每次反射轮询背上数 MB对解析到的每个模型都用同一套宽松能力集描述。调用方用Models来收窄单个模型。实现网关照此形状实现厂商照前述 curated 形状。六、Constrained唯一需要对照提供商文档核实的能力在所有能力字段中Constrained是唯一建议对照提供商文档而不是照抄的。其机制在 generate.go 的applyResponseFormat中只要请求携带 SchemaGenkit 就以json_schema形式发送response_format而是否跳过向 prompt 注入 Schema 指令取决于模型是否宣称 constrained 支持。因此只在提供商文档明确支持response_format的type: json_schema时才设置ConstrainedSupportAll仅支持json_object的提供商DashScope、DeepSeek、Z.ai或干脆忽略response_format的Anthropic 兼容端点必须留空否则结构化输出会失去唯一强制 Schema 的 prompt 指令支持 Schema 但不支持与工具并用的提供商xAI 除 Grok 4 家族外的模型使用ConstrainedSupportNoTools。anthropic插件的实现可作对照anthropic.goClaude 4.5 代起才原生支持结构化输出兼容端点只接受json_schema形式、对其余形式一律 400因此其能力集的Output: []string{text}中不含json——无 Schema 的 JSON 请求就不发response_format改由注入的格式指令承担。七、Genkit 拥有的字段从配置 Schema 中隐藏并拒绝messages、tools、tool_choice、response_format与已废弃的functions/function_call对不属于模型配置的一部分——它们由 Genkit 请求构建分别对应ai.WithMessages()/ai.WithPrompt()、ai.WithTools()、ai.WithToolChoice()、ai.WithOutputType()/ai.WithOutputFormat()。SDK 类型化的模型会把它们从公布的 Schema 中隐藏并在配置试图设置其中之一时报错并指名应使用的 Genkit 选项rejectManagedConfigcompat_oai.go。n以同样的方式被拒绝因为响应路径只返回第一个候选多余候选只会被计费然后丢弃。自定义配置类型直接省略这些字段即可。clearManagedFieldsgenerate.go则会在构建请求时清空这些字段并重新从 Genkit 请求填充——包括扫描 params 的 extra 字段因为 SDK 会以 extra 字段覆盖同名结构体字段不清扫等于让被清零的东西借尸还魂。这套拒绝 清理双保险防止配置偷偷夹带一个框架没有 handler 的工具。Schema 的其余部分携带 OpenAI API 参考中的描述所以 Dev UI 的配置侧栏能逐个字段给出说明sdkSchemaOverridescompat_oai.go则为 SDK 结构体补上描述并标注隐藏字段。八、错误分类让重试中间件正确工作WrapAPIErrorerrors.go把 OpenAI SDK 返回的 HTTP 错误包装成携带服务端状态码的status.Error使 status 感知的中间件重试、回退能区分限流与被拒绝的请求。没有它每个 API 失败都未分类而重试中间件会把未分类错误视为可重试——401 会被原样重发直到次数耗尽。传输层错误如拨号超时保持未分类那确实是值得重试的。流式场景另有wrapStreamErrorgenerate.go处理网关中途失败、错误对象藏在 chunk 顶层 JSON 里的情况。九、运行测试仓库内每个子插件目录都有独立测试。设置 API Key 后运行export OPENAI_API_KEYyour-openai-key export ANTHROPIC_API_KEYyour-anthropic-key export DASHSCOPE_API_KEYyour-dashscope-key export ZAI_API_KEYyour-zai-key export KIMI_API_KEYyour-kimi-key export XAI_API_KEYyour-xai-key export DEEPSEEK_API_KEYyour-deepseek-key export OPENROUTER_API_KEYyour-openrouter-key运行全部测试go test -v ./...运行单个插件测试# OpenAI tests go test -v ./openai # Anthropic tests go test -v ./anthropic # DashScope tests go test -v ./dashscope # Z.ai tests go test -v ./zai # Kimi tests go test -v ./kimi # xAI tests go test -v ./xai # DeepSeek tests go test -v ./deepseek # OpenRouter tests go test -v ./openrouter注意未设置所需 API Key 的测试会被跳过。不依赖真实 API 的契约测试conformance_test.go用t.Setenv注入占位 Key 后即可离线运行验证配置 Schema 契约、Models覆盖生效路径与闭枚举命名规范。十、完整实现参考本目录下的八个插件是上述全部约定的落地样本可直接对照研读openaiopenai/openai.goSDK 请求类型即配置、自带身份、curated 目录 Embedder 目录anthropicanthropic/anthropic.go自定义ChatConfig、thinking 走 extra、constrained 能力分级dashscope、deepseek、kimi、xai、zai各有特色的字段声明与能力裁剪openrouteropenrouter/无目录网关模式。撰写新插件时只需照 README 的 Usage Example 搭好配置类型 能力集 模型目录 单一modelOptions路径四件套认证与错误分类等基础设施由compat_oai全包即可在半小时内为一个新的 OpenAI 兼容服务商交付可用的 Genkit 插件。【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

银河麒麟 V10 常见故障排查:权限、软件安装与桌面存储实战

银河麒麟 V10 常见故障排查:权限、软件安装与桌面存储实战

上周同事抱着一台笔记本过来,说系统装好了,登录进去桌面一片空白,只有鼠标能动,任务栏也没了,问我是不是装废了得重装。我敲了两个命令把ukui-panel拉起来,前后三分钟解决。这种事我这两年遇到过太多次了—…

📅 2026/9/17 6:40:56
Cosign 容器与二进制制品签名指南:基于 Sigstore 的密钥无感签名与透明日志验证实践

Cosign 容器与二进制制品签名指南:基于 Sigstore 的密钥无感签名与透明日志验证实践

Cosign 容器与二进制制品签名指南:基于 Sigstore 的密钥无感签名与透明日志验证实践 【免费下载链接】cosign Code signing and transparency for containers and binaries 项目地址: https://gitcode.com/GitHub_Trending/co/cosign 导读 cosign 是 sigsto…

📅 2026/9/17 6:35:56
Lance 项目贡献指南:从 Conventional Commits 到格式规范投票的完整参与路径

Lance 项目贡献指南:从 Conventional Commits 到格式规范投票的完整参与路径

Lance 项目贡献指南:从 Conventional Commits 到格式规范投票的完整参与路径 【免费下载链接】lance Open Lakehouse Format for Multimodal AI. Convert from Parquet in 2 lines of code for 100x faster random access, vector index, and data versioning. Comp…

📅 2026/9/17 6:35:56
MORE NEWS

更多资讯

📰

基于BlueZ与GATT的BLE配网原理及RDK X5实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

图像超分算法实战:从插值到深度学习的全面对比与Python实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Django二手房屋信息采集系统设计与实现

1. 项目概述与核心价值这个基于Django框架的二手房屋信息采集系统,是我在指导计算机专业毕业设计时反复验证过的经典案例。它完美融合了爬虫技术、数据可视化与Web开发三大热门方向,特别适合作为高校计算机专业的综合实践项目。系统通过自动化爬取主流房…

📰

bcftools+vcftools VCF过滤:从原始变异到干净SNP位点集

手上拿到一份 HaplotypeCaller 或者 DeepVariant 吐出来的原始 VCF,打开一看几十万到上千万个位点密密麻麻铺满整条染色体,直接丢给下游做 GWAS、群体结构分析或者亲缘关系推断,结果大概率是先跑出一堆假阳性,再花两三天回头排查是…

📰

L型3麦音源定位:TDOA物理原理与工程最优解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

游戏引擎入门:Cocos 引擎如何让你 5 分钟跑通第一个跨平台项目

游戏引擎入门:Cocos 引擎如何让你 5 分钟跑通第一个跨平台项目 【免费下载链接】cocos-engine Cocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to cre…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬