
如何用 Genkit Ollama 搭一套完全离线的文档问答本地 RAG 实战教程【免费下载链接】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本文基于 Genkit 仓库中的 Ollama 示例js/testapps/ollama带你接好 genkitx-ollama 插件、用 nomic-embed-text 做嵌入、内存余弦相似度检索、phi3.5 生成搭出一条完全离线的本地 RAG 问答链路并能在 Dev UI 中直接运行调试。数据出不了内网本地 RAG 为什么是首选先说结论如果你的文档不能离开内网、不想按 token 付费、或者干脆要离线可用本地 RAG 几乎是唯一务实的选择。RAGRetrieval-Augmented Generation检索增强生成的原理很简单就三步嵌入把每份文档转成一个向量。你可以把 embedding 理解成把文本变成一组可以算距离的数字检索用户问题也转成向量和文档向量比距离余弦距离越小语义越接近生成把检索出的几条文档塞进 prompt让模型基于上下文回答。Genkit 仓库里有一条现成的完整链路可以直接照着跑js/testapps/ollama示例内置了 5 条宝可梦描述Pikachu、Charmander、Bulbasaur、Squirtle、Jigglypuff用nomic-embed-text生成 768 维嵌入在内存里做余弦相似度取 Top-3再交给phi3.5:latest生成回答。除了本地跑着一个 Ollama 服务整个过程不依赖任何云端 API。一套本地环境Ollama、Node 18 与两个模型的拉取这一步要准备两样东西Node.js18 及以上版本示例基于tsx与tsc运行Ollama本地模型服务端负责嵌入和文本生成。️ 安装 Ollama 后启动服务并拉取模型ollama serve ollama pull nomic-embed-text ollama pull phi3.5:latestollama serve默认监听http://localhost:11434这个地址同时也是 Genkit Ollama 插件在未配置serverAddress时的默认值。模型角色用途nomic-embed-text嵌入模型把宝可梦描述和用户问题转成 768 维向量phi3.5:latest生成模型基于检索到的上下文产出最终回答示例的genkit与genkitx-ollama都以workspace:*形式依赖 monorepo 内的本地包见 js/testapps/ollama/package.json所以不需要单独安装插件。⚠️ 注意运行时报Make sure the Ollama server is running.时先确认ollama serve是否在跑。插件对 Ollama 的请求一旦失败就会抛出这句话见 js/plugins/ollama/src/index.ts。最小可运行配置5 行代码把 Genkit 接到 Ollama核心配置长这样完整源码见 js/testapps/ollama/src/index.tsimport { genkit, z } from genkit; import { ollama } from genkitx-ollama; const ai genkit({ plugins: [ ollama({ embedders: [{ name: nomic-embed-text, dimensions: 768 }], }), ], });插件支持的全部参数定义在 js/plugins/ollama/src/types.ts 的OllamaPluginParams中若与仓库当前版本不符以源码为准参数必填说明models否声明要注册的生成模型如[{ name: gemma }]可带type: chat \| generate默认chat和supports.toolsembedders用嵌入时必填namedimensions必须都提供初始化时注册为ollama/nameactionserverAddress否默认http://localhost:11434requestHeaders否静态对象或异步函数函数会收到{ serverAddress, model, modelRequest?, embedRequest? }适合动态注入鉴权 token示例里还额外挂了一个requestHeaders函数打印serverAddress后返回一个模拟的Authorization: Bearer头——这就是动态鉴权的标准写法把my-token换成真实 token 获取逻辑即可。引用模型和嵌入器有两个便捷函数ollama.model(phi3.5:latest) // - ModelReferenceollama/phi3.5:latest ollama.embedder(nomic-embed-text) // - ollama/nomic-embed-text它们内部只是把名字拼成ollama/name形式的 action 名。这里有个值得知道的细节生成模型是动态解析的——你本地ollama pull过任何模型都可以通过ollama.model(模型名)直接调用不必在models里声明而嵌入器必须提供dimensions所以要在embedders里显式注册。一个问题变成答案的三段路径示例把 RAG 拆成三个独立函数全部在 js/testapps/ollama/src/index.ts 中。第一段给每份文档生成嵌入pokemon.embedding ( await ai.embed({ embedder: ollama.embedder(nomic-embed-text), content: pokemon.description, }) )[0].embedding;ai.embed返回嵌入结果数组示例取[0]拿到唯一那个向量。底层行为插件把文本组装成{ model, input }请求体 POST 到 Ollama 的/api/embed再把响应里的payload.embeddings逐个包成{ embedding }返回实现见 js/plugins/ollama/src/embeddings.ts。第二段内存里算 Top-3 余弦距离findNearestPokemon的检索策略很朴素教学价值很高先做完整性校验只要还有记录的embedding是null直接抛Some Pokemon are not yet embedded对每条记录算余弦距离升序排序取前topN 3条余弦距离 1 - 点积 / (|a| × |b|)越小语义越近遇到零向量会抛Invalid input: zero vector。第三段带着上下文生成回答return await ai.generate({ model: ollama.model(phi3.5:latest), prompt: Given the following context on Pokemon:\n${pokemonContext}\n\nQuestion: ${question}\n\nAnswer:, });关键点有两个用户问题也要先过一遍嵌入模型才能和文档向量在同一语义空间里比距离检索出的 3 条记录被拼成名字: 描述的上下文块塞进 prompt。最终答案从response.text取。⚠️ 注意示例每次执行 Flow 都会对全部 5 条记录重新嵌入。演示无所谓但真实项目里文档索引应该是一次性离线任务运行时只嵌入查询语句。把 RAG 包成 Flow用 Dev UI 直接跑起来export const pokemonFlow ai.defineFlow( { name: Pokedex, inputSchema: z.string(), outputSchema: z.string(), }, async (input) { await embedPokemon(); const response await generateResponse(input); return response.text; } );inputSchema/outputSchema用 Zod 声明带来两个好处Dev UI 里有结构化的输入输出表单运行时也会做参数校验。进入js/testapps/ollama目录启动pnpm run genkit:dev再开另一个终端genkit ui:start 示例的脚本含义见 js/testapps/ollama/package.json脚本命令作用genkit:devgenkit start -- tsx --watch src/index.ts热重载运行入口由 Genkit CLI 托管devtsx --watch src/index.ts不经过 Genkit CLI 的纯开发运行buildtsc编译 TypeScript 到lib/startnode lib/index.js运行编译产物Dev UI 里能看到两个 FlowPokedex是完整的 RAG 入口输入一个自然语言问题比如问哪只宝可梦擅长电系攻击就能看到完整的检索生成链路joker则演示了两个进阶能力——运行时通过model参数动态换模型默认gemma3:latest以及用onChunk: sendChunk把ai.generate的增量片段实时转发给调用方实现流式输出。插件背后Genkit 配置与 Ollama HTTP API 的映射想把示例改造到自己的项目里搞清楚插件底层行为很有帮助。OllamaConfigSchemajs/plugins/ollama/src/index.ts在 Genkit 通用生成配置之上扩展了 Ollama 特有参数映射关系如下默认值来自插件源码描述若与仓库当前版本不符以源码为准Genkit 配置项映射到 Ollama options默认值 / 范围temperaturetemperature0.80.0 ~ 1.0topKtop_k40topPtop_p0.90 ~ 1.0stopSequencesstop多值用空串连接成一个字符串—maxOutputTokensnum_predict—也就是说ai.generate({ config: { temperature: 0.2 } })就能直接控制采样。端点选择由模型的type决定type: chat默认→POST /api/chat走多轮 messages图片会自动剥掉 data URI 前缀再发送工具调用tool_calls也只有 chat API 支持type: generate→POST /api/generateprompt 与 system 消息被分别提取到prompt/system字段。工具方面有个硬限制工具的inputSchema必须是 object 类型否则插件会抛Unsupported tool: ... Ollama only supports tools with object inputs。另外两个底层行为值得知道动态发现插件的listActions会调用 Ollama 的GET /api/tags拉取本地已装模型朴素地过滤掉名字里含 embed 的把其余模型暴露为ollama/nameaction流式读取流式请求通过res.body.getReader()逐块读取解析 Ollama 返回的 NDJSON 流后回调onChunk。动手清单把示例推向生产的 5 个改法索引与检索分离把embedPokemon()挪进一次性脚本或定时任务运行时只对查询语句做嵌入换掉内存线性扫描数据量上来后接向量存储仓库里已有 js/plugins/pinecone/、js/plugins/dev-local-vectorstore/ 等插件可参考换模型零成本ollama pull新模型后在插件配置的models/embedders里声明即可业务代码不用动调采样参数在ai.generate的config里传temperature/topK/topP按问题类型收紧或放松输出用 Dev UI 盯召回质量跑Pokedex时查看每个节点的输入输出与耗时定位是检索召回不准还是生成质量差。【免费下载链接】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),仅供参考